diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java index 92b6c88e92..1ba9cc593a 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java @@ -28,7 +28,6 @@ import org.keycloak.AuthorizationContext; import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.spi.HttpFacade.Request; -import org.keycloak.adapters.spi.HttpFacade.Response; import org.keycloak.authorization.client.AuthzClient; import org.keycloak.representations.AccessToken; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig; @@ -71,7 +70,6 @@ public abstract class AbstractPolicyEnforcer { if (accessToken != null) { Request request = httpFacade.getRequest(); - Response response = httpFacade.getResponse(); String path = getPath(request); PathConfig pathConfig = this.pathMatcher.matches(path, this.paths); @@ -83,7 +81,12 @@ public abstract class AbstractPolicyEnforcer { } LOGGER.debugf("Could not find a configuration for path [%s]", path); - response.sendError(403, "Could not find a configuration for path [" + path + "]."); + + if (isDefaultAccessDeniedUri(request, enforcerConfig)) { + return createAuthorizationContext(accessToken); + } + + handleAccessDenied(httpFacade); return createEmptyAuthorizationContext(false); } @@ -102,9 +105,11 @@ public abstract class AbstractPolicyEnforcer { } } + LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig); + if (!challenge(pathConfig, requiredScopes, httpFacade)) { - LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig); - response.sendError(403, "Authorization failed."); + LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig); + handleAccessDenied(httpFacade); } } } @@ -165,6 +170,10 @@ public abstract class AbstractPolicyEnforcer { return false; } + protected void handleAccessDenied(OIDCHttpFacade httpFacade) { + httpFacade.getResponse().sendError(403); + } + private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) { String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo(); diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/BearerTokenPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/BearerTokenPolicyEnforcer.java index f8f88d4773..0cdfab949c 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/BearerTokenPolicyEnforcer.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/BearerTokenPolicyEnforcer.java @@ -56,6 +56,9 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer { String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement"; response.setStatus(401); response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\""); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Sending Entitlement challenge"); + } } private void challengeUmaAuthentication(PathConfig pathConfig, Set requiredScopes, OIDCHttpFacade facade) { @@ -66,6 +69,9 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer { String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize"; response.setStatus(401); response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\""); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Sending UMA challenge"); + } } private String getPermissionTicket(PathConfig pathConfig, Set requiredScopes, AuthzClient authzClient) { diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java index b6df2eafb3..316a39d41e 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java @@ -83,6 +83,12 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer { @Override protected boolean challenge(PathConfig pathConfig, Set requiredScopes, OIDCHttpFacade facade) { + handleAccessDenied(facade); + return true; + } + + @Override + protected void handleAccessDenied(OIDCHttpFacade facade) { String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo(); HttpFacade.Response response = facade.getResponse(); @@ -92,8 +98,6 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer { } else { response.sendError(403); } - - return true; } private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set requiredScopes, OIDCHttpFacade httpFacade) { diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java index d90a4fd7fd..9efa614573 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java @@ -49,7 +49,6 @@ class PathMatcher { PathConfig matchingAnyPath = null; PathConfig matchingAnySuffixPath = null; - PathConfig matchingPath = null; for (PathConfig entry : paths.values()) { String expectedUri = entry.getPath(); @@ -132,58 +131,79 @@ class PathMatcher { return targetUri.startsWith(expectedUri.substring(0, expectedUri.length() - 2)); } + String suffix = "/*."; + int suffixIndex = expectedUri.indexOf(suffix); + + if (suffixIndex != -1) { + return targetUri.endsWith(expectedUri.substring(suffixIndex + suffix.length() - 1)); + } + return false; } public String buildUriFromTemplate(String expectedUri, String targetUri) { int patternStartIndex = expectedUri.indexOf("{"); - if (patternStartIndex >= targetUri.length()) { + if (patternStartIndex == -1 || patternStartIndex >= targetUri.length()) { + return null; + } + + if (expectedUri.split("/").length > targetUri.split("/").length) { return null; } char[] expectedUriChars = expectedUri.toCharArray(); char[] matchingUri = Arrays.copyOfRange(expectedUriChars, 0, patternStartIndex); + int matchingUriLastIndex = matchingUri.length; + String targetUriParams = targetUri.substring(patternStartIndex); if (Arrays.equals(matchingUri, Arrays.copyOf(targetUri.toCharArray(), matchingUri.length))) { - int matchingLastIndex = matchingUri.length; - matchingUri = Arrays.copyOf(matchingUri, targetUri.length()); // +1 so we can add a slash at the end - int targetPatternStartIndex = patternStartIndex; + matchingUri = Arrays.copyOf(matchingUri, targetUri.length()); + int paramIndex = 0; - while (patternStartIndex != -1) { - int parameterStartIndex = -1; - - for (int i = targetPatternStartIndex; i < targetUri.length(); i++) { - char c = targetUri.charAt(i); - - if (c != '/') { - if (parameterStartIndex == -1) { - parameterStartIndex = matchingLastIndex; - } - matchingUri[matchingLastIndex] = c; - matchingLastIndex++; - } - - if (c == '/' || ((i + 1 == targetUri.length()))) { - if (matchingUri[matchingLastIndex - 1] != '/' && matchingLastIndex < matchingUri.length) { - matchingUri[matchingLastIndex] = '/'; - matchingLastIndex++; - } - - targetPatternStartIndex = targetUri.indexOf('/', i) + 1; - break; - } - } - - if ((patternStartIndex = expectedUri.indexOf('{', patternStartIndex + 1)) == -1) { + for (int i = patternStartIndex; i < expectedUriChars.length; i++) { + if (matchingUriLastIndex >= matchingUri.length) { break; } - if ((targetPatternStartIndex == 0 || targetPatternStartIndex == targetUri.length()) && parameterStartIndex != -1) { - return null; + char c = expectedUriChars[i]; + + if (c == '{' || c == '*') { + String[] params = targetUriParams.split("/"); + + for (int k = paramIndex; k <= (c == '*' ? params.length : paramIndex); k++) { + if (k == params.length) { + break; + } + + int paramLength = params[k].length(); + + if (matchingUriLastIndex + paramLength > matchingUri.length) { + return null; + } + + for (int j = 0; j < paramLength; j++) { + matchingUri[matchingUriLastIndex++] = params[k].charAt(j); + } + + if (c == '*' && matchingUriLastIndex < matchingUri.length) { + matchingUri[matchingUriLastIndex++] = '/'; + } + } + + i = expectedUri.indexOf('}', i); + } else { + if (c == '/') { + paramIndex++; + } + matchingUri[matchingUriLastIndex++] = c; } } + if (matchingUri[matchingUri.length - 1] == '\u0000') { + return null; + } + return String.valueOf(matchingUri); } diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java index 8a6a0a5bb1..679a33c867 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java @@ -220,7 +220,14 @@ public class PolicyEnforcer { pathConfig.setId(resourceDescription.getId()); pathConfig.setName(resourceDescription.getName()); - pathConfig.setPath(resourceDescription.getUri()); + + String uri = resourceDescription.getUri(); + + if (uri == null || "".equals(uri.trim())) { + throw new RuntimeException("Failed to configure paths. Resource [" + resourceDescription.getName() + "] has an invalid or empty URI [" + uri + "]."); + } + + pathConfig.setPath(uri); List scopeNames = new ArrayList<>(); diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js index ff7fb6f5fc..3f09229a42 100755 --- a/adapters/oidc/js/src/main/resources/keycloak.js +++ b/adapters/oidc/js/src/main/resources/keycloak.js @@ -157,7 +157,7 @@ processCallback(callback, initPromise); return; } else if (initOptions) { - if (initOptions.refreshToken) { + if (initOptions.token && initOptions.refreshToken) { setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); if (loginIframe.enable) { @@ -832,7 +832,7 @@ document.body.appendChild(iframe); var messageCallback = function(event) { - if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) { + if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) { return; } @@ -1228,7 +1228,7 @@ break; default: if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) { - oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param]; + oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + encodeURIComponent(queryParams[param]); } break; } diff --git a/adapters/oidc/js/src/main/resources/login-status-iframe-deprecated.html b/adapters/oidc/js/src/main/resources/login-status-iframe-deprecated.html deleted file mode 100755 index 01515e3025..0000000000 --- a/adapters/oidc/js/src/main/resources/login-status-iframe-deprecated.html +++ /dev/null @@ -1,49 +0,0 @@ - - - \ No newline at end of file diff --git a/adapters/oidc/spring-boot/pom.xml b/adapters/oidc/spring-boot/pom.xml index 0abc3d847a..48a2809b6e 100755 --- a/adapters/oidc/spring-boot/pom.xml +++ b/adapters/oidc/spring-boot/pom.xml @@ -56,7 +56,7 @@ org.keycloak - keycloak-jetty92-adapter + keycloak-jetty93-adapter provided diff --git a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java index dc2573d462..657f8e3a03 100755 --- a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java +++ b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java @@ -141,10 +141,10 @@ public class KeycloakSpringBootConfiguration { List undertowSecurityConstraints = new ArrayList(); for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) { - for (KeycloakSpringBootProperties.SecurityCollection collectionDefinition : constraintDefinition.getSecurityCollections()) { + io.undertow.servlet.api.SecurityConstraint undertowSecurityConstraint = new io.undertow.servlet.api.SecurityConstraint(); + undertowSecurityConstraint.addRolesAllowed(constraintDefinition.getAuthRoles()); - io.undertow.servlet.api.SecurityConstraint undertowSecurityConstraint = new io.undertow.servlet.api.SecurityConstraint(); - undertowSecurityConstraint.addRolesAllowed(collectionDefinition.getAuthRoles()); + for (KeycloakSpringBootProperties.SecurityCollection collectionDefinition : constraintDefinition.getSecurityCollections()) { WebResourceCollection webResourceCollection = new WebResourceCollection(); webResourceCollection.addHttpMethods(collectionDefinition.getMethods()); @@ -153,8 +153,9 @@ public class KeycloakSpringBootConfiguration { undertowSecurityConstraint.addWebResourceCollections(webResourceCollection); - undertowSecurityConstraints.add(undertowSecurityConstraint); } + + undertowSecurityConstraints.add(undertowSecurityConstraint); } return undertowSecurityConstraints; } @@ -174,42 +175,70 @@ public class KeycloakSpringBootConfiguration { KeycloakJettyAuthenticator keycloakJettyAuthenticator = new KeycloakJettyAuthenticator(); keycloakJettyAuthenticator.setConfigResolver(new KeycloakSpringBootConfigResolver()); + /* see org.eclipse.jetty.webapp.StandardDescriptorProcessor#visitSecurityConstraint for an example + on how to map servlet spec to Constraints */ + List jettyConstraintMappings = new ArrayList(); for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) { for (KeycloakSpringBootProperties.SecurityCollection securityCollectionDefinition : constraintDefinition .getSecurityCollections()) { - + // securityCollection matches servlet spec's web-resource-collection Constraint jettyConstraint = new Constraint(); + + if (constraintDefinition.getAuthRoles().size() > 0) { + jettyConstraint.setAuthenticate(true); + jettyConstraint.setRoles(constraintDefinition.getAuthRoles().toArray(new String[0])); + } + jettyConstraint.setName(securityCollectionDefinition.getName()); - jettyConstraint.setAuthenticate(true); - if (securityCollectionDefinition.getName() != null) { - jettyConstraint.setName(securityCollectionDefinition.getName()); + // according to the servlet spec each security-constraint has at least one URL pattern + for(String pattern : securityCollectionDefinition.getPatterns()) { + + /* the following code is asymmetric as Jetty's ConstraintMapping accepts only one allowed HTTP method, + but multiple omitted methods. Therefore we add one ConstraintMapping for each allowed + mapping but only one mapping in the cases of omitted methods or no methods. + */ + + if (securityCollectionDefinition.getMethods().size() > 0) { + // according to the servlet spec we have either methods ... + for(String method : securityCollectionDefinition.getMethods()) { + ConstraintMapping jettyConstraintMapping = new ConstraintMapping(); + jettyConstraintMappings.add(jettyConstraintMapping); + + jettyConstraintMapping.setConstraint(jettyConstraint); + jettyConstraintMapping.setPathSpec(pattern); + jettyConstraintMapping.setMethod(method); + } + } else if (securityCollectionDefinition.getOmittedMethods().size() > 0){ + // ... omitted methods ... + ConstraintMapping jettyConstraintMapping = new ConstraintMapping(); + jettyConstraintMappings.add(jettyConstraintMapping); + + jettyConstraintMapping.setConstraint(jettyConstraint); + jettyConstraintMapping.setPathSpec(pattern); + jettyConstraintMapping.setMethodOmissions( + securityCollectionDefinition.getOmittedMethods().toArray(new String[0])); + } else { + // ... or no methods at all + ConstraintMapping jettyConstraintMapping = new ConstraintMapping(); + jettyConstraintMappings.add(jettyConstraintMapping); + + jettyConstraintMapping.setConstraint(jettyConstraint); + jettyConstraintMapping.setPathSpec(pattern); + } + } - jettyConstraint.setRoles(securityCollectionDefinition.getAuthRoles().toArray(new String[0])); - - ConstraintMapping jettyConstraintMapping = new ConstraintMapping(); - if (securityCollectionDefinition.getPatterns().size() > 0) { - //First pattern wins - jettyConstraintMapping.setPathSpec(securityCollectionDefinition.getPatterns().get(0)); - jettyConstraintMapping.setConstraint(jettyConstraint); - } - - if (securityCollectionDefinition.getMethods().size() > 0) { - //First method wins - jettyConstraintMapping.setMethod(securityCollectionDefinition.getMethods().get(0)); - } - - jettyConstraintMapping.setMethodOmissions( - securityCollectionDefinition.getOmittedMethods().toArray(new String[0])); - - jettyConstraintMappings.add(jettyConstraintMapping); } } WebAppContext webAppContext = server.getBean(WebAppContext.class); + //if not found as registered bean let's try the handler + if(webAppContext==null){ + webAppContext = (WebAppContext) server.getHandler(); + } ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler(); securityHandler.setConstraintMappings(jettyConstraintMappings); @@ -235,12 +264,10 @@ public class KeycloakSpringBootConfiguration { Set authRoles = new HashSet(); for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) { - for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) { - for (String authRole : collection.getAuthRoles()) { - if (!authRoles.contains(authRole)) { - context.addSecurityRole(authRole); - authRoles.add(authRole); - } + for (String authRole : constraint.getAuthRoles()) { + if (!authRoles.contains(authRole)) { + context.addSecurityRole(authRole); + authRoles.add(authRole); } } } @@ -248,6 +275,10 @@ public class KeycloakSpringBootConfiguration { for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) { SecurityConstraint tomcatConstraint = new SecurityConstraint(); + for (String authRole : constraint.getAuthRoles()) { + tomcatConstraint.addAuthRole(authRole); + } + for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) { SecurityCollection tomcatSecCollection = new SecurityCollection(); @@ -258,10 +289,6 @@ public class KeycloakSpringBootConfiguration { tomcatSecCollection.setDescription(collection.getDescription()); } - for (String authRole : collection.getAuthRoles()) { - tomcatConstraint.addAuthRole(authRole); - } - for (String pattern : collection.getPatterns()) { tomcatSecCollection.addPattern(pattern); } diff --git a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java index 2c99eba4e4..788412fdb5 100644 --- a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java +++ b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java @@ -43,12 +43,20 @@ public class KeycloakSpringBootProperties extends AdapterConfig { */ private List securityConstraints = new ArrayList(); + /** + * This matches security-constraint of the servlet spec + */ @ConfigurationProperties() public static class SecurityConstraint { /** * A list of security collections */ private List securityCollections = new ArrayList(); + private List authRoles = new ArrayList(); + + public List getAuthRoles() { + return authRoles; + } public List getSecurityCollections() { return securityCollections; @@ -57,7 +65,16 @@ public class KeycloakSpringBootProperties extends AdapterConfig { public void setSecurityCollections(List securityCollections) { this.securityCollections = securityCollections; } + + public void setAuthRoles(List authRoles) { + this.authRoles = authRoles; + } + } + + /** + * This matches web-resource-collection of the servlet spec + */ @ConfigurationProperties() public static class SecurityCollection { /** @@ -68,10 +85,6 @@ public class KeycloakSpringBootProperties extends AdapterConfig { * The description of your security collection */ private String description; - /** - * A list of roles that applies for this security collection - */ - private List authRoles = new ArrayList(); /** * A list of URL patterns that should match to apply the security collection */ @@ -85,10 +98,6 @@ public class KeycloakSpringBootProperties extends AdapterConfig { */ private List omittedMethods = new ArrayList(); - public List getAuthRoles() { - return authRoles; - } - public List getPatterns() { return patterns; } @@ -117,10 +126,6 @@ public class KeycloakSpringBootProperties extends AdapterConfig { this.description = description; } - public void setAuthRoles(List authRoles) { - this.authRoles = authRoles; - } - public void setPatterns(List patterns) { this.patterns = patterns; } diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java index 2221bfd866..e4ff98816a 100755 --- a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java +++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java @@ -167,7 +167,12 @@ public class SharedAttributeDefinitons { .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) .build(); - + protected static final SimpleAttributeDefinition AUTODETECT_BEARER_ONLY = + new SimpleAttributeDefinitionBuilder("autodetect-bearer-only", ModelType.BOOLEAN, true) + .setXmlName("autodetect-bearer-only") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); protected static final List ATTRIBUTES = new ArrayList(); static { @@ -193,6 +198,7 @@ public class SharedAttributeDefinitons { ATTRIBUTES.add(REGISTER_NODE_PERIOD); ATTRIBUTES.add(TOKEN_STORE); ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE); + ATTRIBUTES.add(AUTODETECT_BEARER_ONLY); } /** diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties index e6a581a27c..6244b0c797 100755 --- a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties +++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties @@ -46,7 +46,7 @@ keycloak.realm.register-node-at-startup=Cluster setting keycloak.realm.register-node-period=how often to re-register node keycloak.realm.token-store=cookie or session storage for auth session data keycloak.realm.principal-attribute=token attribute to use to set Principal name - +keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests keycloak.secure-deployment=A deployment secured by Keycloak keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak @@ -83,6 +83,7 @@ keycloak.secure-deployment.principal-attribute=token attribute to use to set Pri keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds +keycloak.secure-deployment.autodetect-bearer-only=autodetect bearer-only requests keycloak.secure-deployment.credential=Credential value diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd index 53549faa62..e9839bc069 100755 --- a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd +++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd @@ -65,6 +65,7 @@ + @@ -107,6 +108,7 @@ + diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java index f9f8a6a3e2..02d14e5b81 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java @@ -166,6 +166,12 @@ public class SharedAttributeDefinitons { .setAllowExpression(true) .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) .build(); + protected static final SimpleAttributeDefinition AUTODETECT_BEARER_ONLY = + new SimpleAttributeDefinitionBuilder("autodetect-bearer-only", ModelType.BOOLEAN, true) + .setXmlName("autodetect-bearer-only") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); @@ -193,6 +199,7 @@ public class SharedAttributeDefinitons { ATTRIBUTES.add(REGISTER_NODE_PERIOD); ATTRIBUTES.add(TOKEN_STORE); ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE); + ATTRIBUTES.add(AUTODETECT_BEARER_ONLY); } private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) { diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties index e6a581a27c..c0ca52e0dc 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties @@ -46,7 +46,7 @@ keycloak.realm.register-node-at-startup=Cluster setting keycloak.realm.register-node-period=how often to re-register node keycloak.realm.token-store=cookie or session storage for auth session data keycloak.realm.principal-attribute=token attribute to use to set Principal name - +keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests keycloak.secure-deployment=A deployment secured by Keycloak keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak @@ -67,6 +67,7 @@ keycloak.secure-deployment.bearer-only=Bearer Token Auth only keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication keycloak.secure-deployment.public-client=Public client keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support +keycloak.secure-deployment.autodetect-bearer-only=autodetect bearer-only requests keycloak.secure-deployment.client-keystore=n/a keycloak.secure-deployment.client-keystore-password=n/a keycloak.secure-deployment.client-key-password=n/a diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd index 7372e82311..84399a361a 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd @@ -65,6 +65,7 @@ + @@ -107,6 +108,7 @@ + diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyAdminResource.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyAdminResource.java deleted file mode 100644 index 0cfb0f9f32..0000000000 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyAdminResource.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.keycloak.authorization.policy.provider.aggregated; - -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Pedro Igor - */ -public class AggregatePolicyAdminResource implements PolicyProviderAdminService { - - private final ResourceServer resourceServer; - - public AggregatePolicyAdminResource(ResourceServer resourceServer) { - this.resourceServer = resourceServer; - } - - @Override - public void onCreate(Policy policy) { - verifyCircularReference(policy, new ArrayList<>()); - } - - @Override - public void onUpdate(Policy policy) { - verifyCircularReference(policy, new ArrayList<>()); - } - - private void verifyCircularReference(Policy policy, List ids) { - if (!policy.getType().equals("aggregate")) { - return; - } - - if (ids.contains(policy.getId())) { - throw new RuntimeException("Circular reference found [" + policy.getName() + "]."); - } - - ids.add(policy.getId()); - - for (Policy associated : policy.getAssociatedPolicies()) { - verifyCircularReference(associated, ids); - } - } - - @Override - public void onRemove(Policy policy) { - - } -} diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java index 3e8697335a..73443d1f06 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java @@ -17,19 +17,24 @@ */ package org.keycloak.authorization.policy.provider.aggregated; +import java.util.ArrayList; +import java.util.List; + import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; /** * @author Pedro Igor */ -public class AggregatePolicyProviderFactory implements PolicyProviderFactory { +public class AggregatePolicyProviderFactory implements PolicyProviderFactory { private AggregatePolicyProvider provider = new AggregatePolicyProvider(); @@ -49,8 +54,8 @@ public class AggregatePolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer) { - return new AggregatePolicyAdminResource(resourceServer); + public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) { + return null; } @Override @@ -58,6 +63,37 @@ public class AggregatePolicyProviderFactory implements PolicyProviderFactory { return null; } + @Override + public void onCreate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) { + verifyCircularReference(policy, new ArrayList<>()); + } + + @Override + public void onUpdate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) { + verifyCircularReference(policy, new ArrayList<>()); + } + + private void verifyCircularReference(Policy policy, List ids) { + if (!policy.getType().equals("aggregate")) { + return; + } + + if (ids.contains(policy.getId())) { + throw new RuntimeException("Circular reference found [" + policy.getName() + "]."); + } + + ids.add(policy.getId()); + + for (Policy associated : policy.getAssociatedPolicies()) { + verifyCircularReference(associated, ids); + } + } + + @Override + public void onRemove(Policy policy, AuthorizationProvider authorization) { + + } + @Override public void init(Config.Scope config) { diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java index 8cb00293a4..a0e78746a5 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java @@ -40,7 +40,7 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer) { + public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) { return null; } @@ -78,9 +78,6 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory { try { if (clients.isEmpty()) { - policyStore.findDependentPolicies(policy.getId(), resourceServer.getId()).forEach(dependentPolicy -> { - dependentPolicy.removeAssociatedPolicy(policy); - }); policyStore.delete(policy.getId()); } else { policy.getConfig().put("clients", JsonSerialization.writeValueAsString(clients)); diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java index b4a5099ebd..617727f7c3 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java @@ -42,7 +42,7 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer) { + public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) { return null; } diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProviderFactory.java index d7a6b2bc0e..6ea7230f58 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProviderFactory.java @@ -1,18 +1,20 @@ package org.keycloak.authorization.policy.provider.resource; +import java.util.Map; + import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.policy.provider.PolicyProvider; -import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; /** * @author Pedro Igor */ -public class ResourcePolicyProviderFactory implements PolicyProviderFactory { +public class ResourcePolicyProviderFactory implements PolicyProviderFactory { private ResourcePolicyProvider provider = new ResourcePolicyProvider(); @@ -32,8 +34,14 @@ public class ResourcePolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer) { - return null; + public Class getRepresentationType() { + return ResourcePermissionRepresentation.class; + } + + @Override + public ResourcePermissionRepresentation toRepresentation(Policy policy, ResourcePermissionRepresentation representation) { + representation.setResourceType(policy.getConfig().get("defaultResourceType")); + return representation; } @Override @@ -41,6 +49,39 @@ public class ResourcePolicyProviderFactory implements PolicyProviderFactory { return null; } + @Override + public void onCreate(Policy policy, ResourcePermissionRepresentation representation, AuthorizationProvider authorization) { + updateResourceType(policy, representation); + } + + @Override + public void onUpdate(Policy policy, ResourcePermissionRepresentation representation, AuthorizationProvider authorization) { + updateResourceType(policy, representation); + } + + private void updateResourceType(Policy policy, ResourcePermissionRepresentation representation) { + if (representation != null) { + //TODO: remove this check once we migrate to new API + if (ResourcePermissionRepresentation.class.equals(representation.getClass())) { + ResourcePermissionRepresentation resourcePermission = ResourcePermissionRepresentation.class.cast(representation); + Map config = policy.getConfig(); + + config.compute("defaultResourceType", (key, value) -> { + String resourceType = resourcePermission.getResourceType(); + return resourceType != null ? resourcePermission.getResourceType() : null; + }); + + policy.setConfig(config); + + } + } + } + + @Override + public void onRemove(Policy policy, AuthorizationProvider authorization) { + + } + @Override public void init(Config.Scope config) { diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java index 33db2d5473..d5d39175da 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java @@ -35,19 +35,23 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleContainerModel.RoleRemovedEvent; import org.keycloak.models.RoleModel; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.RolePolicyRepresentation; import org.keycloak.util.JsonSerialization; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.function.Consumer; +import java.util.Set; /** * @author Pedro Igor */ -public class RolePolicyProviderFactory implements PolicyProviderFactory { +public class RolePolicyProviderFactory implements PolicyProviderFactory { private RolePolicyProvider provider = new RolePolicyProvider(); @@ -67,7 +71,7 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer) { + public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) { return null; } @@ -76,6 +80,107 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory { return new RolePolicyProvider(); } + @Override + public RolePolicyRepresentation toRepresentation(Policy policy, RolePolicyRepresentation representation) { + try { + representation.setRoles(JsonSerialization.readValue(policy.getConfig().get("roles"), Set.class)); + } catch (IOException cause) { + throw new RuntimeException("Failed to deserialize roles", cause); + } + return representation; + } + + @Override + public Class getRepresentationType() { + return RolePolicyRepresentation.class; + } + + @Override + public void onCreate(Policy policy, RolePolicyRepresentation representation, AuthorizationProvider authorization) { + updateRoles(policy, representation, authorization); + } + + @Override + public void onUpdate(Policy policy, RolePolicyRepresentation representation, AuthorizationProvider authorization) { + updateRoles(policy, representation, authorization); + } + + @Override + public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) { + try { + updateRoles(policy, authorization, new HashSet<>(Arrays.asList(JsonSerialization.readValue(representation.getConfig().get("roles"), RolePolicyRepresentation.RoleDefinition[].class)))); + } catch (IOException cause) { + throw new RuntimeException("Failed to deserialize roles during import", cause); + } + } + + private void updateRoles(Policy policy, RolePolicyRepresentation representation, AuthorizationProvider authorization) { + updateRoles(policy, authorization, representation.getRoles()); + } + + private void updateRoles(Policy policy, AuthorizationProvider authorization, Set roles) { + try { + RealmModel realm = authorization.getRealm(); + Set updatedRoles = new HashSet<>(); + + if (roles != null) { + for (RolePolicyRepresentation.RoleDefinition definition : roles) { + String roleName = definition.getId(); + String clientId = null; + int clientIdSeparator = roleName.indexOf("/"); + + if (clientIdSeparator != -1) { + clientId = roleName.substring(0, clientIdSeparator); + roleName = roleName.substring(clientIdSeparator + 1); + } + + RoleModel role; + + if (clientId == null) { + role = realm.getRole(roleName); + + if (role == null) { + role = realm.getRoleById(roleName); + } + } else { + ClientModel client = realm.getClientByClientId(clientId); + + if (client == null) { + throw new RuntimeException("Client with id [" + clientId + "] not found."); + } + + role = client.getRole(roleName); + } + + // fallback to find any client role with the given name + if (role == null) { + String finalRoleName = roleName; + role = realm.getClients().stream().map(clientModel -> clientModel.getRole(finalRoleName)).filter(roleModel -> roleModel != null) + .findFirst().orElse(null); + } + + if (role == null) { + throw new RuntimeException("Error while importing configuration. Role [" + roleName + "] could not be found."); + } + + definition.setId(role.getId()); + + updatedRoles.add(definition); + } + try { + } catch (Exception e) { + throw new RuntimeException("Error while updating policy [" + policy.getName() + "].", e); + } + } + + Map config = policy.getConfig(); + config.put("roles", JsonSerialization.writeValueAsString(updatedRoles)); + policy.setConfig(config); + } catch (IOException cause) { + throw new RuntimeException("Failed to deserialize roles", cause); + } + } + @Override public void init(Config.Scope config) { @@ -125,12 +230,6 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory { try { if (roles.isEmpty()) { - policyStore.findDependentPolicies(policy.getId(), resourceServer.getId()).forEach(dependentPolicy -> { - dependentPolicy.removeAssociatedPolicy(policy); - if (dependentPolicy.getAssociatedPolicies().isEmpty()) { - policyStore.delete(dependentPolicy.getId()); - } - }); policyStore.delete(policy.getId()); } else { Map config = policy.getConfig(); diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProviderFactory.java index 0678eb3152..e677a9e5e9 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProviderFactory.java @@ -2,17 +2,17 @@ package org.keycloak.authorization.policy.provider.scope; import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.policy.provider.PolicyProvider; -import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; /** * @author Pedro Igor */ -public class ScopePolicyProviderFactory implements PolicyProviderFactory { +public class ScopePolicyProviderFactory implements PolicyProviderFactory { private ScopePolicyProvider provider = new ScopePolicyProvider(); @@ -32,13 +32,18 @@ public class ScopePolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer) { + public PolicyProvider create(KeycloakSession session) { return null; } @Override - public PolicyProvider create(KeycloakSession session) { - return null; + public Class getRepresentationType() { + return ScopePermissionRepresentation.class; + } + + @Override + public ScopePermissionRepresentation toRepresentation(Policy policy, ScopePermissionRepresentation representation) { + return representation; } @Override diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyAdminResource.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyAdminResource.java index 96c1d24d25..7c2206af93 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyAdminResource.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyAdminResource.java @@ -18,45 +18,11 @@ package org.keycloak.authorization.policy.provider.time; -import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; -import java.text.SimpleDateFormat; - /** * @author Pedro Igor */ public class TimePolicyAdminResource implements PolicyProviderAdminService { - @Override - public void onCreate(Policy policy) { - validateConfig(policy); - } - - private void validateConfig(Policy policy) { - String nbf = policy.getConfig().get("nbf"); - String noa = policy.getConfig().get("noa"); - - if (nbf != null && noa != null) { - validateFormat(nbf); - validateFormat(noa); - } - } - - @Override - public void onUpdate(Policy policy) { - validateConfig(policy); - } - - @Override - public void onRemove(Policy policy) { - } - - private void validateFormat(String date) { - try { - new SimpleDateFormat(TimePolicyProvider.DEFAULT_DATE_PATTERN).parse(TimePolicyProvider.format(date)); - } catch (Exception e) { - throw new RuntimeException("Could not parse a date using format [" + date + "]"); - } - } } diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java index 94c5aad754..920cf45e5c 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java @@ -1,18 +1,22 @@ package org.keycloak.authorization.policy.provider.time; +import java.text.SimpleDateFormat; + import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; /** * @author Pedro Igor */ -public class TimePolicyProviderFactory implements PolicyProviderFactory { +public class TimePolicyProviderFactory implements PolicyProviderFactory { private TimePolicyProvider provider = new TimePolicyProvider(); @@ -32,7 +36,7 @@ public class TimePolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer) { + public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) { return new TimePolicyAdminResource(); } @@ -41,6 +45,38 @@ public class TimePolicyProviderFactory implements PolicyProviderFactory { return null; } + @Override + public void onCreate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) { + validateConfig(policy); + } + + private void validateConfig(Policy policy) { + String nbf = policy.getConfig().get("nbf"); + String noa = policy.getConfig().get("noa"); + + if (nbf != null && noa != null) { + validateFormat(nbf); + validateFormat(noa); + } + } + + @Override + public void onUpdate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) { + validateConfig(policy); + } + + @Override + public void onRemove(Policy policy, AuthorizationProvider authorization) { + } + + private void validateFormat(String date) { + try { + new SimpleDateFormat(TimePolicyProvider.DEFAULT_DATE_PATTERN).parse(TimePolicyProvider.format(date)); + } catch (Exception e) { + throw new RuntimeException("Could not parse a date using format [" + date + "]"); + } + } + @Override public void init(Config.Scope config) { diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java index 09345ec8e3..0fd54df2b1 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java @@ -20,14 +20,16 @@ package org.keycloak.authorization.policy.provider.user; import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProvider; -import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceServerStore; @@ -37,12 +39,15 @@ import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.UserRemovedEvent; +import org.keycloak.models.UserProvider; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; import org.keycloak.util.JsonSerialization; /** * @author Pedro Igor */ -public class UserPolicyProviderFactory implements PolicyProviderFactory { +public class UserPolicyProviderFactory implements PolicyProviderFactory { private UserPolicyProvider provider = new UserPolicyProvider(); @@ -62,13 +67,86 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer) { + public PolicyProvider create(KeycloakSession session) { return null; } @Override - public PolicyProvider create(KeycloakSession session) { - return null; + public UserPolicyRepresentation toRepresentation(Policy policy, UserPolicyRepresentation representation) { + try { + representation.setUsers(JsonSerialization.readValue(policy.getConfig().get("users"), Set.class)); + } catch (IOException cause) { + throw new RuntimeException("Failed to deserialize roles", cause); + } + return representation; + } + + @Override + public Class getRepresentationType() { + return UserPolicyRepresentation.class; + } + + @Override + public void onCreate(Policy policy, UserPolicyRepresentation representation, AuthorizationProvider authorization) { + updateUsers(policy, representation, authorization); + } + + @Override + public void onUpdate(Policy policy, UserPolicyRepresentation representation, AuthorizationProvider authorization) { + updateUsers(policy, representation, authorization); + } + + @Override + public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) { + try { + updateUsers(policy, authorization, JsonSerialization.readValue(representation.getConfig().get("users"), Set.class)); + } catch (IOException cause) { + throw new RuntimeException("Failed to deserialize users during import", cause); + } + } + + private void updateUsers(Policy policy, UserPolicyRepresentation representation, AuthorizationProvider authorization) { + updateUsers(policy, authorization, representation.getUsers()); + } + + private void updateUsers(Policy policy, AuthorizationProvider authorization, Set users) { + try { + KeycloakSession session = authorization.getKeycloakSession(); + RealmModel realm = authorization.getRealm(); + UserProvider userProvider = session.users(); + Set updatedUsers = new HashSet<>(); + + if (users != null) { + try { + for (String userId : users) { + UserModel user = null; + + try { + user = userProvider.getUserByUsername(userId, realm); + } catch (Exception ignore) { + } + + if (user == null) { + user = userProvider.getUserById(userId, realm); + } + + if (user == null) { + throw new RuntimeException("Error while importing configuration. User [" + userId + "] could not be found."); + } + + updatedUsers.add(user.getId()); + } + } catch (Exception e) { + throw new RuntimeException("Error while updating policy [" + policy.getName() + "].", e); + } + } + + Map config = policy.getConfig(); + config.put("users", JsonSerialization.writeValueAsString(updatedUsers)); + policy.setConfig(config); + } catch (IOException cause) { + throw new RuntimeException("Failed to deserialize roles", cause); + } } @Override @@ -102,12 +180,6 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory { try { if (users.isEmpty()) { - policyStore.findDependentPolicies(policy.getId(), resourceServer.getId()).forEach(dependentPolicy -> { - dependentPolicy.removeAssociatedPolicy(policy); - if (dependentPolicy.getAssociatedPolicies().isEmpty()) { - policyStore.delete(dependentPolicy.getId()); - } - }); policyStore.delete(policy.getId()); } else { policy.getConfig().put("users", JsonSerialization.writeValueAsString(users)); @@ -133,16 +205,16 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory { } static String[] getUsers(Policy policy) { - String roles = policy.getConfig().get("users"); + String users = policy.getConfig().get("users"); - if (roles != null) { + if (users != null) { try { - return JsonSerialization.readValue(roles.getBytes(), String[].class); + return JsonSerialization.readValue(users.getBytes(), String[].class); } catch (IOException e) { - throw new RuntimeException("Could not parse roles [" + roles + "] from policy config [" + policy.getName() + ".", e); + throw new RuntimeException("Could not parse users [" + users + "] from policy config [" + policy.getName() + ".", e); } } - return new String[]{}; + return new String[0]; } } diff --git a/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyAdminResource.java b/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyAdminResource.java index c6e570157e..a9bdbb085e 100644 --- a/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyAdminResource.java +++ b/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyAdminResource.java @@ -16,8 +16,6 @@ */ package org.keycloak.authorization.policy.provider.drools; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.kie.api.runtime.KieContainer; @@ -33,29 +31,12 @@ import javax.ws.rs.core.Response; */ public class DroolsPolicyAdminResource implements PolicyProviderAdminService { - private final ResourceServer resourceServer; private final DroolsPolicyProviderFactory factory; - public DroolsPolicyAdminResource(ResourceServer resourceServer, DroolsPolicyProviderFactory factory) { - this.resourceServer = resourceServer; + public DroolsPolicyAdminResource(DroolsPolicyProviderFactory factory) { this.factory = factory; } - @Override - public void onCreate(Policy policy) { - this.factory.update(policy); - } - - @Override - public void onUpdate(Policy policy) { - this.factory.update(policy); - } - - @Override - public void onRemove(Policy policy) { - this.factory.remove(policy); - } - @Path("/resolveModules") @POST @Consumes("application/json") diff --git a/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java b/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java index ce0f834ad7..8bbed33dd1 100644 --- a/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java +++ b/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java @@ -13,6 +13,8 @@ import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.kie.api.KieServices; import org.kie.api.KieServices.Factory; import org.kie.api.runtime.KieContainer; @@ -49,8 +51,8 @@ public class DroolsPolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer) { - return new DroolsPolicyAdminResource(resourceServer, this); + public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) { + return new DroolsPolicyAdminResource(this); } @Override @@ -58,6 +60,26 @@ public class DroolsPolicyProviderFactory implements PolicyProviderFactory { return null; } + @Override + public void onCreate(Policy policy, AbstractPolicyRepresentation representation, AuthorizationProvider authorization) { + update(policy); + } + + @Override + public void onUpdate(Policy policy, AbstractPolicyRepresentation representation, AuthorizationProvider authorization) { + update(policy); + } + + @Override + public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) { + update(policy); + } + + @Override + public void onRemove(Policy policy, AuthorizationProvider authorization) { + remove(policy); + } + @Override public void init(Config.Scope config) { this.ks = Factory.get(); diff --git a/common/src/main/java/org/keycloak/common/util/StreamUtil.java b/common/src/main/java/org/keycloak/common/util/StreamUtil.java index 72ff6df798..c14c1daf3b 100755 --- a/common/src/main/java/org/keycloak/common/util/StreamUtil.java +++ b/common/src/main/java/org/keycloak/common/util/StreamUtil.java @@ -53,7 +53,6 @@ public final class StreamUtil { * @param charset Charset used to decode the input stream * @return String representation of the input stream contents decoded using given charset * @throws IOException - * @deprecated Use {@link #readString(java.io.InputStream, java.nio.charset.Charset)} variant. */ public static String readString(InputStream in, Charset charset) throws IOException { diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java index 3cef2a0eca..dfe4ae633d 100755 --- a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java +++ b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import java.util.HashMap; import java.util.Map; +import java.util.TreeMap; /** * Common Adapter configuration @@ -58,7 +59,7 @@ public class BaseAdapterConfig extends BaseRealmConfig { @JsonProperty("public-client") protected boolean publicClient; @JsonProperty("credentials") - protected Map credentials = new HashMap<>(); + protected Map credentials = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); public boolean isUseResourceRoleMappings() { diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java index a1bc1790bd..dd94537645 100644 --- a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java +++ b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java @@ -196,10 +196,12 @@ public class PolicyEnforcerConfig { '}'; } + @JsonIgnore public boolean hasPattern() { return getPath().indexOf("{") != -1; } + @JsonIgnore public boolean isInstance() { return this.parentConfig != null; } diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java new file mode 100644 index 0000000000..e0be8006a3 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java @@ -0,0 +1,132 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.representations.idm.authorization; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * @author Pedro Igor + */ +public class AbstractPolicyRepresentation { + + private String id; + private String name; + private String description; + private String type; + private Set policies; + private Set resources; + private Set scopes; + private Logic logic = Logic.POSITIVE; + private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS; + + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + + public DecisionStrategy getDecisionStrategy() { + return this.decisionStrategy; + } + + public void setDecisionStrategy(DecisionStrategy decisionStrategy) { + this.decisionStrategy = decisionStrategy; + } + + public Logic getLogic() { + return logic; + } + + public void setLogic(Logic logic) { + this.logic = logic; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Set getPolicies() { + return policies; + } + + public void addPolicy(String... id) { + if (this.policies == null) { + this.policies = new HashSet<>(); + } + this.policies.addAll(Arrays.asList(id)); + } + + public Set getResources() { + return resources; + } + + public void addResource(String id) { + if (this.resources == null) { + this.resources = new HashSet<>(); + } + this.resources.add(id); + } + + public Set getScopes() { + return scopes; + } + + public void addScope(String... id) { + if (this.scopes == null) { + this.scopes = new HashSet<>(); + } + this.scopes.addAll(Arrays.asList(id)); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final AbstractPolicyRepresentation policy = (AbstractPolicyRepresentation) o; + return Objects.equals(getId(), policy.getId()); + } + + @Override + public int hashCode() { + return Objects.hash(getId()); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java index dc049919d4..e36d7af445 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java @@ -18,53 +18,14 @@ package org.keycloak.representations.idm.authorization; import java.util.HashMap; import java.util.Map; -import java.util.Objects; /** * @author Pedro Igor */ -public class PolicyRepresentation { +public class PolicyRepresentation extends AbstractPolicyRepresentation { - private String id; - private String name; - private String description; - private String type; - private Logic logic = Logic.POSITIVE; - private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS; private Map config = new HashMap(); - public String getId() { - return this.id; - } - - public void setId(String id) { - this.id = id; - } - - public String getType() { - return this.type; - } - - public void setType(String type) { - this.type = type; - } - - public DecisionStrategy getDecisionStrategy() { - return this.decisionStrategy; - } - - public void setDecisionStrategy(DecisionStrategy decisionStrategy) { - this.decisionStrategy = decisionStrategy; - } - - public Logic getLogic() { - return logic; - } - - public void setLogic(Logic logic) { - this.logic = logic; - } - public Map getConfig() { return this.config; } @@ -72,33 +33,4 @@ public class PolicyRepresentation { public void setConfig(Map config) { this.config = config; } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return this.description; - } - - public void setDescription(String description) { - this.description = description; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final PolicyRepresentation policy = (PolicyRepresentation) o; - return Objects.equals(getId(), policy.getId()); - } - - @Override - public int hashCode() { - return Objects.hash(getId()); - } } \ No newline at end of file diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourcePermissionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourcePermissionRepresentation.java new file mode 100644 index 0000000000..80f7106752 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourcePermissionRepresentation.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.representations.idm.authorization; + +/** + * @author Pedro Igor + */ +public class ResourcePermissionRepresentation extends AbstractPolicyRepresentation { + + private String resourceType; + + @Override + public String getType() { + return "resource"; + } + + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + public String getResourceType() { + return resourceType; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/RolePolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/RolePolicyRepresentation.java new file mode 100644 index 0000000000..2a89710623 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/RolePolicyRepresentation.java @@ -0,0 +1,86 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.representations.idm.authorization; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author Pedro Igor + */ +public class RolePolicyRepresentation extends AbstractPolicyRepresentation { + + private Set roles; + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + public void addRole(String name, boolean required) { + if (roles == null) { + roles = new HashSet<>(); + } + roles.add(new RoleDefinition(name, required)); + } + + public void addRole(String name) { + addRole(name, false); + } + + public void addClientRole(String clientId, String name) { + addRole(clientId + "/" +name, false); + } + + public void addClientRole(String clientId, String name, boolean required) { + addRole(clientId + "/" + name, required); + } + + public static class RoleDefinition { + + private String id; + private boolean required; + + public RoleDefinition() { + this(null, false); + } + + public RoleDefinition(String id, boolean required) { + this.id = id; + this.required = required; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public boolean isRequired() { + return required; + } + + public void setRequired(boolean required) { + this.required = required; + } + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ScopePermissionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ScopePermissionRepresentation.java new file mode 100644 index 0000000000..b6a02b414d --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ScopePermissionRepresentation.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.representations.idm.authorization; + +/** + * @author Pedro Igor + */ +public class ScopePermissionRepresentation extends AbstractPolicyRepresentation { + + @Override + public String getType() { + return "scope"; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/UserPolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/UserPolicyRepresentation.java new file mode 100644 index 0000000000..dc38e65a77 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/UserPolicyRepresentation.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.representations.idm.authorization; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author Pedro Igor + */ +public class UserPolicyRepresentation extends AbstractPolicyRepresentation { + + private Set users; + + public Set getUsers() { + return users; + } + + public void setUsers(Set users) { + this.users= users; + } + + public void addUser(String name) { + if (users == null) { + users = new HashSet<>(); + } + users.add(name); + } +} diff --git a/distribution/demo-dist/pom.xml b/distribution/demo-dist/pom.xml index dbe33ccf77..da01c09e79 100755 --- a/distribution/demo-dist/pom.xml +++ b/distribution/demo-dist/pom.xml @@ -206,7 +206,6 @@ - maven-assembly-plugin @@ -223,12 +222,10 @@ target target/assembly/work false - gnu - diff --git a/distribution/feature-packs/adapter-feature-pack/pom.xml b/distribution/feature-packs/adapter-feature-pack/pom.xml index 27f4bc5768..0a3bb68aba 100755 --- a/distribution/feature-packs/adapter-feature-pack/pom.xml +++ b/distribution/feature-packs/adapter-feature-pack/pom.xml @@ -79,6 +79,7 @@ org.wildfly.build wildfly-feature-pack-build-maven-plugin + ${wildfly.build-tools.version} feature-pack-build @@ -111,7 +112,6 @@ false target/ target/assembly/work - gnu diff --git a/distribution/feature-packs/server-feature-pack/assembly.xml b/distribution/feature-packs/server-feature-pack/assembly.xml index 1fdde3f439..c118dec8e7 100644 --- a/distribution/feature-packs/server-feature-pack/assembly.xml +++ b/distribution/feature-packs/server-feature-pack/assembly.xml @@ -28,6 +28,43 @@ target/${project.build.finalName} + + configuration/** + + + + target/unpacked-themes/theme + content/themes + + + src/main/resources/identity/module + + **/** + + modules/system/layers/keycloak/org/jboss/as/product/${product.slot} + true + + + src/main/resources/identity + + product.conf + + content/bin + true + + + ${configDir} + + **/** + + configuration + + + ../../../ + + License.html + + content - + \ No newline at end of file diff --git a/distribution/feature-packs/server-feature-pack/pom.xml b/distribution/feature-packs/server-feature-pack/pom.xml index 46fee49f59..92253a0874 100644 --- a/distribution/feature-packs/server-feature-pack/pom.xml +++ b/distribution/feature-packs/server-feature-pack/pom.xml @@ -74,126 +74,6 @@ - - maven-resources-plugin - - - copy-configuration - validate - - copy-resources - - - target/resources/configuration - - - src/main/resources/configuration - true - - - - - - copy-modules - validate - - copy-resources - - - target/resources/modules - - - src/main/resources/modules - - - - - - copy-content - validate - - copy-resources - - - target/resources/content - - - src/main/resources/content - - - - - - copy-identity - validate - - copy-resources - - - target/resources/content/bin - - - src/main/resources/identity - - **/product.conf - - true - - - - - - copy-identity-module - validate - - copy-resources - - - target/resources/modules/system/layers/keycloak/org/jboss/as/product/${product.slot} - - - src/main/resources/identity/module - true - - - - - - copy-themes - validate - - copy-resources - - - target/resources/content/themes - - - target/unpacked-themes/theme - - - - - - copy-license - validate - - copy-resources - - - target/resources/content - - - ../../../ - - License.html - - - - - - - - org.wildfly.build wildfly-feature-pack-build-maven-plugin @@ -206,11 +86,11 @@ compile feature-pack-build.xml - target/resources + org.apache.maven.plugins maven-assembly-plugin @@ -226,11 +106,9 @@ assembly.xml true - ${project.build.finalName} false target/ target/assembly/work - gnu @@ -248,8 +126,9 @@ + ${wildfly.build-tools.version} org.wildfly:wildfly-feature-pack - urn:jboss:domain:4.0 + src/main/resources/configuration @@ -266,7 +145,9 @@ wf11 - urn:jboss:domain:5.0 + ${wildfly11.build-tools.version} + org.wildfly:wildfly-feature-pack + src/main/resources-wf11/configuration @@ -304,6 +185,7 @@ + product @@ -313,8 +195,9 @@ + ${eap.build-tools.version} org.jboss.eap:wildfly-feature-pack - urn:jboss:domain:5.0 + src/main/resources-wf11/configuration diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/domain/subsystems.xml b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/domain/subsystems.xml new file mode 100755 index 0000000000..d2a8706699 --- /dev/null +++ b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/domain/subsystems.xml @@ -0,0 +1,85 @@ + + + + + + + + logging.xml + io.xml + jmx.xml + naming.xml + remoting.xml + request-controller.xml + security.xml + security-manager.xml + + + + logging.xml + bean-validation.xml + keycloak-datasources.xml + ee.xml + ejb3.xml + io.xml + keycloak-infinispan.xml + jaxrs.xml + jca.xml + jdr.xml + jmx.xml + jpa.xml + jsf.xml + mail.xml + naming.xml + remoting.xml + request-controller.xml + security.xml + security-manager.xml + transactions.xml + undertow.xml + keycloak-server.xml + + + + + logging.xml + bean-validation.xml + keycloak-datasources.xml + ee.xml + ejb3.xml + io.xml + keycloak-infinispan.xml + jaxrs.xml + jca.xml + jdr.xml + jgroups.xml + jmx.xml + jpa.xml + jsf.xml + mail.xml + mod_cluster.xml + naming.xml + remoting.xml + request-controller.xml + security.xml + security-manager.xml + transactions.xml + undertow.xml + keycloak-server.xml + + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/domain/template.xml b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/domain/template.xml new file mode 100755 index 0000000000..e7b5885177 --- /dev/null +++ b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/domain/template.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/host-master.xml b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/host-master.xml new file mode 100755 index 0000000000..f5d89ee791 --- /dev/null +++ b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/host-master.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/host-slave.xml b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/host-slave.xml new file mode 100755 index 0000000000..f8695d71a0 --- /dev/null +++ b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/host-slave.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/host.xml b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/host.xml new file mode 100755 index 0000000000..a5c9afbdcc --- /dev/null +++ b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/host.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/subsystems.xml b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/subsystems.xml new file mode 100644 index 0000000000..ada31ffc20 --- /dev/null +++ b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/host/subsystems.xml @@ -0,0 +1,24 @@ + + + + + + + jmx.xml + + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/standalone/template.xml b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/standalone/template.xml index e28a49ba48..7b13afe79e 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/standalone/template.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources-wf11/configuration/standalone/template.xml @@ -1,6 +1,6 @@ - + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/template.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/template.xml index 7a9d6a0c17..e7b5885177 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/template.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/template.xml @@ -17,7 +17,7 @@ ~ limitations under the License. --> - + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host-master.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host-master.xml index 6366860354..f5d89ee791 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host-master.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host-master.xml @@ -22,7 +22,7 @@ is also started by this host controller file. The other instance must be started via host-slave.xml --> - + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host-slave.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host-slave.xml index e90b782f8d..f8695d71a0 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host-slave.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host-slave.xml @@ -17,7 +17,7 @@ ~ limitations under the License. --> - + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host.xml index 1dce732bec..a5c9afbdcc 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/host/host.xml @@ -23,7 +23,7 @@ via host-slave.xml --> - + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/standalone/template.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/standalone/template.xml index 0a72df3d4c..c0cc9e578a 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/standalone/template.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/standalone/template.xml @@ -17,7 +17,7 @@ ~ limitations under the License. --> - + diff --git a/distribution/server-dist/pom.xml b/distribution/server-dist/pom.xml index c870aba3de..57b9d52f41 100755 --- a/distribution/server-dist/pom.xml +++ b/distribution/server-dist/pom.xml @@ -42,6 +42,7 @@ org.wildfly.build wildfly-server-provisioning-maven-plugin + ${build-tools.version} server-provisioning @@ -74,7 +75,6 @@ false ${project.build.directory} ${project.build.directory}/assembly/work - gnu @@ -114,6 +114,7 @@ + ${wildfly.build-tools.version} assembly.xml @@ -121,6 +122,13 @@ + + wf11 + + ${wildfly11.build-tools.version} + + + product @@ -129,6 +137,7 @@ + ${eap.build-tools.version} assembly.xml %regex[(providers.*)|(docs/contrib.*)|(docs/examples.*)|(docs/schema.*)] diff --git a/distribution/server-overlay/pom.xml b/distribution/server-overlay/pom.xml index 6b6143f64d..a0eec7e824 100755 --- a/distribution/server-overlay/pom.xml +++ b/distribution/server-overlay/pom.xml @@ -138,7 +138,6 @@ false ${project.build.directory} ${project.build.directory}/assembly/work - gnu diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java index fa7ed0586f..a37e085862 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java @@ -20,6 +20,7 @@ package org.keycloak.admin.client; import org.jboss.resteasy.client.jaxrs.ResteasyClient; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget; +import org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider; import org.keycloak.admin.client.resource.BearerAuthFilter; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmsResource; @@ -31,7 +32,6 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSession; import java.net.URI; -import java.security.KeyStore; import static org.keycloak.OAuth2Constants.PASSWORD; @@ -66,12 +66,20 @@ public class Keycloak { } public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, SSLContext sslContext) { - ResteasyClient client = new ResteasyClientBuilder() + return getInstance(serverUrl, realm, username, password, clientId, clientSecret, sslContext, null); + } + + public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, SSLContext sslContext, ResteasyJackson2Provider customJacksonProvider) { + ResteasyClientBuilder clientBuilder = new ResteasyClientBuilder() .sslContext(sslContext) .hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.WILDCARD) - .connectionPoolSize(10).build(); + .connectionPoolSize(10); - return new Keycloak(serverUrl, realm, username, password, clientId, clientSecret, PASSWORD, client, null); + if (customJacksonProvider != null) { + clientBuilder.register(customJacksonProvider); + } + + return new Keycloak(serverUrl, realm, username, password, clientId, clientSecret, PASSWORD, clientBuilder.build(), null); } private static ResteasyClientBuilder newResteasyClientBuilder() { diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthorizationResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthorizationResource.java index 07276eca02..de8d958ab7 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthorizationResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthorizationResource.java @@ -23,6 +23,7 @@ import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @@ -58,4 +59,7 @@ public interface AuthorizationResource { @Path("/policy") PoliciesResource policies(); + + @Path("/permission") + PermissionsResource permissions(); } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PermissionsResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PermissionsResource.java new file mode 100644 index 0000000000..679137bda9 --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PermissionsResource.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.admin.client.resource; + +import javax.ws.rs.Path; + +/** + * @author Pedro Igor + */ +public interface PermissionsResource { + + @Path("resource") + ResourcePermissionsResource resource(); + + @Path("scope") + ScopePermissionsResource scope(); +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java index e5120c621c..433a1125ad 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java @@ -28,6 +28,7 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.List; @@ -45,6 +46,12 @@ public interface PoliciesResource { @Path("{id}") PolicyResource policy(@PathParam("id") String id); + @Path("/search") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + PolicyRepresentation findByName(@QueryParam("name") String name); + @GET @Produces(MediaType.APPLICATION_JSON) @NoCache @@ -62,4 +69,9 @@ public interface PoliciesResource { @Path("evaluate") PolicyEvaluationResponse evaluate(PolicyEvaluationRequest evaluationRequest); + @Path("role") + RolePoliciesResource roles(); + + @Path("user") + UserPoliciesResource users(); } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcePermissionResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcePermissionResource.java new file mode 100644 index 0000000000..ec49ec8d6e --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcePermissionResource.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.admin.client.resource; + +import java.util.List; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; + +/** + * @author Pedro Igor + */ +public interface ResourcePermissionResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + ResourcePermissionRepresentation toRepresentation(); + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + void update(ResourcePermissionRepresentation representation); + + @DELETE + void remove(); + + @Path("/associatedPolicies") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + List associatedPolicies(); + + @Path("/dependentPolicies") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + List dependentPolicies(); + + @Path("/resources") + @GET + @Produces("application/json") + @NoCache + List resources(); + +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcePermissionsResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcePermissionsResource.java new file mode 100644 index 0000000000..c833f01d40 --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcePermissionsResource.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.admin.client.resource; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; + +/** + * @author Pedro Igor + */ +public interface ResourcePermissionsResource { + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + Response create(ResourcePermissionRepresentation representation); + + @Path("{id}") + ResourcePermissionResource findById(@PathParam("id") String id); + + @Path("/search") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + ResourcePermissionRepresentation findByName(@QueryParam("name") String name); +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceScopesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceScopesResource.java index 3a481140cc..ddcaf53055 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceScopesResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceScopesResource.java @@ -25,6 +25,7 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.List; @@ -46,4 +47,10 @@ public interface ResourceScopesResource { @NoCache @Produces(MediaType.APPLICATION_JSON) List scopes(); + + @Path("/search") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + ScopeRepresentation findByName(@QueryParam("name") String name); } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java index 07438d07f4..e7daaa18e8 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java @@ -54,6 +54,11 @@ public interface ResourcesResource { @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResult); + @GET + @NoCache + @Produces(MediaType.APPLICATION_JSON) + List findByName(@QueryParam("name") String name); + @GET @NoCache @Produces(MediaType.APPLICATION_JSON) diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RolePoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RolePoliciesResource.java new file mode 100644 index 0000000000..f9f1f98bbc --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RolePoliciesResource.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.admin.client.resource; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.RolePolicyRepresentation; + +/** + * @author Pedro Igor + */ +public interface RolePoliciesResource { + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + Response create(RolePolicyRepresentation representation); + + @Path("{id}") + RolePolicyResource findById(@PathParam("id") String id); + + @Path("/search") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + RolePolicyRepresentation findByName(@QueryParam("name") String name); +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RolePolicyResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RolePolicyResource.java new file mode 100644 index 0000000000..824554f5b9 --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RolePolicyResource.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.admin.client.resource; + +import java.util.List; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.RolePolicyRepresentation; + +/** + * @author Pedro Igor + */ +public interface RolePolicyResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + RolePolicyRepresentation toRepresentation(); + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + void update(RolePolicyRepresentation representation); + + @DELETE + void remove(); + + @Path("/associatedPolicies") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + List associatedPolicies(); + + @Path("/dependentPolicies") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + List dependentPolicies(); + + @Path("/resources") + @GET + @Produces("application/json") + @NoCache + List resources(); + +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ScopePermissionResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ScopePermissionResource.java new file mode 100644 index 0000000000..199e412ecb --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ScopePermissionResource.java @@ -0,0 +1,75 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.admin.client.resource; + +import java.util.List; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; + +/** + * @author Pedro Igor + */ +public interface ScopePermissionResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + ScopePermissionRepresentation toRepresentation(); + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + void update(ScopePermissionRepresentation representation); + + @DELETE + void remove(); + + @Path("/associatedPolicies") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + List associatedPolicies(); + + @Path("/dependentPolicies") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + List dependentPolicies(); + + @Path("/resources") + @GET + @Produces("application/json") + @NoCache + List resources(); + + @Path("/scopes") + @GET + @Produces("application/json") + @NoCache + List scopes(); +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ScopePermissionsResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ScopePermissionsResource.java new file mode 100644 index 0000000000..ab0dd5c2fb --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ScopePermissionsResource.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.admin.client.resource; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; + +/** + * @author Pedro Igor + */ +public interface ScopePermissionsResource { + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + Response create(ScopePermissionRepresentation representation); + + @Path("{id}") + ScopePermissionResource findById(@PathParam("id") String id); + + @Path("/search") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + ScopePermissionRepresentation findByName(@QueryParam("name") String name); +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserPoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserPoliciesResource.java new file mode 100644 index 0000000000..702995f50a --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserPoliciesResource.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.admin.client.resource; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; + +/** + * @author Pedro Igor + */ +public interface UserPoliciesResource { + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + Response create(UserPolicyRepresentation representation); + + @Path("{id}") + UserPolicyResource findById(@PathParam("id") String id); + + @Path("/search") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + UserPolicyRepresentation findByName(@QueryParam("name") String name); +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserPolicyResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserPolicyResource.java new file mode 100644 index 0000000000..d12378c136 --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserPolicyResource.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.admin.client.resource; + +import java.util.List; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; + +/** + * @author Pedro Igor + */ +public interface UserPolicyResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + UserPolicyRepresentation toRepresentation(); + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + void update(UserPolicyRepresentation representation); + + @DELETE + void remove(); + + @Path("/associatedPolicies") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + List associatedPolicies(); + + @Path("/dependentPolicies") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + List dependentPolicies(); + + @Path("/resources") + @GET + @Produces("application/json") + @NoCache + List resources(); + +} diff --git a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/TestsHelper.java b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/TestsHelper.java index fecfbc76da..27bf7de039 100644 --- a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/TestsHelper.java +++ b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/TestsHelper.java @@ -43,6 +43,8 @@ import java.io.InputStream; import java.net.URI; import java.util.Arrays; +import static org.keycloak.test.builders.ClientBuilder.AccessType.PUBLIC; + public class TestsHelper { public static String baseUrl; @@ -83,7 +85,7 @@ public class TestsHelper { } public static String createDirectGrantClient() { - return createClient(ClientBuilder.create("test-dga").publicClient(true)); + return createClient(ClientBuilder.create("test-dga").accessType(PUBLIC)); } public static void deleteClient(String clientId) { diff --git a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/builders/ClientBuilder.java b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/builders/ClientBuilder.java index 0e51c9058e..b4c78bcd7a 100644 --- a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/builders/ClientBuilder.java +++ b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/builders/ClientBuilder.java @@ -28,6 +28,8 @@ public class ClientBuilder { private ClientRepresentation rep; + public enum AccessType { BEARER_ONLY, PUBLIC, CONFIDENTIAL }; + public static ClientBuilder create(String clientId) { ClientRepresentation rep = new ClientRepresentation(); rep.setEnabled(Boolean.TRUE); @@ -39,9 +41,19 @@ public class ClientBuilder { this.rep = rep; } - public ClientRepresentation bearerOnly(boolean bearerOnly) { - rep.setBearerOnly(bearerOnly); - return rep; + public ClientRepresentation accessType(AccessType accessType) { + switch (accessType) { + case BEARER_ONLY: + rep.setBearerOnly(true); + break; + case PUBLIC: + rep.setPublicClient(true); + break; + case CONFIDENTIAL: + rep.setPublicClient(false); + break; + } + return defaultSettings(); } public ClientBuilder rootUrl(String rootUrl) { @@ -64,9 +76,13 @@ public class ClientBuilder { return this; } - public ClientRepresentation publicClient(boolean publicClient) { + public ClientBuilder secret(String secret) { + rep.setSecret(secret); + return this; + } + + private ClientRepresentation defaultSettings() { rep.setFullScopeAllowed(true); - rep.setPublicClient(publicClient); rep.setDirectAccessGrantsEnabled(true); rep.setAdminUrl(rep.getRootUrl()); diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java index 9cd4e74ae6..b517098951 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java @@ -42,6 +42,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction; import org.keycloak.models.authorization.infinispan.entities.CachedPolicy; import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.Logic; @@ -53,15 +54,14 @@ public class CachedPolicyStore implements PolicyStore { private static final String POLICY_ID_CACHE_PREFIX = "policy-id-"; private final Cache>> cache; - private final KeycloakSession session; + private final CachedStoreFactoryProvider cacheStoreFactory; private final CacheTransaction transaction; private final List cacheKeys; - private StoreFactory storeFactory; + private final StoreFactory storeFactory; private PolicyStore delegate; - private CachedStoreFactoryProvider cachedStoreFactory; - public CachedPolicyStore(KeycloakSession session, CacheTransaction transaction, StoreFactory storeFactory) { - this.session = session; + public CachedPolicyStore(KeycloakSession session, CachedStoreFactoryProvider cacheStoreFactory, CacheTransaction transaction, StoreFactory delegate) { + this.cacheStoreFactory = cacheStoreFactory; this.transaction = transaction; InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); @@ -70,12 +70,12 @@ public class CachedPolicyStore implements PolicyStore { cacheKeys.add("findByResourceType"); cacheKeys.add("findByScopeIds"); cacheKeys.add("findByType"); - this.storeFactory = storeFactory; + this.storeFactory = delegate; } @Override - public Policy create(String name, String type, ResourceServer resourceServer) { - Policy policy = getDelegate().create(name, type, getStoreFactory().getResourceServerStore().findById(resourceServer.getId())); + public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) { + Policy policy = getDelegate().create(representation, getStoreFactory().getResourceServerStore().findById(resourceServer.getId())); String id = policy.getId(); this.transaction.whenRollback(() -> { @@ -102,6 +102,10 @@ public class CachedPolicyStore implements PolicyStore { @Override public Policy findById(String id, String resourceServerId) { + if (resourceServerId == null) { + return getDelegate().findById(id, null); + } + String cacheKeyForPolicy = getCacheKeyForPolicy(id); List cached = resolveResourceServerCache(resourceServerId).get(cacheKeyForPolicy); @@ -285,6 +289,7 @@ public class CachedPolicyStore implements PolicyStore { public void removeScope(Scope scope) { getDelegateForUpdate().removeScope(getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())); cached.removeScope(scope); + scopes.remove(scope); } @Override @@ -297,6 +302,7 @@ public class CachedPolicyStore implements PolicyStore { public void removeAssociatedPolicy(Policy associatedPolicy) { getDelegateForUpdate().removeAssociatedPolicy(getStoreFactory().getPolicyStore().findById(associatedPolicy.getId(), cached.getResourceServerId())); cached.removeAssociatedPolicy(associatedPolicy); + associatedPolicies.remove(associatedPolicy); } @Override @@ -309,6 +315,7 @@ public class CachedPolicyStore implements PolicyStore { public void removeResource(Resource resource) { getDelegateForUpdate().removeResource(getStoreFactory().getResourceStore().findById(resource.getId(), cached.getResourceServerId())); cached.removeResource(resource); + resources.remove(resource); } @Override @@ -401,10 +408,7 @@ public class CachedPolicyStore implements PolicyStore { } private CachedStoreFactoryProvider getCachedStoreFactory() { - if (cachedStoreFactory == null) { - cachedStoreFactory = session.getProvider(CachedStoreFactoryProvider.class); - } - return cachedStoreFactory; + return cacheStoreFactory; } private void invalidateCache(String resourceServerId) { diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java index 95d3dba2dc..a98a34e6c5 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java @@ -41,18 +41,16 @@ public class CachedResourceServerStore implements ResourceServerStore { private static final String RS_ID_CACHE_PREFIX = "rs-id-"; private static final String RS_CLIENT_ID_CACHE_PREFIX = "rs-client-id-"; - private final KeycloakSession session; private final CacheTransaction transaction; private StoreFactory storeFactory; private ResourceServerStore delegate; private final Cache>> cache; - public CachedResourceServerStore(KeycloakSession session, CacheTransaction transaction, StoreFactory storeFactory) { - this.session = session; + public CachedResourceServerStore(KeycloakSession session, CacheTransaction transaction, StoreFactory delegate) { this.transaction = transaction; InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); - this.storeFactory = storeFactory; + this.storeFactory = delegate; } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java index b0167020e3..e820ab0ffa 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java @@ -49,15 +49,15 @@ public class CachedResourceStore implements ResourceStore { private static final String RESOURCE_ID_CACHE_PREFIX = "rsc-id-"; private static final String RESOURCE_NAME_CACHE_PREFIX = "rsc-name-"; - private final KeycloakSession session; + private final CachedStoreFactoryProvider cacheStoreFactory; private final CacheTransaction transaction; private final List cacheKeys; - private StoreFactory storeFactory; + private StoreFactory delegateStoreFactory; private ResourceStore delegate; private final Cache>> cache; - public CachedResourceStore(KeycloakSession session, CacheTransaction transaction, StoreFactory storeFactory) { - this.session = session; + public CachedResourceStore(KeycloakSession session, CachedStoreFactoryProvider cacheStoreFactory, CacheTransaction transaction, StoreFactory delegate) { + this.cacheStoreFactory = cacheStoreFactory; InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); this.transaction = transaction; @@ -65,12 +65,12 @@ public class CachedResourceStore implements ResourceStore { cacheKeys.add("findByOwner"); cacheKeys.add("findByUri"); cacheKeys.add("findByName"); - this.storeFactory = storeFactory; + this.delegateStoreFactory = delegate; } @Override public Resource create(String name, ResourceServer resourceServer, String owner) { - Resource resource = getDelegate().create(name, getStoreFactory().getResourceServerStore().findById(resourceServer.getId()), owner); + Resource resource = getDelegate().create(name, getDelegateStoreFactory().getResourceServerStore().findById(resourceServer.getId()), owner); this.transaction.whenRollback(() -> { resolveResourceServerCache(resourceServer.getId()).remove(getCacheKeyForResource(resource.getId())); @@ -176,14 +176,14 @@ public class CachedResourceStore implements ResourceStore { private ResourceStore getDelegate() { if (this.delegate == null) { - this.delegate = getStoreFactory().getResourceStore(); + this.delegate = getDelegateStoreFactory().getResourceStore(); } return this.delegate; } - private StoreFactory getStoreFactory() { - return this.storeFactory; + private StoreFactory getDelegateStoreFactory() { + return this.delegateStoreFactory; } private Resource createAdapter(CachedResource cached) { @@ -270,7 +270,7 @@ public class CachedResourceStore implements ResourceStore { @Override public void updateScopes(Set scopes) { - getDelegateForUpdate().updateScopes(scopes.stream().map(scope -> getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())).collect(Collectors.toSet())); + getDelegateForUpdate().updateScopes(scopes.stream().map(scope -> getDelegateStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())).collect(Collectors.toSet())); cached.updateScopes(scopes); } @@ -293,7 +293,7 @@ public class CachedResourceStore implements ResourceStore { } private CachedStoreFactoryProvider getCachedStoreFactory() { - return session.getProvider(CachedStoreFactoryProvider.class); + return cacheStoreFactory; } private List cacheResult(String resourceServerId, String key, Supplier> provider) { diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java index ef2e19bfc7..fb878d0a66 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java @@ -44,17 +44,17 @@ public class CachedScopeStore implements ScopeStore { private static final String SCOPE_NAME_CACHE_PREFIX = "scp-name-"; private final Cache>> cache; - private final KeycloakSession session; + private final CachedStoreFactoryProvider cacheStoreFactory; private final CacheTransaction transaction; private ScopeStore delegate; private StoreFactory storeFactory; - public CachedScopeStore(KeycloakSession session, CacheTransaction transaction, StoreFactory storeFactory) { - this.session = session; + public CachedScopeStore(KeycloakSession session, CachedStoreFactoryProvider cacheStoreFactory, CacheTransaction transaction, StoreFactory delegate) { + this.cacheStoreFactory = cacheStoreFactory; this.transaction = transaction; InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); - this.storeFactory = storeFactory; + this.storeFactory = delegate; } @Override @@ -220,7 +220,7 @@ public class CachedScopeStore implements ScopeStore { } private CachedStoreFactoryProvider getCachedStoreFactory() { - return session.getProvider(CachedStoreFactoryProvider.class); + return cacheStoreFactory; } private void invalidateCache(String resourceServerId) { diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java index ff66da6fac..bbc38482b1 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java @@ -18,6 +18,9 @@ package org.keycloak.models.authorization.infinispan; +import java.util.ArrayList; +import java.util.List; + import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceStore; @@ -27,31 +30,25 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; -import java.util.ArrayList; -import java.util.List; - /** * @author Pedro Igor */ public class InfinispanStoreFactoryProvider implements CachedStoreFactoryProvider { - private final KeycloakSession session; private final CacheTransaction transaction; - private final StoreFactory storeFactory; private final CachedResourceStore resourceStore; private final CachedScopeStore scopeStore; private final CachedPolicyStore policyStore; private ResourceServerStore resourceServerStore; - InfinispanStoreFactoryProvider(KeycloakSession delegate) { - this.session = delegate; + public InfinispanStoreFactoryProvider(KeycloakSession session) { this.transaction = new CacheTransaction(); - this.session.getTransactionManager().enlistAfterCompletion(transaction); - storeFactory = this.session.getProvider(StoreFactory.class); - resourceStore = new CachedResourceStore(this.session, this.transaction, storeFactory); - resourceServerStore = new CachedResourceServerStore(this.session, this.transaction, storeFactory); - scopeStore = new CachedScopeStore(this.session, this.transaction, storeFactory); - policyStore = new CachedPolicyStore(this.session, this.transaction, storeFactory); + session.getTransactionManager().enlistAfterCompletion(transaction); + StoreFactory delegate = session.getProvider(StoreFactory.class); + resourceStore = new CachedResourceStore(session, this, this.transaction, delegate); + resourceServerStore = new CachedResourceServerStore(session, this.transaction, delegate); + scopeStore = new CachedScopeStore(session, this, this.transaction, delegate); + policyStore = new CachedPolicyStore(session, this, this.transaction, delegate); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java index ff7452b0b0..ad58890f3a 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java @@ -19,6 +19,8 @@ package org.keycloak.models.authorization.infinispan; import org.keycloak.Config; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/entities/CachedPolicy.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/entities/CachedPolicy.java index 775cf64421..c7bef79092 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/entities/CachedPolicy.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/entities/CachedPolicy.java @@ -160,7 +160,7 @@ public class CachedPolicy implements Policy, Serializable { @Override public void removeResource(Resource resource) { - this.resourcesIds.add(resource.getId()); + this.resourcesIds.remove(resource.getId()); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java index 7d68c18ae4..85ace35908 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java @@ -637,6 +637,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { while(itr.hasNext()) { UserSessionEntity entity = (UserSessionEntity) itr.next().getValue(); Set currClientSessions = entity.getClientSessions(); + + if (currClientSessions == null) { + continue; + } + for (String clientSessionId : currClientSessions) { ClientSessionEntity cls = (ClientSessionEntity) offlineSessionCache.get(clientSessionId); if (cls != null) { diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAAuthorizationStoreFactory.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAAuthorizationStoreFactory.java index 87888848ee..87508071cc 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAAuthorizationStoreFactory.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAAuthorizationStoreFactory.java @@ -18,14 +18,15 @@ package org.keycloak.authorization.jpa.store; +import javax.persistence.EntityManager; + import org.keycloak.Config; +import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.store.AuthorizationStoreFactory; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.models.KeycloakSession; -import javax.persistence.EntityManager; - /** * @author Pedro Igor */ diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java index 22fb951940..c6671de0e5 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java @@ -17,14 +17,10 @@ */ package org.keycloak.authorization.jpa.store; -import org.keycloak.authorization.jpa.entities.PolicyEntity; -import org.keycloak.authorization.jpa.entities.ResourceServerEntity; -import org.keycloak.authorization.jpa.entities.ScopeEntity; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.PolicyStore; -import org.keycloak.models.utils.KeycloakModelUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.NoResultException; @@ -33,11 +29,14 @@ import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; + +import org.keycloak.authorization.jpa.entities.PolicyEntity; +import org.keycloak.authorization.jpa.entities.ResourceServerEntity; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; /** * @author Pedro Igor @@ -51,23 +50,19 @@ public class JPAPolicyStore implements PolicyStore { } @Override - public Policy create(String name, String type, ResourceServer resourceServer) { + public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) { PolicyEntity entity = new PolicyEntity(); entity.setId(KeycloakModelUtils.generateId()); - entity.setName(name); - entity.setType(type); + entity.setType(representation.getType()); + entity.setName(representation.getName()); entity.setResourceServer((ResourceServerEntity) resourceServer); this.entityManager.persist(entity); - + this.entityManager.flush(); return entity; } - public EntityManager getEntityManager() { - return this.entityManager; - } - @Override public void delete(String id) { Policy policy = entityManager.find(PolicyEntity.class, id); @@ -99,7 +94,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public Policy findByName(String name, String resourceServerId) { try { - Query query = getEntityManager().createQuery("from PolicyEntity where name = :name and resourceServer.id = :serverId"); + Query query = entityManager.createQuery("from PolicyEntity where name = :name and resourceServer.id = :serverId"); query.setParameter("name", name); query.setParameter("serverId", resourceServerId); @@ -112,7 +107,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public List findByResourceServer(final String resourceServerId) { - Query query = getEntityManager().createQuery("from PolicyEntity where resourceServer.id = :serverId"); + Query query = entityManager.createQuery("from PolicyEntity where resourceServer.id = :serverId"); query.setParameter("serverId", resourceServerId); @@ -158,7 +153,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public List findByResource(final String resourceId, String resourceServerId) { - Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.resources r where p.resourceServer.id = :serverId and (r.resourceServer.id = :serverId and r.id = :resourceId)"); + Query query = entityManager.createQuery("select p from PolicyEntity p inner join p.resources r where p.resourceServer.id = :serverId and (r.resourceServer.id = :serverId and r.id = :resourceId)"); query.setParameter("resourceId", resourceId); query.setParameter("serverId", resourceServerId); @@ -168,7 +163,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public List findByResourceType(final String resourceType, String resourceServerId) { - Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.config c where p.resourceServer.id = :serverId and KEY(c) = 'defaultResourceType' and c like :type"); + Query query = entityManager.createQuery("select p from PolicyEntity p inner join p.config c where p.resourceServer.id = :serverId and KEY(c) = 'defaultResourceType' and c like :type"); query.setParameter("serverId", resourceServerId); query.setParameter("type", resourceType); @@ -183,7 +178,7 @@ public class JPAPolicyStore implements PolicyStore { } // Use separate subquery to handle DB2 and MSSSQL - Query query = getEntityManager().createQuery("select pe from PolicyEntity pe where pe.resourceServer.id = :serverId and pe.id IN (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds)))"); + Query query = entityManager.createQuery("select pe from PolicyEntity pe where pe.resourceServer.id = :serverId and pe.id IN (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds)))"); query.setParameter("serverId", resourceServerId); query.setParameter("scopeIds", scopeIds); @@ -193,7 +188,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public List findByType(String type, String resourceServerId) { - Query query = getEntityManager().createQuery("select p from PolicyEntity p where p.resourceServer.id = :serverId and p.type = :type"); + Query query = entityManager.createQuery("select p from PolicyEntity p where p.resourceServer.id = :serverId and p.type = :type"); query.setParameter("serverId", resourceServerId); query.setParameter("type", type); @@ -203,7 +198,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public List findDependentPolicies(String policyId, String resourceServerId) { - Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.associatedPolicies ap where p.resourceServer.id = :serverId and (ap.resourceServer.id = :serverId and ap.id = :policyId)"); + Query query = entityManager.createQuery("select p from PolicyEntity p inner join p.associatedPolicies ap where p.resourceServer.id = :serverId and (ap.resourceServer.id = :serverId and ap.id = :policyId)"); query.setParameter("serverId", resourceServerId); query.setParameter("policyId", policyId); diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAStoreFactory.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAStoreFactory.java index 5dad6af60b..e45d343af2 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAStoreFactory.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAStoreFactory.java @@ -18,43 +18,49 @@ package org.keycloak.authorization.jpa.store; +import javax.persistence.EntityManager; + import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; -import javax.persistence.EntityManager; - /** * @author Pedro Igor */ public class JPAStoreFactory implements StoreFactory { - private final EntityManager entityManager; + private final PolicyStore policyStore; + private final ResourceServerStore resourceServerStore; + private final ResourceStore resourceStore; + private final ScopeStore scopeStore; public JPAStoreFactory(EntityManager entityManager) { - this.entityManager = entityManager; + policyStore = new JPAPolicyStore(entityManager); + resourceServerStore = new JPAResourceServerStore(entityManager); + resourceStore = new JPAResourceStore(entityManager); + scopeStore = new JPAScopeStore(entityManager); } @Override public PolicyStore getPolicyStore() { - return new JPAPolicyStore(this.entityManager); + return policyStore; } @Override public ResourceServerStore getResourceServerStore() { - return new JPAResourceServerStore(this.entityManager); + return resourceServerStore; } @Override public ResourceStore getResourceStore() { - return new JPAResourceStore(this.entityManager); + return resourceStore; } @Override public ScopeStore getScopeStore() { - return new JPAScopeStore(this.entityManager); + return scopeStore; } @Override diff --git a/pom.xml b/pom.xml index 364121c10c..9fcf0f1266 100755 --- a/pom.xml +++ b/pom.xml @@ -42,10 +42,12 @@ 7.2.0.Final 10.0.0.Final + 1.1.3.Final 11.0.0.Alpha1 + 1.1.8.Final 7.1.0.Alpha1-redhat-16 + 1.1.8.Final 2.0.10.Final - 1.1.8.Final 1.1.0.Beta32 1.0.0.Beta14 @@ -1391,6 +1393,13 @@ jboss-releases-repository + + org.apache.maven.plugins + maven-assembly-plugin + + posix + + org.apache.maven.plugins maven-release-plugin @@ -1461,11 +1470,6 @@ liquibase-maven-plugin ${liquibase.version} - - org.wildfly.build - wildfly-feature-pack-build-maven-plugin - ${wildfly.build-tools.version} - org.wildfly.build wildfly-server-provisioning-maven-plugin diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java index 3aafae31b5..c5b75d38db 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java @@ -21,17 +21,23 @@ package org.keycloak.authorization; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.concurrent.Executor; -import java.util.function.Supplier; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.permission.evaluator.Evaluators; import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; +import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.ResourceServerStore; +import org.keycloak.authorization.store.ResourceStore; +import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.provider.Provider; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; /** *

The main contract here is the creation of {@link org.keycloak.authorization.permission.evaluator.PermissionEvaluator} instances. Usually @@ -62,25 +68,19 @@ import org.keycloak.provider.Provider; public final class AuthorizationProvider implements Provider { private final DefaultPolicyEvaluator policyEvaluator; - private final Executor scheduller; - private final Supplier storeFactory; + private StoreFactory storeFactory; private final Map policyProviderFactories; private final KeycloakSession keycloakSession; private final RealmModel realm; - public AuthorizationProvider(KeycloakSession session, RealmModel realm, Supplier storeFactory, Map policyProviderFactories, Executor scheduller) { + public AuthorizationProvider(KeycloakSession session, RealmModel realm, StoreFactory storeFactory, Map policyProviderFactories) { this.keycloakSession = session; this.realm = realm; this.storeFactory = storeFactory; - this.scheduller = scheduller; this.policyProviderFactories = policyProviderFactories; this.policyEvaluator = new DefaultPolicyEvaluator(this); } - public AuthorizationProvider(KeycloakSession session, RealmModel realm, StoreFactory storeFactory, Map policyProviderFactories) { - this(session, realm, () -> storeFactory, policyProviderFactories, Runnable::run); - } - /** * Returns a {@link Evaluators} instance from where {@link org.keycloak.authorization.policy.evaluation.PolicyEvaluator} instances * can be obtained. @@ -88,7 +88,7 @@ public final class AuthorizationProvider implements Provider { * @return a {@link Evaluators} instance */ public Evaluators evaluators() { - return new Evaluators(this.policyEvaluator, this.scheduller); + return new Evaluators(policyEvaluator); } /** @@ -97,7 +97,105 @@ public final class AuthorizationProvider implements Provider { * @return the {@link StoreFactory} */ public StoreFactory getStoreFactory() { - return this.storeFactory.get(); + return createStoreFactory(); + } + + private StoreFactory createStoreFactory() { + return new StoreFactory() { + @Override + public ResourceStore getResourceStore() { + return storeFactory.getResourceStore(); + } + + @Override + public ResourceServerStore getResourceServerStore() { + return storeFactory.getResourceServerStore(); + } + + @Override + public ScopeStore getScopeStore() { + return storeFactory.getScopeStore(); + } + + @Override + public PolicyStore getPolicyStore() { + PolicyStore policyStore = storeFactory.getPolicyStore(); + return new PolicyStore() { + @Override + public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) { + return RepresentationToModel.toModel(representation, AuthorizationProvider.this, policyStore.create(representation, resourceServer)); + } + + @Override + public void delete(String id) { + Policy policy = findById(id, null); + + if (policy != null) { + ResourceServer resourceServer = policy.getResourceServer(); + + findDependentPolicies(policy.getId(), resourceServer.getId()).forEach(dependentPolicy -> { + dependentPolicy.removeAssociatedPolicy(policy); + if (dependentPolicy.getAssociatedPolicies().isEmpty()) { + delete(dependentPolicy.getId()); + } + }); + + policyStore.delete(id); + } + } + + @Override + public Policy findById(String id, String resourceServerId) { + return policyStore.findById(id, resourceServerId); + } + + @Override + public Policy findByName(String name, String resourceServerId) { + return policyStore.findByName(name, resourceServerId); + } + + @Override + public List findByResourceServer(String resourceServerId) { + return policyStore.findByResourceServer(resourceServerId); + } + + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + return policyStore.findByResourceServer(attributes, resourceServerId, firstResult, maxResult); + } + + @Override + public List findByResource(String resourceId, String resourceServerId) { + return policyStore.findByResource(resourceId, resourceServerId); + } + + @Override + public List findByResourceType(String resourceType, String resourceServerId) { + return policyStore.findByResourceType(resourceType, resourceServerId); + } + + @Override + public List findByScopeIds(List scopeIds, String resourceServerId) { + return policyStore.findByScopeIds(scopeIds, resourceServerId); + } + + @Override + public List findByType(String type, String resourceServerId) { + return policyStore.findByType(type, resourceServerId); + } + + @Override + public List findDependentPolicies(String id, String resourceServerId) { + return policyStore.findDependentPolicies(id, resourceServerId); + } + }; + } + + @Override + public void close() { + storeFactory.close(); + } + }; } /** diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/DefaultPermissionEvaluator.java similarity index 80% rename from server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java rename to server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/DefaultPermissionEvaluator.java index 463050746d..d7f2bde401 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/DefaultPermissionEvaluator.java @@ -17,22 +17,18 @@ */ package org.keycloak.authorization.permission.evaluator; -import java.util.concurrent.Executor; - import org.keycloak.authorization.Decision; /** * @author Pedro Igor * @see PermissionEvaluator */ -class ScheduledPermissionEvaluator implements PermissionEvaluator { +class DefaultPermissionEvaluator implements PermissionEvaluator { private final PermissionEvaluator publisher; - private final Executor scheduler; - ScheduledPermissionEvaluator(PermissionEvaluator publisher, Executor scheduler) { + DefaultPermissionEvaluator(PermissionEvaluator publisher) { this.publisher = publisher; - this.scheduler = scheduler; } @Override diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java index ed1aa89172..00e1191097 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java @@ -19,7 +19,6 @@ package org.keycloak.authorization.permission.evaluator; import java.util.List; -import java.util.concurrent.Executor; import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator; @@ -33,11 +32,9 @@ import org.keycloak.authorization.policy.evaluation.EvaluationContext; public final class Evaluators { private final DefaultPolicyEvaluator policyEvaluator; - private final Executor scheduler; - public Evaluators(DefaultPolicyEvaluator policyEvaluator, Executor scheduler) { + public Evaluators(DefaultPolicyEvaluator policyEvaluator) { this.policyEvaluator = policyEvaluator; - this.scheduler = scheduler; } public PermissionEvaluator from(List permissions, EvaluationContext evaluationContext) { @@ -45,6 +42,6 @@ public final class Evaluators { } public PermissionEvaluator schedule(List permissions, EvaluationContext evaluationContext) { - return new ScheduledPermissionEvaluator(new IterablePermissionEvaluator(permissions.iterator(), evaluationContext, this.policyEvaluator), this.scheduler); + return new DefaultPermissionEvaluator(new IterablePermissionEvaluator(permissions.iterator(), evaluationContext, this.policyEvaluator)); } } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java index 094607a6c1..1ec8887d5d 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java @@ -35,6 +35,7 @@ import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; @@ -46,11 +47,13 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator { private final AuthorizationProvider authorization; private final StoreFactory storeFactory; private final PolicyStore policyStore; + private final ResourceStore resourceStore; public DefaultPolicyEvaluator(AuthorizationProvider authorization) { this.authorization = authorization; storeFactory = this.authorization.getStoreFactory(); policyStore = storeFactory.getPolicyStore(); + resourceStore = storeFactory.getResourceStore(); } @Override @@ -159,7 +162,7 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator { String type = resource.getType(); if (type != null) { - List resourcesByType = authorization.getStoreFactory().getResourceStore().findByType(type, resource.getResourceServer().getId()); + List resourcesByType = resourceStore.findByType(type, resource.getResourceServer().getId()); for (Resource resourceType : resourcesByType) { if (resourceType.getOwner().equals(resource.getResourceServer().getClientId())) { diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderAdminService.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderAdminService.java index d26208efff..b19c55b579 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderAdminService.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderAdminService.java @@ -18,16 +18,11 @@ package org.keycloak.authorization.policy.provider; -import org.keycloak.authorization.model.Policy; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; /** * @author Pedro Igor */ -public interface PolicyProviderAdminService { +public interface PolicyProviderAdminService { - void onCreate(Policy policy); - - void onUpdate(Policy policy); - - void onRemove(Policy policy); } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java index f7041b58ae..af95b008b9 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java @@ -22,11 +22,13 @@ import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.provider.ProviderFactory; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; /** * @author Pedro Igor */ -public interface PolicyProviderFactory extends ProviderFactory { +public interface PolicyProviderFactory extends ProviderFactory { String getName(); @@ -34,5 +36,31 @@ public interface PolicyProviderFactory extends ProviderFactory { PolicyProvider create(AuthorizationProvider authorization); - PolicyProviderAdminService getAdminResource(ResourceServer resourceServer); + default R toRepresentation(Policy policy, R representation) { + return representation; + } + + default Class getRepresentationType() { + return (Class) PolicyRepresentation.class; + } + + default void onCreate(Policy policy, R representation, AuthorizationProvider authorization) { + + } + + default void onUpdate(Policy policy, R representation, AuthorizationProvider authorization) { + + } + + default void onRemove(Policy policy, AuthorizationProvider authorization) { + + } + + default void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) { + + } + + default PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) { + return null; + } } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java index 78a08bd42a..76a3fab2fe 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java @@ -18,6 +18,9 @@ package org.keycloak.authorization.store; +import java.util.HashMap; +import java.util.Map; + import org.keycloak.authorization.store.syncronization.ClientApplicationSynchronizer; import org.keycloak.authorization.store.syncronization.RealmSynchronizer; import org.keycloak.authorization.store.syncronization.Synchronizer; @@ -29,9 +32,6 @@ import org.keycloak.models.UserModel.UserRemovedEvent; import org.keycloak.provider.ProviderEvent; import org.keycloak.provider.ProviderFactory; -import java.util.HashMap; -import java.util.Map; - /** * @author Pedro Igor */ diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java index 626e31704b..793185e713 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java @@ -18,12 +18,13 @@ package org.keycloak.authorization.store; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.ResourceServer; - import java.util.List; import java.util.Map; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; + /** * A {@link PolicyStore} is responsible to manage the persistence of {@link Policy} instances. * @@ -35,12 +36,11 @@ public interface PolicyStore { * Creates a new {@link Policy} instance. The new instance is not necessarily persisted though, which may require * a call to the {#save} method to actually make it persistent. * - * @param name the name of the policy - * @param type the type of the policy + * @param representation the policy representation * @param resourceServer the resource server to which this policy belongs * @return a new instance of {@link Policy} */ - Policy create(String name, String type, ResourceServer resourceServer); + Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer); /** * Deletes a policy from the underlying persistence mechanism. diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 3090c6e13f..360f287e96 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -33,6 +33,7 @@ import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; +import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.Time; @@ -87,6 +88,7 @@ import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserConsentRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserSessionRepresentation; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; @@ -793,16 +795,29 @@ public class ModelToRepresentation { return server; } - public static PolicyRepresentation toRepresentation(Policy model) { - PolicyRepresentation representation = new PolicyRepresentation(); + public static R toRepresentation(Policy policy, Class representationType, AuthorizationProvider authorization) { + R representation; - representation.setId(model.getId()); - representation.setName(model.getName()); - representation.setDescription(model.getDescription()); - representation.setType(model.getType()); - representation.setDecisionStrategy(model.getDecisionStrategy()); - representation.setLogic(model.getLogic()); - representation.setConfig(new HashMap<>(model.getConfig())); + try { + representation = representationType.newInstance(); + } catch (Exception cause) { + throw new RuntimeException("Could not create policy [" + policy.getType() + "] representation", cause); + } + + PolicyProviderFactory providerFactory = authorization.getProviderFactory(policy.getType()); + + representation.setId(policy.getId()); + representation.setName(policy.getName()); + representation.setDescription(policy.getDescription()); + representation.setType(policy.getType()); + representation.setDecisionStrategy(policy.getDecisionStrategy()); + representation.setLogic(policy.getLogic()); + + if (representation instanceof PolicyRepresentation) { + PolicyRepresentation.class.cast(representation).setConfig(policy.getConfig()); + } else { + representation = (R) providerFactory.toRepresentation(policy, representation); + } return representation; } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 2557bfd085..439134159b 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -17,6 +17,18 @@ package org.keycloak.models.utils; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + import org.jboss.logging.Logger; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProviderFactory; @@ -24,7 +36,7 @@ import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.policy.provider.PolicyProvider; +import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceStore; @@ -92,6 +104,7 @@ import org.keycloak.representations.idm.UserConsentRepresentation; import org.keycloak.representations.idm.UserFederationMapperRepresentation; import org.keycloak.representations.idm.UserFederationProviderRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation; @@ -103,18 +116,6 @@ import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.util.JsonSerialization; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - public class RepresentationToModel { private static Logger logger = Logger.getLogger(RepresentationToModel.class); @@ -1896,9 +1897,6 @@ public class RepresentationToModel { resourceServer.setPolicyEnforcementMode(rep.getPolicyEnforcementMode()); resourceServer.setAllowRemoteResourceManagement(rep.isAllowRemoteResourceManagement()); - StoreFactory storeFactory = authorization.getStoreFactory(); - ScopeStore scopeStore = storeFactory.getScopeStore(); - rep.getScopes().forEach(scope -> { toModel(scope, resourceServer, authorization); }); @@ -1932,138 +1930,12 @@ public class RepresentationToModel { private static Policy importPolicies(AuthorizationProvider authorization, ResourceServer resourceServer, List policiesToImport, String parentPolicyName) { StoreFactory storeFactory = authorization.getStoreFactory(); - KeycloakSession session = authorization.getKeycloakSession(); - RealmModel realm = authorization.getRealm(); for (PolicyRepresentation policyRepresentation : policiesToImport) { if (parentPolicyName != null && !parentPolicyName.equals(policyRepresentation.getName())) { continue; } Map config = policyRepresentation.getConfig(); - - String roles = config.get("roles"); - - if (roles != null && !roles.isEmpty()) { - try { - List rolesMap = (List) JsonSerialization.readValue(roles, List.class); - config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleConfig -> { - String roleName = roleConfig.get("id").toString(); - String clientId = null; - int clientIdSeparator = roleName.indexOf("/"); - - if (clientIdSeparator != -1) { - clientId = roleName.substring(0, clientIdSeparator); - roleName = roleName.substring(clientIdSeparator + 1); - } - - RoleModel role; - - if (clientId == null) { - role = realm.getRole(roleName); - } else { - role = realm.getClientByClientId(clientId).getRole(roleName); - } - - // fallback to find any client role with the given name - if (role == null) { - String finalRoleName = roleName; - role = realm.getClients().stream().map(clientModel -> clientModel.getRole(finalRoleName)).filter(roleModel -> roleModel != null) - .findFirst().orElse(null); - } - - if (role == null) { - role = realm.getRoleById(roleName); - - if (role == null) { - String finalRoleName1 = roleName; - role = realm.getClients().stream().map(clientModel -> clientModel.getRole(finalRoleName1)).filter(roleModel -> roleModel != null) - .findFirst().orElse(null); - } - } - - if (role == null) { - throw new RuntimeException("Error while importing configuration. Role [" + roleName + "] could not be found."); - } - - roleConfig.put("id", role.getId()); - return roleConfig; - }).collect(Collectors.toList()))); - } catch (Exception e) { - throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e); - } - } - - String users = config.get("users"); - - if (users != null && !users.isEmpty()) { - try { - List usersMap = (List) JsonSerialization.readValue(users, List.class); - config.put("users", JsonSerialization.writeValueAsString(usersMap.stream().map(userId -> { - UserModel user = session.users().getUserByUsername(userId, realm); - - if (user == null) { - user = session.users().getUserById(userId, realm); - } - - if (user == null) { - throw new RuntimeException("Error while importing configuration. User [" + userId + "] could not be found."); - } - - return user.getId(); - }).collect(Collectors.toList()))); - } catch (Exception e) { - throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e); - } - } - - String scopes = config.get("scopes"); - - if (scopes != null && !scopes.isEmpty()) { - try { - ScopeStore scopeStore = storeFactory.getScopeStore(); - List scopesMap = (List) JsonSerialization.readValue(scopes, List.class); - config.put("scopes", JsonSerialization.writeValueAsString(scopesMap.stream().map(scopeName -> { - Scope newScope = scopeStore.findByName(scopeName, resourceServer.getId()); - - if (newScope == null) { - newScope = scopeStore.findById(scopeName, resourceServer.getId()); - } - - if (newScope == null) { - throw new RuntimeException("Scope with name [" + scopeName + "] not defined."); - } - - return newScope.getId(); - }).collect(Collectors.toList()))); - } catch (Exception e) { - throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e); - } - } - - String policyResources = config.get("resources"); - - if (policyResources != null && !policyResources.isEmpty()) { - ResourceStore resourceStore = storeFactory.getResourceStore(); - try { - List resources = JsonSerialization.readValue(policyResources, List.class); - config.put("resources", JsonSerialization.writeValueAsString(resources.stream().map(resourceName -> { - Resource resource = resourceStore.findByName(resourceName, resourceServer.getId()); - - if (resource == null) { - resource = resourceStore.findById(resourceName, resourceServer.getId()); - } - - if (resource == null) { - throw new RuntimeException("Resource with name [" + resourceName + "] not defined."); - } - - return resource.getId(); - }).collect(Collectors.toList()))); - } catch (Exception e) { - throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e); - } - } - String applyPolicies = config.get("applyPolicies"); if (applyPolicies != null && !applyPolicies.isEmpty()) { @@ -2087,91 +1959,111 @@ public class RepresentationToModel { return policy.getId(); }).collect(Collectors.toList()))); } catch (Exception e) { - throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e); + throw new RuntimeException("Error while importing policy [" + policyRepresentation.getName() + "].", e); } } - if (parentPolicyName == null) { - toModel(policyRepresentation, resourceServer, authorization); - } else if (parentPolicyName.equals(policyRepresentation.getName())) { - return toModel(policyRepresentation, resourceServer, authorization); + PolicyStore policyStore = storeFactory.getPolicyStore(); + Policy policy = policyStore.findById(policyRepresentation.getId(), resourceServer.getId()); + + if (policy == null) { + policy = policyStore.findByName(policyRepresentation.getName(), resourceServer.getId()); + } + + if (policy == null) { + policy = policyStore.create(policyRepresentation, resourceServer); + } else { + policy = toModel(policyRepresentation, authorization, policy); + } + + if (parentPolicyName != null && parentPolicyName.equals(policyRepresentation.getName())) { + return policy; } } return null; } - public static Policy toModel(PolicyRepresentation policy, ResourceServer resourceServer, AuthorizationProvider authorization) { - String type = policy.getType(); - PolicyProvider provider = authorization.getProvider(type); + public static Policy toModel(AbstractPolicyRepresentation representation, AuthorizationProvider authorization, Policy model) { + model.setName(representation.getName()); + model.setDescription(representation.getDescription()); + model.setDecisionStrategy(representation.getDecisionStrategy()); + model.setLogic(representation.getLogic()); - if (provider == null) { - //TODO: temporary, remove this check on future versions as drools type is now deprecated - if ("drools".equalsIgnoreCase(type)) { - type = "rules"; + Set resources = representation.getResources(); + Set scopes = representation.getScopes(); + Set policies = representation.getPolicies(); + + if (representation instanceof PolicyRepresentation) { + PolicyRepresentation policy = PolicyRepresentation.class.cast(representation); + String resourcesConfig = policy.getConfig().get("resources"); + + if (resourcesConfig != null) { + try { + resources = JsonSerialization.readValue(resourcesConfig, Set.class); + } catch (IOException e) { + throw new RuntimeException(e); + } } - if (authorization.getProvider(type) == null) { - throw new RuntimeException("Unknown polucy type [" + type + "]. Could not find a provider for this type."); + + String scopesConfig = policy.getConfig().get("scopes"); + + if (scopesConfig != null) { + try { + scopes = JsonSerialization.readValue(scopesConfig, Set.class); + } catch (IOException e) { + throw new RuntimeException(e); + } } + + String policiesConfig = policy.getConfig().get("applyPolicies"); + + if (policiesConfig != null) { + try { + policies = JsonSerialization.readValue(policiesConfig, Set.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + model.setConfig(policy.getConfig()); } - PolicyStore policyStore = authorization.getStoreFactory().getPolicyStore(); - Policy existing; + StoreFactory storeFactory = authorization.getStoreFactory(); - if (policy.getId() != null) { - existing = policyStore.findById(policy.getId(), resourceServer.getId()); + updateResources(resources, model, storeFactory); + updateScopes(scopes, model, storeFactory); + updateAssociatedPolicies(policies, model, storeFactory); + + PolicyProviderFactory provider = authorization.getProviderFactory(model.getType()); + + if (representation instanceof PolicyRepresentation) { + provider.onImport(model, PolicyRepresentation.class.cast(representation), authorization); + } else if (representation.getId() == null) { + provider.onCreate(model, representation, authorization); } else { - existing = policyStore.findByName(policy.getName(), resourceServer.getId()); + provider.onUpdate(model, representation, authorization); } - if (existing != null) { - existing.setName(policy.getName()); - existing.setDescription(policy.getDescription()); - existing.setConfig(policy.getConfig()); - existing.setDecisionStrategy(policy.getDecisionStrategy()); - existing.setLogic(policy.getLogic()); - updateResources(existing, authorization); - updateAssociatedPolicies(existing, resourceServer, authorization); - updateScopes(existing, authorization); - - return existing; - } - - Policy model = policyStore.create(policy.getName(), type, resourceServer); - - model.setDescription(policy.getDescription()); - model.setDecisionStrategy(policy.getDecisionStrategy()); - model.setLogic(policy.getLogic()); - model.setConfig(policy.getConfig()); - - updateResources(model, authorization); - updateAssociatedPolicies(model, resourceServer, authorization); - updateScopes(model, authorization); - - policy.setId(model.getId()); + representation.setId(model.getId()); return model; } - private static void updateScopes(Policy policy, AuthorizationProvider authorization) { - String scopes = policy.getConfig().get("scopes"); - if (scopes != null) { - String[] scopeIds; - - try { - scopeIds = JsonSerialization.readValue(scopes, String[].class); - } catch (IOException e) { - throw new RuntimeException(e); + private static void updateScopes(Set scopeIds, Policy policy, StoreFactory storeFactory) { + if (scopeIds != null) { + if (scopeIds.isEmpty()) { + for (Scope scope : new HashSet(policy.getScopes())) { + policy.removeScope(scope); + } + return; } - - StoreFactory storeFactory = authorization.getStoreFactory(); - for (String scopeId : scopeIds) { boolean hasScope = false; for (Scope scopeModel : new HashSet(policy.getScopes())) { - if (scopeModel.getId().equals(scopeId)) { + if (scopeModel.getId().equals(scopeId) || scopeModel.getName().equals(scopeId)) { hasScope = true; } } @@ -2180,7 +2072,10 @@ public class RepresentationToModel { Scope scope = storeFactory.getScopeStore().findById(scopeId, resourceServer.getId()); if (scope == null) { - storeFactory.getScopeStore().findByName(scopeId, resourceServer.getId()); + scope = storeFactory.getScopeStore().findByName(scopeId, resourceServer.getId()); + if (scope == null) { + throw new RuntimeException("Scope with id or name [" + scopeId + "] does not exist"); + } } policy.addScope(scope); @@ -2191,7 +2086,7 @@ public class RepresentationToModel { boolean hasScope = false; for (String scopeId : scopeIds) { - if (scopeModel.getId().equals(scopeId)) { + if (scopeModel.getId().equals(scopeId) || scopeModel.getName().equals(scopeId)) { hasScope = true; } } @@ -2199,24 +2094,22 @@ public class RepresentationToModel { policy.removeScope(scopeModel); } } - - policy.getConfig().remove("scopes"); } + + policy.getConfig().remove("scopes"); } - private static void updateAssociatedPolicies(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization) { - String policies = policy.getConfig().get("applyPolicies"); + private static void updateAssociatedPolicies(Set policyIds, Policy policy, StoreFactory storeFactory) { + ResourceServer resourceServer = policy.getResourceServer(); - if (policies != null) { - String[] policyIds; - - try { - policyIds = JsonSerialization.readValue(policies, String[].class); - } catch (IOException e) { - throw new RuntimeException(e); + if (policyIds != null) { + if (policyIds.isEmpty()) { + for (Policy associated: new HashSet(policy.getAssociatedPolicies())) { + policy.removeAssociatedPolicy(associated); + } + return; } - StoreFactory storeFactory = authorization.getStoreFactory(); PolicyStore policyStore = storeFactory.getPolicyStore(); for (String policyId : policyIds) { @@ -2228,12 +2121,14 @@ public class RepresentationToModel { } } - if (!hasPolicy) { Policy associatedPolicy = policyStore.findById(policyId, resourceServer.getId()); if (associatedPolicy == null) { associatedPolicy = policyStore.findByName(policyId, resourceServer.getId()); + if (associatedPolicy == null) { + throw new RuntimeException("Policy with id or name [" + policyId + "] does not exist"); + } } policy.addAssociatedPolicy(associatedPolicy); @@ -2250,31 +2145,24 @@ public class RepresentationToModel { } if (!hasPolicy) { policy.removeAssociatedPolicy(policyModel); - ; } } - - policy.getConfig().remove("applyPolicies"); } + + policy.getConfig().remove("applyPolicies"); } - private static void updateResources(Policy policy, AuthorizationProvider authorization) { - String resources = policy.getConfig().get("resources"); - if (resources != null) { - String[] resourceIds; - - try { - resourceIds = JsonSerialization.readValue(resources, String[].class); - } catch (IOException e) { - throw new RuntimeException(e); + private static void updateResources(Set resourceIds, Policy policy, StoreFactory storeFactory) { + if (resourceIds != null) { + if (resourceIds.isEmpty()) { + for (Scope scope : new HashSet(policy.getScopes())) { + policy.removeScope(scope); + } } - - StoreFactory storeFactory = authorization.getStoreFactory(); - for (String resourceId : resourceIds) { boolean hasResource = false; for (Resource resourceModel : new HashSet(policy.getResources())) { - if (resourceModel.getId().equals(resourceId)) { + if (resourceModel.getId().equals(resourceId) || resourceModel.getName().equals(resourceId)) { hasResource = true; } } @@ -2282,7 +2170,10 @@ public class RepresentationToModel { Resource resource = storeFactory.getResourceStore().findById(resourceId, policy.getResourceServer().getId()); if (resource == null) { - throw new RuntimeException("Resource [" + resourceId + "] not found."); + resource = storeFactory.getResourceStore().findByName(resourceId, policy.getResourceServer().getId()); + if (resource == null) { + throw new RuntimeException("Resource with id or name [" + resourceId + "] does not exist"); + } } policy.addResource(resource); @@ -2293,7 +2184,7 @@ public class RepresentationToModel { boolean hasResource = false; for (String resourceId : resourceIds) { - if (resourceModel.getId().equals(resourceId)) { + if (resourceModel.getId().equals(resourceId) || resourceModel.getName().equals(resourceId)) { hasResource = true; } } @@ -2302,9 +2193,9 @@ public class RepresentationToModel { policy.removeResource(resourceModel); } } - - policy.getConfig().remove("resources"); } + + policy.getConfig().remove("resources"); } public static Resource toModel(ResourceRepresentation resource, ResourceServer resourceServer, AuthorizationProvider authorization) { diff --git a/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java b/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java index 159e5aa81f..cc06284c6c 100644 --- a/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java +++ b/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java @@ -21,7 +21,6 @@ package org.keycloak.authorization; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Executor; import org.keycloak.Config; import org.keycloak.authorization.policy.provider.PolicyProvider; @@ -38,7 +37,6 @@ import org.keycloak.provider.ProviderFactory; */ public class DefaultAuthorizationProviderFactory implements AuthorizationProviderFactory { - private Executor scheduler; private Map policyProviderFactories; @Override @@ -48,15 +46,6 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide @Override public void init(Config.Scope config) { - //TODO: user-defined configuration -// Executor executor = Executors.newWorkStealingPool(); -// this.scheduler = command -> { -// Map, Object> contextDataMap = ResteasyProviderFactory.getContextDataMap(); -// executor.execute(() -> { -// ResteasyProviderFactory.pushContextDataMap(contextDataMap); -// command.run(); -// }); -// }; } @Override @@ -77,11 +66,9 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide @Override public AuthorizationProvider create(KeycloakSession session, RealmModel realm) { StoreFactory storeFactory = session.getProvider(CachedStoreFactoryProvider.class); - if (storeFactory == null) { storeFactory = session.getProvider(StoreFactory.class); } - return new AuthorizationProvider(session, realm, storeFactory, policyProviderFactories); } diff --git a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java new file mode 100644 index 0000000000..4ada87d316 --- /dev/null +++ b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.authorization.admin; + +import java.util.List; +import java.util.Map; + +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.services.resources.admin.RealmAuth; + +/** + * @author Pedro Igor + */ +public class PermissionService extends PolicyService { + + public PermissionService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + super(resourceServer, authorization, auth); + } + + @Override + protected PolicyResourceService doCreatePolicyResource(Policy policy) { + return new PolicyTypeResourceService(policy, resourceServer, authorization, auth); + } + + @Override + protected List doSearch(Integer firstResult, Integer maxResult, Map filters) { + filters.put("permission", new String[] {Boolean.TRUE.toString()}); + return super.doSearch(firstResult, maxResult, filters); + } +} diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java new file mode 100644 index 0000000000..85e0943221 --- /dev/null +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java @@ -0,0 +1,228 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.authorization.admin; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.policy.provider.PolicyProviderFactory; +import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.util.JsonSerialization; + +/** + * @author Pedro Igor + */ +public class PolicyResourceService { + + private final Policy policy; + protected final ResourceServer resourceServer; + protected final AuthorizationProvider authorization; + protected final RealmAuth auth; + + public PolicyResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + this.policy = policy; + this.resourceServer = resourceServer; + this.authorization = authorization; + this.auth = auth; + } + + @PUT + @Consumes("application/json") + @Produces("application/json") + @NoCache + public Response update(String payload) { + this.auth.requireManage(); + + AbstractPolicyRepresentation representation = doCreateRepresentation(payload); + + if (policy == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + representation.setId(policy.getId()); + + RepresentationToModel.toModel(representation, authorization, policy); + + return Response.status(Status.CREATED).build(); + } + + @DELETE + public Response delete() { + this.auth.requireManage(); + + if (policy == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + StoreFactory storeFactory = authorization.getStoreFactory(); + PolicyStore policyStore = storeFactory.getPolicyStore(); + PolicyProviderFactory resource = getProviderFactory(policy.getType()); + + resource.onRemove(policy, authorization); + + policyStore.delete(policy.getId()); + + return Response.noContent().build(); + } + + @GET + @Produces("application/json") + @NoCache + public Response findById() { + this.auth.requireView(); + + if (policy == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + return Response.ok(toRepresentation(policy, authorization)).build(); + } + + protected AbstractPolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) { + return ModelToRepresentation.toRepresentation(policy, PolicyRepresentation.class, authorization); + } + + @Path("/dependentPolicies") + @GET + @Produces("application/json") + @NoCache + public Response getDependentPolicies() { + this.auth.requireView(); + + if (policy == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + List policies = authorization.getStoreFactory().getPolicyStore().findDependentPolicies(policy.getId(), resourceServer.getId()); + + return Response.ok(policies.stream().map(policy -> { + PolicyRepresentation representation1 = new PolicyRepresentation(); + + representation1.setId(policy.getId()); + representation1.setName(policy.getName()); + representation1.setType(policy.getType()); + + return representation1; + }).collect(Collectors.toList())).build(); + } + + @Path("/scopes") + @GET + @Produces("application/json") + @NoCache + public Response getScopes() { + this.auth.requireView(); + + if (policy == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + return Response.ok(policy.getScopes().stream().map(scope -> { + ScopeRepresentation representation = new ScopeRepresentation(); + + representation.setId(scope.getId()); + representation.setName(scope.getName()); + + return representation; + }).collect(Collectors.toList())).build(); + } + + @Path("/resources") + @GET + @Produces("application/json") + @NoCache + public Response getResources() { + this.auth.requireView(); + + if (policy == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + return Response.ok(policy.getResources().stream().map(resource -> { + ResourceRepresentation representation = new ResourceRepresentation(); + + representation.setId(resource.getId()); + representation.setName(resource.getName()); + + return representation; + }).collect(Collectors.toList())).build(); + } + + @Path("/associatedPolicies") + @GET + @Produces("application/json") + @NoCache + public Response getAssociatedPolicies() { + this.auth.requireView(); + + if (policy == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + return Response.ok(policy.getAssociatedPolicies().stream().map(policy -> { + PolicyRepresentation representation1 = new PolicyRepresentation(); + + representation1.setId(policy.getId()); + representation1.setName(policy.getName()); + representation1.setType(policy.getType()); + + return representation1; + }).collect(Collectors.toList())).build(); + } + + protected AbstractPolicyRepresentation doCreateRepresentation(String payload) { + PolicyRepresentation representation; + + try { + representation = JsonSerialization.readValue(payload, PolicyRepresentation.class); + } catch (IOException cause) { + throw new RuntimeException("Failed to deserialize representation", cause); + } + + return representation; + } + + private PolicyProviderFactory getProviderFactory(String policyType) { + return authorization.getProviderFactory(policyType); + } + + protected Policy getPolicy() { + return policy; + } +} diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java index 8982a298f1..011aa2d3a1 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java @@ -17,9 +17,7 @@ */ package org.keycloak.authorization.admin; -import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; -import static org.keycloak.models.utils.RepresentationToModel.toModel; - +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -29,14 +27,13 @@ import java.util.Map; import java.util.stream.Collectors; import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; -import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -48,24 +45,24 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.PolicyStore; -import org.keycloak.authorization.store.ResourceStore; -import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.Constants; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation; import org.keycloak.representations.idm.authorization.PolicyRepresentation; -import org.keycloak.representations.idm.authorization.ResourceRepresentation; -import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.services.ErrorResponseException; import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.util.JsonSerialization; /** * @author Pedro Igor */ public class PolicyService { - private final ResourceServer resourceServer; - private final AuthorizationProvider authorization; - private final RealmAuth auth; + protected final ResourceServer resourceServer; + protected final AuthorizationProvider authorization; + protected final RealmAuth auth; public PolicyService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { this.resourceServer = resourceServer; @@ -73,210 +70,66 @@ public class PolicyService { this.auth = auth; } - @POST - @Consumes("application/json") - @Produces("application/json") - @NoCache - public Response create(PolicyRepresentation representation) { - this.auth.requireManage(); - Policy policy = toModel(representation, this.resourceServer, authorization); - PolicyProviderAdminService resource = getPolicyProviderAdminResource(policy.getType(), authorization); + @Path("{type}") + public Object getResource(@PathParam("type") String type) { + PolicyProviderFactory providerFactory = getPolicyProviderFactory(type); - if (resource != null) { - try { - resource.onCreate(policy); - } catch (Exception e) { - throw new RuntimeException(e); - } + if (providerFactory != null) { + return new PolicyTypeService(type, resourceServer, authorization, auth); } + Policy policy = authorization.getStoreFactory().getPolicyStore().findById(type, resourceServer.getId()); + + return doCreatePolicyResource(policy); + } + + protected Object doCreatePolicyResource(Policy policy) { + return new PolicyResourceService(policy, resourceServer, authorization, auth); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @NoCache + public Response create(String payload) { + this.auth.requireManage(); + + AbstractPolicyRepresentation representation = doCreateRepresentation(payload); + Policy policy = create(representation); + representation.setId(policy.getId()); return Response.status(Status.CREATED).entity(representation).build(); } - @Path("{id}") - @PUT - @Consumes("application/json") - @Produces("application/json") - @NoCache - public Response update(@PathParam("id") String id, PolicyRepresentation representation) { - this.auth.requireManage(); - representation.setId(id); - StoreFactory storeFactory = authorization.getStoreFactory(); - Policy policy = storeFactory.getPolicyStore().findById(representation.getId(), resourceServer.getId()); + protected AbstractPolicyRepresentation doCreateRepresentation(String payload) { + PolicyRepresentation representation; - if (policy == null) { - return Response.status(Status.NOT_FOUND).build(); + try { + representation = JsonSerialization.readValue(payload, PolicyRepresentation.class); + } catch (IOException cause) { + throw new RuntimeException("Failed to deserialize representation", cause); } - policy = toModel(representation, resourceServer, authorization); - - PolicyProviderAdminService resource = getPolicyProviderAdminResource(policy.getType(), authorization); - - if (resource != null) { - try { - resource.onUpdate(policy); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - return Response.status(Status.CREATED).build(); + return representation; } - @Path("{id}") - @DELETE - public Response delete(@PathParam("id") String id) { - this.auth.requireManage(); - StoreFactory storeFactory = authorization.getStoreFactory(); - PolicyStore policyStore = storeFactory.getPolicyStore(); - Policy policy = policyStore.findById(id, resourceServer.getId()); + public Policy create(AbstractPolicyRepresentation representation) { + PolicyStore policyStore = authorization.getStoreFactory().getPolicyStore(); + Policy existing = policyStore.findByName(representation.getName(), resourceServer.getId()); - if (policy == null) { - return Response.status(Status.NOT_FOUND).build(); + if (existing != null) { + throw new ErrorResponseException("Policy with name [" + representation.getName() + "] already exists", "Conflicting policy", Status.CONFLICT); } - PolicyProviderAdminService resource = getPolicyProviderAdminResource(policy.getType(), authorization); - - if (resource != null) { - try { - resource.onRemove(policy); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - policyStore.findDependentPolicies(id, resourceServer.getId()).forEach(dependentPolicy -> { - if (dependentPolicy.getAssociatedPolicies().size() == 1) { - policyStore.delete(dependentPolicy.getId()); - } else { - dependentPolicy.removeAssociatedPolicy(policy); - } - }); - - policyStore.delete(policy.getId()); - - return Response.noContent().build(); - } - - @Path("{id}") - @GET - @Produces("application/json") - @NoCache - public Response findById(@PathParam("id") String id) { - this.auth.requireView(); - StoreFactory storeFactory = authorization.getStoreFactory(); - Policy model = storeFactory.getPolicyStore().findById(id, resourceServer.getId()); - - if (model == null) { - return Response.status(Status.NOT_FOUND).build(); - } - - return Response.ok(toRepresentation(model)).build(); - } - - @Path("{id}/dependentPolicies") - @GET - @Produces("application/json") - @NoCache - public Response getDependentPolicies(@PathParam("id") String id) { - this.auth.requireView(); - StoreFactory storeFactory = authorization.getStoreFactory(); - Policy model = storeFactory.getPolicyStore().findById(id, resourceServer.getId()); - - if (model == null) { - return Response.status(Status.NOT_FOUND).build(); - } - - List policies = authorization.getStoreFactory().getPolicyStore().findDependentPolicies(model.getId(), resourceServer.getId()); - - return Response.ok(policies.stream().map(policy -> { - PolicyRepresentation representation1 = new PolicyRepresentation(); - - representation1.setId(policy.getId()); - representation1.setName(policy.getName()); - representation1.setType(policy.getType()); - - return representation1; - }).collect(Collectors.toList())).build(); - } - - @Path("{id}/scopes") - @GET - @Produces("application/json") - @NoCache - public Response getScopes(@PathParam("id") String id) { - this.auth.requireView(); - StoreFactory storeFactory = authorization.getStoreFactory(); - Policy model = storeFactory.getPolicyStore().findById(id, resourceServer.getId()); - - if (model == null) { - return Response.status(Status.NOT_FOUND).build(); - } - - return Response.ok(model.getScopes().stream().map(scope -> { - ScopeRepresentation representation = new ScopeRepresentation(); - - representation.setId(scope.getId()); - representation.setName(scope.getName()); - - return representation; - }).collect(Collectors.toList())).build(); - } - - @Path("{id}/resources") - @GET - @Produces("application/json") - @NoCache - public Response getResources(@PathParam("id") String id) { - this.auth.requireView(); - StoreFactory storeFactory = authorization.getStoreFactory(); - Policy model = storeFactory.getPolicyStore().findById(id, resourceServer.getId()); - - if (model == null) { - return Response.status(Status.NOT_FOUND).build(); - } - - return Response.ok(model.getResources().stream().map(resource -> { - ResourceRepresentation representation = new ResourceRepresentation(); - - representation.setId(resource.getId()); - representation.setName(resource.getName()); - - return representation; - }).collect(Collectors.toList())).build(); - } - - @Path("{id}/associatedPolicies") - @GET - @Produces("application/json") - @NoCache - public Response getAssociatedPolicies(@PathParam("id") String id) { - this.auth.requireView(); - StoreFactory storeFactory = authorization.getStoreFactory(); - Policy model = storeFactory.getPolicyStore().findById(id, resourceServer.getId()); - - if (model == null) { - return Response.status(Status.NOT_FOUND).build(); - } - - return Response.ok(model.getAssociatedPolicies().stream().map(policy -> { - PolicyRepresentation representation1 = new PolicyRepresentation(); - - representation1.setId(policy.getId()); - representation1.setName(policy.getName()); - representation1.setType(policy.getType()); - - return representation1; - }).collect(Collectors.toList())).build(); + return policyStore.create(representation, resourceServer); } @Path("/search") @GET - @Produces("application/json") + @Produces(MediaType.APPLICATION_JSON) @NoCache - public Response find(@QueryParam("name") String name) { + public Response findByName(@QueryParam("name") String name) { this.auth.requireView(); StoreFactory storeFactory = authorization.getStoreFactory(); @@ -290,11 +143,11 @@ public class PolicyService { return Response.status(Status.OK).build(); } - return Response.ok(toRepresentation(model)).build(); + return Response.ok(toRepresentation(model, authorization)).build(); } @GET - @Produces("application/json") + @Produces(MediaType.APPLICATION_JSON) @NoCache public Response findAll(@QueryParam("policyId") String id, @QueryParam("name") String name, @@ -363,15 +216,24 @@ public class PolicyService { } return Response.ok( - policyStore.findByResourceServer(search, resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS).stream() - .map(policy -> toRepresentation(policy)) - .collect(Collectors.toList())) + doSearch(firstResult, maxResult, search)) .build(); } + protected AbstractPolicyRepresentation toRepresentation(Policy model, AuthorizationProvider authorization) { + return ModelToRepresentation.toRepresentation(model, PolicyRepresentation.class, authorization); + } + + protected List doSearch(Integer firstResult, Integer maxResult, Map filters) { + PolicyStore policyStore = authorization.getStoreFactory().getPolicyStore(); + return policyStore.findByResourceServer(filters, resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS).stream() + .map(policy -> toRepresentation(policy, authorization)) + .collect(Collectors.toList()); + } + @Path("providers") @GET - @Produces("application/json") + @Produces(MediaType.APPLICATION_JSON) @NoCache public Response findPolicyProviders() { this.auth.requireView(); @@ -400,20 +262,12 @@ public class PolicyService { return resource; } - @Path("{policyType}") - public Object getPolicyTypeResource(@PathParam("policyType") String policyType) { - this.auth.requireView(); - return getPolicyProviderAdminResource(policyType, this.authorization); + protected PolicyProviderAdminService getPolicyProviderAdminResource(String policyType) { + return getPolicyProviderFactory(policyType).getAdminResource(resourceServer, authorization); } - private PolicyProviderAdminService getPolicyProviderAdminResource(String policyType, AuthorizationProvider authorization) { - PolicyProviderFactory providerFactory = authorization.getProviderFactory(policyType); - - if (providerFactory != null) { - return providerFactory.getAdminResource(this.resourceServer); - } - - return null; + protected PolicyProviderFactory getPolicyProviderFactory(String policyType) { + return authorization.getProviderFactory(policyType); } private void findAssociatedPolicies(Policy policy, List policies) { diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java new file mode 100644 index 0000000000..8756721e9d --- /dev/null +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.authorization.admin; + +import java.io.IOException; + +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.policy.provider.PolicyProviderFactory; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; +import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.util.JsonSerialization; + +/** + * @author Pedro Igor + */ +public class PolicyTypeResourceService extends PolicyResourceService { + + public PolicyTypeResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + super(policy, resourceServer, authorization, auth); + } + + @Override + protected AbstractPolicyRepresentation doCreateRepresentation(String payload) { + String type = getPolicy().getType(); + Class representationType = authorization.getProviderFactory(type).getRepresentationType(); + + if (representationType == null) { + throw new RuntimeException("Policy provider for type [" + type + "] returned a null representation type."); + } + + AbstractPolicyRepresentation representation; + + try { + representation = JsonSerialization.readValue(payload, representationType); + } catch (IOException e) { + throw new RuntimeException("Failed to deserialize JSON using policy provider for type [" + type + "].", e); + } + + representation.setType(type); + + return representation; + } + + @Override + protected AbstractPolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) { + PolicyProviderFactory providerFactory = authorization.getProviderFactory(policy.getType()); + return ModelToRepresentation.toRepresentation(policy, providerFactory.getRepresentationType(), authorization); + } +} diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java new file mode 100644 index 0000000000..c2e4db5c55 --- /dev/null +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.authorization.admin; + +import java.io.IOException; + +import javax.ws.rs.Path; + +import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; +import org.keycloak.authorization.policy.provider.PolicyProviderFactory; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; +import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.util.JsonSerialization; + +/** + * @author Pedro Igor + */ +public class PolicyTypeService extends PolicyService { + + private final String type; + + PolicyTypeService(String type, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + super(resourceServer, authorization, auth); + this.type = type; + } + + @Path("/provider") + public Object getPolicyAdminResourceProvider() { + PolicyProviderAdminService resource = getPolicyProviderAdminResource(type); + + if (resource == null) { + return null; + } + + ResteasyProviderFactory.getInstance().injectProperties(resource); + + return resource; + } + + @Override + protected Object doCreatePolicyResource(Policy policy) { + return new PolicyTypeResourceService(policy, resourceServer,authorization, auth); + } + + @Override + protected AbstractPolicyRepresentation doCreateRepresentation(String payload) { + PolicyProviderFactory provider = getPolicyProviderFactory(type); + Class representationType = provider.getRepresentationType(); + + if (representationType == null) { + throw new RuntimeException("Policy provider for type [" + type + "] returned a null representation type."); + } + + AbstractPolicyRepresentation representation; + + try { + representation = JsonSerialization.readValue(payload, representationType); + } catch (IOException e) { + throw new RuntimeException("Failed to deserialize JSON using policy provider for type [" + type + "].", e); + } + + representation.setType(type); + + return representation; + } + + @Override + protected AbstractPolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) { + PolicyProviderFactory providerFactory = authorization.getProviderFactory(policy.getType()); + return ModelToRepresentation.toRepresentation(policy, providerFactory.getRepresentationType(), authorization); + } +} diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java index 52d6a38e83..15f1db7a52 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java @@ -17,6 +17,22 @@ */ package org.keycloak.authorization.admin; +import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; + +import java.io.IOException; +import java.util.HashMap; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.ResourceServer; @@ -34,25 +50,11 @@ import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.Logic; import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.services.resources.admin.RealmAuth; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; -import java.io.IOException; -import java.util.HashMap; - -import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; - /** * @author Pedro Igor */ @@ -171,22 +173,26 @@ public class ResourceServerService { return resource; } + @Path("/permission") + public Object getPermissionTypeResource() { + this.auth.requireView(); + PermissionService resource = new PermissionService(this.resourceServer, this.authorization, this.auth); + + ResteasyProviderFactory.getInstance().injectProperties(resource); + + return resource; + } + private void createDefaultPermission(ResourceRepresentation resource, PolicyRepresentation policy) { - PolicyRepresentation defaultPermission = new PolicyRepresentation(); + ResourcePermissionRepresentation defaultPermission = new ResourcePermissionRepresentation(); defaultPermission.setName("Default Permission"); - defaultPermission.setType("resource"); defaultPermission.setDescription("A permission that applies to the default resource type"); defaultPermission.setDecisionStrategy(DecisionStrategy.UNANIMOUS); defaultPermission.setLogic(Logic.POSITIVE); - HashMap defaultPermissionConfig = new HashMap<>(); - - defaultPermissionConfig.put("default", "true"); - defaultPermissionConfig.put("defaultResourceType", resource.getType()); - defaultPermissionConfig.put("applyPolicies", "[\"" + policy.getName() + "\"]"); - - defaultPermission.setConfig(defaultPermissionConfig); + defaultPermission.setResourceType(resource.getType()); + defaultPermission.addPolicy(policy.getName()); getPolicyResource().create(defaultPermission); } diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java index bb241d8805..5d6592366d 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java @@ -58,7 +58,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; diff --git a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java index 7724830a97..1ec9a139e0 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java @@ -41,6 +41,7 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -69,8 +70,8 @@ public class ScopeService { } @POST - @Consumes("application/json") - @Produces("application/json") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) public Response create(ScopeRepresentation scope) { this.auth.requireManage(); Scope model = toModel(scope, this.resourceServer, authorization); @@ -82,8 +83,8 @@ public class ScopeService { @Path("{id}") @PUT - @Consumes("application/json") - @Produces("application/json") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) public Response update(@PathParam("id") String id, ScopeRepresentation scope) { this.auth.requireManage(); scope.setId(id); @@ -134,7 +135,7 @@ public class ScopeService { @Path("{id}") @GET - @Produces("application/json") + @Produces(MediaType.APPLICATION_JSON) public Response findById(@PathParam("id") String id) { this.auth.requireView(); Scope model = this.authorization.getStoreFactory().getScopeStore().findById(id, resourceServer.getId()); @@ -148,7 +149,7 @@ public class ScopeService { @Path("{id}/resources") @GET - @Produces("application/json") + @Produces(MediaType.APPLICATION_JSON) public Response getResources(@PathParam("id") String id) { this.auth.requireView(); StoreFactory storeFactory = this.authorization.getStoreFactory(); @@ -170,7 +171,7 @@ public class ScopeService { @Path("{id}/permissions") @GET - @Produces("application/json") + @Produces(MediaType.APPLICATION_JSON) public Response getPermissions(@PathParam("id") String id) { this.auth.requireView(); StoreFactory storeFactory = this.authorization.getStoreFactory(); @@ -195,7 +196,7 @@ public class ScopeService { @Path("/search") @GET - @Produces("application/json") + @Produces(MediaType.APPLICATION_JSON) @NoCache public Response find(@QueryParam("name") String name) { this.auth.requireView(); diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java index 7891e184e1..6b5b0275ab 100644 --- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java +++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java @@ -139,12 +139,19 @@ public class KeycloakIdentity implements Identity { if (this.accessToken.getClientSession() != null) { ClientSessionModel clientSession = this.keycloakSession.sessions().getClientSession(this.accessToken.getClientSession()); - clientUser = this.keycloakSession.users().getServiceAccount(clientSession.getClient()); - } else if (this.accessToken.getIssuedFor() != null) { - ClientModel clientModel = this.keycloakSession.realms().getClientById(this.accessToken.getIssuedFor(), this.realm); - clientUser = this.keycloakSession.users().getServiceAccount(clientModel); + + if (clientSession != null) { + clientUser = this.keycloakSession.users().getServiceAccount(clientSession.getClient()); + } } + if (this.accessToken.getIssuedFor() != null) { + ClientModel clientModel = this.keycloakSession.realms().getClientById(this.accessToken.getIssuedFor(), this.realm); + + if (clientModel != null) { + clientUser = this.keycloakSession.users().getServiceAccount(clientModel); + } + } if (clientUser == null) { return false; diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java index 620916c324..326deb6453 100644 --- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java +++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java @@ -139,7 +139,7 @@ public class EntitlementService { .allowedOrigins(identity.getAccessToken()) .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build()); } else { - asyncResponse.resume(Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements)))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build()); + asyncResponse.resume(Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements, identity.getAccessToken())))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build()); } } }); @@ -192,7 +192,7 @@ public class EntitlementService { .allowedOrigins(identity.getAccessToken()) .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build()); } else { - asyncResponse.resume(Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements)))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build()); + asyncResponse.resume(Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements, identity.getAccessToken())))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build()); } } }); @@ -207,8 +207,7 @@ public class EntitlementService { } } - private String createRequestingPartyToken(List permissions) { - AccessToken accessToken = Tokens.getAccessToken(this.authorization.getKeycloakSession()); + private String createRequestingPartyToken(List permissions, AccessToken accessToken) { RealmModel realm = this.authorization.getKeycloakSession().getContext().getRealm(); AccessToken.Authorization authorization = new AccessToken.Authorization(); diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index 403551fc3b..51ed405e10 100755 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -337,7 +337,7 @@ public class ExportUtils { RealmModel realm = authorizationProvider.getRealm(); StoreFactory storeFactory = authorizationProvider.getStoreFactory(); try { - PolicyRepresentation rep = toRepresentation(policy); + PolicyRepresentation rep = toRepresentation(policy, PolicyRepresentation.class, authorizationProvider); rep.setId(null); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java index d1fc565ee9..b4f8622d5d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java @@ -17,24 +17,22 @@ package org.keycloak.protocol.oidc.endpoints; -import org.keycloak.Config; -import org.keycloak.common.util.StreamUtil; import org.keycloak.common.util.UriUtils; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.protocol.oidc.utils.WebOriginsUtils; import org.keycloak.services.util.CacheControlUtil; import org.keycloak.services.util.P3PHelper; import org.keycloak.utils.MediaType; -import javax.ws.rs.*; -import javax.ws.rs.core.CacheControl; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; -import java.io.IOException; import java.io.InputStream; import java.util.Set; @@ -59,10 +57,6 @@ public class LoginStatusIframeEndpoint { @Produces(MediaType.TEXT_HTML_UTF_8) public Response getLoginStatusIframe(@QueryParam("client_id") String client_id, @QueryParam("origin") String origin) { - if (client_id != null && origin != null) { - return getLoginStatusIframeDeprecated(client_id, origin); - } - InputStream resource = getClass().getClassLoader().getResourceAsStream("login-status-iframe.html"); if (resource != null) { P3PHelper.addP3PHeader(session); @@ -90,60 +84,4 @@ public class LoginStatusIframeEndpoint { return Response.status(Response.Status.FORBIDDEN).build(); } - // Support for old keycloak.js - private Response getLoginStatusIframeDeprecated(@QueryParam("client_id") String client_id, - @QueryParam("origin") String origin) { - if (!UriUtils.isOrigin(origin)) { - throw new WebApplicationException(Response.Status.BAD_REQUEST); - } - - ClientModel client = realm.getClientByClientId(client_id); - if (client == null) { - throw new WebApplicationException(Response.Status.BAD_REQUEST); - } - - InputStream is = getClass().getClassLoader().getResourceAsStream("login-status-iframe-deprecated.html"); - if (is == null) throw new org.jboss.resteasy.spi.NotFoundException("Could not find login-status-iframe-deprecated.html "); - - boolean valid = false; - for (String o : client.getWebOrigins()) { - if (o.equals("*") || o.equals(origin)) { - valid = true; - break; - } - } - - for (String r : RedirectUtils.resolveValidRedirects(uriInfo, client.getRootUrl(), client.getRedirectUris())) { - int i = r.indexOf('/', 8); - if (i != -1) { - r = r.substring(0, i); - } - - if (r.equals(origin)) { - valid = true; - break; - } - } - - if (!valid) { - throw new WebApplicationException(Response.Status.BAD_REQUEST); - } - - try { - String file = StreamUtil.readString(is); - file = file.replace("ORIGIN", origin); - - P3PHelper.addP3PHeader(session); - - CacheControl cacheControl = new CacheControl(); - cacheControl.setNoTransform(false); - cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1)); - - return Response.ok(file).cacheControl(cacheControl).build(); - } catch (IOException e) { - throw new WebApplicationException(e, Response.Status.BAD_REQUEST); - } - } - - } diff --git a/services/src/main/java/org/keycloak/scripting/DefaultScriptingProvider.java b/services/src/main/java/org/keycloak/scripting/DefaultScriptingProvider.java index 5772f53055..601da8e5d5 100644 --- a/services/src/main/java/org/keycloak/scripting/DefaultScriptingProvider.java +++ b/services/src/main/java/org/keycloak/scripting/DefaultScriptingProvider.java @@ -110,7 +110,13 @@ public class DefaultScriptingProvider implements ScriptingProvider { * Looks-up a {@link ScriptEngine} based on the MIME-type provided by the given {@link Script}. */ private ScriptEngine lookupScriptEngineFor(ScriptModel script) { - return scriptEngineManager.getEngineByMimeType(script.getMimeType()); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(DefaultScriptingProvider.class.getClassLoader()); + return scriptEngineManager.getEngineByMimeType(script.getMimeType()); + } finally { + Thread.currentThread().setContextClassLoader(cl); + } } @Override diff --git a/services/src/main/java/org/keycloak/scripting/DefaultScriptingProviderFactory.java b/services/src/main/java/org/keycloak/scripting/DefaultScriptingProviderFactory.java index b00a058eee..4ed1efd2da 100644 --- a/services/src/main/java/org/keycloak/scripting/DefaultScriptingProviderFactory.java +++ b/services/src/main/java/org/keycloak/scripting/DefaultScriptingProviderFactory.java @@ -29,9 +29,13 @@ public class DefaultScriptingProviderFactory implements ScriptingProviderFactory static final String ID = "script-based-auth"; + private ScriptEngineManager scriptEngineManager; + @Override public ScriptingProvider create(KeycloakSession session) { - return new DefaultScriptingProvider(ScriptEngineManagerHolder.SCRIPT_ENGINE_MANAGER); + lazyInit(); + + return new DefaultScriptingProvider(scriptEngineManager); } @Override @@ -54,11 +58,14 @@ public class DefaultScriptingProviderFactory implements ScriptingProviderFactory return ID; } - /** - * Holder class for lazy initialization of {@link ScriptEngineManager}. - */ - private static class ScriptEngineManagerHolder { - - private static final ScriptEngineManager SCRIPT_ENGINE_MANAGER = new ScriptEngineManager(); + private void lazyInit() { + if (scriptEngineManager == null) { + synchronized (this) { + if (scriptEngineManager == null) { + scriptEngineManager = new ScriptEngineManager(); + } + } + } } + } diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java index a23a26a0e2..059c84d8bc 100755 --- a/services/src/main/java/org/keycloak/services/Urls.java +++ b/services/src/main/java/org/keycloak/services/Urls.java @@ -25,6 +25,7 @@ import org.keycloak.services.resources.IdentityBrokerService; import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.ThemeResource; +import org.keycloak.services.resources.admin.AdminRoot; import javax.ws.rs.core.UriBuilder; import java.net.URI; @@ -34,6 +35,10 @@ import java.net.URI; */ public class Urls { + public static URI adminConsoleRoot(URI baseUri, String realmId) { + return UriBuilder.fromUri(baseUri).path(AdminRoot.class).path("{realm}/console/").build(realmId); + } + public static URI accountApplicationsPage(URI baseUri, String realmId) { return accountBase(baseUri).path(AccountService.class, "applicationsPage").build(realmId); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java index 2b796c54e0..645afafee0 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java @@ -296,6 +296,7 @@ public class AdminConsole { authUrl = authUrl.substring(0, authUrl.length() - 1); map.put("authUrl", authUrl); + map.put("consoleBaseUrl", Urls.adminConsoleRoot(baseUri, realm.getId())); map.put("resourceUrl", Urls.themeRoot(baseUri) + "/admin/" + theme.getName()); map.put("masterRealm", Config.getAdminRealm()); map.put("resourceVersion", Version.RESOURCES_VERSION); diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java index b3a04754db..8e07ee21a8 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java @@ -47,7 +47,7 @@ public class TestPolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer) { + public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) { return null; } diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java index 5f7520fc44..c89467466d 100644 --- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java +++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java @@ -79,6 +79,7 @@ public class KeycloakOnUndertow implements DeployableContainerCreate Bearer Request - + + + + + + + + {{:: 'authz-permission-resource-apply-to-resource-type.tooltip' | translate}} -
+
- +
{{:: 'authz-permission-resource-resource.tooltip' | translate}}
-
- +
+
- +
{{:: 'authz-permission-resource-type.tooltip' | translate}} @@ -56,7 +56,7 @@
- +
{{:: 'authz-policy-apply-policy.tooltip' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html index 90a3dc698b..5da3a12798 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html @@ -32,27 +32,28 @@
- +
{{:: 'authz-permission-scope-resource.tooltip' | translate}}
-
+
{{:: 'authz-permission-scope-scope.tooltip' | translate}}
-
+
- +
{{:: 'authz-permission-scope-scope.tooltip' | translate}}
@@ -60,7 +61,7 @@
- +
{{:: 'authz-policy-apply-policy.tooltip' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-detail.html index 7187c22945..e3bda1e507 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-detail.html @@ -44,10 +44,10 @@ {{:: 'authz-resource-uri.tooltip' | translate}}
- +
- +
{{:: 'authz-resource-scopes.tooltip' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-detail.html index 6bfadfe86f..a505bbcd23 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-detail.html @@ -25,7 +25,7 @@
- +
{{:: 'authz-icon-uri.tooltip' | translate}}
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html b/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html index 4b24d3fd85..0491364b81 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html @@ -2,7 +2,7 @@ -