Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #LR-676 feat: User Delete - ownership transfer api #1236

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
550b2f8
Issue #LR-676 feat: User Delete - ownership transfer api
BharathwajShankar Dec 18, 2023
a132bd9
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 19, 2023
d9c3efb
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 19, 2023
a7db399
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 19, 2023
3728802
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 19, 2023
abc52c1
Merge branch 'release-7.0.0' of https://github.com/Sunbird-Lern/usero…
BharathwajShankar Dec 19, 2023
bba7bbc
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 19, 2023
57437fc
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 19, 2023
c598f67
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 20, 2023
eecce11
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 26, 2023
5152ec1
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 27, 2023
a15a84b
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 27, 2023
31c17aa
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 27, 2023
993642e
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Dec 27, 2023
7cb80d6
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 2, 2024
7830b4b
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 2, 2024
81675b1
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 2, 2024
b541498
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 2, 2024
b52f0b8
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 2, 2024
4026be0
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 2, 2024
49f71de
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 3, 2024
da12385
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 3, 2024
64c5490
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 8, 2024
1d566d6
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 9, 2024
8100c2d
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 9, 2024
25b9870
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 10, 2024
175eddf
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 10, 2024
8eabd9f
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 24, 2024
e009cc1
Merge branch 'release-7.0.0' of https://github.com/Sunbird-Lern/usero…
BharathwajShankar Jan 24, 2024
af01e05
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Jan 24, 2024
2dd218f
Merge branch 'release-7.0.0' of https://github.com/Sunbird-Lern/usero…
BharathwajShankar Feb 27, 2024
d6538e6
Merge branch 'release-8.0.0' of https://github.com/Sunbird-Lern/usero…
BharathwajShankar Feb 27, 2024
469f27e
Issue #LR-676 feat: User Delete - ownership transfer api - unittestcase
BharathwajShankar Feb 27, 2024
98c4eec
Issue #LR-676 feat: User Delete - ownership transfer api - adding new…
BharathwajShankar Mar 18, 2024
031a2cb
LR-676 - added Missing topic related settings
BharathwajShankar Mar 20, 2024
500c82b
LR-676 - added Missing topic related settings
BharathwajShankar Mar 20, 2024
30bc54b
LR-676 - added Missing topic related settings and topic info from cache
BharathwajShankar Apr 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions controller/app/controllers/usermanagement/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public class UserController extends BaseController {
@Inject
@Named("user_self_declaration_management_actor")
private ActorRef userSelfDeclarationManagementActor;
@Inject
@Named("user_ownership_transfer_actor")
private ActorRef userOwnershipTransferActor;

public CompletionStage<Result> createUser(Http.Request httpRequest) {
return handleRequest(
Expand Down Expand Up @@ -472,4 +475,9 @@ public CompletionStage<Result> updateUserDeclarations(Http.Request httpRequest)
true,
httpRequest);
}
public CompletionStage<Result> ownershipTransferUser(Http.Request httpRequest) {
return handleRequest(userOwnershipTransferActor,
ActorOperations.USER_OWNERSHIP_TRANSFER.getValue(),
httpRequest.body().asJson(), httpRequest);
}
}
3 changes: 2 additions & 1 deletion controller/app/util/ACTORS.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ public enum ACTORS {
USER_UPDATE_ACTOR(UserUpdateActor.class, "user_update_actor"),
BACKGROUND_JOB_MANAGER_ACTOR(BackgroundJobManager.class, "background_job_manager_actor"),
USER_DELETION_BACKGROUND_JOB_ACTOR(
UserDeletionBackgroundJobActor.class, "user_deletion_background_job_actor");
UserDeletionBackgroundJobActor.class, "user_deletion_background_job_actor"),
USER_OWNERSHIP_TRANSFER_ACTOR(UserOwnershipTransferActor.class,"user_ownership_transfer_actor");

