From 9d444bdf17668c4d670b1feb12fbdf1f4b8e2869 Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Fri, 22 Jul 2022 15:54:23 +0530 Subject: [PATCH 01/21] bug(jans-config-api): fixed swagger format issue --- jans-config-api/docs/jans-config-api-swagger.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jans-config-api/docs/jans-config-api-swagger.yaml b/jans-config-api/docs/jans-config-api-swagger.yaml index 79bc7565b99..1946267b4ab 100644 --- a/jans-config-api/docs/jans-config-api-swagger.yaml +++ b/jans-config-api/docs/jans-config-api-swagger.yaml @@ -4770,10 +4770,10 @@ components: example: '\"RS256\", \"RS512\", \"ES384\", \"PS256\"' discoveryDenyKeys: type: array - description: List of configuration response claims which must not be displayed in discovery endpoint response. - items: - type: string - example: 'id_generation_endpoint, auth_level_mapping, etc.' + description: List of configuration response claims which must not be displayed in discovery endpoint response. + items: + type: string + example: 'id_generation_endpoint, auth_level_mapping, etc.' discoveryAllowedKeys: type: array description: List of configuration response claim allowed to be displayed in discovery endpoint. From ac97cc6f8b0ba8680010168da402969701d9a9f3 Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Mon, 25 Jul 2022 15:42:48 +0530 Subject: [PATCH 02/21] fix(jans-config-api): fixed due to couchbase clustter change --- .../auth/CouchbaseConfigurationResource.java | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/CouchbaseConfigurationResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/CouchbaseConfigurationResource.java index 8f1669f0342..38b1bb769da 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/CouchbaseConfigurationResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/CouchbaseConfigurationResource.java @@ -6,7 +6,8 @@ package io.jans.configapi.rest.resource.auth; -import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; +import com.couchbase.client.java.env.ClusterEnvironment; +import com.github.fge.jsonpatch.JsonPatchException; import com.google.common.base.Joiner; import io.jans.configapi.core.rest.ProtectedApi; import io.jans.configapi.service.auth.CouchbaseConfService; @@ -15,6 +16,7 @@ import io.jans.configapi.core.util.Jackson; import io.jans.orm.couchbase.model.CouchbaseConnectionConfiguration; import io.jans.orm.couchbase.operation.impl.CouchbaseConnectionProvider; + import org.slf4j.Logger; import jakarta.inject.Inject; @@ -23,6 +25,8 @@ import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; + +import java.io.IOException; import java.util.Optional; import java.util.Properties; @@ -31,12 +35,9 @@ @Consumes(MediaType.APPLICATION_JSON) public class CouchbaseConfigurationResource extends ConfigBaseResource { - @Inject - Logger log; - @Inject CouchbaseConfService couchbaseConfService; - + @GET @ProtectedApi(scopes = { ApiAccessConstants.DATABASE_COUCHBASE_READ_ACCESS }) public Response get() { @@ -47,14 +48,14 @@ public Response get() { @Path(ApiConstants.NAME_PARAM_PATH) @ProtectedApi(scopes = { ApiAccessConstants.DATABASE_COUCHBASE_READ_ACCESS }) public Response getWithName(@PathParam(ApiConstants.NAME) String name) { - log.debug("CouchbaseConfigurationResource::getWithName() - name = " + name + "\n\n"); + logger.debug("CouchbaseConfigurationResource::getWithName() - name:{}" ,name); return Response.ok(findByName(name)).build(); } @POST @ProtectedApi(scopes = { ApiAccessConstants.DATABASE_COUCHBASE_WRITE_ACCESS }) public Response add(@Valid @NotNull CouchbaseConnectionConfiguration conf) { - log.debug("COUCHBASE details to be added - conf = " + conf); + logger.debug("COUCHBASE details to be added - conf:{}",conf); couchbaseConfService.save(conf); conf = findByName(conf.getConfigId()); return Response.status(Response.Status.CREATED).entity(conf).build(); @@ -63,7 +64,7 @@ public Response add(@Valid @NotNull CouchbaseConnectionConfiguration conf) { @PUT @ProtectedApi(scopes = { ApiAccessConstants.DATABASE_COUCHBASE_WRITE_ACCESS }) public Response update(@Valid @NotNull CouchbaseConnectionConfiguration conf) { - log.debug("COUCHBASE details to be updated - conf = " + conf); + logger.debug("COUCHBASE details to be updated - conf:{}",conf); findByName(conf.getConfigId()); couchbaseConfService.save(conf); return Response.ok(conf).build(); @@ -73,9 +74,9 @@ public Response update(@Valid @NotNull CouchbaseConnectionConfiguration conf) { @Path(ApiConstants.NAME_PARAM_PATH) @ProtectedApi(scopes = { ApiAccessConstants.DATABASE_COUCHBASE_DELETE_ACCESS }) public Response delete(@PathParam(ApiConstants.NAME) String name) { - log.debug("COUCHBASE to be deleted - name = " + name); + logger.debug("COUCHBASE to be deleted - name:{}",name); findByName(name); - log.trace("Delete configuration by name " + name); + logger.trace("Delete configuration by name:{} ",name); this.couchbaseConfService.remove(name); return Response.noContent().build(); } @@ -84,10 +85,10 @@ public Response delete(@PathParam(ApiConstants.NAME) String name) { @Path(ApiConstants.NAME_PARAM_PATH) @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON) @ProtectedApi(scopes = { ApiAccessConstants.DATABASE_COUCHBASE_WRITE_ACCESS }) - public Response patch(@PathParam(ApiConstants.NAME) String name, @NotNull String requestString) throws Exception { - log.debug("COUCHBASE to be patched - name = " + name + " , requestString = " + requestString); + public Response patch(@PathParam(ApiConstants.NAME) String name, @NotNull String requestString) throws JsonPatchException, IOException { + logger.debug("COUCHBASE to be patched - name:{}, requestString:{}", name, requestString); CouchbaseConnectionConfiguration conf = findByName(name); - log.info("Patch configuration by name " + name); + logger.info("Patch configuration by name:{} ",name); conf = Jackson.applyPatch(requestString, conf); couchbaseConfService.save(conf); return Response.ok(conf).build(); @@ -97,7 +98,7 @@ public Response patch(@PathParam(ApiConstants.NAME) String name, @NotNull String @Path(ApiConstants.TEST) @ProtectedApi(scopes = { ApiAccessConstants.DATABASE_COUCHBASE_READ_ACCESS }) public Response test(@Valid @NotNull CouchbaseConnectionConfiguration conf) { - log.debug("COUCHBASE to be tested - conf = " + conf); + logger.debug("COUCHBASE to be tested - conf:{}",conf); Properties properties = new Properties(); properties.put("couchbase.servers", Joiner.on(",").join(conf.getServers())); @@ -107,15 +108,17 @@ public Response test(@Valid @NotNull CouchbaseConnectionConfiguration conf) { properties.put("couchbase.bucket.default", conf.getDefaultBucket()); properties.put("couchbase.password.encryption.method", conf.getPasswordEncryptionMethod()); - CouchbaseConnectionProvider connectionProvider = new CouchbaseConnectionProvider(properties, - DefaultCouchbaseEnvironment.create()); + ClusterEnvironment.Builder clusterEnvironmentBuilder = ClusterEnvironment.builder(); + ClusterEnvironment clusterEnvironment = clusterEnvironmentBuilder.build(); + logger.error("clusterEnvironment:{}",clusterEnvironment); + CouchbaseConnectionProvider connectionProvider = new CouchbaseConnectionProvider(properties, clusterEnvironment); return Response.ok(connectionProvider.isConnected()).build(); } private CouchbaseConnectionConfiguration findByName(String name) { final Optional optional = this.couchbaseConfService.findByName(name); if (optional.isEmpty()) { - log.trace("Could not find configuration by name '" + name + "'"); + logger.trace("Could not find configuration by name:{}", name); throw new NotFoundException(getNotFoundError("Configuration - '" + name + "'")); } return optional.get(); From c8003fda8119bd253f28d980ae17776ce02dedd6 Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Wed, 27 Jul 2022 18:33:37 +0530 Subject: [PATCH 03/21] feat(jans-config-api): new endpoint to get UmaResource based on associatedClient --- .../io/jans/configapi/util/ApiConstants.java | 2 + .../docs/jans-config-api-swagger.yaml | 32 ++++ .../resource/auth/UmaResourcesResource.java | 39 ++++- .../service/auth/UmaResourceService.java | 26 ++- .../feature/openid/clients/clients.feature | 10 ++ .../openid/clients/openid_clients_create.json | 154 ++++++++++++++++++ .../src/test/resources/karate.properties | 7 +- .../server/src/test/resources/test.properties | 15 +- 8 files changed, 265 insertions(+), 20 deletions(-) create mode 100644 jans-config-api/server/src/test/resources/feature/openid/clients/openid_clients_create.json diff --git a/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java b/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java index 79b3dac5420..77f41617198 100644 --- a/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java +++ b/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java @@ -74,6 +74,7 @@ private ApiConstants() {} public static final String ORG = "/org"; public static final String SERVER_STAT = "/server-stat"; public static final String USERNAME_PATH = "/{username}"; + public static final String CLIENTID_PATH = "/{clientId}"; public static final String LIMIT = "limit"; public static final String START_INDEX = "startIndex"; @@ -86,6 +87,7 @@ private ApiConstants() {} public static final String NAME = "name"; public static final String DISPLAY_NAME = "displayName"; public static final String KID = "kid"; + public static final String CLIENTID = "clientId"; public static final String ALL = "all"; public static final String ACTIVE = "active"; diff --git a/jans-config-api/docs/jans-config-api-swagger.yaml b/jans-config-api/docs/jans-config-api-swagger.yaml index 1946267b4ab..f16d1739746 100644 --- a/jans-config-api/docs/jans-config-api-swagger.yaml +++ b/jans-config-api/docs/jans-config-api-swagger.yaml @@ -2168,6 +2168,38 @@ paths: description: Internal Server Error security: - oauth2: [https://jans.io/oauth/config/uma/resources.readonly] + /jans-config-api/api/v1/uma/resources/{clientId}: + parameters: + - name: clientId + in: path + required: true + description: Client ID. + schema: + type: string + get: + tags: + - OAuth - UMA Resources + summary: Fetch uma resources by client id. + description: Fetch uma resources by client id. + operationId: get-oauth-uma-resources-by-clientid + responses: + '200': + description: OK + content: + application/json: + schema: + title: UMA Resource list. + description: List of UMA Resource. + items: + $ref: '#/components/schemas/UmaResource' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/uma/resources.readonly] delete: tags: - OAuth - UMA Resources diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/UmaResourcesResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/UmaResourcesResource.java index 3ac77c1d39c..2e69c22ad42 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/UmaResourcesResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/UmaResourcesResource.java @@ -9,6 +9,7 @@ import com.github.fge.jsonpatch.JsonPatchException; import io.jans.as.model.uma.persistence.UmaResource; import io.jans.configapi.core.rest.ProtectedApi; +import io.jans.configapi.service.auth.ClientService; import io.jans.configapi.service.auth.UmaResourceService; import io.jans.configapi.util.ApiAccessConstants; import io.jans.configapi.util.ApiConstants; @@ -40,17 +41,17 @@ public class UmaResourcesResource extends ConfigBaseResource { private static final String UMA_RESOURCE = "Uma resource"; @Inject - Logger log; + UmaResourceService umaResourceService; @Inject - UmaResourceService umaResourceService; + ClientService clientService; @GET @ProtectedApi(scopes = { ApiAccessConstants.UMA_RESOURCES_READ_ACCESS }) public Response fetchUmaResources( @DefaultValue(DEFAULT_LIST_SIZE) @QueryParam(value = ApiConstants.LIMIT) int limit, @DefaultValue("") @QueryParam(value = ApiConstants.PATTERN) String pattern) { - log.debug("UMA_RESOURCE to be fetched - limit = " + limit + " , pattern = " + pattern); + logger.debug("UMA_RESOURCE to be fetched - limit:{}, pattern:{}", limit, pattern); final List resources; if (!pattern.isEmpty() && pattern.length() >= 2) { resources = umaResourceService.findResources(pattern, 1000); @@ -63,15 +64,25 @@ public Response fetchUmaResources( @GET @Path(ApiConstants.ID_PATH) @ProtectedApi(scopes = { ApiAccessConstants.UMA_RESOURCES_READ_ACCESS }) - public Response getUmaResourceByImun(@PathParam(value = ApiConstants.ID) @NotNull String id) { - log.debug("UMA_RESOURCE to fetch by id = " + id); + public Response getUmaResourceByInum(@PathParam(value = ApiConstants.ID) @NotNull String id) { + logger.debug("UMA_RESOURCE to fetch by id:{}", id); return Response.ok(findOrThrow(id)).build(); } + @GET + @Path("/"+ApiConstants.CLIENTID + ApiConstants.CLIENTID_PATH) + @ProtectedApi(scopes = { ApiAccessConstants.UMA_RESOURCES_READ_ACCESS }) + public Response getUmaResourceByAssociatedClient( + @PathParam(value = ApiConstants.CLIENTID) @NotNull String associatedClientId) { + logger.debug("UMA_RESOURCE to fetch by associatedClientId:{} ", associatedClientId); + + return Response.ok(getUmaResourceByClient(associatedClientId)).build(); + } + @POST @ProtectedApi(scopes = { ApiAccessConstants.UMA_RESOURCES_WRITE_ACCESS }) public Response createUmaResource(@Valid UmaResource umaResource) { - log.debug("UMA_RESOURCE to be added umaResource = " + umaResource); + logger.debug("UMA_RESOURCE to be added umaResource:{}", umaResource); checkNotNull(umaResource.getName(), AttributeNames.NAME); checkNotNull(umaResource.getDescription(), AttributeNames.DESCRIPTION); String id = UUID.randomUUID().toString(); @@ -96,7 +107,7 @@ private UmaResource findOrThrow(String id) { @PUT @ProtectedApi(scopes = { ApiAccessConstants.UMA_RESOURCES_WRITE_ACCESS }) public Response updateUmaResource(@Valid UmaResource resource) { - log.debug("UMA_RESOURCE to be upated - umaResource = " + resource); + logger.debug("UMA_RESOURCE to be upated - umaResource:{}", resource); String id = resource.getId(); checkNotNull(id, AttributeNames.ID); UmaResource existingResource = findOrThrow(id); @@ -113,7 +124,7 @@ public Response updateUmaResource(@Valid UmaResource resource) { @Path(ApiConstants.ID_PATH) public Response patchResource(@PathParam(ApiConstants.ID) @NotNull String id, @NotNull String pathString) throws JsonPatchException, IOException { - log.debug("UMA_RESOURCE to be patched - id = " + id + " , pathString = " + pathString); + logger.debug("Patch for id:{} , pathString:{}", id, pathString); UmaResource existingResource = findOrThrow(id); existingResource = Jackson.applyPatch(pathString, existingResource); @@ -125,9 +136,19 @@ public Response patchResource(@PathParam(ApiConstants.ID) @NotNull String id, @N @Path(ApiConstants.ID_PATH) @ProtectedApi(scopes = { ApiAccessConstants.UMA_RESOURCES_DELETE_ACCESS }) public Response deleteUmaResource(@PathParam(value = ApiConstants.ID) @NotNull String id) { - log.debug("UMA_RESOURCE to delete - id = " + id); + logger.debug("UMA_RESOURCE to delete - id:{}", id); UmaResource umaResource = findOrThrow(id); umaResourceService.remove(umaResource); return Response.status(Response.Status.NO_CONTENT).build(); } + + private List getUmaResourceByClient(String associatedClientId) { + logger.debug("UMA RESOURCE to be fetched based on associatedClientId:{}", associatedClientId); + + // Get client DN + String associatedClientDn = this.clientService.getDnForClient(associatedClientId); + logger.debug("UMA RESOURCE to be fetched based on associatedClientId:{}", associatedClientId); + + return umaResourceService.getResourcesByClient(associatedClientDn); + } } diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/UmaResourceService.java b/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/UmaResourceService.java index b2a5769ea73..5f1a25b9ab5 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/UmaResourceService.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/UmaResourceService.java @@ -17,8 +17,12 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; + +import java.util.Collections; import java.util.List; +import org.slf4j.Logger; + /** * @author Yuriy Zabrovarnyy */ @@ -31,6 +35,9 @@ public class UmaResourceService { @Inject private StaticConfiguration staticConfiguration; + @Inject + private Logger logger; + public void addBranch() { SimpleBranch branch = new SimpleBranch(); branch.setOrganizationalUnitName("resources"); @@ -49,12 +56,13 @@ public List findResources(String pattern, int sizeLimit) { } public List findResourcesByName(String name, int sizeLimit) { + if (StringUtils.isNotBlank(name)) { Filter searchFilter = Filter.createEqualityFilter(AttributeConstants.DISPLAY_NAME, name); return persistenceEntryManager.findEntries(getDnForResource(null), UmaResource.class, searchFilter, sizeLimit); } - return null; + return Collections.emptyList(); } public List getAllResources(int sizeLimit) { @@ -83,6 +91,22 @@ public UmaResource getResourceById(String id) { return persistenceEntryManager.find(UmaResource.class, dn); } + public List getResourcesByClient(String clientDn) { + try { + logger.debug(" Fetch UmaResource based on client - clientDn:{} ", clientDn); + prepareBranch(); + + if (StringUtils.isNotBlank(clientDn)) { + return persistenceEntryManager.findEntries(getBaseDnForResource(), UmaResource.class, + Filter.createEqualityFilter("jansAssociatedClnt", clientDn)); + } + + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + return Collections.emptyList(); + } + private void prepareBranch() { if (!persistenceEntryManager.hasBranchesSupport(getDnForResource(null))) { return; diff --git a/jans-config-api/server/src/test/resources/feature/openid/clients/clients.feature b/jans-config-api/server/src/test/resources/feature/openid/clients/clients.feature index e37b54d7e81..6ccffc4b618 100644 --- a/jans-config-api/server/src/test/resources/feature/openid/clients/clients.feature +++ b/jans-config-api/server/src/test/resources/feature/openid/clients/clients.feature @@ -128,3 +128,13 @@ Then status 200 And print response And assert response.length !=0 +@CreateUpdateDelete +Scenario: Create new OpenId Connect Client +Given url mainUrl +And header Authorization = 'Bearer ' + accessToken +And request read('openid_clients_create.json') +When method POST +Then status 201 +And print response + + diff --git a/jans-config-api/server/src/test/resources/feature/openid/clients/openid_clients_create.json b/jans-config-api/server/src/test/resources/feature/openid/clients/openid_clients_create.json new file mode 100644 index 00000000000..3fc37f29cb2 --- /dev/null +++ b/jans-config-api/server/src/test/resources/feature/openid/clients/openid_clients_create.json @@ -0,0 +1,154 @@ +{ + "clientSecret": "test@123", + "clientName": "ABCDE", + "description": "sdjkhfjksdfjksdkfj", + "applicationType": "web", + "subjectType": "pairwise", + "initiateLoginUri": "DFGDFDFG", + "logoUri": "sdfsdfsdf", + "clientUri": "sdfsdf", + "tosUri": "fddfdfgdf", + "jwksUri": "JKHJKHSDHKJSDFH", + "jwks": "JKJKHJKJHK", + "expirable": [ + "on" + ], + "expirationDate": "2022-07-25T09:30:00.656Z", + "softwareStatement": "12", + "softwareVersion": "11", + "softwareId": "123", + "softwareSection": false, + "cibaSection": false, + "idTokenSignedResponseAlg": "HS256", + "idTokenEncryptedResponseAlg": "A128KW", + "tokenEndpointAuthMethod": "client_secret_post", + "accessTokenSigningAlg": "HS384", + "idTokenEncryptedResponseEnc": "A256CBC+HS512", + "requestObjectEncryptionAlg": "RSA-OAEP", + "requestObjectSigningAlg": "HS256", + "requestObjectEncryptionEnc": "A256CBC+HS512", + "userInfoEncryptedResponseAlg": "A128KW", + "userInfoSignedResponseAlg": "RS256", + "userInfoEncryptedResponseEnc": "A128GCM", + "idTokenTokenBindingCnf": "dsfsdfdsf", + "backchannelUserCodeParameter": true, + "refreshTokenLifetime": 2, + "defaultMaxAge": 2, + "accessTokenLifetime": 12, + "backchannelTokenDeliveryMode": "push", + "backchannelClientNotificationEndpoint": "sdhjfjhsdf", + "frontChannelLogoutUri": "dfdsdgsdg", + "policyUri": "sfsdsdf", + "sectorIdentifierUri": "HJKFDJHKHJKDS", + "redirectUris": [ + "sdfsdfsdf" + ], + "claimRedirectUris": [ + "NBMNSMNBSDMV" + ], + "authorizedOrigins": [ + "dgffddfg" + ], + "requestUris": [ + "FDDGDFGG" + ], + "postLogoutRedirectUris": [ + "sdfsdfds" + ], + "responseTypes": [ + "token" + ], + "grantTypes": [ + "implicit" + ], + "contacts": [ + "ashash@ggg.ccc" + ], + "defaultAcrValues": [ + "inum=3000-F75A,ou=scripts,o=jans" + ], + "scopes": [ + "inum=10B2,ou=scopes,o=jans" + ], + "attributes": { + "tlsClientAuthSubjectDn": "123", + "runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims": "true", + "keepClientAuthorizationAfterExpiration": false, + "allowSpontaneousScopes": "true", + "backchannelLogoutSessionRequired": "true", + "backchannelLogoutUri": [ + "dsfsdfsdf" + ], + "rptClaimsScripts": [], + "consentGatheringScripts": [], + "spontaneousScopeScriptDns": [], + "introspectionScripts": [ + "inum=A44E-4F3D,ou=scripts,o=jans" + ], + "postAuthnScripts": [], + "additionalAudience": [ + "dsfsdfsdf" + ], + "spontaneousScopes": [ + "inum=1200.6928E4,ou=scopes,o=jans" + ], + "redirectUrisRegex": "sdfsdf", + "parLifetime": 3600, + "requirePar": true, + "defaultPromptLogin": true, + "jansAuthorizedAcr": [ + "inum=3000-F75A,ou=scripts,o=jans" + ], + "updateTokenScriptDns": [], + "ropcScripts": [], + "jansAuthSignedRespAlg": "HS384", + "authorizationEncryptedResponseAlg": "A128KW", + "authorizationEncryptedResponseEnc": "A128GCM" + }, + "tlsClientAuthSubjectDn": "123", + "frontChannelLogoutSessionRequired": true, + "runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims": "true", + "backchannelLogoutSessionRequired": "true", + "keepClientAuthorizationAfterExpiration": false, + "allowSpontaneousScopes": "true", + "spontaneousScopes": [ + "inum=1200.6928E4,ou=scopes,o=jans" + ], + "introspectionScripts": [ + "inum=A44E-4F3D,ou=scripts,o=jans" + ], + "spontaneousScopeScriptDns": [], + "consentGatheringScripts": [], + "redirectUrisRegex": "sdfsdf", + "parLifetime": "bnxcbmx", + "requirePar": true, + "updateTokenScriptDns": [], + "ropcScripts": [], + "jansAuthEncRespAlg": "HS384", + "authorizationEncryptedResponseAlg": "A128KW", + "authorizationEncryptedResponseEnc": "A128GCM", + "postAuthnScripts": [], + "rptClaimsScripts": [], + "additionalAudience": [ + "dsfsdfsdf" + ], + "backchannelLogoutUri": [ + "dsfsdfsdf" + ], + "defaultPromptLogin": true, + "authorizedAcrValues": [ + "inum=3000-F75A,ou=scripts,o=jans" + ], + "customObjectClasses": [], + "requireAuthTime": true, + "trustedClient": true, + "persistClientAuthorizations": true, + "includeClaimsInIdToken": true, + "rptAsJwt": true, + "accessTokenAsJwt": true, + "disabled": true, + "claimRedirectURIs": [ + "NBMNSMNBSDMV" + ], + "action_message": "SDN KD KJDK " +} \ No newline at end of file diff --git a/jans-config-api/server/src/test/resources/karate.properties b/jans-config-api/server/src/test/resources/karate.properties index 41c0d369aff..a849093586c 100644 --- a/jans-config-api/server/src/test/resources/karate.properties +++ b/jans-config-api/server/src/test/resources/karate.properties @@ -1,5 +1,6 @@ -#karate.test.url=http://localhost -#karate.test.port=8080 +#LOCAL +karate.test.url=http://localhost +karate.test.port=8080 #karate.test.url=https://jenkins-config-api.gluu.org/jans-config-api #karate.test.port=443 -karate.test.url=${test.server} +#karate.test.url=https://jenkins-config-api.gluu.org diff --git a/jans-config-api/server/src/test/resources/test.properties b/jans-config-api/server/src/test/resources/test.properties index 4257f297907..2d54c91a81e 100644 --- a/jans-config-api/server/src/test/resources/test.properties +++ b/jans-config-api/server/src/test/resources/test.properties @@ -1,8 +1,9 @@ -test.scopes=${test.scopes} +#LOCAL +test.scopes=https://jans.io/oauth/config/acrs.readonly https://jans.io/oauth/config/acrs.write https://jans.io/oauth/config/attributes.readonly https://jans.io/oauth/config/attributes.write https://jans.io/oauth/config/attributes.delete https://jans.io/oauth/config/cache.readonly https://jans.io/oauth/config/cache.write https://jans.io/oauth/config/openid/clients.readonly https://jans.io/oauth/config/openid/clients.write https://jans.io/oauth/config/openid/clients.delete https://jans.io/oauth/jans-auth-server/config/properties.readonly https://jans.io/oauth/jans-auth-server/config/properties.write https://jans.io/oauth/config/smtp.readonly https://jans.io/oauth/config/smtp.write https://jans.io/oauth/config/smtp.delete https://jans.io/oauth/config/database/couchbase.readonly https://jans.io/oauth/config/database/couchbase.write https://jans.io/oauth/config/database/couchbase.delete https://jans.io/oauth/config/scripts.readonly https://jans.io/oauth/config/scripts.write https://jans.io/oauth/config/scripts.delete https://jans.io/oauth/config/fido2.readonly https://jans.io/oauth/config/fido2.write https://jans.io/oauth/config/jwks.readonly https://jans.io/oauth/config/jwks.write https://jans.io/oauth/config/database/ldap.readonly https://jans.io/oauth/config/database/ldap.write https://jans.io/oauth/config/database/ldap.delete https://jans.io/oauth/config/logging.readonly https://jans.io/oauth/config/logging.write https://jans.io/oauth/config/scopes.readonly https://jans.io/oauth/config/scopes.write https://jans.io/oauth/config/scopes.delete https://jans.io/oauth/config/uma/resources.readonly https://jans.io/oauth/config/uma/resources.write https://jans.io/oauth/config/uma/resources.delete https://jans.io/oauth/config/database/sql.readonly https://jans.io/oauth/config/database/sql.write https://jans.io/oauth/config/database/sql.delete https://jans.io/oauth/config/stats.readonly jans_stat https://jans.io/scim/users.read https://jans.io/scim/users.write https://jans.io/oauth/config/scim/users.read https://jans.io/oauth/config/scim/users.write https://jans.io/scim/config.readonly https://jans.io/scim/config.write https://jans.io/oauth/config/organization.readonly https://jans.io/oauth/config/organization.write https://jans.io/oauth/config/user.readonly https://jans.io/oauth/config/user.write https://jans.io/oauth/config/user.delete -# Test env Setting -token.endpoint=${token.endpoint} -token.grant.type=${token.grant.type} -test.client.id=${test.client.id} -test.client.secret=${test.client.secret} -test.issuer=${test.issuer} \ No newline at end of file +# jans.server +token.endpoint=https://jans.server1/jans-auth/restv1/token +token.grant.type=client_credentials +test.client.id=1800.d2d3ab98-e018-4a75-bc1d-15b5ac8cb455 +test.client.secret=oGjYGFjhBr97 +test.issuer=https://jans.server1 \ No newline at end of file From fc5ebe0616877fcea3fbdcd11c8fcca3825ea860 Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Wed, 27 Jul 2022 19:48:46 +0530 Subject: [PATCH 04/21] fix(jans-config-api): swagger spec fix for client attributes --- jans-config-api/docs/jans-config-api-swagger.yaml | 12 ++++++------ .../rest/resource/auth/ClientsResource.java | 5 +++-- .../openid/clients/openid_clients_create.json | 10 +++++----- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/jans-config-api/docs/jans-config-api-swagger.yaml b/jans-config-api/docs/jans-config-api-swagger.yaml index f16d1739746..acaf8ebb8fd 100644 --- a/jans-config-api/docs/jans-config-api-swagger.yaml +++ b/jans-config-api/docs/jans-config-api-swagger.yaml @@ -5154,27 +5154,27 @@ components: requirePar: description: boolean value to indicate of Pushed Authorisation Request(PAR)is required. type: boolean - authorizationSignedResponseAlg: + jansAuthSignedRespAlg: description: JWS alg algorithm JWA required for signing authorization responses. type: string - authorizationEncryptedResponseAlg: + jansAuthEncRespAlg: description: JWE alg algorithm JWA required for encrypting authorization responses. type: string - authorizationEncryptedResponseEnc: + jansAuthEncRespEnc: description: JWE enc algorithm JWA required for encrypting auhtorization responses. type: string - publicSubjectIdentifierAttribute: + jansSubAttr: description: custom subject identifier attribute. type: string redirectUrisRegex: description: If set, redirectUri must match to this regexp type: string - authorizedAcrValues: + jansAuthorizedAcr: description: List of thentication Context Class Reference (ACR) that must exist. type: array items: type: string - defaultPromptLogin: + jansDefaultPromptLogin: description: sets prompt=login to the authorization request, which causes the authorization server to force the user to sign in again before it will show the authorization prompt. type: boolean diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/ClientsResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/ClientsResource.java index 796c57d7e90..5bbbe06a1ff 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/ClientsResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/ClientsResource.java @@ -109,8 +109,9 @@ public Response getOpenIdClientByInum(@PathParam(ApiConstants.INUM) @NotNull Str @ProtectedApi(scopes = { ApiAccessConstants.OPENID_CLIENTS_WRITE_ACCESS }) public Response createOpenIdConnect(@Valid Client client) throws EncryptionException { if (logger.isDebugEnabled()) { - logger.debug("Client details to be added - client:{}", escapeLog(client)); + logger.debug("Client to be added - client:{}, client.getAttributes():{}, client.getCustomAttributes():{}", escapeLog(client), escapeLog(client.getAttributes()), escapeLog(client.getCustomAttributes())); } + String inum = client.getClientId(); if (inum == null || inum.isEmpty() || inum.isBlank()) { inum = inumService.generateClientInum(); @@ -132,7 +133,7 @@ public Response createOpenIdConnect(@Valid Client client) throws EncryptionExcep client.setDeletable(client.getClientSecretExpiresAt() != null); ignoreCustomObjectClassesForNonLDAP(client); - logger.debug("Final Client details to be added - client:{}", client); + logger.trace("Final Client details to be added - client:{}, client.getAttributes():{}, client.getCustomAttributes():{}", client, client.getAttributes(), client.getCustomAttributes()); clientService.addClient(client); Client result = clientService.getClientByInum(inum); result.setClientSecret(encryptionService.decrypt(result.getClientSecret())); diff --git a/jans-config-api/server/src/test/resources/feature/openid/clients/openid_clients_create.json b/jans-config-api/server/src/test/resources/feature/openid/clients/openid_clients_create.json index 3fc37f29cb2..daeb222e96b 100644 --- a/jans-config-api/server/src/test/resources/feature/openid/clients/openid_clients_create.json +++ b/jans-config-api/server/src/test/resources/feature/openid/clients/openid_clients_create.json @@ -95,15 +95,15 @@ "redirectUrisRegex": "sdfsdf", "parLifetime": 3600, "requirePar": true, - "defaultPromptLogin": true, + "jansDefaultPromptLogin": true, "jansAuthorizedAcr": [ "inum=3000-F75A,ou=scripts,o=jans" ], "updateTokenScriptDns": [], "ropcScripts": [], "jansAuthSignedRespAlg": "HS384", - "authorizationEncryptedResponseAlg": "A128KW", - "authorizationEncryptedResponseEnc": "A128GCM" + "jansAuthEncRespAlg": "A128KW", + "jansAuthEncRespEnc": "A128GCM" }, "tlsClientAuthSubjectDn": "123", "frontChannelLogoutSessionRequired": true, @@ -124,7 +124,7 @@ "requirePar": true, "updateTokenScriptDns": [], "ropcScripts": [], - "jansAuthEncRespAlg": "HS384", + "authorizationSignedResponseAlg": "HS384", "authorizationEncryptedResponseAlg": "A128KW", "authorizationEncryptedResponseEnc": "A128GCM", "postAuthnScripts": [], @@ -136,7 +136,7 @@ "dsfsdfsdf" ], "defaultPromptLogin": true, - "authorizedAcrValues": [ + "jansAuthorizedAcr": [ "inum=3000-F75A,ou=scripts,o=jans" ], "customObjectClasses": [], From 77d19bb9ba20d2f244280dee3c2ce888f2d251eb Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Wed, 27 Jul 2022 19:50:37 +0530 Subject: [PATCH 05/21] fix(jans-config-api): reverted the local test properties --- .../server/src/test/resources/karate.properties | 7 +++---- .../server/src/test/resources/test.properties | 15 +++++++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/jans-config-api/server/src/test/resources/karate.properties b/jans-config-api/server/src/test/resources/karate.properties index a849093586c..41c0d369aff 100644 --- a/jans-config-api/server/src/test/resources/karate.properties +++ b/jans-config-api/server/src/test/resources/karate.properties @@ -1,6 +1,5 @@ -#LOCAL -karate.test.url=http://localhost -karate.test.port=8080 +#karate.test.url=http://localhost +#karate.test.port=8080 #karate.test.url=https://jenkins-config-api.gluu.org/jans-config-api #karate.test.port=443 -#karate.test.url=https://jenkins-config-api.gluu.org +karate.test.url=${test.server} diff --git a/jans-config-api/server/src/test/resources/test.properties b/jans-config-api/server/src/test/resources/test.properties index 2d54c91a81e..4257f297907 100644 --- a/jans-config-api/server/src/test/resources/test.properties +++ b/jans-config-api/server/src/test/resources/test.properties @@ -1,9 +1,8 @@ -#LOCAL -test.scopes=https://jans.io/oauth/config/acrs.readonly https://jans.io/oauth/config/acrs.write https://jans.io/oauth/config/attributes.readonly https://jans.io/oauth/config/attributes.write https://jans.io/oauth/config/attributes.delete https://jans.io/oauth/config/cache.readonly https://jans.io/oauth/config/cache.write https://jans.io/oauth/config/openid/clients.readonly https://jans.io/oauth/config/openid/clients.write https://jans.io/oauth/config/openid/clients.delete https://jans.io/oauth/jans-auth-server/config/properties.readonly https://jans.io/oauth/jans-auth-server/config/properties.write https://jans.io/oauth/config/smtp.readonly https://jans.io/oauth/config/smtp.write https://jans.io/oauth/config/smtp.delete https://jans.io/oauth/config/database/couchbase.readonly https://jans.io/oauth/config/database/couchbase.write https://jans.io/oauth/config/database/couchbase.delete https://jans.io/oauth/config/scripts.readonly https://jans.io/oauth/config/scripts.write https://jans.io/oauth/config/scripts.delete https://jans.io/oauth/config/fido2.readonly https://jans.io/oauth/config/fido2.write https://jans.io/oauth/config/jwks.readonly https://jans.io/oauth/config/jwks.write https://jans.io/oauth/config/database/ldap.readonly https://jans.io/oauth/config/database/ldap.write https://jans.io/oauth/config/database/ldap.delete https://jans.io/oauth/config/logging.readonly https://jans.io/oauth/config/logging.write https://jans.io/oauth/config/scopes.readonly https://jans.io/oauth/config/scopes.write https://jans.io/oauth/config/scopes.delete https://jans.io/oauth/config/uma/resources.readonly https://jans.io/oauth/config/uma/resources.write https://jans.io/oauth/config/uma/resources.delete https://jans.io/oauth/config/database/sql.readonly https://jans.io/oauth/config/database/sql.write https://jans.io/oauth/config/database/sql.delete https://jans.io/oauth/config/stats.readonly jans_stat https://jans.io/scim/users.read https://jans.io/scim/users.write https://jans.io/oauth/config/scim/users.read https://jans.io/oauth/config/scim/users.write https://jans.io/scim/config.readonly https://jans.io/scim/config.write https://jans.io/oauth/config/organization.readonly https://jans.io/oauth/config/organization.write https://jans.io/oauth/config/user.readonly https://jans.io/oauth/config/user.write https://jans.io/oauth/config/user.delete +test.scopes=${test.scopes} -# jans.server -token.endpoint=https://jans.server1/jans-auth/restv1/token -token.grant.type=client_credentials -test.client.id=1800.d2d3ab98-e018-4a75-bc1d-15b5ac8cb455 -test.client.secret=oGjYGFjhBr97 -test.issuer=https://jans.server1 \ No newline at end of file +# Test env Setting +token.endpoint=${token.endpoint} +token.grant.type=${token.grant.type} +test.client.id=${test.client.id} +test.client.secret=${test.client.secret} +test.issuer=${test.issuer} \ No newline at end of file From a977d1faf6e82495a11c06af2caed7283dfd6724 Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Wed, 27 Jul 2022 22:08:56 +0530 Subject: [PATCH 06/21] test(jans-config-api): commented test case --- .../src/test/resources/feature/openid/clients/clients.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/jans-config-api/server/src/test/resources/feature/openid/clients/clients.feature b/jans-config-api/server/src/test/resources/feature/openid/clients/clients.feature index 6ccffc4b618..4958c966e5d 100644 --- a/jans-config-api/server/src/test/resources/feature/openid/clients/clients.feature +++ b/jans-config-api/server/src/test/resources/feature/openid/clients/clients.feature @@ -128,6 +128,7 @@ Then status 200 And print response And assert response.length !=0 +@ignore @CreateUpdateDelete Scenario: Create new OpenId Connect Client Given url mainUrl From b409633dca7fdd394421d105cf33b9210d29d38e Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Thu, 28 Jul 2022 22:08:37 +0530 Subject: [PATCH 07/21] feat(jans-config-api): scim config endpoint enhancment --- jans-config-api/plugins/scim-plugin/pom.xml | 5 + .../ScimConfigurationFactory.java | 188 +++++++++++++++++- .../plugin/scim/rest/ScimConfigResource.java | 43 +++- .../scim/service/ScimConfigService.java | 42 ++-- 4 files changed, 248 insertions(+), 30 deletions(-) diff --git a/jans-config-api/plugins/scim-plugin/pom.xml b/jans-config-api/plugins/scim-plugin/pom.xml index 9c0c80318d7..54a63b7430a 100644 --- a/jans-config-api/plugins/scim-plugin/pom.xml +++ b/jans-config-api/plugins/scim-plugin/pom.xml @@ -42,6 +42,11 @@ jans-scim-client ${jans.version} + + io.jans + jans-scim-service + ${jans.version} + diff --git a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/configuration/ScimConfigurationFactory.java b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/configuration/ScimConfigurationFactory.java index a4b027e5bce..4b6e3961f08 100644 --- a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/configuration/ScimConfigurationFactory.java +++ b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/configuration/ScimConfigurationFactory.java @@ -6,11 +6,29 @@ package io.jans.configapi.plugin.scim.configuration; +import io.jans.scim.model.conf.Conf; +import io.jans.scim.model.conf.AppConfiguration; +import io.jans.config.oxtrust.Configuration; +import io.jans.service.cdi.async.Asynchronous; +import io.jans.service.cdi.event.ConfigurationEvent; +import io.jans.service.cdi.event.ConfigurationUpdate; +import io.jans.service.cdi.event.Scheduled; +import io.jans.service.timer.event.TimerEvent; +import io.jans.service.timer.schedule.TimerSchedule; import io.jans.configapi.configuration.ConfigurationFactory; - +import io.jans.exception.ConfigurationException; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.exception.BasePersistenceException; +import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Event; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; +import java.util.concurrent.atomic.AtomicBoolean; + import org.slf4j.Logger; @ApplicationScoped @@ -21,11 +39,167 @@ public class ScimConfigurationFactory { @Inject private Logger log; - @Inject - ConfigurationFactory configurationFactory; - - public String getScimConfigurationDn() { - return configurationFactory.getConfigurationDn(CONFIGURATION_ENTRY_DN); - } + @Inject + ConfigurationFactory configurationFactory; + + @Inject + PersistenceEntryManager persistenceManager; + + @Inject + private Instance configurationInstance; + + @Inject + private Event configurationUpdateEvent; + + @Inject + private Event timerEvent; + + private AppConfiguration scimConfiguration; + private boolean confLoaded = false; + private long loadedRevision = -1; + private boolean loadedFromLdap = true; + private AtomicBoolean isActive; + private static final int DEFAULT_INTERVAL = 30; // 30 seconds + + @Produces + @ApplicationScoped + public AppConfiguration getAppConfiguration() { + return scimConfiguration; + } + + @PostConstruct + public void init() { + this.isActive = new AtomicBoolean(true); + try { + create(); + } finally { + this.isActive.set(false); + } + } + + public String getScimConfigurationDn() { + return configurationFactory.getConfigurationDn(CONFIGURATION_ENTRY_DN); + } + + public void create() { + log.info("Loading SCIM App Configuration"); + + if (!loadScimConfigFromDb()) { + log.error("Failed to load auth configuration from persistence. Please fix it!!!."); + throw new ConfigurationException("Failed to load auth configuration from persistence."); + } else { + log.info("Auth Configuration loaded successfully - loadedRevision:{}", this.loadedRevision); + } + } + + private boolean loadScimConfigFromDb() { + log.debug("Loading Scim configuration from '{}' DB...", + configurationFactory.getBaseConfiguration().getString("persistence.type")); + try { + final Conf c = loadConfigurationFromDb(getScimConfigurationDn()); + log.trace("Auth configuration '{}' DB...", c); + + if (c != null) { + initSimConf(c); + + // Destroy old configuration + if (this.confLoaded) { + destroy(AppConfiguration.class); + } + + this.confLoaded = true; + configurationUpdateEvent.select(ConfigurationUpdate.Literal.INSTANCE).fire(scimConfiguration); + + return true; + } + } catch (Exception ex) { + log.error("Unable to find auth configuration in DB " + ex.getMessage(), ex); + } + return false; + } + + private Conf loadConfigurationFromDb(String dn, String... returnAttributes) { + log.debug("loadConfigurationFromDb dn:{}, , returnAttributes:{}", dn, returnAttributes); + + try { + return this.persistenceManager.find(dn, Conf.class, returnAttributes); + } catch (BasePersistenceException ex) { + log.error(ex.getMessage()); + } + + return null; + } + + private void initSimConf(Conf conf) { + initScimConfiguration(conf); + this.loadedRevision = conf.getRevision(); + } + + private void initScimConfiguration(Conf conf) { + if (conf.getDynamicConf() != null) { + scimConfiguration = conf.getDynamicConf(); + log.trace("Scim Config - appConfiguration: {}", scimConfiguration); + } + } + + public boolean reloadScimConfFromLdap() { + if (!isRevisionIncreased()) { + return false; + } + + return loadScimConfigFromDb(); + } + + private boolean isRevisionIncreased() { + final Conf conf = loadConfigurationFromDb("jansRevision"); + if (conf == null) { + return false; + } + + log.trace("Scim DB revision: " + conf.getRevision() + ", server revision:" + loadedRevision); + return conf.getRevision() > this.loadedRevision; + } + + private void destroy(Class clazz) { + Instance configInstance = configurationInstance.select(clazz); + configurationInstance.destroy(configInstance.get()); + } + + public void initTimer() { + log.debug("Initializing Configuration Timer"); + + final int delay = 30; + final int interval = DEFAULT_INTERVAL; + + timerEvent.fire(new TimerEvent(new TimerSchedule(delay, interval), new ConfigurationEvent(), + Scheduled.Literal.INSTANCE)); + } + + @Asynchronous + public void reloadConfigurationTimerEvent(@Observes @Scheduled ConfigurationEvent configurationEvent) { + if (this.isActive.get()) { + return; + } + + if (!this.isActive.compareAndSet(false, true)) { + return; + } + + try { + reloadConfiguration(); + } catch (Throwable ex) { + log.error("Exception happened while reloading application configuration", ex); + } finally { + this.isActive.set(false); + } + } + + private void reloadConfiguration() { + if (!loadedFromLdap) { + return; + } + + reloadScimConfFromLdap(); + } } diff --git a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/rest/ScimConfigResource.java b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/rest/ScimConfigResource.java index 89b41cbe4e7..6efc44d6e58 100644 --- a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/rest/ScimConfigResource.java +++ b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/rest/ScimConfigResource.java @@ -2,14 +2,13 @@ import com.github.fge.jsonpatch.JsonPatchException; +import io.jans.scim.model.conf.Conf; import io.jans.configapi.core.rest.ProtectedApi; import io.jans.configapi.plugin.scim.service.ScimConfigService; -import io.jans.configapi.plugin.scim.model.config.ScimAppConfiguration; -import io.jans.configapi.plugin.scim.model.config.ScimConf; +import io.jans.scim.model.conf.AppConfiguration; import io.jans.configapi.core.util.Jackson; import io.jans.configapi.plugin.scim.util.Constants; - -import org.slf4j.Logger; +import io.jans.configapi.util.ApiAccessConstants; import java.io.IOException; import jakarta.inject.Inject; @@ -18,6 +17,8 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.slf4j.Logger; + @Path(Constants.SCIM_CONFIG) @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @@ -32,29 +33,49 @@ public class ScimConfigResource { @GET @ProtectedApi(scopes = { "https://jans.io/scim/config.readonly" }) public Response getAppConfiguration() { - ScimAppConfiguration appConfiguration = scimConfigService.find(); + AppConfiguration appConfiguration = scimConfigService.find(); log.debug("SCIM appConfiguration:{}", appConfiguration); return Response.ok(appConfiguration).build(); } - + + @PATCH + @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON) + @ProtectedApi(scopes = { ApiAccessConstants.JANS_AUTH_CONFIG_WRITE_ACCESS }) + public Response patchAppConfigurationProperty(@NotNull String requestString) throws JsonPatchException , IOException{ + log.debug("AUTH CONF details to patch - requestString:{} ", requestString); + + Conf conf = scimConfigService.findConf(); + AppConfiguration appConfiguration = scimConfigService.find(); + log.debug("AUTH CONF details BEFORE patch - appConfiguration :{}", appConfiguration); + appConfiguration = Jackson.applyPatch(requestString, conf.getDynamicConf()); + log.debug("AUTH CONF details BEFORE patch merge - appConfiguration:{}", appConfiguration); + conf.setDynamicConf(appConfiguration); + + + scimConfigService.merge(conf); + appConfiguration = scimConfigService.find(); + log.debug("AUTH CONF details AFTER patch merge - appConfiguration:{}", appConfiguration); + return Response.ok(appConfiguration).build(); + } + /* @PATCH @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON) @ProtectedApi(scopes = { "https://jans.io/scim/config.write" }) public Response patchAppConfigurationProperty(@NotNull String requestString) throws IOException,JsonPatchException { log.debug("AUTH CONF details to patch - requestString:{}", requestString); - ScimConf conf = scimConfigService.findConf(); - ScimAppConfiguration appConfiguration = conf.getDynamicConf(); - log.trace("AUTH CONF details BEFORE patch - conf:{}, appConfiguration:{}", conf, appConfiguration); + AppConfiguration appConfiguration = scimConfigService.find(); + //AppConfiguration appConfiguration = conf.getDynamicConf(); + log.trace("AUTH CONF details BEFORE patch - appConfiguration:{}", appConfiguration); appConfiguration = Jackson.applyPatch(requestString, appConfiguration); log.trace("AUTH CONF details BEFORE patch merge - appConfiguration:{}" ,appConfiguration); - conf.setDynamicConf(appConfiguration); + //conf.setDynamicConf(appConfiguration); scimConfigService.merge(conf); appConfiguration = scimConfigService.find(); log.debug("AUTH CONF details AFTER patch merge - appConfiguration:{}", appConfiguration); return Response.ok(appConfiguration).build(); } - +*/ } diff --git a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/service/ScimConfigService.java b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/service/ScimConfigService.java index 3e7dddbbf78..ae1dde41711 100644 --- a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/service/ScimConfigService.java +++ b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/service/ScimConfigService.java @@ -1,8 +1,10 @@ package io.jans.configapi.plugin.scim.service; +import io.jans.scim.model.conf.Conf; import io.jans.configapi.plugin.scim.configuration.ScimConfigurationFactory; -import io.jans.configapi.plugin.scim.model.config.ScimAppConfiguration; -import io.jans.configapi.plugin.scim.model.config.ScimConf; +//import io.jans.configapi.plugin.scim.model.config.ScimAppConfiguration; +//import io.jans.configapi.plugin.scim.model.config.ScimConf; +import io.jans.scim.model.conf.AppConfiguration; import io.jans.orm.PersistenceEntryManager; import jakarta.enterprise.context.ApplicationScoped; @@ -22,23 +24,39 @@ public class ScimConfigService { @Inject ScimConfigurationFactory scimConfigurationFactory; - public ScimConf findConf() { + public AppConfiguration find() { + final Conf conf = findConf(); + return conf.getDynamicConf(); + } + public Conf findConf() { final String dn = scimConfigurationFactory.getScimConfigurationDn(); - log.debug("\n\n ScimConfigService::findConf() - dn:{} ", dn); - return persistenceManager.find(dn, ScimConf.class, null); + return persistenceManager.find(dn, Conf.class, null); } - public void merge(ScimConf conf) { + public void merge(Conf conf) { conf.setRevision(conf.getRevision() + 1); persistenceManager.merge(conf); } + + /* + public AppConfiguration findConf() { + final String dn = scimConfigurationFactory.getScimConfigurationDn(); + log.debug("\n\n ScimConfigService::findConf() - dn:{} ", dn); + return persistenceManager.find(dn, AppConfiguration.class, null); + } + + public void merge(AppConfiguration appConfiguration) { + conf.setRevision(appConfiguration.getRevision() + 1); + persistenceManager.merge(conf); + } - public ScimAppConfiguration find() { - final ScimConf conf = findConf(); - log.debug( - "\n\n ScimConfigService::find() - new - conf.getDn:{}, conf.getDynamicConf:{}, conf.getStaticConf:{}, conf.getRevision:{}", - conf.getDn(), conf.getDynamicConf(), conf.getStaticConf(), conf.getRevision()); - return conf.getDynamicConf(); + public AppConfiguration find() { + final AppConfiguration conf = findConf(); + log.debug( + "\n\n ScimConfigService::find() - new - conf.getDn:{}, conf.getDynamicConf:{}, conf.getStaticConf:{}, conf.getRevision:{}", + conf.getDn(), conf.getDynamicConf(), conf.getStaticConf(), conf.getRevision()); + return conf; } + */ } From 1498d7404226c66c1e5192b5b8a004ddea89a47d Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Fri, 29 Jul 2022 16:19:29 +0530 Subject: [PATCH 08/21] feat(jans-config-api): swagger and DTO change for new fields for scim config endpoint --- .../docs/jans-config-api-swagger.yaml | 7 +++++ .../model/config/ScimAppConfiguration.java | 28 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/jans-config-api/docs/jans-config-api-swagger.yaml b/jans-config-api/docs/jans-config-api-swagger.yaml index acaf8ebb8fd..d66c8164036 100644 --- a/jans-config-api/docs/jans-config-api-swagger.yaml +++ b/jans-config-api/docs/jans-config-api-swagger.yaml @@ -6853,6 +6853,13 @@ components: useLocalCache: type: boolean description: Boolean value specifying whether to enable local in-memory cache. + bulkMaxOperations: + type: integer + description: Specifies maximum bulk operations. + bulkMaxPayloadSize: + type: integer + format: int64 + description: Specifies maximum payload size of bulk operations. Organization: type: object diff --git a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/model/config/ScimAppConfiguration.java b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/model/config/ScimAppConfiguration.java index c700a62de78..7430d199748 100644 --- a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/model/config/ScimAppConfiguration.java +++ b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/model/config/ScimAppConfiguration.java @@ -68,6 +68,14 @@ public class ScimAppConfiguration implements Configuration, Serializable { private Boolean disableJdkLogger = true; private Boolean useLocalCache = false; + @JsonProperty("bulkMaxOperations") + @AttributeName(name = "bulkMaxOperations") + private int bulkMaxOperations; + + @JsonProperty("bulkMaxPayloadSize") + @AttributeName(name = "bulkMaxPayloadSize") + private long bulkMaxPayloadSize; + public String getBaseDn() { return baseDn; } @@ -195,6 +203,22 @@ public Boolean getUseLocalCache() { public void setUseLocalCache(Boolean useLocalCache) { this.useLocalCache = useLocalCache; } + + public int getBulkMaxOperations() { + return bulkMaxOperations; + } + + public void setBulkMaxOperations(int bulkMaxOperations) { + this.bulkMaxOperations = bulkMaxOperations; + } + + public long getBulkMaxPayloadSize() { + return bulkMaxPayloadSize; + } + + public void setBulkMaxPayloadSize(long bulkMaxPayloadSize) { + this.bulkMaxPayloadSize = bulkMaxPayloadSize; + } @Override public String toString() { @@ -205,7 +229,7 @@ public String toString() { + externalLoggerConfig + ", metricReportInterval=" + metricReportInterval + ", metricReportKeepDataDays=" + metricReportKeepDataDays + ", metricReportEnabled=" + metricReportEnabled + ", disableJdkLogger=" + disableJdkLogger + ", useLocalCache=" + useLocalCache - + "]"; + + ", bulkMaxOperations=" + bulkMaxOperations + ", bulkMaxPayloadSize=" + bulkMaxPayloadSize + "]"; } - + } From 3ac7ea18327c684c735f33a77183e4ea9b49719a Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Fri, 29 Jul 2022 16:25:58 +0530 Subject: [PATCH 09/21] feat(jans-config-api): swagger and DTO change for new fields for scim config endpoint --- jans-config-api/plugins/scim-plugin/pom.xml | 5 - .../ScimConfigurationFactory.java | 188 +----------------- .../plugin/scim/rest/ScimConfigResource.java | 43 +--- .../scim/service/ScimConfigService.java | 42 ++-- 4 files changed, 30 insertions(+), 248 deletions(-) diff --git a/jans-config-api/plugins/scim-plugin/pom.xml b/jans-config-api/plugins/scim-plugin/pom.xml index 54a63b7430a..9c0c80318d7 100644 --- a/jans-config-api/plugins/scim-plugin/pom.xml +++ b/jans-config-api/plugins/scim-plugin/pom.xml @@ -42,11 +42,6 @@ jans-scim-client ${jans.version} - - io.jans - jans-scim-service - ${jans.version} - diff --git a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/configuration/ScimConfigurationFactory.java b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/configuration/ScimConfigurationFactory.java index 4b6e3961f08..a4b027e5bce 100644 --- a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/configuration/ScimConfigurationFactory.java +++ b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/configuration/ScimConfigurationFactory.java @@ -6,29 +6,11 @@ package io.jans.configapi.plugin.scim.configuration; -import io.jans.scim.model.conf.Conf; -import io.jans.scim.model.conf.AppConfiguration; -import io.jans.config.oxtrust.Configuration; -import io.jans.service.cdi.async.Asynchronous; -import io.jans.service.cdi.event.ConfigurationEvent; -import io.jans.service.cdi.event.ConfigurationUpdate; -import io.jans.service.cdi.event.Scheduled; -import io.jans.service.timer.event.TimerEvent; -import io.jans.service.timer.schedule.TimerSchedule; import io.jans.configapi.configuration.ConfigurationFactory; -import io.jans.exception.ConfigurationException; -import io.jans.orm.PersistenceEntryManager; -import io.jans.orm.exception.BasePersistenceException; -import jakarta.annotation.PostConstruct; + import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Event; -import jakarta.enterprise.event.Observes; -import jakarta.enterprise.inject.Instance; -import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; -import java.util.concurrent.atomic.AtomicBoolean; - import org.slf4j.Logger; @ApplicationScoped @@ -39,167 +21,11 @@ public class ScimConfigurationFactory { @Inject private Logger log; - @Inject - ConfigurationFactory configurationFactory; - - @Inject - PersistenceEntryManager persistenceManager; - - @Inject - private Instance configurationInstance; - - @Inject - private Event configurationUpdateEvent; - - @Inject - private Event timerEvent; - - private AppConfiguration scimConfiguration; - private boolean confLoaded = false; - private long loadedRevision = -1; - private boolean loadedFromLdap = true; - private AtomicBoolean isActive; - private static final int DEFAULT_INTERVAL = 30; // 30 seconds - - @Produces - @ApplicationScoped - public AppConfiguration getAppConfiguration() { - return scimConfiguration; - } - - @PostConstruct - public void init() { - this.isActive = new AtomicBoolean(true); - try { - create(); - } finally { - this.isActive.set(false); - } - } - - public String getScimConfigurationDn() { - return configurationFactory.getConfigurationDn(CONFIGURATION_ENTRY_DN); - } - - public void create() { - log.info("Loading SCIM App Configuration"); - - if (!loadScimConfigFromDb()) { - log.error("Failed to load auth configuration from persistence. Please fix it!!!."); - throw new ConfigurationException("Failed to load auth configuration from persistence."); - } else { - log.info("Auth Configuration loaded successfully - loadedRevision:{}", this.loadedRevision); - } - } - - private boolean loadScimConfigFromDb() { - log.debug("Loading Scim configuration from '{}' DB...", - configurationFactory.getBaseConfiguration().getString("persistence.type")); - try { - final Conf c = loadConfigurationFromDb(getScimConfigurationDn()); - log.trace("Auth configuration '{}' DB...", c); - - if (c != null) { - initSimConf(c); - - // Destroy old configuration - if (this.confLoaded) { - destroy(AppConfiguration.class); - } - - this.confLoaded = true; - configurationUpdateEvent.select(ConfigurationUpdate.Literal.INSTANCE).fire(scimConfiguration); - - return true; - } - } catch (Exception ex) { - log.error("Unable to find auth configuration in DB " + ex.getMessage(), ex); - } - return false; - } - - private Conf loadConfigurationFromDb(String dn, String... returnAttributes) { - log.debug("loadConfigurationFromDb dn:{}, , returnAttributes:{}", dn, returnAttributes); - - try { - return this.persistenceManager.find(dn, Conf.class, returnAttributes); - } catch (BasePersistenceException ex) { - log.error(ex.getMessage()); - } - - return null; - } - - private void initSimConf(Conf conf) { - initScimConfiguration(conf); - this.loadedRevision = conf.getRevision(); - } - - private void initScimConfiguration(Conf conf) { - if (conf.getDynamicConf() != null) { - scimConfiguration = conf.getDynamicConf(); - log.trace("Scim Config - appConfiguration: {}", scimConfiguration); - } - } - - public boolean reloadScimConfFromLdap() { - if (!isRevisionIncreased()) { - return false; - } - - return loadScimConfigFromDb(); - } - - private boolean isRevisionIncreased() { - final Conf conf = loadConfigurationFromDb("jansRevision"); - if (conf == null) { - return false; - } - - log.trace("Scim DB revision: " + conf.getRevision() + ", server revision:" + loadedRevision); - return conf.getRevision() > this.loadedRevision; - } - - private void destroy(Class clazz) { - Instance configInstance = configurationInstance.select(clazz); - configurationInstance.destroy(configInstance.get()); - } - - public void initTimer() { - log.debug("Initializing Configuration Timer"); - - final int delay = 30; - final int interval = DEFAULT_INTERVAL; - - timerEvent.fire(new TimerEvent(new TimerSchedule(delay, interval), new ConfigurationEvent(), - Scheduled.Literal.INSTANCE)); - } - - @Asynchronous - public void reloadConfigurationTimerEvent(@Observes @Scheduled ConfigurationEvent configurationEvent) { - if (this.isActive.get()) { - return; - } - - if (!this.isActive.compareAndSet(false, true)) { - return; - } - - try { - reloadConfiguration(); - } catch (Throwable ex) { - log.error("Exception happened while reloading application configuration", ex); - } finally { - this.isActive.set(false); - } - } - - private void reloadConfiguration() { - if (!loadedFromLdap) { - return; - } - - reloadScimConfFromLdap(); - } + @Inject + ConfigurationFactory configurationFactory; + + public String getScimConfigurationDn() { + return configurationFactory.getConfigurationDn(CONFIGURATION_ENTRY_DN); + } } diff --git a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/rest/ScimConfigResource.java b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/rest/ScimConfigResource.java index 6efc44d6e58..89b41cbe4e7 100644 --- a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/rest/ScimConfigResource.java +++ b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/rest/ScimConfigResource.java @@ -2,13 +2,14 @@ import com.github.fge.jsonpatch.JsonPatchException; -import io.jans.scim.model.conf.Conf; import io.jans.configapi.core.rest.ProtectedApi; import io.jans.configapi.plugin.scim.service.ScimConfigService; -import io.jans.scim.model.conf.AppConfiguration; +import io.jans.configapi.plugin.scim.model.config.ScimAppConfiguration; +import io.jans.configapi.plugin.scim.model.config.ScimConf; import io.jans.configapi.core.util.Jackson; import io.jans.configapi.plugin.scim.util.Constants; -import io.jans.configapi.util.ApiAccessConstants; + +import org.slf4j.Logger; import java.io.IOException; import jakarta.inject.Inject; @@ -17,8 +18,6 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.slf4j.Logger; - @Path(Constants.SCIM_CONFIG) @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @@ -33,49 +32,29 @@ public class ScimConfigResource { @GET @ProtectedApi(scopes = { "https://jans.io/scim/config.readonly" }) public Response getAppConfiguration() { - AppConfiguration appConfiguration = scimConfigService.find(); + ScimAppConfiguration appConfiguration = scimConfigService.find(); log.debug("SCIM appConfiguration:{}", appConfiguration); return Response.ok(appConfiguration).build(); } - - @PATCH - @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON) - @ProtectedApi(scopes = { ApiAccessConstants.JANS_AUTH_CONFIG_WRITE_ACCESS }) - public Response patchAppConfigurationProperty(@NotNull String requestString) throws JsonPatchException , IOException{ - log.debug("AUTH CONF details to patch - requestString:{} ", requestString); - - Conf conf = scimConfigService.findConf(); - AppConfiguration appConfiguration = scimConfigService.find(); - log.debug("AUTH CONF details BEFORE patch - appConfiguration :{}", appConfiguration); - appConfiguration = Jackson.applyPatch(requestString, conf.getDynamicConf()); - log.debug("AUTH CONF details BEFORE patch merge - appConfiguration:{}", appConfiguration); - conf.setDynamicConf(appConfiguration); - - - scimConfigService.merge(conf); - appConfiguration = scimConfigService.find(); - log.debug("AUTH CONF details AFTER patch merge - appConfiguration:{}", appConfiguration); - return Response.ok(appConfiguration).build(); - } - /* + @PATCH @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON) @ProtectedApi(scopes = { "https://jans.io/scim/config.write" }) public Response patchAppConfigurationProperty(@NotNull String requestString) throws IOException,JsonPatchException { log.debug("AUTH CONF details to patch - requestString:{}", requestString); - AppConfiguration appConfiguration = scimConfigService.find(); - //AppConfiguration appConfiguration = conf.getDynamicConf(); - log.trace("AUTH CONF details BEFORE patch - appConfiguration:{}", appConfiguration); + ScimConf conf = scimConfigService.findConf(); + ScimAppConfiguration appConfiguration = conf.getDynamicConf(); + log.trace("AUTH CONF details BEFORE patch - conf:{}, appConfiguration:{}", conf, appConfiguration); appConfiguration = Jackson.applyPatch(requestString, appConfiguration); log.trace("AUTH CONF details BEFORE patch merge - appConfiguration:{}" ,appConfiguration); - //conf.setDynamicConf(appConfiguration); + conf.setDynamicConf(appConfiguration); scimConfigService.merge(conf); appConfiguration = scimConfigService.find(); log.debug("AUTH CONF details AFTER patch merge - appConfiguration:{}", appConfiguration); return Response.ok(appConfiguration).build(); } -*/ + } diff --git a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/service/ScimConfigService.java b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/service/ScimConfigService.java index ae1dde41711..3e7dddbbf78 100644 --- a/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/service/ScimConfigService.java +++ b/jans-config-api/plugins/scim-plugin/src/main/java/io/jans/configapi/plugin/scim/service/ScimConfigService.java @@ -1,10 +1,8 @@ package io.jans.configapi.plugin.scim.service; -import io.jans.scim.model.conf.Conf; import io.jans.configapi.plugin.scim.configuration.ScimConfigurationFactory; -//import io.jans.configapi.plugin.scim.model.config.ScimAppConfiguration; -//import io.jans.configapi.plugin.scim.model.config.ScimConf; -import io.jans.scim.model.conf.AppConfiguration; +import io.jans.configapi.plugin.scim.model.config.ScimAppConfiguration; +import io.jans.configapi.plugin.scim.model.config.ScimConf; import io.jans.orm.PersistenceEntryManager; import jakarta.enterprise.context.ApplicationScoped; @@ -24,39 +22,23 @@ public class ScimConfigService { @Inject ScimConfigurationFactory scimConfigurationFactory; - public AppConfiguration find() { - final Conf conf = findConf(); - return conf.getDynamicConf(); - } - public Conf findConf() { - final String dn = scimConfigurationFactory.getScimConfigurationDn(); - return persistenceManager.find(dn, Conf.class, null); - } - - public void merge(Conf conf) { - conf.setRevision(conf.getRevision() + 1); - persistenceManager.merge(conf); - } - - /* - public AppConfiguration findConf() { + public ScimConf findConf() { final String dn = scimConfigurationFactory.getScimConfigurationDn(); log.debug("\n\n ScimConfigService::findConf() - dn:{} ", dn); - return persistenceManager.find(dn, AppConfiguration.class, null); + return persistenceManager.find(dn, ScimConf.class, null); } - public void merge(AppConfiguration appConfiguration) { - conf.setRevision(appConfiguration.getRevision() + 1); + public void merge(ScimConf conf) { + conf.setRevision(conf.getRevision() + 1); persistenceManager.merge(conf); } - public AppConfiguration find() { - final AppConfiguration conf = findConf(); - log.debug( - "\n\n ScimConfigService::find() - new - conf.getDn:{}, conf.getDynamicConf:{}, conf.getStaticConf:{}, conf.getRevision:{}", - conf.getDn(), conf.getDynamicConf(), conf.getStaticConf(), conf.getRevision()); - return conf; + public ScimAppConfiguration find() { + final ScimConf conf = findConf(); + log.debug( + "\n\n ScimConfigService::find() - new - conf.getDn:{}, conf.getDynamicConf:{}, conf.getStaticConf:{}, conf.getRevision:{}", + conf.getDn(), conf.getDynamicConf(), conf.getStaticConf(), conf.getRevision()); + return conf.getDynamicConf(); } - */ } From 0e40cbecd1605170122abd238243afad781892cf Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Mon, 1 Aug 2022 19:08:58 +0530 Subject: [PATCH 10/21] fix(jans-config-api): rectified endpoint url in swagger spec for uma resource --- jans-config-api/docs/jans-config-api-swagger.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jans-config-api/docs/jans-config-api-swagger.yaml b/jans-config-api/docs/jans-config-api-swagger.yaml index 56457220ef6..7a847817348 100644 --- a/jans-config-api/docs/jans-config-api-swagger.yaml +++ b/jans-config-api/docs/jans-config-api-swagger.yaml @@ -2169,7 +2169,7 @@ paths: description: Internal Server Error security: - oauth2: [https://jans.io/oauth/config/uma/resources.readonly] - /jans-config-api/api/v1/uma/resources/{clientId}: + /jans-config-api/api/v1/uma/resources/clientId/{clientId}: parameters: - name: clientId in: path From ef33d5c077e6a949c812f1a68c22c480e5bc6897 Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Tue, 2 Aug 2022 17:04:57 +0530 Subject: [PATCH 11/21] feat(jans-config-api): agama endpoint fixes --- .../io/jans/configapi/util/ApiConstants.java | 1 + .../profiles/local/test.properties | 8 +- .../rest/resource/auth/AgamaResource.java | 129 ++++++++++++++++-- .../service/auth/AgamaFlowService.java | 2 +- .../resources/feature/agama/agama.feature | 108 +++++++++++++++ 5 files changed, 228 insertions(+), 20 deletions(-) diff --git a/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java b/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java index 47a1c776905..2bc7e9f88a9 100644 --- a/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java +++ b/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java @@ -79,6 +79,7 @@ private ApiConstants() {} public static final String QNAME_PATH = "{qname}"; public static final String ENABLED = "enabled"; public static final String QNAME = "qname"; + public static final String INCLUDE_SOURCE = "includeSource"; public static final String LIMIT = "limit"; public static final String START_INDEX = "startIndex"; diff --git a/jans-config-api/profiles/local/test.properties b/jans-config-api/profiles/local/test.properties index 55e4b0d691f..03f62767111 100644 --- a/jans-config-api/profiles/local/test.properties +++ b/jans-config-api/profiles/local/test.properties @@ -2,8 +2,8 @@ test.scopes=https://jans.io/oauth/config/acrs.readonly https://jans.io/oauth/config/acrs.write https://jans.io/oauth/config/attributes.readonly https://jans.io/oauth/config/attributes.write https://jans.io/oauth/config/attributes.delete https://jans.io/oauth/config/cache.readonly https://jans.io/oauth/config/cache.write https://jans.io/oauth/config/openid/clients.readonly https://jans.io/oauth/config/openid/clients.write https://jans.io/oauth/config/openid/clients.delete https://jans.io/oauth/jans-auth-server/config/properties.readonly https://jans.io/oauth/jans-auth-server/config/properties.write https://jans.io/oauth/config/smtp.readonly https://jans.io/oauth/config/smtp.write https://jans.io/oauth/config/smtp.delete https://jans.io/oauth/config/database/couchbase.readonly https://jans.io/oauth/config/database/couchbase.write https://jans.io/oauth/config/database/couchbase.delete https://jans.io/oauth/config/scripts.readonly https://jans.io/oauth/config/scripts.write https://jans.io/oauth/config/scripts.delete https://jans.io/oauth/config/fido2.readonly https://jans.io/oauth/config/fido2.write https://jans.io/oauth/config/jwks.readonly https://jans.io/oauth/config/jwks.write https://jans.io/oauth/config/database/ldap.readonly https://jans.io/oauth/config/database/ldap.write https://jans.io/oauth/config/database/ldap.delete https://jans.io/oauth/config/logging.readonly https://jans.io/oauth/config/logging.write https://jans.io/oauth/config/scopes.readonly https://jans.io/oauth/config/scopes.write https://jans.io/oauth/config/scopes.delete https://jans.io/oauth/config/uma/resources.readonly https://jans.io/oauth/config/uma/resources.write https://jans.io/oauth/config/uma/resources.delete https://jans.io/oauth/config/database/sql.readonly https://jans.io/oauth/config/database/sql.write https://jans.io/oauth/config/database/sql.delete https://jans.io/oauth/config/stats.readonly jans_stat https://jans.io/scim/users.read https://jans.io/scim/users.write https://jans.io/oauth/config/scim/users.read https://jans.io/oauth/config/scim/users.write https://jans.io/scim/config.readonly https://jans.io/scim/config.write https://jans.io/oauth/config/organization.readonly https://jans.io/oauth/config/organization.write https://jans.io/oauth/config/user.readonly https://jans.io/oauth/config/user.write https://jans.io/oauth/config/user.delete https://jans.io/oauth/config/agama.readonly https://jans.io/oauth/config/agama.write https://jans.io/oauth/config/agama.delete # jans.server -token.endpoint=https://jans.server1/jans-auth/restv1/token +token.endpoint=https://jans.server2/jans-auth/restv1/token token.grant.type=client_credentials -test.client.id=1800.d2d3ab98-e018-4a75-bc1d-15b5ac8cb455 -test.client.secret=oGjYGFjhBr97 -test.issuer=https://jans.server1 \ No newline at end of file +test.client.id=1800.47b78bb8-2bb9-45d2-a748-58b3888c4bd1 +test.client.secret=1TPHmZl5ycI4 +test.issuer=https://jans.server2 \ No newline at end of file diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java index eb11ebc1124..f63220d80cc 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java @@ -7,6 +7,8 @@ package io.jans.configapi.rest.resource.auth; import io.jans.agama.model.Flow; +import io.jans.agama.model.FlowMetadata; +import io.jans.as.common.model.registration.Client; import io.jans.agama.dsl.Transpiler; import io.jans.agama.dsl.TranspilerException; import io.jans.agama.dsl.error.SyntaxException; @@ -30,12 +32,16 @@ import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static java.nio.charset.StandardCharsets.UTF_8; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; +import com.github.fge.jsonpatch.JsonPatchException; + @Path(ApiConstants.AGAMA) @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @@ -50,10 +56,12 @@ public class AgamaResource extends ConfigBaseResource { @GET @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_READ_ACCESS }) public Response getFlows(@DefaultValue("") @QueryParam(value = ApiConstants.PATTERN) String pattern, - @DefaultValue(DEFAULT_LIST_SIZE) @QueryParam(value = ApiConstants.LIMIT) int limit) { + @DefaultValue(DEFAULT_LIST_SIZE) @QueryParam(value = ApiConstants.LIMIT) int limit, + @DefaultValue("false") @QueryParam(value = ApiConstants.INCLUDE_SOURCE) boolean includeSource) { if (log.isDebugEnabled()) { - log.debug("Search Agama Flow with pattern:{}, sizeLimit:{}, ", escapeLog(pattern), escapeLog(limit)); + log.debug("Search Agama Flow with pattern:{}, sizeLimit:{}, includeSource:{}", escapeLog(pattern), + escapeLog(limit), escapeLog(includeSource)); } List flows = null; @@ -63,20 +71,23 @@ public Response getFlows(@DefaultValue("") @QueryParam(value = ApiConstants.PATT flows = agamaFlowService.getAllAgamaFlows(limit); } + // filter values + getAgamaFlowDetails(flows, includeSource); return Response.ok(flows).build(); } @GET @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_READ_ACCESS }) @Path(ApiConstants.QNAME_PATH) - public Response getFlowByName(@PathParam(ApiConstants.QNAME) @NotNull String flowName) { + public Response getFlowByName(@PathParam(ApiConstants.QNAME) @NotNull String flowName, + @DefaultValue("false") @QueryParam(value = ApiConstants.INCLUDE_SOURCE) boolean includeSource) { if (log.isDebugEnabled()) { - log.debug("Search Agama with flowName:{}, ", escapeLog(flowName)); + log.debug("Search Agama with flowName:{}, includeSource:{}", escapeLog(flowName), escapeLog(includeSource)); } String decodedFlowName = getURLDecodedValue(flowName); log.trace(" Agama Decoded flow name decodedFlowName:{}", decodedFlowName); - Flow flow = findFlow(decodedFlowName, true); + Flow flow = findFlow(decodedFlowName, true, includeSource); return Response.ok(flow).build(); } @@ -89,7 +100,7 @@ public Response createFlow(@Valid Flow flow) flow.getSource()); // check if flow with same name already exists - Flow existingFlow = findFlow(flow.getQname(), false); + Flow existingFlow = findFlow(flow.getQname(), false, false); log.debug(" existingFlow:{}", existingFlow); if (existingFlow != null) { thorwBadRequestException("Flow identified by name '" + flow.getQname() + "' already exist!"); @@ -98,9 +109,12 @@ public Response createFlow(@Valid Flow flow) // validate flow data validateAgamaFlowData(flow, true); flow.setRevision(-1); + FlowMetadata flowMetadata = new FlowMetadata(); + flowMetadata.setTimestamp(System.currentTimeMillis()); + flow.setMetadata(flowMetadata); agamaFlowService.addAgamaFlow(flow); - flow = findFlow(flow.getQname(), true); + flow = findFlow(flow.getQname(), true, false); return Response.status(Response.Status.CREATED).entity(flow).build(); } @@ -116,8 +130,8 @@ public Response createFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull Strin log.trace(" Agama Decoded flow name for create is:{}", decodedFlowName); // check if flow with same name already exists - Flow existingFlow = findFlow(decodedFlowName, false); - log.debug(" existingFlow:{}", existingFlow); + Flow existingFlow = findFlow(decodedFlowName, false, false); + log.debug(" existing-flow:{}", existingFlow); if (existingFlow != null) { thorwBadRequestException("Flow identified by name '" + decodedFlowName + "' already exist!"); } @@ -127,12 +141,15 @@ public Response createFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull Strin flow.setSource(source); flow.setEnabled(true); flow.setRevision(-1); + FlowMetadata flowMetadata = new FlowMetadata(); + flowMetadata.setTimestamp(System.currentTimeMillis()); + flow.setMetadata(flowMetadata); // validate flow data validateAgamaFlowData(flow, true); agamaFlowService.addAgamaFlow(flow); - flow = findFlow(flow.getQname(), true); + flow = findFlow(flow.getQname(), true, false); return Response.status(Response.Status.CREATED).entity(flow).build(); } @@ -148,7 +165,7 @@ public Response updateFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowNa log.trace(" Agama Decoded flow name for update is:{}", decodedFlowName); // check if flow exists - Flow existingFlow = findFlow(decodedFlowName, true); + Flow existingFlow = findFlow(decodedFlowName, true, false); // set flow data flow.setQname(decodedFlowName); @@ -162,10 +179,69 @@ public Response updateFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowNa log.debug("Updating flow after validation"); agamaFlowService.updateFlow(flow); - flow = findFlow(decodedFlowName, true); + flow = findFlow(decodedFlowName, true, false); return Response.status(Response.Status.OK).entity(flow).build(); } + @PUT + @Consumes(MediaType.TEXT_PLAIN) + @Path(ApiConstants.QNAME_PATH) + @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_WRITE_ACCESS }) + public Response updateFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull String flowName, @Valid String source) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + log.debug(" Flow to be updated flowName:{}, source:{}", flowName, source); + + String decodedFlowName = getURLDecodedValue(flowName); + log.trace(" Agama flow name for update is:{}", decodedFlowName); + + // check if flow with same name already exists + Flow existingFlow = findFlow(decodedFlowName, false, false); + log.debug(" Agama existingFlow:{}", existingFlow); + + // Update source and revision + if (existingFlow != null) { + existingFlow.setSource(source); + + getRevision(existingFlow, existingFlow); + + // validate flow data + validateAgamaFlowData(existingFlow, false); + log.debug("Update flow after validation"); + agamaFlowService.updateFlow(existingFlow); + + existingFlow = findFlow(existingFlow.getQname(), true, false); + } + return Response.status(Response.Status.OK).entity(existingFlow).build(); + } + + @PATCH + @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON) + @Path(ApiConstants.QNAME_PATH) + @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_WRITE_ACCESS }) + public Response patchFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowName, @NotNull String pathString) + throws JsonPatchException, IOException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + if (logger.isDebugEnabled()) { + logger.debug("Flow details to be patched - flowName:{}, pathString:{}", escapeLog(flowName), + escapeLog(pathString)); + } + String decodedFlowName = getURLDecodedValue(flowName); + log.trace(" Flow name for update is:{}", decodedFlowName); + + // check if flow exists + Flow existingFlow = findFlow(decodedFlowName, false, false); + log.debug(" existingFlow:{}", existingFlow); + + existingFlow = Jackson.applyPatch(pathString, existingFlow); + getRevision(existingFlow, existingFlow); + + // validate flow data + validateAgamaFlowData(existingFlow, false); + log.debug("Updating flow after validation"); + agamaFlowService.updateFlow(existingFlow); + return Response.ok(existingFlow).build(); + } + @DELETE @Path(ApiConstants.QNAME_PATH) @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_DELETE_ACCESS }) @@ -175,16 +251,23 @@ public Response deleteAttribute(@PathParam(ApiConstants.QNAME) @NotNull String f log.trace(" Agama Decoded flow name is:{}", decodedFlowName); // check if flow exists - Flow flow = findFlow(decodedFlowName, true); + Flow flow = findFlow(decodedFlowName, true, false); agamaFlowService.removeAgamaFlow(flow); return Response.noContent().build(); } - private Flow findFlow(String flowName, boolean throwError) { + private Flow findFlow(String flowName, boolean throwError, boolean includeSource) { Flow flow = null; try { flow = agamaFlowService.getFlowByName(flowName); + + // filter values + List flows = Arrays.asList(flow); + getAgamaFlowDetails(flows, includeSource); + if (flows != null && !flows.isEmpty()) { + flow = flows.get(0); + } } catch (EntryPersistenceException e) { log.error("No flow found with the name:{} ", flowName); if (throwError) { @@ -224,7 +307,7 @@ private void validateSyntax(Flow flow) { } catch (SyntaxException se) { log.error("Transpiler syntax check error", se); try { - log.debug("Throwing BadRequestException 400 :{} ", Jackson.asJson(se)); + log.debug("Throwing BadRequestException 400 :{} ", Jackson.asPrettyJson(se)); thorwBadRequestException(Jackson.asJson(se)); } catch (IOException io) { log.error("Agama Flow Transpiler syntax error parsing error", io); @@ -266,4 +349,20 @@ private Flow getRevision(Flow flow, Flow existingFlow) { log.debug("Final flow revision to be updated to - flow.getRevision():{}", flow.getRevision()); return flow; } + + private List getAgamaFlowDetails(List flows, boolean includeSource) { + if (flows == null || flows.isEmpty() || !includeSource) { + return flows; + } + + for (Flow flow : flows) { + flow.setBaseDn(null); + flow.setDn(null); + flow.setSource(null); + flow.setTransHash(null); + flow.setTranspiled(null); + } + return flows; + + } } diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/AgamaFlowService.java b/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/AgamaFlowService.java index 70a98b993f1..f77c6a97026 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/AgamaFlowService.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/AgamaFlowService.java @@ -181,7 +181,7 @@ public String validateFlowFields(Flow flow, List mandatoryAttributes, Li logger.debug("Checking mandatory errorMsg:{} ", errorMsg); if (errorMsg.length() > 0) { - errorMsg.insert(0, "Required feilds missing -> ("); + errorMsg.insert(0, "Required fields missing -> ("); errorMsg.replace(errorMsg.lastIndexOf(","), errorMsg.length(), ""); errorMsg.append("). "); } diff --git a/jans-config-api/server/src/test/resources/feature/agama/agama.feature b/jans-config-api/server/src/test/resources/feature/agama/agama.feature index d757960aef3..e652b79c4f0 100644 --- a/jans-config-api/server/src/test/resources/feature/agama/agama.feature +++ b/jans-config-api/server/src/test/resources/feature/agama/agama.feature @@ -91,6 +91,28 @@ Scenario: Create, update and delete agama flow And print response And print response.qname Then def flowName = response.qname + And print flowName + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + #Fetch agama flow by name and with source + Given url mainUrl + '/' +encodedFlowName + '?includeSource' + And header Authorization = 'Bearer ' + accessToken + When method GET + Then status 200 + And print response + And print response.qname + Then def flowName = response.qname + And print flowName + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + #Fetch agama flow by name and with source + Given url mainUrl + '?includeSource' + And header Authorization = 'Bearer ' + accessToken + When method GET + Then status 200 + And print response + And print response.qname + Then def flowName = response.qname And print flowName Then def encodedFlowName = funGetEncodedValue(flowName) And print encodedFlowName @@ -156,4 +178,90 @@ Scenario: Create agama flow with source data in request body Then status 204 And print response +@CreateAndUpdateFlowWithDataInRequestBodyUpdateDelete +Scenario: Create agama flow with source data in request body + #Create agama flow + Then def flowName = 'test' + And print flowName + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + Given url mainUrl + '/' +encodedFlowName + And header Authorization = 'Bearer ' + accessToken + And header Content-Type = 'text/plain' + And header Accept = 'application/json' + And request read('agama-source.txt') + When method POST + Then status 201 + And print response + And print response.qname + Then def flowName = response.qname + And print flowName + Then def result = response + And print 'Old transHash ='+response.transHash + Then set result.transHash = 'UpdatedAgamaFlowtransHash' + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + #Update agama flow + Given url mainUrl + '/' +encodedFlowName + And header Authorization = 'Bearer ' + accessToken + And header Content-Type = 'text/plain' + And header Accept = 'application/json' + And request read('agama-source.txt') + When method PUT + Then status 200 + And print response + And print response.qname + And print response.transHash + Then def flowName = response.qname + And print flowName + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + #Fetch agama flow by name + Given url mainUrl + '/' +encodedFlowName + And header Authorization = 'Bearer ' + accessToken + When method GET + Then status 200 + And print response + And print response.qname + Then def flowName = response.qname + And print flowName + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + #Delete agama flow by name + Given url mainUrl + '/' +encodedFlowName + And header Authorization = 'Bearer ' + accessToken + When method DELETE + Then status 204 + And print response +@CreateAndPatchFlow +Scenario: Create and Patch agama flow + #Create agama flow + Given url mainUrl + And header Authorization = 'Bearer ' + accessToken + And request read('agama.json') + When method POST + Then status 201 + And print response + Then def result = response + And print response.qname + Then def flowName = response.qname + And print flowName + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + #Patch agama flow + Then def revision_before = response.revision + And print 'revision = '+revision_before + And print result.jansHideOnDiscovery + And def orig_jansHideOnDiscovery = (result.jansHideOnDiscovery == null ? false : result.jansHideOnDiscovery) + And def request_body = (result.jansHideOnDiscovery == null ? "[ {\"op\":\"add\", \"path\": \"/jansHideOnDiscovery\", \"value\":"+orig_jansHideOnDiscovery+" } ]" : "[ {\"op\":\"replace\", \"path\": \"/jansHideOnDiscovery\", \"value\":"+orig_jansHideOnDiscovery+" } ]") + And print 'request_body ='+request_body + Given url mainUrl + '/' +encodedFlowName + And header Authorization = 'Bearer ' + accessToken + And header Content-Type = 'application/json-patch+json' + And header Accept = 'application/json' + And request request_body + Then print request + When method PATCH + Then status 200 + And print response From 0477b099294fd9fed24cd41b1e98dd30e0916193 Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Wed, 3 Aug 2022 17:47:30 +0530 Subject: [PATCH 12/21] fix(jans-config-api): agama endpoint enhancements --- .../docs/jans-config-api-swagger.yaml | 50 +++++++++++++- .../profiles/local/test.properties | 8 +-- .../rest/resource/auth/AgamaResource.java | 15 ++-- .../resources/feature/agama/agama.feature | 69 +++++++++++++++---- 4 files changed, 118 insertions(+), 24 deletions(-) diff --git a/jans-config-api/docs/jans-config-api-swagger.yaml b/jans-config-api/docs/jans-config-api-swagger.yaml index 7a847817348..62600977370 100644 --- a/jans-config-api/docs/jans-config-api-swagger.yaml +++ b/jans-config-api/docs/jans-config-api-swagger.yaml @@ -2561,7 +2561,52 @@ paths: '500': $ref: '#/components/responses/InternalServerError' security: - - oauth2: [https://jans.io/oauth/config/agama.write] + - oauth2: [https://jans.io/oauth/config/agama.write] + + put: + summary: Updates agama flow from source file. + description: Updates agama flow from source file.. + operationId: put-agama-flow-from-source + tags: + - Configuration – Agama Flow + requestBody: + content: + text/plain: + schema: + type: string + + responses: + '201': + description: CREATED + content: + application/json: + schema: + $ref: '#/components/schemas/AgamaFlow' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/agama.write] + + patch: + summary: Partially modify Agama Flow. + description: Partially modify Agama Flow. + operationId: patch-agama-flow + security: + - oauth2: [https://jans.io/oauth/config/agama.write] + tags: + - Configuration – Agama Flow + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: revision, value: \"2\" } ]' + delete: summary: Deletes an agama flow based on Qname. description: Deletes an agama flow based on Qname. @@ -3577,6 +3622,9 @@ components: https://jans.io/oauth/config/user.readonly: View user related information https://jans.io/oauth/config/user.write: Manage user related information https://jans.io/oauth/config/user.delete: Delete user related information + https://jans.io/oauth/config/agama.readonly: View Agama Flow related information + https://jans.io/oauth/config/agama.write: Manage Agama Flow related information + https://jans.io/oauth/config/agama.delete: Delete Agama Flow related information responses: diff --git a/jans-config-api/profiles/local/test.properties b/jans-config-api/profiles/local/test.properties index 03f62767111..fea5d396a4c 100644 --- a/jans-config-api/profiles/local/test.properties +++ b/jans-config-api/profiles/local/test.properties @@ -2,8 +2,8 @@ test.scopes=https://jans.io/oauth/config/acrs.readonly https://jans.io/oauth/config/acrs.write https://jans.io/oauth/config/attributes.readonly https://jans.io/oauth/config/attributes.write https://jans.io/oauth/config/attributes.delete https://jans.io/oauth/config/cache.readonly https://jans.io/oauth/config/cache.write https://jans.io/oauth/config/openid/clients.readonly https://jans.io/oauth/config/openid/clients.write https://jans.io/oauth/config/openid/clients.delete https://jans.io/oauth/jans-auth-server/config/properties.readonly https://jans.io/oauth/jans-auth-server/config/properties.write https://jans.io/oauth/config/smtp.readonly https://jans.io/oauth/config/smtp.write https://jans.io/oauth/config/smtp.delete https://jans.io/oauth/config/database/couchbase.readonly https://jans.io/oauth/config/database/couchbase.write https://jans.io/oauth/config/database/couchbase.delete https://jans.io/oauth/config/scripts.readonly https://jans.io/oauth/config/scripts.write https://jans.io/oauth/config/scripts.delete https://jans.io/oauth/config/fido2.readonly https://jans.io/oauth/config/fido2.write https://jans.io/oauth/config/jwks.readonly https://jans.io/oauth/config/jwks.write https://jans.io/oauth/config/database/ldap.readonly https://jans.io/oauth/config/database/ldap.write https://jans.io/oauth/config/database/ldap.delete https://jans.io/oauth/config/logging.readonly https://jans.io/oauth/config/logging.write https://jans.io/oauth/config/scopes.readonly https://jans.io/oauth/config/scopes.write https://jans.io/oauth/config/scopes.delete https://jans.io/oauth/config/uma/resources.readonly https://jans.io/oauth/config/uma/resources.write https://jans.io/oauth/config/uma/resources.delete https://jans.io/oauth/config/database/sql.readonly https://jans.io/oauth/config/database/sql.write https://jans.io/oauth/config/database/sql.delete https://jans.io/oauth/config/stats.readonly jans_stat https://jans.io/scim/users.read https://jans.io/scim/users.write https://jans.io/oauth/config/scim/users.read https://jans.io/oauth/config/scim/users.write https://jans.io/scim/config.readonly https://jans.io/scim/config.write https://jans.io/oauth/config/organization.readonly https://jans.io/oauth/config/organization.write https://jans.io/oauth/config/user.readonly https://jans.io/oauth/config/user.write https://jans.io/oauth/config/user.delete https://jans.io/oauth/config/agama.readonly https://jans.io/oauth/config/agama.write https://jans.io/oauth/config/agama.delete # jans.server -token.endpoint=https://jans.server2/jans-auth/restv1/token +token.endpoint=https://jans.server1/jans-auth/restv1/token token.grant.type=client_credentials -test.client.id=1800.47b78bb8-2bb9-45d2-a748-58b3888c4bd1 -test.client.secret=1TPHmZl5ycI4 -test.issuer=https://jans.server2 \ No newline at end of file +test.client.id=1800.c3d63983-6883-438d-b974-38348645de42 +test.client.secret=QzZFg9RnQjSP +test.issuer=https://jans.server1 \ No newline at end of file diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java index f63220d80cc..c666939675b 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java @@ -351,16 +351,21 @@ private Flow getRevision(Flow flow, Flow existingFlow) { } private List getAgamaFlowDetails(List flows, boolean includeSource) { - if (flows == null || flows.isEmpty() || !includeSource) { + + log.debug("Flow data filter - flows:{}, includeSource:{}", flows, includeSource); + if (flows == null || flows.isEmpty()) { return flows; } for (Flow flow : flows) { - flow.setBaseDn(null); - flow.setDn(null); - flow.setSource(null); - flow.setTransHash(null); + flow.setTranspiled(null); + flow.setTransHash(null); + + if (!includeSource) { + flow.setSource(null); + } + } return flows; diff --git a/jans-config-api/server/src/test/resources/feature/agama/agama.feature b/jans-config-api/server/src/test/resources/feature/agama/agama.feature index e652b79c4f0..cca56be2c31 100644 --- a/jans-config-api/server/src/test/resources/feature/agama/agama.feature +++ b/jans-config-api/server/src/test/resources/feature/agama/agama.feature @@ -61,10 +61,23 @@ Scenario: Create, update and delete agama flow And request read('agama.json') When method POST Then status 201 + And print response + And print response.qname + Then def flowName = response.qname + And print flowName + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + #Fetch agama flow by name and with source + Given url mainUrl + '/' +encodedFlowName+ '?includeSource=true' + And header Authorization = 'Bearer ' + accessToken + When method GET + Then status 200 And print response Then def result = response - And print 'Old transHash ='+response.transHash - Then set result.transHash = 'UpdatedAgamaFlowtransHash' + And print result + And print 'Old revision ='+response.revision + Then set result.revision = response.revision+1 + And print result And print response.qname Then def flowName = response.qname And print flowName @@ -78,7 +91,7 @@ Scenario: Create, update and delete agama flow Then status 200 And print response And print response.qname - And print response.transHash + And print response.revision Then def flowName = response.qname And print flowName Then def encodedFlowName = funGetEncodedValue(flowName) @@ -95,7 +108,7 @@ Scenario: Create, update and delete agama flow Then def encodedFlowName = funGetEncodedValue(flowName) And print encodedFlowName #Fetch agama flow by name and with source - Given url mainUrl + '/' +encodedFlowName + '?includeSource' + Given url mainUrl + '/' +encodedFlowName + '?includeSource=true' And header Authorization = 'Bearer ' + accessToken When method GET Then status 200 @@ -106,7 +119,7 @@ Scenario: Create, update and delete agama flow Then def encodedFlowName = funGetEncodedValue(flowName) And print encodedFlowName #Fetch agama flow by name and with source - Given url mainUrl + '?includeSource' + Given url mainUrl + '?includeSource=true' And header Authorization = 'Bearer ' + accessToken When method GET Then status 200 @@ -139,13 +152,22 @@ Scenario: Create agama flow with source data in request body When method POST Then status 201 And print response + Then def flowName = response.qname + And print flowName + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + #Fetch agama flow by name and with source + Given url mainUrl + '/' +encodedFlowName+ '?includeSource=true' + And header Authorization = 'Bearer ' + accessToken + When method GET + Then status 200 + And print response And print response.qname Then def flowName = response.qname And print flowName Then def result = response - And print 'Old transHash ='+response.transHash - Then set result.transHash = 'UpdatedAgamaFlowtransHash' - Then def encodedFlowName = funGetEncodedValue(flowName) + And print 'Old revision ='+response.revision + Then set result.revision = response.revision+1 And print encodedFlowName #Update agama flow Given url mainUrl + '/' +encodedFlowName @@ -155,7 +177,7 @@ Scenario: Create agama flow with source data in request body Then status 200 And print response And print response.qname - And print response.transHash + And print response.revision Then def flowName = response.qname And print flowName Then def encodedFlowName = funGetEncodedValue(flowName) @@ -193,12 +215,22 @@ Scenario: Create agama flow with source data in request body When method POST Then status 201 And print response + Then def flowName = response.qname + And print flowName + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + #Fetch agama flow by name and with source + Given url mainUrl + '/' +encodedFlowName+ '?includeSource=true' + And header Authorization = 'Bearer ' + accessToken + When method GET + Then status 200 + And print response And print response.qname Then def flowName = response.qname And print flowName Then def result = response - And print 'Old transHash ='+response.transHash - Then set result.transHash = 'UpdatedAgamaFlowtransHash' + And print 'Old revision ='+response.revision + Then set result.revision = response.revision+1 Then def encodedFlowName = funGetEncodedValue(flowName) And print encodedFlowName #Update agama flow @@ -211,7 +243,7 @@ Scenario: Create agama flow with source data in request body Then status 200 And print response And print response.qname - And print response.transHash + And print response.revision Then def flowName = response.qname And print flowName Then def encodedFlowName = funGetEncodedValue(flowName) @@ -242,6 +274,16 @@ Scenario: Create and Patch agama flow And request read('agama.json') When method POST Then status 201 + And print response + Then def flowName = response.qname + And print flowName + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + #Fetch agama flow by name and with source + Given url mainUrl + '/' +encodedFlowName+ '?includeSource=true' + And header Authorization = 'Bearer ' + accessToken + When method GET + Then status 200 And print response Then def result = response And print response.qname @@ -253,8 +295,7 @@ Scenario: Create and Patch agama flow Then def revision_before = response.revision And print 'revision = '+revision_before And print result.jansHideOnDiscovery - And def orig_jansHideOnDiscovery = (result.jansHideOnDiscovery == null ? false : result.jansHideOnDiscovery) - And def request_body = (result.jansHideOnDiscovery == null ? "[ {\"op\":\"add\", \"path\": \"/jansHideOnDiscovery\", \"value\":"+orig_jansHideOnDiscovery+" } ]" : "[ {\"op\":\"replace\", \"path\": \"/jansHideOnDiscovery\", \"value\":"+orig_jansHideOnDiscovery+" } ]") + And def request_body = (result.revision == null ? "[ {\"op\":\"add\", \"path\": \"/revision\", \"value\":revision_before+1 } ]" : "[ {\"op\":\"replace\", \"path\": \"/revision\", \"value\":revision_before+1 } ]") And print 'request_body ='+request_body Given url mainUrl + '/' +encodedFlowName And header Authorization = 'Bearer ' + accessToken From 44415b9c1b4c6ca277f76f0ab5619989fbd61d0f Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Wed, 3 Aug 2022 18:43:02 +0530 Subject: [PATCH 13/21] fix(jans-config-api): fixed swagger spec for Uma Resource delete --- .../docs/jans-config-api-swagger.yaml | 117 ++++++------------ 1 file changed, 40 insertions(+), 77 deletions(-) diff --git a/jans-config-api/docs/jans-config-api-swagger.yaml b/jans-config-api/docs/jans-config-api-swagger.yaml index 62600977370..48a0c69fca2 100644 --- a/jans-config-api/docs/jans-config-api-swagger.yaml +++ b/jans-config-api/docs/jans-config-api-swagger.yaml @@ -2053,6 +2053,7 @@ paths: description: Internal Server Error security: - oauth2: [https://jans.io/oauth/config/openid/clients.write] + /jans-config-api/api/v1/uma/resources: get: tags: @@ -2169,38 +2170,7 @@ paths: description: Internal Server Error security: - oauth2: [https://jans.io/oauth/config/uma/resources.readonly] - /jans-config-api/api/v1/uma/resources/clientId/{clientId}: - parameters: - - name: clientId - in: path - required: true - description: Client ID. - schema: - type: string - get: - tags: - - OAuth - UMA Resources - summary: Fetch uma resources by client id. - description: Fetch uma resources by client id. - operationId: get-oauth-uma-resources-by-clientid - responses: - '200': - description: OK - content: - application/json: - schema: - title: UMA Resource list. - description: List of UMA Resource. - items: - $ref: '#/components/schemas/UmaResource' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - $ref: '#/components/responses/NotFound' - '500': - description: Internal Server Error - security: - - oauth2: [https://jans.io/oauth/config/uma/resources.readonly] + delete: tags: - OAuth - UMA Resources @@ -2218,6 +2188,7 @@ paths: description: Internal Server Error security: - oauth2: [https://jans.io/oauth/config/uma/resources.delete] + patch: tags: - OAuth - UMA Resources @@ -2249,6 +2220,42 @@ paths: description: Internal Server Error security: - oauth2: [https://jans.io/oauth/config/uma/resources.write] + + + /jans-config-api/api/v1/uma/resources/clientId/{clientId}: + parameters: + - name: clientId + in: path + required: true + description: Client ID. + schema: + type: string + get: + tags: + - OAuth - UMA Resources + summary: Fetch uma resources by client id. + description: Fetch uma resources by client id. + operationId: get-oauth-uma-resources-by-clientid + responses: + '200': + description: OK + content: + application/json: + schema: + title: UMA Resource list. + description: List of UMA Resource. + items: + $ref: '#/components/schemas/UmaResource' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/uma/resources.readonly] + + /jans-config-api/api/v1/scopes: get: tags: @@ -2561,51 +2568,7 @@ paths: '500': $ref: '#/components/responses/InternalServerError' security: - - oauth2: [https://jans.io/oauth/config/agama.write] - - put: - summary: Updates agama flow from source file. - description: Updates agama flow from source file.. - operationId: put-agama-flow-from-source - tags: - - Configuration – Agama Flow - requestBody: - content: - text/plain: - schema: - type: string - - responses: - '201': - description: CREATED - content: - application/json: - schema: - $ref: '#/components/schemas/AgamaFlow' - '401': - $ref: '#/components/responses/Unauthorized' - '500': - $ref: '#/components/responses/InternalServerError' - security: - - oauth2: [https://jans.io/oauth/config/agama.write] - - patch: - summary: Partially modify Agama Flow. - description: Partially modify Agama Flow. - operationId: patch-agama-flow - security: - - oauth2: [https://jans.io/oauth/config/agama.write] - tags: - - Configuration – Agama Flow - requestBody: - content: - application/json-patch+json: - schema: - type: array - items: - $ref: '#/components/schemas/PatchRequest' - description: String representing patch-document. - example: '[ {op:replace, path: revision, value: \"2\" } ]' + - oauth2: [https://jans.io/oauth/config/agama.write] delete: summary: Deletes an agama flow based on Qname. From 77547bb11cbbfd44af066905c9cfb5caa79b592f Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Wed, 3 Aug 2022 19:07:16 +0530 Subject: [PATCH 14/21] fix(jans-config-api): agama endpoint enhancements --- .../resources/feature/agama/agama.feature | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/jans-config-api/server/src/test/resources/feature/agama/agama.feature b/jans-config-api/server/src/test/resources/feature/agama/agama.feature index cca56be2c31..07550ad2ed6 100644 --- a/jans-config-api/server/src/test/resources/feature/agama/agama.feature +++ b/jans-config-api/server/src/test/resources/feature/agama/agama.feature @@ -113,18 +113,6 @@ Scenario: Create, update and delete agama flow When method GET Then status 200 And print response - And print response.qname - Then def flowName = response.qname - And print flowName - Then def encodedFlowName = funGetEncodedValue(flowName) - And print encodedFlowName - #Fetch agama flow by name and with source - Given url mainUrl + '?includeSource=true' - And header Authorization = 'Bearer ' + accessToken - When method GET - Then status 200 - And print response - And print response.qname Then def flowName = response.qname And print flowName Then def encodedFlowName = funGetEncodedValue(flowName) @@ -136,7 +124,7 @@ Scenario: Create, update and delete agama flow Then status 204 And print response - +@ignore @CreateFlowWithDataInRequestBodyUpdateDelete Scenario: Create agama flow with source data in request body #Create agama flow @@ -199,7 +187,8 @@ Scenario: Create agama flow with source data in request body When method DELETE Then status 204 And print response - + +@ignore @CreateAndUpdateFlowWithDataInRequestBodyUpdateDelete Scenario: Create agama flow with source data in request body #Create agama flow @@ -265,7 +254,8 @@ Scenario: Create agama flow with source data in request body When method DELETE Then status 204 And print response - + +@ignore @CreateAndPatchFlow Scenario: Create and Patch agama flow #Create agama flow From ee4afb309298e070e299bf81ad15e6bf143b018e Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Wed, 3 Aug 2022 20:48:04 +0530 Subject: [PATCH 15/21] fix(jans-config-api): agama endpoint enhancements --- .../rest/resource/auth/AgamaResource.java | 2 +- .../service/auth/AgamaFlowService.java | 4 ++-- .../resources/feature/agama/agama.feature | 24 +++++++++++++------ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java index c666939675b..42b68df1a3b 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java @@ -229,7 +229,7 @@ public Response patchFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowNam log.trace(" Flow name for update is:{}", decodedFlowName); // check if flow exists - Flow existingFlow = findFlow(decodedFlowName, false, false); + Flow existingFlow = findFlow(decodedFlowName, false, true); log.debug(" existingFlow:{}", existingFlow); existingFlow = Jackson.applyPatch(pathString, existingFlow); diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/AgamaFlowService.java b/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/AgamaFlowService.java index f77c6a97026..d299806a747 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/AgamaFlowService.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/service/auth/AgamaFlowService.java @@ -255,7 +255,7 @@ public String validateNonMandatoryFields(Flow flow, List mandatoryAttrib logger.debug("Checking mandatory unwantedAttributes:{} ", unwantedAttributes); if (unwantedAttributes.length() > 0) { - unwantedAttributes.insert(0, "Value of these feilds should be null -> ("); + unwantedAttributes.insert(0, "Value of these fields should be null -> ("); unwantedAttributes.replace(unwantedAttributes.lastIndexOf(","), unwantedAttributes.length(), ""); unwantedAttributes.append(")."); } @@ -304,7 +304,7 @@ else if ("io.jans.agama.model.FlowMetadata".equalsIgnoreCase(dataType.getName()) || StringUtils.isNotBlank(flowMetadata.getAuthor()) || StringUtils.isNotBlank(flowMetadata.getDescription()) || flowMetadata.getInputs() != null || (flowMetadata.getTimeout() != null && flowMetadata.getTimeout() > 0) - || flowMetadata.getProperties() != null || flowMetadata.getTimestamp() > 0) { + || flowMetadata.getProperties() != null ) { logger.debug("FlowMetadata is not null !!!"); return true; } diff --git a/jans-config-api/server/src/test/resources/feature/agama/agama.feature b/jans-config-api/server/src/test/resources/feature/agama/agama.feature index 07550ad2ed6..5f60f76876a 100644 --- a/jans-config-api/server/src/test/resources/feature/agama/agama.feature +++ b/jans-config-api/server/src/test/resources/feature/agama/agama.feature @@ -124,7 +124,7 @@ Scenario: Create, update and delete agama flow Then status 204 And print response -@ignore + @CreateFlowWithDataInRequestBodyUpdateDelete Scenario: Create agama flow with source data in request body #Create agama flow @@ -188,7 +188,7 @@ Scenario: Create agama flow with source data in request body Then status 204 And print response -@ignore + @CreateAndUpdateFlowWithDataInRequestBodyUpdateDelete Scenario: Create agama flow with source data in request body #Create agama flow @@ -218,8 +218,6 @@ Scenario: Create agama flow with source data in request body Then def flowName = response.qname And print flowName Then def result = response - And print 'Old revision ='+response.revision - Then set result.revision = response.revision+1 Then def encodedFlowName = funGetEncodedValue(flowName) And print encodedFlowName #Update agama flow @@ -255,8 +253,8 @@ Scenario: Create agama flow with source data in request body Then status 204 And print response -@ignore -@CreateAndPatchFlow + +@CreateAndPatchFlowAndDelete Scenario: Create and Patch agama flow #Create agama flow Given url mainUrl @@ -284,8 +282,10 @@ Scenario: Create and Patch agama flow #Patch agama flow Then def revision_before = response.revision And print 'revision = '+revision_before + Then def revision_updated = revision_before+1 + And print 'revision_updated = '+revision_updated And print result.jansHideOnDiscovery - And def request_body = (result.revision == null ? "[ {\"op\":\"add\", \"path\": \"/revision\", \"value\":revision_before+1 } ]" : "[ {\"op\":\"replace\", \"path\": \"/revision\", \"value\":revision_before+1 } ]") + And def request_body = (result.revision == null ? "[ {\"op\":\"add\", \"path\": \"/revision\", \"value\":"+revision_updated+" } ]" : "[ {\"op\":\"replace\", \"path\": \"/revision\", \"value\":"+revision_updated+" } ]") And print 'request_body ='+request_body Given url mainUrl + '/' +encodedFlowName And header Authorization = 'Bearer ' + accessToken @@ -296,3 +296,13 @@ Scenario: Create and Patch agama flow When method PATCH Then status 200 And print response + Then def flowName = response.qname + And print flowName + Then def encodedFlowName = funGetEncodedValue(flowName) + And print encodedFlowName + #Delete agama flow by name + Given url mainUrl + '/' +encodedFlowName + And header Authorization = 'Bearer ' + accessToken + When method DELETE + Then status 204 + And print response From f22cd3b54a0a398aac69cb844d41264c9a52b3c7 Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Wed, 3 Aug 2022 21:24:35 +0530 Subject: [PATCH 16/21] fix(jans-config-api): agama endpoint enhancements --- .../io/jans/configapi/util/ApiConstants.java | 1 + .../docs/jans-config-api-swagger.yaml | 71 ++++++++++++++++++- .../rest/resource/auth/AgamaResource.java | 2 +- 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java b/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java index 2bc7e9f88a9..0d6a29892a5 100644 --- a/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java +++ b/jans-config-api/common/src/main/java/io/jans/configapi/util/ApiConstants.java @@ -80,6 +80,7 @@ private ApiConstants() {} public static final String ENABLED = "enabled"; public static final String QNAME = "qname"; public static final String INCLUDE_SOURCE = "includeSource"; + public static final String SOURCE = "/source/"; public static final String LIMIT = "limit"; public static final String START_INDEX = "startIndex"; diff --git a/jans-config-api/docs/jans-config-api-swagger.yaml b/jans-config-api/docs/jans-config-api-swagger.yaml index 48a0c69fca2..b248078b1b4 100644 --- a/jans-config-api/docs/jans-config-api-swagger.yaml +++ b/jans-config-api/docs/jans-config-api-swagger.yaml @@ -2541,8 +2541,7 @@ paths: $ref: '#/components/responses/InternalServerError' security: - oauth2: [https://jans.io/oauth/config/agama.write] - - + put: summary: Updates an agama flow based on Qname. description: Updates an agama based on Qname. @@ -2568,7 +2567,38 @@ paths: '500': $ref: '#/components/responses/InternalServerError' security: - - oauth2: [https://jans.io/oauth/config/agama.write] + - oauth2: [https://jans.io/oauth/config/agama.write] + + patch: + summary: Partially modify a Agama Flow. + description: Partially modify a Agama Flow. + operationId: patch-agama-flow + tags: + - Configuration – Agama Flow + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: enabled, value: \"false\" } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/AgamaFlow' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/agama.write] delete: summary: Deletes an agama flow based on Qname. @@ -2588,6 +2618,41 @@ paths: security: - oauth2: [https://jans.io/oauth/config/agama.delete] + /jans-config-api/api/v1/agama/source/{qname}: + parameters: + - schema: + type: string + name: qname + in: path + description: flow qname. + required: true + + put: + summary: Update agama flow from source file. + description: Update agama flow from source file. + operationId: put-agama-flow-from-source + tags: + - Configuration – Agama Flow + requestBody: + content: + text/plain: + schema: + type: string + + responses: + '201': + description: CREATED + content: + application/json: + schema: + $ref: '#/components/schemas/AgamaFlow' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/agama.write] + /jans-config-api/api/v1/stat: get: summary: Provides server with basic statistic. diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java index 42b68df1a3b..76051fbfa5d 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java @@ -185,7 +185,7 @@ public Response updateFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowNa @PUT @Consumes(MediaType.TEXT_PLAIN) - @Path(ApiConstants.QNAME_PATH) + @Path(ApiConstants.SOURCE + ApiConstants.QNAME_PATH) @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_WRITE_ACCESS }) public Response updateFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull String flowName, @Valid String source) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { From 7ac9cde1c4f4f7a711b753432b88ca82aca77351 Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Wed, 3 Aug 2022 21:39:46 +0530 Subject: [PATCH 17/21] fix(jans-config-api): agama endpoint enhancements --- .../server/src/test/resources/feature/agama/agama.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jans-config-api/server/src/test/resources/feature/agama/agama.feature b/jans-config-api/server/src/test/resources/feature/agama/agama.feature index 5f60f76876a..5b503944f00 100644 --- a/jans-config-api/server/src/test/resources/feature/agama/agama.feature +++ b/jans-config-api/server/src/test/resources/feature/agama/agama.feature @@ -221,7 +221,7 @@ Scenario: Create agama flow with source data in request body Then def encodedFlowName = funGetEncodedValue(flowName) And print encodedFlowName #Update agama flow - Given url mainUrl + '/' +encodedFlowName + Given url mainUrl + '/source/' +encodedFlowName And header Authorization = 'Bearer ' + accessToken And header Content-Type = 'text/plain' And header Accept = 'application/json' From 3a0501945576cd6f67ebfc6f51143ddf4713ce2b Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Wed, 3 Aug 2022 21:56:23 +0530 Subject: [PATCH 18/21] fix(jans-config-api): agama endpoint enhancements --- .../java/io/jans/configapi/rest/resource/auth/AgamaResource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java index 76051fbfa5d..d4ce0ee3e79 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java @@ -8,7 +8,6 @@ import io.jans.agama.model.Flow; import io.jans.agama.model.FlowMetadata; -import io.jans.as.common.model.registration.Client; import io.jans.agama.dsl.Transpiler; import io.jans.agama.dsl.TranspilerException; import io.jans.agama.dsl.error.SyntaxException; From d740c68aa37ebd8c7a60f38455b62a2631a96066 Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Thu, 4 Aug 2022 19:16:28 +0530 Subject: [PATCH 19/21] feat(jans-config-api): agama patch endpoint --- .../rest/resource/auth/AgamaResource.java | 95 +++++++++++++------ .../io/jans/configapi/core/util/DataUtil.java | 40 ++++---- .../io/jans/configapi/core/util/Jackson.java | 5 + 3 files changed, 90 insertions(+), 50 deletions(-) diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java index d4ce0ee3e79..6870af0967b 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java @@ -40,6 +40,7 @@ import org.slf4j.Logger; import com.github.fge.jsonpatch.JsonPatchException; +import com.github.fge.jsonpatch.JsonPatch; @Path(ApiConstants.AGAMA) @Consumes(MediaType.APPLICATION_JSON) @@ -107,10 +108,7 @@ public Response createFlow(@Valid Flow flow) // validate flow data validateAgamaFlowData(flow, true); - flow.setRevision(-1); - FlowMetadata flowMetadata = new FlowMetadata(); - flowMetadata.setTimestamp(System.currentTimeMillis()); - flow.setMetadata(flowMetadata); + updateFlowDetails(flow, null); agamaFlowService.addAgamaFlow(flow); flow = findFlow(flow.getQname(), true, false); @@ -139,10 +137,7 @@ public Response createFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull Strin flow.setQname(decodedFlowName); flow.setSource(source); flow.setEnabled(true); - flow.setRevision(-1); - FlowMetadata flowMetadata = new FlowMetadata(); - flowMetadata.setTimestamp(System.currentTimeMillis()); - flow.setMetadata(flowMetadata); + updateFlowDetails(flow, null); // validate flow data validateAgamaFlowData(flow, true); @@ -170,7 +165,7 @@ public Response updateFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowNa flow.setQname(decodedFlowName); log.trace("Flow revision check - flow.getRevision():{}, existingFlow.getRevision():{}", flow.getRevision(), existingFlow.getRevision()); - getRevision(flow, existingFlow); + updateFlowDetails(flow, existingFlow); log.debug("Flow revision after update - flow.getRevision():{}", flow.getRevision()); // validate flow data @@ -201,7 +196,7 @@ public Response updateFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull Strin if (existingFlow != null) { existingFlow.setSource(source); - getRevision(existingFlow, existingFlow); + updateFlowDetails(existingFlow, existingFlow); // validate flow data validateAgamaFlowData(existingFlow, false); @@ -213,26 +208,31 @@ public Response updateFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull Strin return Response.status(Response.Status.OK).entity(existingFlow).build(); } + @PATCH @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON) @Path(ApiConstants.QNAME_PATH) @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_WRITE_ACCESS }) - public Response patchFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowName, @NotNull String pathString) + public Response patchFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowName, @NotNull JsonPatch jsonPatch) throws JsonPatchException, IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (logger.isDebugEnabled()) { - logger.debug("Flow details to be patched - flowName:{}, pathString:{}", escapeLog(flowName), - escapeLog(pathString)); + logger.debug("Flow details to be patched - flowName:{}, jsonPatch:{}", escapeLog(flowName), + escapeLog(jsonPatch)); } + logger.error("Flow details to be patched - flowName:{}, jsonPatch:{}", escapeLog(flowName), + escapeLog(jsonPatch)); + String decodedFlowName = getURLDecodedValue(flowName); - log.trace(" Flow name for update is:{}", decodedFlowName); + log.error(" Flow to be patched is name:{}", decodedFlowName); // check if flow exists Flow existingFlow = findFlow(decodedFlowName, false, true); - log.debug(" existingFlow:{}", existingFlow); + log.error(" Flow to be patched:{}", existingFlow); - existingFlow = Jackson.applyPatch(pathString, existingFlow); - getRevision(existingFlow, existingFlow); + existingFlow = Jackson.applyJsonPatch(jsonPatch, existingFlow); + log.error(" After patch flow:{}", existingFlow); + updateFlowDetails(existingFlow, existingFlow); // validate flow data validateAgamaFlowData(existingFlow, false); @@ -262,10 +262,12 @@ private Flow findFlow(String flowName, boolean throwError, boolean includeSource flow = agamaFlowService.getFlowByName(flowName); // filter values - List flows = Arrays.asList(flow); - getAgamaFlowDetails(flows, includeSource); - if (flows != null && !flows.isEmpty()) { - flow = flows.get(0); + if (flow != null) { + List flows = Arrays.asList(flow); + getAgamaFlowDetails(flows, includeSource); + if (flows != null && !flows.isEmpty()) { + flow = flows.get(0); + } } } catch (EntryPersistenceException e) { log.error("No flow found with the name:{} ", flowName); @@ -328,24 +330,57 @@ private String getURLDecodedValue(String pathParam) { return pathParam; } - private Flow getRevision(Flow flow, Flow existingFlow) { - log.debug("Flow revision check - flow:{}, existingFlow:{}", flow, existingFlow); + private Flow updateFlowDetails(Flow flow, Flow existingFlow) { + log.error("Update Flow details - flow:{}, existingFlow:{}", flow, existingFlow); + + updateRevision(flow, existingFlow); + updateMetadata(flow); + return flow; + } + + private Flow updateRevision(Flow flow, Flow existingFlow) { + log.error("Flow revision check - flow:{}, existingFlow:{}", flow, existingFlow); - if (flow == null || existingFlow == null) { + if (flow == null) { return flow; } - log.debug("Flow revision check - flow.getRevision():{}, existingFlow.getRevision():{}", flow.getRevision(), + if(existingFlow == null) { + flow.setRevision(-1); + return flow; + } + + log.error("Flow revision before update - flow.getRevision():{}, existingFlow.getRevision():{}", flow.getRevision(), existingFlow.getRevision()); - if (flow.getSource() != null && flow.getRevision() == 0) { - if (existingFlow.getRevision() == 0 || existingFlow.getRevision() == -1) { + if (flow.getSource() != null && (flow.getRevision() <=0 || flow.getRevision() == existingFlow.getRevision()) ){ + if (existingFlow.getRevision() <= 0 ) { flow.setRevision(1); } else { flow.setRevision(existingFlow.getRevision() + 1); } } - log.debug("Final flow revision to be updated to - flow.getRevision():{}", flow.getRevision()); + log.error("Flow revision after update - flow.getRevision():{}", flow.getRevision()); + return flow; + } + + private Flow updateMetadata(Flow flow) { + log.error("Update Flow Metadata - flow:{}", flow); + + if (flow == null) { + return flow; + } + + FlowMetadata flowMetadata = flow.getMetadata(); + if(flowMetadata == null) { + flowMetadata = new FlowMetadata(); + } + + log.error("Flow Metadata Timestamp before update - flowMetadata.getTimestamp():{}", flowMetadata.getTimestamp()); + flowMetadata.setTimestamp(System.currentTimeMillis()); + flow.setMetadata(flowMetadata); + + log.error("Flow Metadata Timestamp after update - flowMetadata.getTimestamp():{}", flowMetadata.getTimestamp()); return flow; } @@ -357,10 +392,10 @@ private List getAgamaFlowDetails(List flows, boolean includeSource) } for (Flow flow : flows) { - + flow.setTranspiled(null); flow.setTransHash(null); - + if (!includeSource) { flow.setSource(null); } diff --git a/jans-config-api/shared/src/main/java/io/jans/configapi/core/util/DataUtil.java b/jans-config-api/shared/src/main/java/io/jans/configapi/core/util/DataUtil.java index 16b6408fb85..c58ea277495 100644 --- a/jans-config-api/shared/src/main/java/io/jans/configapi/core/util/DataUtil.java +++ b/jans-config-api/shared/src/main/java/io/jans/configapi/core/util/DataUtil.java @@ -32,22 +32,22 @@ public class DataUtil { private static final Logger logger = LoggerFactory.getLogger(DataUtil.class); public static Class getPropertType(String className, String name) throws MappingException { - logger.error("className:{} , name:{} ", className, name); + logger.debug("className:{} , name:{} ", className, name); return ReflectHelper.reflectedPropertyClass(className, name); } public static Getter getGetterMethod(Class clazz, String name) throws MappingException { - logger.error("Get Getter fromclazz:{} , name:{} ", clazz, name); + logger.debug("Get Getter fromclazz:{} , name:{} ", clazz, name); return ReflectHelper.getGetter(clazz, name); } public static Setter getSetterMethod(Class clazz, String name) throws MappingException { - logger.error("Get Setter from clazz:{} for name:{} ", clazz, name); + logger.debug("Get Setter from clazz:{} for name:{} ", clazz, name); return ReflectHelper.getSetter(clazz, name); } public static Object getValue(Object object, String property) throws MappingException { - logger.error("Get value from object:{} for property:{} ", object, property); + logger.debug("Get value from object:{} for property:{} ", object, property); return ReflectHelper.getValue(object, property); } @@ -61,10 +61,10 @@ public static Method getSetter(String fieldName, Class clazz) throws Introspe public static Object invokeMethod(Class clazz, String methodName, Class... parameterTypes) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - logger.error("Invoke clazz:{} on methodName:{} with name:{} ", clazz, methodName, parameterTypes); + logger.debug("Invoke clazz:{} on methodName:{} with name:{} ", clazz, methodName, parameterTypes); Method m = clazz.getDeclaredMethod(methodName, parameterTypes); Object obj = m.invoke(null, parameterTypes); - logger.error("methodName:{} returned obj:{} ", methodName, obj); + logger.debug("methodName:{} returned obj:{} ", methodName, obj); return obj; } @@ -105,41 +105,41 @@ public static void invokeReflectionSetter(Object obj, String propertyName, Objec } public static boolean containsField(List allFields, String attribute) { - logger.error("allFields:{}, attribute:{}, allFields.contains(attribute):{} ", allFields, attribute, + logger.debug("allFields:{}, attribute:{}, allFields.contains(attribute):{} ", allFields, attribute, allFields.stream().anyMatch(f -> f.getName().equals(attribute))); return allFields.stream().anyMatch(f -> f.getName().equals(attribute)); } public boolean isStringField(Map objectPropertyMap, String attribute) { - logger.error("Check if field is string objectPropertyMap:{}, attribute:{} ", objectPropertyMap, attribute); + logger.debug("Check if field is string objectPropertyMap:{}, attribute:{} ", objectPropertyMap, attribute); if (objectPropertyMap == null || StringUtils.isBlank(attribute)) { return false; } - logger.error("attribute:{} , datatype:{}", attribute, objectPropertyMap.get(attribute)); + logger.debug("attribute:{} , datatype:{}", attribute, objectPropertyMap.get(attribute)); return ("java.lang.String".equalsIgnoreCase(objectPropertyMap.get(attribute))); } public static List getAllFields(Class type) { List allFields = new ArrayList<>(); getAllFields(allFields, type); - logger.error("Fields:{} of type:{} ", allFields, type); + logger.debug("Fields:{} of type:{} ", allFields, type); return allFields; } public static List getAllFields(List fields, Class type) { - logger.error("Getting fields type:{} - fields:{} ", type, fields); + logger.debug("Getting fields type:{} - fields:{} ", type, fields); fields.addAll(Arrays.asList(type.getDeclaredFields())); if (type.getSuperclass() != null) { getAllFields(fields, type.getSuperclass()); } - logger.error("Final fields:{} of type:{} ", fields, type); + logger.debug("Final fields:{} of type:{} ", fields, type); return fields; } public static Map getFieldTypeMap(Class clazz) { - logger.error("clazz:{} ", clazz); + logger.debug("clazz:{} ", clazz); Map propertyTypeMap = new HashMap<>(); if (clazz == null) { @@ -147,17 +147,17 @@ public static Map getFieldTypeMap(Class clazz) { } List fields = getAllFields(clazz); - logger.error("AllFields:{} ", fields); + logger.debug("AllFields:{} ", fields); for (Field field : fields) { - logger.error( + logger.debug( "field:{} , field.getAnnotatedType():{}, field.getAnnotations():{} , field.getType().getAnnotations():{}, field.getType().getCanonicalName():{} , field.getType().getClass():{} , field.getType().getClasses():{} , field.getType().getComponentType():{}", field, field.getAnnotatedType(), field.getAnnotations(), field.getType().getAnnotations(), field.getType().getCanonicalName(), field.getType().getClass(), field.getType().getClasses(), field.getType().getComponentType()); propertyTypeMap.put(field.getName(), field.getType().getSimpleName()); } - logger.error("Final propertyTypeMap{} ", propertyTypeMap); + logger.debug("Final propertyTypeMap{} ", propertyTypeMap); return propertyTypeMap; } @@ -166,23 +166,23 @@ public static Object invokeGetterMethod(Object obj, String variableName) { } public static boolean isKeyPresentInMap(String key, Map map) { - logger.error("Check key:{} is present in map:{}", key, map); + logger.debug("Check key:{} is present in map:{}", key, map); if (StringHelper.isEmpty(key) || map == null || map.isEmpty()) { return false; } - logger.error(" key:{} present in map:{} ?:{}", key, map, map.keySet().contains(key)); + logger.debug(" key:{} present in map:{} ?:{}", key, map, map.keySet().contains(key)); return map.keySet().contains(key); } public static boolean isAttributeInExclusion(String className, String attribute, Map> exclusionMap) { - logger.error("Check if object:{} attribute:{} is in exclusionMap:{}", className, attribute, exclusionMap); + logger.debug("Check if object:{} attribute:{} is in exclusionMap:{}", className, attribute, exclusionMap); if (StringHelper.isEmpty(className) || StringHelper.isEmpty(attribute) || exclusionMap == null || exclusionMap.isEmpty()) { return false; } - logger.error("Map contains key exclusionMap.keySet().contains(className):{}", + logger.debug("Map contains key exclusionMap.keySet().contains(className):{}", exclusionMap.keySet().contains(className)); if (exclusionMap.keySet().contains(className)) { diff --git a/jans-config-api/shared/src/main/java/io/jans/configapi/core/util/Jackson.java b/jans-config-api/shared/src/main/java/io/jans/configapi/core/util/Jackson.java index 89fbc36e898..a454897181f 100644 --- a/jans-config-api/shared/src/main/java/io/jans/configapi/core/util/Jackson.java +++ b/jans-config-api/shared/src/main/java/io/jans/configapi/core/util/Jackson.java @@ -55,6 +55,11 @@ public static T applyPatch(String patchAsString, T obj) throws JsonPatchExce return applyPatch(jsonPatch, obj); } + public static T applyJsonPatch(JsonPatch jsonPatch, T obj) throws JsonPatchException, IOException { + LOG.debug("Patch details - jsonPatch:{}, obj:{}", jsonPatch, obj ); + return applyPatch(jsonPatch, obj); + } + @SuppressWarnings("unchecked") public static T applyPatch(JsonPatch jsonPatch, T obj) throws JsonPatchException, JsonProcessingException { Preconditions.checkNotNull(jsonPatch); From 0a8e5e6272d0f8e9986e9cf25050319cd98d0a0a Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Thu, 4 Aug 2022 20:43:11 +0530 Subject: [PATCH 20/21] feat(jans-config-api): agama patch endpoint --- .../rest/resource/auth/AgamaResource.java | 125 +++++++++--------- .../configapi/core/rest/BaseResource.java | 9 +- 2 files changed, 68 insertions(+), 66 deletions(-) diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java index 6870af0967b..0ba98cdc7ed 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java @@ -47,9 +47,6 @@ @Produces(MediaType.APPLICATION_JSON) public class AgamaResource extends ConfigBaseResource { - @Inject - Logger log; - @Inject AgamaFlowService agamaFlowService; @@ -59,8 +56,8 @@ public Response getFlows(@DefaultValue("") @QueryParam(value = ApiConstants.PATT @DefaultValue(DEFAULT_LIST_SIZE) @QueryParam(value = ApiConstants.LIMIT) int limit, @DefaultValue("false") @QueryParam(value = ApiConstants.INCLUDE_SOURCE) boolean includeSource) { - if (log.isDebugEnabled()) { - log.debug("Search Agama Flow with pattern:{}, sizeLimit:{}, includeSource:{}", escapeLog(pattern), + if (logger.isDebugEnabled()) { + logger.debug("Search Agama Flow with pattern:{}, sizeLimit:{}, includeSource:{}", escapeLog(pattern), escapeLog(limit), escapeLog(includeSource)); } @@ -81,12 +78,13 @@ public Response getFlows(@DefaultValue("") @QueryParam(value = ApiConstants.PATT @Path(ApiConstants.QNAME_PATH) public Response getFlowByName(@PathParam(ApiConstants.QNAME) @NotNull String flowName, @DefaultValue("false") @QueryParam(value = ApiConstants.INCLUDE_SOURCE) boolean includeSource) { - if (log.isDebugEnabled()) { - log.debug("Search Agama with flowName:{}, includeSource:{}", escapeLog(flowName), escapeLog(includeSource)); + if (logger.isDebugEnabled()) { + logger.debug("Search Agama with flowName:{}, includeSource:{}", escapeLog(flowName), + escapeLog(includeSource)); } String decodedFlowName = getURLDecodedValue(flowName); - log.trace(" Agama Decoded flow name decodedFlowName:{}", decodedFlowName); + logger.trace(" Agama Decoded flow name decodedFlowName:{}", decodedFlowName); Flow flow = findFlow(decodedFlowName, true, includeSource); return Response.ok(flow).build(); @@ -96,19 +94,19 @@ public Response getFlowByName(@PathParam(ApiConstants.QNAME) @NotNull String flo @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_WRITE_ACCESS }) public Response createFlow(@Valid Flow flow) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - log.debug(" Flow to be added flow:{}, flow.getQName():{}, flow.getSource():{} ", flow, flow.getQname(), + logger.debug(" Flow to be added flow:{}, flow.getQName():{}, flow.getSource():{} ", flow, flow.getQname(), flow.getSource()); // check if flow with same name already exists Flow existingFlow = findFlow(flow.getQname(), false, false); - log.debug(" existingFlow:{}", existingFlow); + logger.debug(" existingFlow:{}", existingFlow); if (existingFlow != null) { thorwBadRequestException("Flow identified by name '" + flow.getQname() + "' already exist!"); } // validate flow data - validateAgamaFlowData(flow, true); updateFlowDetails(flow, null); + validateAgamaFlowData(flow, true); agamaFlowService.addAgamaFlow(flow); flow = findFlow(flow.getQname(), true, false); @@ -121,14 +119,14 @@ public Response createFlow(@Valid Flow flow) @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_WRITE_ACCESS }) public Response createFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull String flowName, @Valid String source) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - log.debug(" Flow to be created flowName:{}, source:{}", flowName, source); + logger.debug(" Flow to be created flowName:{}, source:{}", flowName, source); String decodedFlowName = getURLDecodedValue(flowName); - log.trace(" Agama Decoded flow name for create is:{}", decodedFlowName); + logger.trace(" Agama Decoded flow name for create is:{}", decodedFlowName); // check if flow with same name already exists Flow existingFlow = findFlow(decodedFlowName, false, false); - log.debug(" existing-flow:{}", existingFlow); + logger.debug(" existing-flow:{}", existingFlow); if (existingFlow != null) { thorwBadRequestException("Flow identified by name '" + decodedFlowName + "' already exist!"); } @@ -152,25 +150,23 @@ public Response createFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull Strin @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_WRITE_ACCESS }) public Response updateFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowName, @Valid Flow flow) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - log.debug(" Flow to update flowName:{}, flow:{}, flow.getQName():{}, flow.getSource():{} ", flowName, flow, + logger.debug(" Flow to update flowName:{}, flow:{}, flow.getQName():{}, flow.getSource():{} ", flowName, flow, flow.getQname(), flow.getSource()); String decodedFlowName = getURLDecodedValue(flowName); - log.trace(" Agama Decoded flow name for update is:{}", decodedFlowName); + logger.trace(" Agama Decoded flow name for update is:{}", decodedFlowName); // check if flow exists Flow existingFlow = findFlow(decodedFlowName, true, false); // set flow data flow.setQname(decodedFlowName); - log.trace("Flow revision check - flow.getRevision():{}, existingFlow.getRevision():{}", flow.getRevision(), - existingFlow.getRevision()); updateFlowDetails(flow, existingFlow); - log.debug("Flow revision after update - flow.getRevision():{}", flow.getRevision()); + logger.debug("Flow revision after update - flow.getRevision():{}", flow.getRevision()); // validate flow data validateAgamaFlowData(flow, false); - log.debug("Updating flow after validation"); + logger.debug("Updating flow after validation"); agamaFlowService.updateFlow(flow); flow = findFlow(decodedFlowName, true, false); @@ -183,14 +179,14 @@ public Response updateFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowNa @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_WRITE_ACCESS }) public Response updateFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull String flowName, @Valid String source) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - log.debug(" Flow to be updated flowName:{}, source:{}", flowName, source); + logger.debug(" Flow to be updated flowName:{}, source:{}", flowName, source); String decodedFlowName = getURLDecodedValue(flowName); - log.trace(" Agama flow name for update is:{}", decodedFlowName); + logger.trace(" Agama flow name for update is:{}", decodedFlowName); // check if flow with same name already exists Flow existingFlow = findFlow(decodedFlowName, false, false); - log.debug(" Agama existingFlow:{}", existingFlow); + logger.debug(" Agama existingFlow:{}", existingFlow); // Update source and revision if (existingFlow != null) { @@ -200,7 +196,7 @@ public Response updateFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull Strin // validate flow data validateAgamaFlowData(existingFlow, false); - log.debug("Update flow after validation"); + logger.debug("Update flow after validation"); agamaFlowService.updateFlow(existingFlow); existingFlow = findFlow(existingFlow.getQname(), true, false); @@ -208,7 +204,6 @@ public Response updateFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull Strin return Response.status(Response.Status.OK).entity(existingFlow).build(); } - @PATCH @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON) @Path(ApiConstants.QNAME_PATH) @@ -220,23 +215,21 @@ public Response patchFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowNam logger.debug("Flow details to be patched - flowName:{}, jsonPatch:{}", escapeLog(flowName), escapeLog(jsonPatch)); } - logger.error("Flow details to be patched - flowName:{}, jsonPatch:{}", escapeLog(flowName), - escapeLog(jsonPatch)); - + String decodedFlowName = getURLDecodedValue(flowName); - log.error(" Flow to be patched is name:{}", decodedFlowName); + logger.debug(" Flow to be patched is name:{}", decodedFlowName); // check if flow exists Flow existingFlow = findFlow(decodedFlowName, false, true); - log.error(" Flow to be patched:{}", existingFlow); + logger.debug(" Flow to be patched:{}", existingFlow); existingFlow = Jackson.applyJsonPatch(jsonPatch, existingFlow); - log.error(" After patch flow:{}", existingFlow); + logger.debug(" After patch flow:{}", existingFlow); updateFlowDetails(existingFlow, existingFlow); // validate flow data validateAgamaFlowData(existingFlow, false); - log.debug("Updating flow after validation"); + logger.debug("Updating flow after validation"); agamaFlowService.updateFlow(existingFlow); return Response.ok(existingFlow).build(); } @@ -245,9 +238,9 @@ public Response patchFlow(@PathParam(ApiConstants.QNAME) @NotNull String flowNam @Path(ApiConstants.QNAME_PATH) @ProtectedApi(scopes = { ApiAccessConstants.AGAMA_DELETE_ACCESS }) public Response deleteAttribute(@PathParam(ApiConstants.QNAME) @NotNull String flowName) { - log.debug(" Flow to delete - flowName:{}", flowName); + logger.debug(" Flow to delete - flowName:{}", flowName); String decodedFlowName = getURLDecodedValue(flowName); - log.trace(" Agama Decoded flow name is:{}", decodedFlowName); + logger.trace(" Agama Decoded flow name is:{}", decodedFlowName); // check if flow exists Flow flow = findFlow(decodedFlowName, true, false); @@ -270,7 +263,7 @@ private Flow findFlow(String flowName, boolean throwError, boolean includeSource } } } catch (EntryPersistenceException e) { - log.error("No flow found with the name:{} ", flowName); + logger.error("No flow found with the name:{} ", flowName); if (throwError) { throw new NotFoundException(getNotFoundError("Flow - " + flowName + "!!!")); } @@ -280,15 +273,15 @@ private Flow findFlow(String flowName, boolean throwError, boolean includeSource private void validateAgamaFlowData(Flow flow, boolean checkNonMandatoryFields) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - log.debug(" Validate Agama Flow data - flow:{}, checkNonMandatoryFields:{}", flow, checkNonMandatoryFields); + logger.debug(" Validate Agama Flow data - flow:{}, checkNonMandatoryFields:{}", flow, checkNonMandatoryFields); if (flow == null) { return; } - log.debug("Agama Flow to be added flow:{}, flow.getQname():{}, flow.getSource():{} ", flow, flow.getQname(), + logger.debug("Agama Flow to be added flow:{}, flow.getQname():{}, flow.getSource():{} ", flow, flow.getQname(), flow.getSource()); String validateMsg = agamaFlowService.validateFlowFields(flow, checkNonMandatoryFields); - log.debug("Agama Flow to be validation msg:{} ", validateMsg); + logger.debug("Agama Flow to be validation msg:{} ", validateMsg); if (StringUtils.isNotBlank(validateMsg)) { thorwBadRequestException(validateMsg); } @@ -298,7 +291,7 @@ private void validateAgamaFlowData(Flow flow, boolean checkNonMandatoryFields) } private void validateSyntax(Flow flow) { - log.debug("Validate Flow Source Syntax - flow:{}", flow); + logger.debug("Validate Flow Source Syntax - flow:{}", flow); if (flow == null) { return; } @@ -306,87 +299,89 @@ private void validateSyntax(Flow flow) { try { Transpiler.runSyntaxCheck(flow.getQname(), flow.getSource()); } catch (SyntaxException se) { - log.error("Transpiler syntax check error", se); + logger.error("Transpiler syntax check error", se); try { - log.debug("Throwing BadRequestException 400 :{} ", Jackson.asPrettyJson(se)); - thorwBadRequestException(Jackson.asJson(se)); + logger.debug("Throwing BadRequestException 400 :{} ", Jackson.asPrettyJson(se)); + thorwBadRequestException(se); } catch (IOException io) { - log.error("Agama Flow Transpiler syntax error parsing error", io); + logger.error("Agama Flow Transpiler syntax error parsing error", io); thorwBadRequestException("Transpiler syntax check error" + se); } } catch (TranspilerException te) { - log.error("Agama Flow transpiler exception", te); - thorwBadRequestException(te.toString()); + logger.error("Agama Flow transpiler exception", te); + thorwBadRequestException(te); } } private String getURLDecodedValue(String pathParam) { - log.debug(" Decode pathParam():{} ", pathParam); + logger.debug(" Decode pathParam():{} ", pathParam); try { return URLDecoder.decode(pathParam, UTF_8.name()); } catch (UnsupportedEncodingException uee) { - log.error("Agama Flow error while URL decoding pathParam:{}, is:{}", pathParam, uee); + logger.error("Agama Flow error while URL decoding pathParam:{}, is:{}", pathParam, uee); } return pathParam; } private Flow updateFlowDetails(Flow flow, Flow existingFlow) { - log.error("Update Flow details - flow:{}, existingFlow:{}", flow, existingFlow); + logger.debug("Update Flow details - flow:{}, existingFlow:{}", flow, existingFlow); updateRevision(flow, existingFlow); updateMetadata(flow); return flow; } - + private Flow updateRevision(Flow flow, Flow existingFlow) { - log.error("Flow revision check - flow:{}, existingFlow:{}", flow, existingFlow); + logger.debug("Flow revision check - flow:{}, existingFlow:{}", flow, existingFlow); if (flow == null) { return flow; } - if(existingFlow == null) { + if (existingFlow == null) { flow.setRevision(-1); return flow; } - - log.error("Flow revision before update - flow.getRevision():{}, existingFlow.getRevision():{}", flow.getRevision(), - existingFlow.getRevision()); - if (flow.getSource() != null && (flow.getRevision() <=0 || flow.getRevision() == existingFlow.getRevision()) ){ - if (existingFlow.getRevision() <= 0 ) { + logger.trace("Flow revision before update - flow.getRevision():{}, existingFlow.getRevision():{}", + flow.getRevision(), existingFlow.getRevision()); + + if (flow.getSource() != null && (flow.getRevision() <= 0 || flow.getRevision() == existingFlow.getRevision())) { + if (existingFlow.getRevision() <= 0) { flow.setRevision(1); } else { flow.setRevision(existingFlow.getRevision() + 1); } } - log.error("Flow revision after update - flow.getRevision():{}", flow.getRevision()); + logger.trace("Flow revision after update - flow.getRevision():{}", flow.getRevision()); return flow; } - + private Flow updateMetadata(Flow flow) { - log.error("Update Flow Metadata - flow:{}", flow); + logger.debug("Update Flow Metadata - flow:{}", flow); if (flow == null) { return flow; } FlowMetadata flowMetadata = flow.getMetadata(); - if(flowMetadata == null) { + if (flowMetadata == null) { flowMetadata = new FlowMetadata(); } - - log.error("Flow Metadata Timestamp before update - flowMetadata.getTimestamp():{}", flowMetadata.getTimestamp()); + + logger.trace("Flow Metadata Timestamp before update - flowMetadata.getTimestamp():{}", + flowMetadata.getTimestamp()); flowMetadata.setTimestamp(System.currentTimeMillis()); flow.setMetadata(flowMetadata); - - log.error("Flow Metadata Timestamp after update - flowMetadata.getTimestamp():{}", flowMetadata.getTimestamp()); + + logger.trace("Flow Metadata Timestamp after update - flowMetadata.getTimestamp():{}", + flowMetadata.getTimestamp()); return flow; } private List getAgamaFlowDetails(List flows, boolean includeSource) { - log.debug("Flow data filter - flows:{}, includeSource:{}", flows, includeSource); + logger.debug("Flow data filter - flows:{}, includeSource:{}", flows, includeSource); if (flows == null || flows.isEmpty()) { return flows; } diff --git a/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java b/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java index 5fb4bf0dfb3..60fb33552ae 100644 --- a/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java +++ b/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java @@ -17,7 +17,6 @@ import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.core.Response; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -93,6 +92,10 @@ public static void thorwBadRequestException(String msg) { throw new BadRequestException(getBadRequestException(msg)); } + public static void thorwBadRequestException(Object obj) { + throw new BadRequestException(getBadRequestException(obj)); + } + public static void thorwInternalServerException(String msg) { throw new InternalServerErrorException(getInternalServerException(msg)); } @@ -126,6 +129,10 @@ protected static Response getBadRequestException(String msg) { return Response.status(Response.Status.BAD_REQUEST).entity(error).build(); } + protected static Response getBadRequestException(Object obj) { + return Response.status(Response.Status.BAD_REQUEST).entity(obj).build(); + } + protected static Response getInternalServerException(String msg) { ApiError error = new ApiError.ErrorBuilder() .withCode(String.valueOf(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode())).withMessage(msg) From 8ed2ba894b1a9f8e340c83c8d4584b1a312bc25b Mon Sep 17 00:00:00 2001 From: Puja Sharma Date: Thu, 4 Aug 2022 22:50:43 +0530 Subject: [PATCH 21/21] feat(jans-config-api): agama patch endpoint --- .../rest/resource/auth/AgamaResource.java | 7 ++--- .../configapi/core/rest/BaseResource.java | 31 ++++++++----------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java index 0ba98cdc7ed..6bad7eb3cf0 100644 --- a/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java +++ b/jans-config-api/server/src/main/java/io/jans/configapi/rest/resource/auth/AgamaResource.java @@ -184,8 +184,8 @@ public Response updateFlowFromFile(@PathParam(ApiConstants.QNAME) @NotNull Strin String decodedFlowName = getURLDecodedValue(flowName); logger.trace(" Agama flow name for update is:{}", decodedFlowName); - // check if flow with same name already exists - Flow existingFlow = findFlow(decodedFlowName, false, false); + // check if flow with same name exists + Flow existingFlow = findFlow(decodedFlowName, true, false); logger.debug(" Agama existingFlow:{}", existingFlow); // Update source and revision @@ -265,7 +265,7 @@ private Flow findFlow(String flowName, boolean throwError, boolean includeSource } catch (EntryPersistenceException e) { logger.error("No flow found with the name:{} ", flowName); if (throwError) { - throw new NotFoundException(getNotFoundError("Flow - " + flowName + "!!!")); + throw new NotFoundException(getNotFoundError("Flow - '" + flowName + "'")); } } return flow; @@ -387,7 +387,6 @@ private List getAgamaFlowDetails(List flows, boolean includeSource) } for (Flow flow : flows) { - flow.setTranspiled(null); flow.setTransHash(null); diff --git a/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java b/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java index 60fb33552ae..af3e1aed3c0 100644 --- a/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java +++ b/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java @@ -11,22 +11,20 @@ import io.jans.configapi.core.model.SearchRequest; import io.jans.orm.model.SortOrder; -import jakarta.inject.Inject; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.core.Response; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.Map.Entry; import java.util.stream.Collectors; -import java.util.Optional; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + public class BaseResource { private static Logger log = LoggerFactory.getLogger(BaseResource.class); @@ -34,7 +32,6 @@ public class BaseResource { public static final String MISSING_ATTRIBUTE_CODE = "OCA001"; public static final String MISSING_ATTRIBUTE_MESSAGE = "A required attribute is missing."; - public static void checkResourceNotNull(T resource, String objectName) { if (resource == null) { throw new NotFoundException(getNotFoundError(objectName)); @@ -52,20 +49,18 @@ public static void checkNotNull(String[] attributes, String attributeName) { throw new BadRequestException(getMissingAttributeError(attributeName)); } } - - public static void checkNotNull(HashMap attributeMap) { + + public static void checkNotNull(Map attributeMap) { if (attributeMap.isEmpty()) { return; } - - Map map = (Map) attributeMap.entrySet() - .stream() - .filter(k -> (k.getValue() == null || StringUtils.isNotEmpty(k.getValue())) ) - .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue())); - - + + Map map = attributeMap.entrySet().stream() + .filter(k -> (k.getValue() == null || StringUtils.isNotEmpty(k.getValue()))) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + log.debug(" map:{}", map); - if(!map.isEmpty()) { + if (!map.isEmpty()) { throw new BadRequestException(getMissingAttributeError(map.keySet().toString())); } } @@ -91,11 +86,11 @@ public static void checkNotEmpty(String attribute, String attributeName) { public static void thorwBadRequestException(String msg) { throw new BadRequestException(getBadRequestException(msg)); } - + public static void thorwBadRequestException(Object obj) { throw new BadRequestException(getBadRequestException(obj)); } - + public static void thorwInternalServerException(String msg) { throw new InternalServerErrorException(getInternalServerException(msg)); } @@ -132,7 +127,7 @@ protected static Response getBadRequestException(String msg) { protected static Response getBadRequestException(Object obj) { return Response.status(Response.Status.BAD_REQUEST).entity(obj).build(); } - + protected static Response getInternalServerException(String msg) { ApiError error = new ApiError.ErrorBuilder() .withCode(String.valueOf(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode())).withMessage(msg)