diff --git a/jans-cli/cli/jca.yaml b/jans-cli/cli/jca.yaml index 1e64d0ee49a..78baa70a806 100644 --- a/jans-cli/cli/jca.yaml +++ b/jans-cli/cli/jca.yaml @@ -2556,18 +2556,17 @@ paths: patch: tags: - User Management - summary: Update modified properties of user by Inum. - description: Update modified properties of user by Inum. + summary: Patch user properties by Inum. + description: Patch user properties by Inum. operationId: patch-user-by-inum requestBody: content: - application/json-patch+json: + application/json: schema: - type: array - items: - $ref: '#/components/schemas/PatchRequest' - description: String representing patch-document. - example: '[ {op:replace, path: userId, value: test_user_100 } ]' + type: object + $ref: '#/components/schemas/UserPatchRequest' + description: Patch request object + example: '[ {"jsonPatchString": {"op": "add", "path": "userId","value": "test-user" }, "customAttributes": [{"name": "name, displayName, birthdate, email","multiValued": true,"values": ["string"]}]}]' responses: '200': description: OK @@ -6989,5 +6988,20 @@ components: type: array items: $ref: '#/components/schemas/CustomAttribute' + + UserPatchRequest: + title: User Patch Request object + description: UserPatchRequest. + type: object + properties: + jsonPatchString: + type: object + description: Possible errors assosiated with the script. + $ref: '#/components/schemas/PatchRequest' + customAttributes: + description: dn of associated clients with the user. + type: array + items: + $ref: '#/components/schemas/CustomAttribute' \ No newline at end of file diff --git a/jans-config-api/docs/jans-config-api-swagger.yaml b/jans-config-api/docs/jans-config-api-swagger.yaml index 59b736f9cae..78baa70a806 100644 --- a/jans-config-api/docs/jans-config-api-swagger.yaml +++ b/jans-config-api/docs/jans-config-api-swagger.yaml @@ -2553,7 +2553,36 @@ paths: description: Internal Server Error security: - oauth2: [https://jans.io/oauth/config/user.delete] - + patch: + tags: + - User Management + summary: Patch user properties by Inum. + description: Patch user properties by Inum. + operationId: patch-user-by-inum + requestBody: + content: + application/json: + schema: + type: object + $ref: '#/components/schemas/UserPatchRequest' + description: Patch request object + example: '[ {"jsonPatchString": {"op": "add", "path": "userId","value": "test-user" }, "customAttributes": [{"name": "name, displayName, birthdate, email","multiValued": true,"values": ["string"]}]}]' + responses: + '200': + description: OK + content: + application/json: + schema: + title: User Details. + $ref: '#/components/schemas/User' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/user.write] /jans-config-api/scim/user: get: @@ -6959,5 +6988,20 @@ components: type: array items: $ref: '#/components/schemas/CustomAttribute' + + UserPatchRequest: + title: User Patch Request object + description: UserPatchRequest. + type: object + properties: + jsonPatchString: + type: object + description: Possible errors assosiated with the script. + $ref: '#/components/schemas/PatchRequest' + customAttributes: + description: dn of associated clients with the user. + type: array + items: + $ref: '#/components/schemas/CustomAttribute' \ No newline at end of file diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/UserResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/UserResource.java index 017d6717b70..58d599f8ba4 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/UserResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/UserResource.java @@ -51,7 +51,8 @@ public Response getUsers(@DefaultValue(DEFAULT_LIST_SIZE) @QueryParam(value = Ap @DefaultValue("") @QueryParam(value = ApiConstants.PATTERN) String pattern, @DefaultValue(DEFAULT_LIST_START_INDEX) @QueryParam(value = ApiConstants.START_INDEX) int startIndex, @QueryParam(value = ApiConstants.SORT_BY) String sortBy, - @QueryParam(value = ApiConstants.SORT_ORDER) String sortOrder) throws IllegalAccessException, InvocationTargetException { + @QueryParam(value = ApiConstants.SORT_ORDER) String sortOrder) + throws IllegalAccessException, InvocationTargetException { if (logger.isDebugEnabled()) { logger.debug("User search param - limit:{}, pattern:{}, startIndex:{}, sortBy:{}, sortOrder:{}", escapeLog(limit), escapeLog(pattern), escapeLog(startIndex), escapeLog(sortBy), @@ -61,24 +62,26 @@ public Response getUsers(@DefaultValue(DEFAULT_LIST_SIZE) @QueryParam(value = Ap limit, null, ApiConstants.USER_EXCLUDED_ATTRIBUTES); List users = this.doSearch(searchReq); - logger.debug("User search result:{}", users); - + logger.debug("User search result:{}", users); + return Response.ok(users).build(); } @GET @ProtectedApi(scopes = { ApiAccessConstants.USER_WRITE_ACCESS }) @Path(ApiConstants.INUM_PATH) - public Response getUserByInum(@PathParam(ApiConstants.INUM) @NotNull String inum) throws IllegalAccessException, InvocationTargetException { + public Response getUserByInum(@PathParam(ApiConstants.INUM) @NotNull String inum) + throws IllegalAccessException, InvocationTargetException { if (logger.isDebugEnabled()) { logger.debug("User search by inum:{}", escapeLog(inum)); } - User user = userSrv.getUserByInum(inum); + User user = userSrv.getUserBasedOnInum(inum); + checkResourceNotNull(user, USER); logger.debug("user:{}", user); - - //excludedAttributes + + // excludedAttributes user = userSrv.excludedAttributes(user, ApiConstants.USER_EXCLUDED_ATTRIBUTES); - + return Response.ok(user).build(); } @@ -106,27 +109,24 @@ public Response updateUser(@Valid User user) { } @PATCH - //@Consumes(MediaType.APPLICATION_JSON_PATCH_JSON) @ProtectedApi(scopes = { ApiAccessConstants.USER_WRITE_ACCESS }) @Path(ApiConstants.INUM_PATH) - public Response patchUser(@PathParam(ApiConstants.INUM) @NotNull String inum, @NotNull UserPatchRequest userPatchRequest) throws JsonPatchException, IOException { + public Response patchUser(@PathParam(ApiConstants.INUM) @NotNull String inum, + @NotNull UserPatchRequest userPatchRequest) throws JsonPatchException, IOException { if (logger.isDebugEnabled()) { - logger.debug("User:{} to be patched with :{} ", escapeLog(inum),escapeLog(userPatchRequest)); + logger.debug("User:{} to be patched with :{} ", escapeLog(inum), escapeLog(userPatchRequest)); } - logger.error("User:{} to be patched with :{} ", escapeLog(inum),escapeLog(userPatchRequest)); - - //check if user exists - User existingUser = userSrv.getUserByInum(inum); + // check if user exists + User existingUser = userSrv.getUserBasedOnInum(inum); checkResourceNotNull(existingUser, USER); - //patch user + // patch user existingUser = userSrv.patchUser(inum, userPatchRequest); logger.error("Patched user:{}", existingUser); - + return Response.ok(existingUser).build(); } - - + @DELETE @Path(ApiConstants.INUM_PATH) @ProtectedApi(scopes = { ApiAccessConstants.USER_DELETE_ACCESS }) @@ -134,13 +134,13 @@ public Response deleteUser(@PathParam(ApiConstants.INUM) @NotNull String inum) { if (logger.isDebugEnabled()) { logger.debug("User to be deleted - inum:{} ", escapeLog(inum)); } - User user = userSrv.getUserByInum(inum); + User user = userSrv.getUserBasedOnInum(inum); checkResourceNotNull(user, USER); userSrv.removeUser(user); return Response.noContent().build(); } - private List doSearch(SearchRequest searchReq) throws IllegalAccessException, InvocationTargetException{ + private List doSearch(SearchRequest searchReq) throws IllegalAccessException, InvocationTargetException { if (logger.isDebugEnabled()) { logger.debug("User search params - searchReq:{} ", escapeLog(searchReq)); } @@ -158,14 +158,11 @@ private List doSearch(SearchRequest searchReq) throws IllegalAccessExcepti if (logger.isDebugEnabled()) { logger.debug("Users fetched - users:{}", users); } - - //excludedAttributes + + // excludedAttributes users = userSrv.excludedAttributes(users, searchReq.getExcludedAttributesStr()); - + return users; } - - - } diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/UserService.java b/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/UserService.java index ff41639f6a2..a5ceeaef255 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/UserService.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/UserService.java @@ -47,7 +47,7 @@ public class UserService extends io.jans.as.common.service.common.UserService { @Inject private AppConfiguration appConfiguration; - + @Inject AuthUtil authUtil; @@ -86,10 +86,11 @@ public void removeUser(User user) { persistenceEntryManager.removeRecursively(user.getDn(), User.class); } - public User patchUser(String inum, UserPatchRequest userPatchRequest) - throws JsonPatchException, IOException { - - logger.error("Details to patch user inum:{}, UserPatchRequest:{} ", escapeLog(inum), escapeLog(userPatchRequest)); + public User patchUser(String inum, UserPatchRequest userPatchRequest) throws JsonPatchException, IOException { + if (logger.isDebugEnabled()) { + logger.debug("Details to patch user inum:{}, UserPatchRequest:{} ", escapeLog(inum), + escapeLog(userPatchRequest)); + } if (StringHelper.isEmpty(inum)) { return null; } @@ -99,41 +100,51 @@ public User patchUser(String inum, UserPatchRequest userPatchRequest) return null; } - logger.error("User to be patched- user:{}", user); + logger.debug("User to be patched- user:{}", user); // apply direct patch for basic attributes - if( StringUtils.isNotEmpty(userPatchRequest.getJsonPatchString())) { - logger.error("Patch basic attributes"); + if (StringUtils.isNotEmpty(userPatchRequest.getJsonPatchString())) { + logger.debug("Patch basic attributes"); user = Jackson.applyPatch(userPatchRequest.getJsonPatchString(), user); - logger.error("User after patching basic attributes - user:{}", user); + logger.debug("User after patching basic attributes - user:{}", user); } // patch for customAttributes if (userPatchRequest.getCustomAttributes() != null && !userPatchRequest.getCustomAttributes().isEmpty()) { - user = updateCustomAttributes(user, userPatchRequest.getCustomAttributes()); + updateCustomAttributes(user, userPatchRequest.getCustomAttributes()); } - logger.error("User before patch user:{}", user); + logger.debug("User before patch user:{}", user); // persist user user = updateUser(user); - logger.error("User after patch user:{}", user); + logger.debug("User after patch user:{}", user); return user; } + + public User getUserBasedOnInum(String inum) { + User result = null; + try { + result = getUserByInum(inum); + } catch (Exception ex) { + logger.error("Failed to load user entry", ex); + } + return result; + } private User updateCustomAttributes(User user, List customAttributes) { - logger.error("Custom Attributes to update for - user:{}, customAttributes:{} ", user, customAttributes); + logger.debug("Custom Attributes to update for - user:{}, customAttributes:{} ", user, customAttributes); if (customAttributes != null && !customAttributes.isEmpty()) { for (CustomObjectAttribute attribute : customAttributes) { CustomObjectAttribute existingAttribute = getCustomAttribute(user, attribute.getName()); - logger.error("Existing CustomAttributes with existingAttribute:{} ", existingAttribute); + logger.debug("Existing CustomAttributes with existingAttribute:{} ", existingAttribute); // add if (existingAttribute == null) { boolean result = addUserAttribute(user, attribute.getName(), attribute.getValues(), attribute.isMultiValued()); - logger.error("Result of adding CustomAttributes attribute:{} , result:{} ", attribute, result); + logger.debug("Result of adding CustomAttributes attribute:{} , result:{} ", attribute, result); } // remove attribute else if (attribute.getValue() == null || attribute.getValues() == null) { @@ -146,44 +157,46 @@ else if (attribute.getValue() == null || attribute.getValues() == null) { existingAttribute.setValues(attribute.getValues()); } // Final attribute - logger.error("Finally user CustomAttributes user.getCustomAttributes:{} ", user.getCustomAttributes()); + logger.debug("Finally user CustomAttributes user.getCustomAttributes:{} ", user.getCustomAttributes()); } } return user; } - - public List excludedAttributes(List users, String commaSeparatedString) throws IllegalAccessException, InvocationTargetException { - logger.error("Attributes:{} to be excluded from users:{} ", commaSeparatedString, users); - for(User user: users) { - user = excludedAttributes(user, commaSeparatedString); + + public List excludedAttributes(List users, String commaSeparatedString) + throws IllegalAccessException, InvocationTargetException { + logger.debug("Attributes:{} to be excluded from users:{} ", commaSeparatedString, users); + for (User user : users) { + excludedAttributes(user, commaSeparatedString); } - logger.error("Users:{} after excluding attribute:{} ", users, commaSeparatedString); - + logger.debug("Users:{} after excluding attribute:{} ", users, commaSeparatedString); + return users; } - - public User excludedAttributes(User user, String commaSeparatedString) throws IllegalAccessException, InvocationTargetException { - logger.error("Attributes:{} to be excluded from user:{} ", commaSeparatedString, user); - if(user == null || StringUtils.isEmpty(commaSeparatedString)) { + + public User excludedAttributes(User user, String commaSeparatedString) + throws IllegalAccessException, InvocationTargetException { + logger.debug("Attributes:{} to be excluded from user:{} ", commaSeparatedString, user); + if (user == null || StringUtils.isEmpty(commaSeparatedString)) { return user; } List excludedAttributes = Arrays.asList(commaSeparatedString.split(",")); - logger.error("Attributes List:{} to be excluded ", excludedAttributes); - - for(String attribute : excludedAttributes) { - logger.error("User class conatins attribute:{} ? :{} ", attribute, authUtil.doesObjectContainField(user,attribute)); - if(authUtil.doesObjectContainField(user,attribute)) { - BeanUtils.setProperty(user,attribute,null); + logger.debug("Attributes List:{} to be excluded ", excludedAttributes); - } - else { - logger.error("Removing custom attribute:{} from user:{} ", attribute, user); + for (String attribute : excludedAttributes) { + logger.debug("User class conatins attribute:{} ? :{} ", attribute, + authUtil.doesObjectContainField(user, attribute)); + if (authUtil.doesObjectContainField(user, attribute)) { + BeanUtils.setProperty(user, attribute, null); + + } else { + logger.debug("Removing custom attribute:{} from user:{} ", attribute, user); user.removeAttribute(attribute); } - } - + } + return user; } diff --git a/jans-config-api/server/src/test/resources/feature/user/user-patch.json b/jans-config-api/server/src/test/resources/feature/user/user-patch.json new file mode 100644 index 00000000000..cbcde1c6a7f --- /dev/null +++ b/jans-config-api/server/src/test/resources/feature/user/user-patch.json @@ -0,0 +1,40 @@ +{ + "jsonPatchString": "[ {\"op\":\"add\", \"path\": \"/userId\", \"value\":\"config_test_user_100_final_patch\" } ]", + "customAttributes": [{ + "name": "emailVerified", + "multiValued": false, + "values": [ + "TRUE" + ], + "value": "TRUE", + "displayValue": "TRUE" + }, + { + "name": "secretAnswer", + "multiValued": false, + "values": [ + "james-bond@123" + ], + "value": "james-bond@123", + "displayValue": "james-bond@123" + }, + { + "name": "jansImsValue", + "multiValued": true, + "values": [{ + "value": "123456", + "display": "Home phone", + "type": "home", + "primary": true + }, + { + "value": "9821789", + "display": "Work phone", + "type": "work", + "primary": false + } + + ] + } + ] +} \ No newline at end of file diff --git a/jans-config-api/server/src/test/resources/feature/user/user.feature b/jans-config-api/server/src/test/resources/feature/user/user.feature index dc045a067be..582e1cb4b8b 100644 --- a/jans-config-api/server/src/test/resources/feature/user/user.feature +++ b/jans-config-api/server/src/test/resources/feature/user/user.feature @@ -3,6 +3,22 @@ Feature: User endpoint Background: * def mainUrl = user_url + * def funGetCustomAttributes = +""" +function(attributes_array,attribute_name) { +print(' attributes_array = '+attributes_array); +print(' attribute_name = '+attribute_name); +var attribute_value; +for (var i = 0; i < attributes_array.length; i++) { +print(' attributes_array[i] = '+attributes_array[i]); +if ( attributes_array[i].name == attribute_name ){ + attribute_value = attributes_array[i].value; + print(' attribute_value= '+attribute_value); +} +} +return attribute_value; +} +""" Scenario: Fetch all user without bearer token Given url mainUrl @@ -28,14 +44,15 @@ Then status 200 And print response And assert response.length == 3 -@ignore + Scenario: Get an user by inum(unexisting user) Given url mainUrl + '/53553532727272772' And header Authorization = 'Bearer ' + accessToken When method GET +And print response Then status 404 -@ignore + Scenario: Delete a non-existion user by inum Given url mainUrl + '/1402.66633-8675-473e-a749' And header Authorization = 'Bearer ' + accessToken @@ -44,3 +61,44 @@ Then status 404 And print response +@CreateUpdateDelete +Scenario: Create new user, update and delete + Given url mainUrl + And header Authorization = 'Bearer ' + accessToken + And request read('user.json') + When method POST + Then status 201 + And print response + Then def result = response + And print result + And assert result != null + And assert result.customAttributes.length != null + Then def inum = funGetCustomAttributes(result.customAttributes,'inum') + And print inum + And assert inum != null + And print 'Updating user ' + '-' +inum + And print result.userId + Given url mainUrl + And header Authorization = 'Bearer ' + accessToken + And print result + And request result + When method PUT + Then status 200 + And print response + And print 'Successfully updated user' + And print response.userId + And print 'Patching user ' + '-' +response.userId + '-' +inum + Given url mainUrl + '/' +inum + And header Authorization = 'Bearer ' + accessToken + And request read('user-patch.json') + When method PATCH + Then status 200 + And print response + Then def result = response + And print 'About to delete user' + '-' +inum + Given url mainUrl + '/' +inum + And header Authorization = 'Bearer ' + accessToken + When method DELETE + Then status 204 + And print response + And print 'User successfully deleted' diff --git a/jans-config-api/server/src/test/resources/feature/user/user.json b/jans-config-api/server/src/test/resources/feature/user/user.json index d87fa615038..7ccf1434a05 100644 --- a/jans-config-api/server/src/test/resources/feature/user/user.json +++ b/jans-config-api/server/src/test/resources/feature/user/user.json @@ -1,5 +1,5 @@ { - "userId": "config_test_user_1", + "userId": "config_test_user_01", "customAttributes": [ { "name": "birthdate", @@ -23,10 +23,10 @@ "name": "cn", "multiValued": false, "values": [ - "Config Test User 1" + "Config Test User 01" ], - "value": "Config Test User 1", - "displayValue": "Config Test User 1" + "value": "Config Test User 01", + "displayValue": "Config Test User 01" }, { "name": "displayName", @@ -34,8 +34,8 @@ "values": [ "Config Test User" ], - "value": "Config Test User", - "displayValue": "Config Test User" + "value": "Config Test User 01", + "displayValue": "Config Test User 01" }, { "name": "emailVerified", @@ -70,8 +70,8 @@ "values": [ "config_user_1" ], - "value": "config_user_1", - "displayValue": "config_user_1" + "value": "config_user_01", + "displayValue": "config_user_01" }, { "name": "jansStatus", @@ -104,10 +104,10 @@ "name": "mail", "multiValued": false, "values": [ - "config_user_1@test.org" + "config_user_01@test.org" ], - "value": "config_user_1@test.org", - "displayValue": "config_user_1@test.org" + "value": "config_user_01@test.org", + "displayValue": "config_user_01@test.org" }, { "name": "memberOf", @@ -123,10 +123,10 @@ "name": "middleName", "multiValued": false, "values": [ - "config_user_1" + "config_user_01" ], - "value": "config_user_1", - "displayValue": "config_user_1" + "value": "config_user_01", + "displayValue": "config_user_01" }, { "name": "mobile", @@ -143,8 +143,8 @@ "values": [ "user1" ], - "value": "user1", - "displayValue": "user1" + "value": "user01", + "displayValue": "user01" }, { "name": "o", @@ -227,15 +227,6 @@ "value": "(512) 516-2414", "displayValue": "(512) 516-2414" }, - { - "name": "userPassword", - "multiValued": false, - "values": [ - "{SSHA512}gFcj2ucCCyO8NLN+IfBIbdzr3xjI7b07vAdwkJ8sw9Ynghj4uWPjfSHI2pHZ9dsyAdL3+mFl8g0ywxp6jcKs3exo/gRLQtre" - ], - "value": "{SSHA512}gFcj2ucCCyO8NLN+IfBIbdzr3xjI7b07vAdwkJ8sw9Ynghj4uWPjfSHI2pHZ9dsyAdL3+mFl8g0ywxp6jcKs3exo/gRLQtre", - "displayValue": "{SSHA512}gFcj2ucCCyO8NLN+IfBIbdzr3xjI7b07vAdwkJ8sw9Ynghj4uWPjfSHI2pHZ9dsyAdL3+mFl8g0ywxp6jcKs3exo/gRLQtre" - }, { "name": "website", "multiValued": false, @@ -254,9 +245,5 @@ "value": "America/Chicago", "displayValue": "America/Chicago" } - ], - "customObjectClasses": [ - "top", - "jansCustomPerson" - ] + ] } \ No newline at end of file