ACTORS(Class clazz, String name) {
actorClass = clazz;
Expand Down
10 changes: 10 additions & 0 deletions controller/conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ akka {
{
dispatcher = akka.actor.brr-usr-dispatcher
}
"/user_ownership_transfer_actor"
{
router = smallest-mailbox-pool
nr-of-instances = 5
dispatcher = brr-usr-dispatcher
}
"/user_ownership_transfer_actor/*"
{
dispatcher = akka.actor.brr-usr-dispatcher
}
"/background_job_manager_actor"
{
router = smallest-mailbox-pool
Expand Down
2 changes: 1 addition & 1 deletion controller/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,4 @@ POST /v1/system/settings/set @controllers.systemsettings.Syst


POST /v1/user/delete @controllers.usermanagement.UserStatusController.deleteUser(request: play.mvc.Http.Request)

POST /v1/user/ownership/transfer @controllers.usermanagement.UserController.ownershipTransferUser(request: play.mvc.Http.Request)
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ public enum ResponseCode {
ResponseMessage.Message.INVALID_TENANT_SECURITY_LEVEL_LOWER),
cannotDeleteUser(
ResponseMessage.Key.CANNOT_DELETE_USER, ResponseMessage.Message.CANNOT_DELETE_USER),

cannotTransferOwnership(
ResponseMessage.Key.CANNOT_TRANSFER_OWNERSHIP, ResponseMessage.Message.CANNOT_TRANSFER_OWNERSHIP),
OK(200),
SUCCESS(200),
CLIENT_ERROR(400),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ interface Message {
String INVALID_TENANT_SECURITY_LEVEL_LOWER =
"Tenant level's security {0} cannot be lower than system level's security {1}. Please provide a valid data security level.";
String CANNOT_DELETE_USER = "User is restricted from deleting account based on roles!";
String CANNOT_TRANSFER_OWNERSHIP = "User is restricted from transfering the ownership based on roles!";
}

interface Key {
Expand Down Expand Up @@ -208,5 +209,6 @@ interface Key {
String MISSING_DEFAULT_SECURITY_LEVEL = "0081";
String INVALID_TENANT_SECURITY_LEVEL_LOWER = "0082";
String CANNOT_DELETE_USER = "0083";
String CANNOT_TRANSFER_OWNERSHIP = "0084";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,14 @@ public final class JsonKey {
public static final String USER_TABLE_STATUS = "userTable";
public static final String SUGGESTED_USERS = "suggested_users";
public static final String DELETE_USER_ACTON = "delete-user";
public static final String ACTION_BY = "actionBy";
public static final String FROM_USER = "fromUser";
public static final String TO_USER = "toUser";
public static final String USER_OWNERSHIP_TRANSFER_ACTION = "ownership-transfer";
public static final String FROM_USER_PROFILE = "fromUserProfile";
public static final String TO_USER_PROFILE = "toUserProfile";
public static final String ASSET_INFORMATION = "assetInformation";
public static final String USER_OWNERSHIP_TRANSFER_TOPIC = "user-ownership-transfer-topic";
public static final String OBJECT = "object";
public static final String EDATA = "edata";
public static final String MANAGED_USERS = "managed_users";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ public enum ActorOperations {
DELETE_LOCATION_FROM_ES("deleteLocationDataFromES", "LBKGDEL"),
ADD_ENCRYPTION_KEY("addEncryptionKey", "ADENCKEY"),
USER_CURRENT_LOGIN("userCurrentLogin", "USRLOG"),
DELETE_USER("deleteUser", "USRDLT");
DELETE_USER("deleteUser", "USRDLT"),
USER_OWNERSHIP_TRANSFER("userOwnershipTransfer","UOWNTRANS");

private String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,6 @@ sunbird_password_reset_login_page_url=/resources
isFormValidationRequired=true
userProfileConfigMap={\"type\":\"profileconfig\",\"subtype\":\"28\",\"action\":\"get\",\"component\":\"*\",\"framework\":\"*\",\"data\":{\"templateName\":\"profileConfig_v2\",\"action\":\"get\",\"fields\":[{\"code\":\"persona\",\"children\":{\"administrator\":[{\"code\":\"district\"},{\"code\":\"state\"},{\"code\":\"subPersona\",\"type\":\"select\",\"default\":null,\"templateOptions\":{\"options\":[{\"label\":\"Headmaster\",\"value\":\"hm\"},{\"label\":\"Cluster Resource Person\",\"value\":\"crp\"}]}},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"teacher\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"student\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"parent\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"other\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"subPersona\",\"templateOptions\":{\"options\":[{\"value\":\"Doctor (Allopathy)\",\"label\":\"Doctor (Allopathy)\"},{\"value\":\"AYUSH Professional\",\"label\":\"AYUSH Professional\"}]}},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}]}}]},\"created_on\":\"2022-02-10T14:16:51.852Z\",\"last_modified_on\":\"2022-11-14T05:45:02.685Z\",\"rootOrgId\":\"*\"}
sunbird_userorg_keyspace=sunbird

user-ownership-transfer-topic={{env_name}}.user.ownership.transfer
user-deletion-roles=public
user-deletion-broadcast-topic={{env_name}}.delete.user
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package org.sunbird.actor.user;

import org.apache.commons.lang.StringUtils;
import org.sunbird.actor.core.BaseActor;
import org.sunbird.exception.ProjectCommonException;
import org.sunbird.exception.ResponseCode;
import org.sunbird.kafka.InstructionEventGenerator;
import org.sunbird.keys.JsonKey;
import org.sunbird.request.Request;
import org.sunbird.request.RequestContext;
import org.sunbird.response.Response;
import org.sunbird.response.ResponseParams;
import org.sunbird.service.user.UserRoleService;
import org.sunbird.service.user.UserService;
import org.sunbird.service.user.impl.UserRoleServiceImpl;
import org.sunbird.service.user.impl.UserServiceImpl;
import org.sunbird.util.ProjectUtil;
import org.sunbird.util.PropertiesCache;

import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;

import static org.sunbird.validator.orgvalidator.BaseOrgRequestValidator.ERROR_CODE;

public class UserOwnershipTransferActor extends BaseActor {

private final UserRoleService userRoleService = UserRoleServiceImpl.getInstance();
private final UserService userService = UserServiceImpl.getInstance();

@Override
public void onReceive(Request request) throws Throwable {
handleOwnershipTransfer(request);
}

private void handleOwnershipTransfer(Request request) {
validateUserDetails(request.getRequest(), request.getRequestContext());
String userId = (String) ((Map<String, Object>) request.getRequest().get(JsonKey.ACTION_BY))
.get(JsonKey.USER_ID);
validateActionByUserRole(userId, request);
List<Map<String, Object>> objects = getObjectsFromRequest(request);
if (!objects.isEmpty()) {
objects.forEach(object -> sendInstructionEvent(request, object));
} else {
sendInstructionEvent(request, Collections.emptyMap());
}
Response response = sendResponse("Ownership transfer process is submitted successfully!");
sender().tell(response, self());
}

private void validateUserDetails(Map<String, Object> data, RequestContext requestContext) {
validateAndProceed(data, JsonKey.ACTION_BY, requestContext);
validateAndProceed(data, JsonKey.FROM_USER, requestContext);
validateAndProceed(data, JsonKey.TO_USER, requestContext);
}

private void validateAndProceed(Map<String, Object> data, String key, RequestContext requestContext) {
if (data.containsKey(key)) {
validateUser(data.get(key), key, requestContext, data);
} else {
throwInvalidRequestDataException(key + " key is not present in the data.");
}
}

private void validateUser(Object userNode, String userLabel, RequestContext requestContext,
Map<String, Object> data) {
if (userNode instanceof Map) {
Map<String, Object> user = (Map<String, Object>) userNode;
String userId = StringUtils.trimToNull(Objects.toString(user.get(JsonKey.USER_ID), ""));
String userName = StringUtils.trimToNull(Objects.toString(user.get(JsonKey.USERNAME), ""));

if (StringUtils.isBlank(StringUtils.trimToNull(userId)) ||
StringUtils.isBlank(StringUtils.trimToNull(userName))) {
throwInvalidRequestDataException("User id / user name key is not present in the " + userLabel);
}

if (validUser(userId, requestContext)) {
validateAndFilterRoles(user, userLabel, data);
} else {
throwClientErrorException();
}
}
}

private void validateAndFilterRoles(Map<String, Object> user, String userLabel, Map<String, Object> data) {
if (!userLabel.equals(JsonKey.ACTION_BY) && !user.containsKey(JsonKey.ROLES)) {
throwInvalidRequestDataException("Roles key is not present for " + userLabel);
}
if (user.containsKey(JsonKey.ROLES)) {
Object roles = user.get(JsonKey.ROLES);
if (roles instanceof List) {
List<?> rolesList = (List<?>) roles;
if (rolesList.isEmpty()) {
throwInvalidRequestDataException("Roles are empty in " + userLabel + " details.");
} else {
List<String> filteredRoles = filterRolesByOrganisationId((List<?>) roles,
(String) data.get(JsonKey.ORGANISATION_ID));
user.put(JsonKey.ROLES, filteredRoles);
}
} else {
throwDataTypeErrorException();
}
}
}

private List<String> filterRolesByOrganisationId(List<?> roles, String targetOrganisationId) {
List<String> filteredRoles = new ArrayList<>();
for (Object role : roles) {
if (role instanceof Map) {
Map<String, Object> roleMap = (Map<String, Object>) role;
List<Map<String, Object>> scopeList = (List<Map<String, Object>>) roleMap.get("scope");
if (scopeList != null && !scopeList.isEmpty()) {
for (Map<String, Object> scope : scopeList) {
String organisationId = (String) scope.get("organisationId");
if (targetOrganisationId.equals(organisationId)) {
filteredRoles.add((String) roleMap.get("role"));
break;
}
}
}
}
}
return filteredRoles;
}

private void throwInvalidRequestDataException(String message) {
throw new ProjectCommonException(
ResponseCode.invalidRequestData,
message,
ResponseCode.CLIENT_ERROR.getResponseCode());
}

private void throwClientErrorException() {
ProjectCommonException.throwClientErrorException(
ResponseCode.invalidParameter,
MessageFormat.format(ResponseCode.invalidParameter.getErrorMessage(), JsonKey.USER_ID));
}

private void throwDataTypeErrorException() {
throw new ProjectCommonException(
ResponseCode.dataTypeError,
ProjectUtil.formatMessage(
ResponseCode.dataTypeError.getErrorMessage(), JsonKey.ROLES, JsonKey.LIST),
ERROR_CODE);
}

private boolean validUser(String userId, RequestContext context) {
return StringUtils.isNotBlank(userId) && userExists(userId, context);
}

private boolean userExists(String userId, RequestContext context) {
try {
userService.getUserById(userId, context);
return true;
} catch (Exception ex) {
return false;
}
}

private void validateActionByUserRole(String userId, Request request) {
List<Map<String, Object>> userRoles = userRoleService.getUserRoles(userId, request.getRequestContext());
boolean hasOrgAdminRole = userRoles.stream().anyMatch(role -> JsonKey.ORG_ADMIN.equals(role.get(JsonKey.ROLE)));
if (!hasOrgAdminRole) {
throw new ProjectCommonException(
ResponseCode.cannotTransferOwnership,
ResponseCode.cannotTransferOwnership.getErrorMessage(),
ResponseCode.CLIENT_ERROR.getResponseCode());
}
}

private List<Map<String, Object>> getObjectsFromRequest(Request request) {
return Optional.ofNullable((List<Map<String, Object>>) request.getRequest().get("objects"))
.orElse(Collections.emptyList());
}

private void sendInstructionEvent(Request request, Map<String, Object> object) {
Map<String, Object> data = prepareEventData(request, object);
CompletableFuture.runAsync(() -> {
try {
PropertiesCache propertiesCache = PropertiesCache.getInstance();
InstructionEventGenerator.pushInstructionEvent(propertiesCache.getProperty(JsonKey.USER_OWNERSHIP_TRANSFER_TOPIC), data);
} catch (Exception e) {
logger.error("Error pushing to instruction event", e);
}
});
}

private Map<String, Object> prepareEventData(Request request, Map<String, Object> object) {
Map<String, Object> actor = Map.of("id", "ownership-transfer", "type", "System");
Map<String, Object> edataBase = Map.of(
JsonKey.ACTION, JsonKey.USER_OWNERSHIP_TRANSFER_ACTION,
JsonKey.ORGANISATION_ID, request.getRequest().get(JsonKey.ORGANISATION_ID),
JsonKey.CONTEXT, request.getRequest().get(JsonKey.CONTEXT),
JsonKey.ACTION_BY, request.getRequest().get(JsonKey.ACTION_BY),
JsonKey.FROM_USER_PROFILE, request.getRequest().get(JsonKey.FROM_USER),
JsonKey.TO_USER_PROFILE, request.getRequest().get(JsonKey.TO_USER)
);
Map<String, Object> edata = new HashMap<>(edataBase);
Map<String, Object> assetInformation = new HashMap<>(object);
edata.put(JsonKey.ASSET_INFORMATION, assetInformation);

Map<String, Object> result = new HashMap<>();
Map<String, Object> fromUserDetails = (Map<String, Object>) request.getRequest().get(JsonKey.FROM_USER);
Map<String, Object> objectDetails = Map.of(JsonKey.ID, fromUserDetails.get(JsonKey.USER_ID), JsonKey.TYPE,
JsonKey.USER);
result.put("actor", actor);
result.put(JsonKey.OBJECT, objectDetails);
result.put(JsonKey.EDATA, edata);
return result;
}

Response sendResponse(String statusMessage) {
Response response = new Response();
response.setId("api.user.ownership.transfer");
response.setVer("1.0");
response.setTs(String.valueOf(Calendar.getInstance().getTime().getTime()));
ResponseParams params = new ResponseParams();
params.setResmsgid(UUID.randomUUID().toString());
params.setStatus(String.valueOf(ResponseParams.StatusType.SUCCESSFUL));
response.setParams(params);
response.setResponseCode(ResponseCode.OK);
Map<String, Object> result = Map.of(JsonKey.STATUS, statusMessage);
response.putAll(result);
return response;
}
}
Loading