keycloak-aplcache

Merge branch 'master' into master

6/27/2017 5:27:07 AM

Changes

examples/demo-template/angular2-product-app/pom.xml 101(+0 -101)

examples/demo-template/angular2-product-app/src/main/frontend/.angular-cli.json 57(+0 -57)

examples/demo-template/angular2-product-app/src/main/frontend/.editorconfig 13(+0 -13)

examples/demo-template/angular2-product-app/src/main/frontend/.gitignore 41(+0 -41)

examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts 20(+0 -20)

examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts 28(+0 -28)

examples/demo-template/angular2-product-app/src/main/frontend/e2e/tsconfig.json 19(+0 -19)

examples/demo-template/angular2-product-app/src/main/frontend/karma.conf.js 45(+0 -45)

examples/demo-template/angular2-product-app/src/main/frontend/package.json 46(+0 -46)

examples/demo-template/angular2-product-app/src/main/frontend/protractor.conf.js 31(+0 -31)

examples/demo-template/angular2-product-app/src/main/frontend/README.md 36(+0 -36)

examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.css 0(+0 -0)

examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html 20(+0 -20)

examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts 66(+0 -66)

examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts 32(+0 -32)

examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts 24(+0 -24)

examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.spec.ts 47(+0 -47)

examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts 42(+0 -42)

examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.service.ts 60(+0 -60)

examples/demo-template/angular2-product-app/src/main/frontend/src/assets/.gitkeep 0(+0 -0)

examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.prod.ts 5(+0 -5)

examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.ts 10(+0 -10)

examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.war.ts 5(+0 -5)

examples/demo-template/angular2-product-app/src/main/frontend/src/favicon.ico 0(+0 -0)

examples/demo-template/angular2-product-app/src/main/frontend/src/index.html 15(+0 -15)

examples/demo-template/angular2-product-app/src/main/frontend/src/main.ts 14(+0 -14)

examples/demo-template/angular2-product-app/src/main/frontend/src/polyfills.ts 68(+0 -68)

examples/demo-template/angular2-product-app/src/main/frontend/src/styles.css 1(+0 -1)

examples/demo-template/angular2-product-app/src/main/frontend/src/test.ts 32(+0 -32)

examples/demo-template/angular2-product-app/src/main/frontend/src/tsconfig.json 21(+0 -21)

examples/demo-template/angular2-product-app/src/main/frontend/tslint.json 116(+0 -116)

model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java 86(+0 -86)

model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java 96(+0 -96)

model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/ClientInitialAccessPredicate.java 76(+0 -76)

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminLocalTest.java 215(+0 -215)

Details

diff --git a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
index fabf30e..7e235ae 100644
--- a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
+++ b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
@@ -61,15 +61,19 @@ import org.springframework.util.Assert;
  * @version $Revision: 1 $
  */
 public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
-    
+
+    public static final String DEFAULT_LOGIN_URL = "/sso/login";
     public static final String AUTHORIZATION_HEADER = "Authorization";
     public static final String SCHEME_BEARER = "bearer ";
     public static final String SCHEME_BASIC = "basic ";
 
+
     /**
-     * Request matcher that matches all requests.
+     * Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
+     * and any request with a <code>Authorization</code> header.
      */
-    private static RequestMatcher DEFAULT_REQUEST_MATCHER = new AntPathRequestMatcher("/**");
+    public static final RequestMatcher DEFAULT_REQUEST_MATCHER =
+            new OrRequestMatcher(new AntPathRequestMatcher(DEFAULT_LOGIN_URL), new RequestHeaderRequestMatcher(AUTHORIZATION_HEADER));
 
     private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticationProcessingFilter.class);
 
@@ -107,7 +111,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
      *
      */
     public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager, RequestMatcher
-                requiresAuthenticationRequestMatcher) {
+            requiresAuthenticationRequestMatcher) {
         super(requiresAuthenticationRequestMatcher);
         Assert.notNull(authenticationManager, "authenticationManager cannot be null");
         this.authenticationManager = authenticationManager;
@@ -138,20 +142,27 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
         log.debug("Auth outcome: {}", result);
 
         if (AuthOutcome.FAILED.equals(result)) {
-        	AuthChallenge challenge = authenticator.getChallenge();
+            AuthChallenge challenge = authenticator.getChallenge();
             if (challenge != null) {
                 challenge.challenge(facade);
             }
             throw new KeycloakAuthenticationException("Invalid authorization header, see WWW-Authenticate header for details");
         }
+
         if (AuthOutcome.NOT_ATTEMPTED.equals(result)) {
-        	AuthChallenge challenge = authenticator.getChallenge();
+            AuthChallenge challenge = authenticator.getChallenge();
             if (challenge != null) {
                 challenge.challenge(facade);
             }
-            throw new KeycloakAuthenticationException("Authorization header not found, see WWW-Authenticate header");
+            if (deployment.isBearerOnly()) {
+                // no redirection in this mode, throwing exception for the spring handler
+                throw new KeycloakAuthenticationException("Authorization header not found,  see WWW-Authenticate header");
+            } else {
+                // let continue if challenged, it may redirect
+                return null;
+            }
         }
-       
+
         else if (AuthOutcome.AUTHENTICATED.equals(result)) {
             Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
             Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
@@ -193,7 +204,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
 
     @Override
     protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
-            Authentication authResult) throws IOException, ServletException {
+                                            Authentication authResult) throws IOException, ServletException {
 
         if (!(this.isBearerTokenRequest(request) || this.isBasicAuthRequest(request))) {
             super.successfulAuthentication(request, response, chain, authResult);
@@ -220,10 +231,10 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
     }
 
     @Override
-	protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
-			AuthenticationException failed) throws IOException, ServletException {
-		super.unsuccessfulAuthentication(request, response, failed);
-	}
+    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
+                                              AuthenticationException failed) throws IOException, ServletException {
+        super.unsuccessfulAuthentication(request, response, failed);
+    }
 
     @Override
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
@@ -259,4 +270,4 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
     public final void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
         throw new UnsupportedOperationException("This filter does not support explicitly setting a continue chain before success policy");
     }
-}
+}
\ No newline at end of file
diff --git a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakConfiguration.java b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakConfiguration.java
new file mode 100644
index 0000000..f434b97
--- /dev/null
+++ b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakConfiguration.java
@@ -0,0 +1,25 @@
+package org.keycloak.adapters.springsecurity;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Add this annotation to a class that extends {@code KeycloakWebSecurityConfigurerAdapter} to provide
+ * a keycloak based Spring security configuration.
+ *
+ * @author Hendrik Ebbers
+ */
+@Retention(value = RUNTIME)
+@Target(value = { TYPE })
+@Configuration
+@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
+@EnableWebSecurity
+public @interface KeycloakConfiguration {
+}
diff --git a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
index 2414c38..a6a378a 100755
--- a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
+++ b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
@@ -159,12 +159,10 @@ public class KeycloakAuthenticationProcessingFilterTest {
         when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
         when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
         when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
-        try {
-        	filter.attemptAuthentication(request, response);
-        } catch (KeycloakAuthenticationException e) {
-        	verify(response).setStatus(302);
-            verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
-        }
+
+        filter.attemptAuthentication(request, response);
+        verify(response).setStatus(302);
+        verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
     }
 
     @Test(expected = KeycloakAuthenticationException.class)
@@ -173,6 +171,13 @@ public class KeycloakAuthenticationProcessingFilterTest {
         filter.attemptAuthentication(request, response);
     }
 
+    @Test(expected = KeycloakAuthenticationException.class)
+    public void testAttemptAuthenticationWithInvalidTokenBearerOnly() throws Exception {
+        when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.TRUE);
+        request.addHeader("Authorization", "Bearer xxx");
+        filter.attemptAuthentication(request, response);
+    }
+
     @Test
     public void testSuccessfulAuthenticationInteractive() throws Exception {
         Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, authorities);
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
index 256bfa2..ecde04a 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
@@ -33,6 +33,7 @@ public class ConfigXmlConstants {
     public static final String SIGNATURE_ALGORITHM_ATTR = "signatureAlgorithm";
     public static final String SIGNATURE_CANONICALIZATION_METHOD_ATTR = "signatureCanonicalizationMethod";
     public static final String LOGOUT_PAGE_ATTR = "logoutPage";
+    public static final String AUTODETECT_BEARER_ONLY_ATTR = "autodetectBearerOnly";
 
     public static final String KEYS_ELEMENT = "Keys";
     public static final String KEY_ELEMENT = "Key";
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
index b5f8aba..92254a2 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
@@ -68,6 +68,7 @@ public class DeploymentBuilder {
         deployment.setNameIDPolicyFormat(sp.getNameIDPolicyFormat());
         deployment.setLogoutPage(sp.getLogoutPage());
         deployment.setSignatureCanonicalizationMethod(sp.getIdp().getSignatureCanonicalizationMethod());
+        deployment.setAutodetectBearerOnly(sp.isAutodetectBearerOnly());
         deployment.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256);
         if (sp.getIdp().getSignatureAlgorithm() != null) {
             deployment.setSignatureAlgorithm(SignatureAlgorithm.valueOf(sp.getIdp().getSignatureAlgorithm()));
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
index be6d682..57d5f58 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
@@ -89,6 +89,7 @@ public class SPXmlParser extends AbstractParser {
         sp.setNameIDPolicyFormat(getAttributeValue(startElement, ConfigXmlConstants.NAME_ID_POLICY_FORMAT_ATTR));
         sp.setForceAuthentication(getBooleanAttributeValue(startElement, ConfigXmlConstants.FORCE_AUTHENTICATION_ATTR));
         sp.setIsPassive(getBooleanAttributeValue(startElement, ConfigXmlConstants.IS_PASSIVE_ATTR));
+        sp.setAutodetectBearerOnly(getBooleanAttributeValue(startElement, ConfigXmlConstants.AUTODETECT_BEARER_ONLY_ATTR));
         sp.setTurnOffChangeSessionIdOnLogin(getBooleanAttributeValue(startElement, ConfigXmlConstants.TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN_ATTR));
         while (xmlEventReader.hasNext()) {
             XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
index 475a0f4..071b170 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
@@ -58,6 +58,7 @@ public class SP implements Serializable {
     private PrincipalNameMapping principalNameMapping;
     private Set<String> roleAttributes;
     private IDP idp;
+    private boolean autodetectBearerOnly;
 
     public String getEntityID() {
         return entityID;
@@ -147,4 +148,11 @@ public class SP implements Serializable {
         this.logoutPage = logoutPage;
     }
 
+    public boolean isAutodetectBearerOnly() {
+        return autodetectBearerOnly;
+    }
+
+    public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
+        this.autodetectBearerOnly = autodetectBearerOnly;
+    }
 }
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
index d92884f..9a6e288 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
@@ -294,6 +294,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
     private String logoutPage;
     private SignatureAlgorithm signatureAlgorithm;
     private String signatureCanonicalizationMethod;
+    private boolean autodetectBearerOnly;
 
     @Override
     public boolean turnOffChangeSessionIdOnLogin() {
@@ -439,4 +440,13 @@ public class DefaultSamlDeployment implements SamlDeployment {
     public void setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {
         this.signatureAlgorithm = signatureAlgorithm;
     }
+
+    @Override
+    public boolean isAutodetectBearerOnly() {
+        return autodetectBearerOnly;
+    }
+
+    public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
+        this.autodetectBearerOnly = autodetectBearerOnly;
+    }
 }
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
index 5721b03..08ce4a9 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
@@ -364,26 +364,26 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
 
         if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
             try {
-                validateSamlSignature(new SAMLDocumentHolder(buildAssertionDocument(responseHolder, assertion)), postBinding, GeneralConstants.SAML_RESPONSE_KEY);
-            } catch (VerificationException e) {
-                log.error("Failed to verify saml assertion signature", e);
+                if (!AssertionUtil.isSignatureValid(getAssertionFromResponse(responseHolder), deployment.getIDP().getSignatureValidationKeyLocator())) {
+                    log.error("Failed to verify saml assertion signature");
 
-                challenge = new AuthChallenge() {
+                    challenge = new AuthChallenge() {
 
-                    @Override
-                    public boolean challenge(HttpFacade exchange) {
-                        SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
-                        exchange.getRequest().setError(error);
-                        exchange.getResponse().sendError(403);
-                        return true;
-                    }
+                        @Override
+                        public boolean challenge(HttpFacade exchange) {
+                            SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
+                            exchange.getRequest().setError(error);
+                            exchange.getResponse().sendError(403);
+                            return true;
+                        }
 
-                    @Override
-                    public int getResponseCode() {
-                        return 403;
-                    }
-                };
-                return AuthOutcome.FAILED;
+                        @Override
+                        public int getResponseCode() {
+                            return 403;
+                        }
+                    };
+                    return AuthOutcome.FAILED;
+                }
             } catch (Exception e) {
                 log.error("Error processing validation of SAML assertion: " + e.getMessage());
                 challenge = new AuthChallenge() {
@@ -504,19 +504,16 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
           && Objects.equals(responseType.getStatus().getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
     }
 
-    private Document buildAssertionDocument(final SAMLDocumentHolder responseHolder, AssertionType assertion) throws ConfigurationException, ProcessingException {
-        Element encryptedAssertion = org.keycloak.saml.common.util.DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
+    private Element getAssertionFromResponse(final SAMLDocumentHolder responseHolder) throws ConfigurationException, ProcessingException {
+        Element encryptedAssertion = DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
         if (encryptedAssertion != null) {
             // encrypted assertion.
             // We'll need to decrypt it first.
             Document encryptedAssertionDocument = DocumentUtil.createDocument();
             encryptedAssertionDocument.appendChild(encryptedAssertionDocument.importNode(encryptedAssertion, true));
-            Element assertionElement = XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
-            Document assertionDocument = DocumentUtil.createDocument();
-            assertionDocument.appendChild(assertionDocument.importNode(assertionElement, true));
-            return assertionDocument;
+            return XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
         }
-        return AssertionUtil.asDocument(assertion);
+        return DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ASSERTION.get()));
     }
 
     private String getAttributeValue(Object attrValue) {
@@ -568,9 +565,15 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
         return new AbstractInitiateLogin(deployment, sessionStore) {
             @Override
             protected void sendAuthnRequest(HttpFacade httpFacade, SAML2AuthnRequestBuilder authnRequestBuilder, BaseSAML2BindingBuilder binding) throws ProcessingException, ConfigurationException, IOException {
-                Document document = authnRequestBuilder.toDocument();
-                SamlDeployment.Binding samlBinding = deployment.getIDP().getSingleSignOnService().getRequestBinding();
-                SamlUtil.sendSaml(true, httpFacade, deployment.getIDP().getSingleSignOnService().getRequestBindingUrl(), binding, document, samlBinding);
+                if (isAutodetectedBearerOnly(httpFacade.getRequest())) {
+                    httpFacade.getResponse().setStatus(401);
+                    httpFacade.getResponse().end();
+                }
+                else {
+                    Document document = authnRequestBuilder.toDocument();
+                    SamlDeployment.Binding samlBinding = deployment.getIDP().getSingleSignOnService().getRequestBinding();
+                    SamlUtil.sendSaml(true, httpFacade, deployment.getIDP().getSingleSignOnService().getRequestBindingUrl(), binding, document, samlBinding);
+                }
             }
         };
     }
@@ -693,4 +696,34 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
 
         return signature.verify(decodedSignature);
     }
+
+    protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
+        if (!deployment.isAutodetectBearerOnly()) return false;
+
+        String headerValue = facade.getRequest().getHeader(GeneralConstants.HTTP_HEADER_X_REQUESTED_WITH);
+        if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
+            return true;
+        }
+
+        headerValue = facade.getRequest().getHeader("Faces-Request");
+        if (headerValue != null && headerValue.startsWith("partial/")) {
+            return true;
+        }
+
+        headerValue = facade.getRequest().getHeader("SOAPAction");
+        if (headerValue != null) {
+            return true;
+        }
+
+        List<String> accepts = facade.getRequest().getHeaders("Accept");
+        if (accepts == null) accepts = Collections.emptyList();
+
+        for (String accept : accepts) {
+            if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
+                return false;
+            }
+        }
+
+        return true;
+    }
 }
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
index a9df7e9..0b7ac40 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
@@ -168,6 +168,6 @@ public interface SamlDeployment {
     }
     PrincipalNamePolicy getPrincipalNamePolicy();
     String getPrincipalAttributeName();
-
+    boolean isAutodetectBearerOnly();
 
 }
diff --git a/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_9.xsd b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_9.xsd
new file mode 100644
index 0000000..cf9c05c
--- /dev/null
+++ b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_9.xsd
@@ -0,0 +1,461 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ 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.
+  -->
+
+<xs:schema version="1.0"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema"
+           xmlns="urn:keycloak:saml:adapter"
+           targetNamespace="urn:keycloak:saml:adapter"
+           elementFormDefault="qualified"
+           attributeFormDefault="unqualified">
+
+    <xs:element name="keycloak-saml-adapter" type="adapter-type"/>
+    <xs:complexType name="adapter-type">
+        <xs:annotation>
+            <xs:documentation>Keycloak SAML Adapter configuration file.</xs:documentation>
+        </xs:annotation>
+        <xs:all>
+            <xs:element name="SP" maxOccurs="1" minOccurs="0" type="sp-type">
+                <xs:annotation>
+                    <xs:documentation>Describes SAML service provider configuration.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:all>
+    </xs:complexType>
+
+    <xs:complexType name="sp-type">
+        <xs:all>
+            <xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>
+                        List of service provider encryption and validation keys.
+
+                        If the IDP requires that the client application (SP) sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. For client signed documents you must define both the private and public key or certificate that will be used to sign documents. For encryption, you only have to define the private key that will be used to decrypt.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="PrincipalNameMapping" type="principal-name-mapping-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>When creating a Java Principal object that you obtain from methods like HttpServletRequest.getUserPrincipal(), you can define what name that is returned by the Principal.getName() method.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="RoleIdentifiers" type="role-identifiers-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Defines what SAML attributes within the assertion received from the user should be used as role identifiers within the Java EE Security Context for the user.
+                    By default Role attribute values are converted to Java EE roles. Some IDPs send roles via a member or memberOf attribute assertion. You can define one or more Attribute elements to specify which SAML attributes must be converted into roles.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="IDP" type="idp-type" minOccurs="1" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Describes configuration of SAML identity provider for this service provider.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:all>
+        <xs:attribute name="entityID" type="xs:string" use="required">
+                <xs:annotation>
+                    <xs:documentation>This is the identifier for this client. The IDP needs this value to determine who the client is that is communicating with it.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="sslPolicy" type="ssl-policy-type" use="optional">
+                <xs:annotation>
+                    <xs:documentation>SSL policy the adapter will enforce.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="nameIDPolicyFormat" type="xs:string" use="optional">
+                <xs:annotation>
+                    <xs:documentation>SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. It must be a standard SAML format identifier, i.e. urn:oasis:names:tc:SAML:2.0:nameid-format:transient. By default, no special format is requested.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="logoutPage" type="xs:string" use="optional">
+                <xs:annotation>
+                    <xs:documentation>URL of the logout page.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="forceAuthentication" type="xs:boolean" use="optional">
+                <xs:annotation>
+                    <xs:documentation>SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. Default value is false.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="isPassive" type="xs:boolean" use="optional">
+                <xs:annotation>
+                    <xs:documentation>SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. Set this to true if you want this. Do not use together with forceAuthentication as they are opposite. Default value is false.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="turnOffChangeSessionIdOnLogin" type="xs:boolean" use="optional">
+                <xs:annotation>
+                    <xs:documentation>The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to true to disable this. It is recommended you do not turn it off. Default value is false.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="autodetectBearerOnly" type="xs:boolean" use="optional" default="false">
+                <xs:annotation>
+                  <xs:documentation>This should be set to true if your application serves both a web application and web services (e.g. SOAP or REST). It allows you to redirect unauthenticated users of the web application to the Keycloak login page, but send an HTTP 401 status code to unauthenticated SOAP or REST clients instead as they would not understand a redirect to the login page. Keycloak auto-detects SOAP or REST clients based on typical headers like X-Requested-With, SOAPAction or Accept. The default value is false.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+
+    <xs:complexType name="keys-type">
+        <xs:sequence>
+            <xs:element name="Key" type="key-type" minOccurs="1" maxOccurs="unbounded">
+                <xs:annotation>
+                    <xs:documentation>Describes a single key used for signing or encryption.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+    <xs:complexType name="key-type">
+        <xs:all>
+            <xs:element name="KeyStore" maxOccurs="1" minOccurs="0" type="key-store-type">
+                <xs:annotation>
+                    <xs:documentation>Java keystore to load keys and certificates from.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="PrivateKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Private key (PEM format)</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="PublicKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Public key (PEM format)</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="CertificatePem" type="xs:string" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Certificate key (PEM format)</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:all>
+        <xs:attribute name="signing" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Flag defining whether the key should be used for signing.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="encryption" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Flag defining whether the key should be used for encryption</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="key-store-type">
+        <xs:all>
+            <xs:element name="PrivateKey" maxOccurs="1" minOccurs="0" type="private-key-type">
+                <xs:annotation>
+                    <xs:documentation>Private key declaration</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="Certificate" type="certificate-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Certificate declaration</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:all>
+        <xs:attribute name="file" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>File path to the key store.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="resource" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>WAR resource path to the key store. This is a path used in method call to ServletContext.getResourceAsStream().</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="password" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>The password of the key store.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="private-key-type">
+        <xs:attribute name="alias" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="password" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>Keystores require an additional password to access private keys. In the PrivateKey element you must define this password within a password attribute.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="certificate-type">
+        <xs:attribute name="alias" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="principal-name-mapping-type">
+        <xs:attribute name="policy" type="principal-name-mapping-policy-type" use="required">
+            <xs:annotation>
+                <xs:documentation>Policy used to populate value of Java Principal object obtained from methods like HttpServletRequest.getUserPrincipal().</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="attribute" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>Name of the SAML assertion attribute to use within.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:simpleType name="principal-name-mapping-policy-type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="FROM_NAME_ID">
+                <xs:annotation>
+                    <xs:documentation>This policy just uses whatever the SAML subject value is. This is the default setting</xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+            <xs:enumeration value="FROM_ATTRIBUTE">
+                <xs:annotation>
+                    <xs:documentation>This will pull the value from one of the attributes declared in the SAML assertion received from the server. You'll need to specify the name of the SAML assertion attribute to use within the attribute XML attribute.</xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="ssl-policy-type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="ALL">
+                <xs:annotation>
+                    <xs:documentation>All requests must come in via HTTPS.</xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+            <xs:enumeration value="EXTERNAL">
+                <xs:annotation>
+                    <xs:documentation>Only non-private IP addresses must come over the wire via HTTPS.</xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+            <xs:enumeration value="NONE">
+                <xs:annotation>
+                    <xs:documentation>no requests are required to come over via HTTPS.</xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="signature-algorithm-type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="RSA_SHA1"/>
+            <xs:enumeration value="RSA_SHA256"/>
+            <xs:enumeration value="RSA_SHA512"/>
+            <xs:enumeration value="DSA_SHA1"/>
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="binding-type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="POST"/>
+            <xs:enumeration value="REDIRECT"/>
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:complexType name="role-identifiers-type">
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="Attribute" maxOccurs="unbounded" minOccurs="0" type="attribute-type">
+                <xs:annotation>
+                    <xs:documentation>Specifies SAML attribute to be converted into roles.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:choice>
+    </xs:complexType>
+    <xs:complexType name="attribute-type">
+        <xs:attribute name="name" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>Specifies name of the SAML attribute to be converted into roles.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="idp-type">
+        <xs:sequence minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="SingleSignOnService" maxOccurs="1" minOccurs="1" type="sign-on-type">
+                <xs:annotation>
+                    <xs:documentation>Configuration of the login SAML endpoint of the IDP.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="SingleLogoutService" type="logout-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Configuration of the logout SAML endpoint of the IDP</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="HttpClient" type="http-client-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Configuration of HTTP client used for automatic obtaining of certificates containing public keys for IDP signature verification via SAML descriptor of the IDP.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:sequence>
+        <xs:attribute name="entityID" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>issuer ID of the IDP.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="signaturesRequired" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>If set to true, the client adapter will sign every document it sends to the IDP. Also, the client will expect that the IDP will be signing any documents sent to it. This switch sets the default for all request and response types.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="signatureAlgorithm" type="signature-algorithm-type" use="optional">
+            <xs:annotation>
+                <xs:documentation>Signature algorithm that the IDP expects signed documents to use. Defaults to RSA_SHA256</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the signature canonicalization method that the IDP expects signed documents to use. The default value is https://www.w3.org/2001/10/xml-exc-c14n# and should be good for most IDPs.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="encryption" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation></xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="sign-on-type">
+        <xs:attribute name="signRequest" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="validateAssertionSignature" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client expect the IDP to sign the individual assertions sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="requestBinding" type="binding-type" use="optional">
+            <xs:annotation>
+                <xs:documentation>SAML binding type used for communicating with the IDP. The default value is POST, but you can set it to REDIRECT as well.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="responseBinding" type="binding-type" use="optional">
+            <xs:annotation>
+                <xs:documentation>SAML allows the client to request what binding type it wants authn responses to use. This value maps to ProtocolBinding attribute in SAML AuthnRequest. The default is that the client will not request a specific binding type for responses.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="bindingUrl" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>This is the URL for the IDP login service that the client will send requests to.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="assertionConsumerServiceUrl" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>URL of the assertion consumer service (ACS) where the IDP login service should send responses to. By default it is unset, relying on the IdP settings. When set, it must end in "/saml". This property is typically accompanied by the responseBinding attribute.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+
+    <xs:complexType name="logout-type">
+        <xs:attribute name="signRequest" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="signResponse" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client sign logout responses it sends to the IDP requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client expect signed logout request documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client expect signed logout response documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="requestBinding" type="binding-type" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the SAML binding type used for communicating SAML requests to the IDP. The default value is POST.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="responseBinding" type="binding-type" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the SAML binding type used for communicating SAML responses to the IDP. The default value is POST.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="postBindingUrl" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the URL for the IDP's logout service when using the POST binding. This setting is REQUIRED if using the POST binding.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="redirectBindingUrl" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the URL for the IDP's logout service when using the REDIRECT binding. This setting is REQUIRED if using the REDIRECT binding.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+
+    <xs:complexType name="http-client-type">
+        <xs:attribute name="allowAnyHostname" type="xs:boolean" use="optional" default="false">
+            <xs:annotation>
+                <xs:documentation>If the the IDP server requires HTTPS and this config option is set to true the IDP's certificate
+                    is validated via the truststore, but host name validation is not done. This setting should only be used during
+                    development and never in production as it will partly disable verification of SSL certificates.
+                    This seting may be useful in test environments. The default value is false.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="clientKeystore" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the file path to a keystore file. This keystore contains client certificate 
+                    for two-way SSL when the adapter makes HTTPS requests to the IDP server.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="clientKeystorePassword" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>Password for the client keystore and for the client's key.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="connectionPoolSize" type="xs:int" use="optional" default="10">
+            <xs:annotation>
+                <xs:documentation>Defines number of pooled connections.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="disableTrustManager" type="xs:boolean" use="optional" default="false">
+            <xs:annotation>
+                <xs:documentation>If the the IDP server requires HTTPS and this config option is set to true you do not have to specify a truststore.
+                    This setting should only be used during development and never in production as it will disable verification of SSL certificates.
+                    The default value is false.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="proxyUrl" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>URL to HTTP proxy to use for HTTP connections.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="truststore" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
+                    then the truststore will be obtained from the deployment's classpath instead. Used for outgoing 
+                    HTTPS communications to the IDP server. Client making HTTPS requests need
+                    a way to verify the host of the server they are talking to. This is what the trustore does.
+                    The keystore contains one or more trusted host certificates or certificate authorities.
+                    You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
+                </xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="truststorePassword" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>Password for the truststore keystore.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+
+</xs:schema>
diff --git a/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
index b64f181..88a96cb 100755
--- a/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
+++ b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
@@ -37,7 +37,7 @@ import org.keycloak.saml.common.exceptions.ParsingException;
  */
 public class KeycloakSamlAdapterXMLParserTest {
 
-    private static final String CURRENT_XSD_LOCATION = "/schema/keycloak_saml_adapter_1_7.xsd";
+    private static final String CURRENT_XSD_LOCATION = "/schema/keycloak_saml_adapter_1_9.xsd";
 
     @Rule
     public ExpectedException expectedException = ExpectedException.none();
@@ -91,6 +91,7 @@ public class KeycloakSamlAdapterXMLParserTest {
         assertEquals("format", sp.getNameIDPolicyFormat());
         assertTrue(sp.isForceAuthentication());
         assertTrue(sp.isIsPassive());
+        assertFalse(sp.isAutodetectBearerOnly());
         assertEquals(2, sp.getKeys().size());
         Key signing = sp.getKeys().get(0);
         assertTrue(signing.isSigning());
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml
index 193525a..31f62e5 100755
--- a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml
@@ -17,7 +17,7 @@
 
 <keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_9.xsd">
     <SP entityID="sp"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="format"
@@ -73,4 +73,4 @@
             </Keys>
         </IDP>
     </SP>
-</keycloak-saml-adapter>
\ No newline at end of file
+</keycloak-saml-adapter>
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java
new file mode 100644
index 0000000..2ae1bdc
--- /dev/null
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2017 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.group;
+
+import static org.keycloak.models.utils.ModelToRepresentation.buildGroupPath;
+
+import java.util.function.Function;
+
+import org.keycloak.authorization.attribute.Attributes;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.policy.evaluation.Evaluation;
+import org.keycloak.authorization.policy.provider.PolicyProvider;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class GroupPolicyProvider implements PolicyProvider {
+
+    private final Function<Policy, GroupPolicyRepresentation> representationFunction;
+
+    public GroupPolicyProvider(Function<Policy, GroupPolicyRepresentation> representationFunction) {
+        this.representationFunction = representationFunction;
+    }
+
+    @Override
+    public void evaluate(Evaluation evaluation) {
+        GroupPolicyRepresentation policy = representationFunction.apply(evaluation.getPolicy());
+        RealmModel realm = evaluation.getAuthorizationProvider().getRealm();
+        Attributes.Entry groupsClaim = evaluation.getContext().getIdentity().getAttributes().getValue(policy.getGroupsClaim());
+
+        if (groupsClaim == null || groupsClaim.isEmpty()) {
+            return;
+        }
+
+        for (GroupPolicyRepresentation.GroupDefinition definition : policy.getGroups()) {
+            GroupModel allowedGroup = realm.getGroupById(definition.getId());
+
+            for (int i = 0; i < groupsClaim.size(); i++) {
+                String group = groupsClaim.asString(i);
+
+                if (group.indexOf('/') != -1) {
+                    String allowedGroupPath = buildGroupPath(allowedGroup);
+                    if (group.equals(allowedGroupPath) || (definition.isExtendChildren() && group.startsWith(allowedGroupPath))) {
+                        evaluation.grant();
+                        return;
+                    }
+                }
+
+                // in case the group from the claim does not represent a path, we just check an exact name match
+                if (group.equals(allowedGroup.getName())) {
+                    evaluation.grant();
+                    return;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
\ No newline at end of file
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java
new file mode 100644
index 0000000..f558449
--- /dev/null
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2017 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.group;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+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.policy.provider.PolicyProvider;
+import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class GroupPolicyProviderFactory implements PolicyProviderFactory<GroupPolicyRepresentation> {
+
+    private GroupPolicyProvider provider = new GroupPolicyProvider(policy -> toRepresentation(policy, new GroupPolicyRepresentation()));
+
+    @Override
+    public String getId() {
+        return "group";
+    }
+
+    @Override
+    public String getName() {
+        return "Group";
+    }
+
+    @Override
+    public String getGroup() {
+        return "Identity Based";
+    }
+
+    @Override
+    public PolicyProvider create(AuthorizationProvider authorization) {
+        return provider;
+    }
+
+    @Override
+    public PolicyProvider create(KeycloakSession session) {
+        return provider;
+    }
+
+    @Override
+    public GroupPolicyRepresentation toRepresentation(Policy policy, GroupPolicyRepresentation representation) {
+        representation.setGroupsClaim(policy.getConfig().get("groupsClaim"));
+        try {
+            representation.setGroups(getGroupsDefinition(policy.getConfig()));
+        } catch (IOException cause) {
+            throw new RuntimeException("Failed to deserialize groups", cause);
+        }
+        return representation;
+    }
+
+    @Override
+    public Class<GroupPolicyRepresentation> getRepresentationType() {
+        return GroupPolicyRepresentation.class;
+    }
+
+    @Override
+    public void onCreate(Policy policy, GroupPolicyRepresentation representation, AuthorizationProvider authorization) {
+        updatePolicy(policy, representation.getGroupsClaim(), representation.getGroups(), authorization);
+    }
+
+    @Override
+    public void onUpdate(Policy policy, GroupPolicyRepresentation representation, AuthorizationProvider authorization) {
+        updatePolicy(policy, representation.getGroupsClaim(), representation.getGroups(), authorization);
+    }
+
+    @Override
+    public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
+        try {
+            updatePolicy(policy, representation.getConfig().get("groupsClaim"), getGroupsDefinition(representation.getConfig()), authorization);
+        } catch (IOException cause) {
+            throw new RuntimeException("Failed to deserialize groups", cause);
+        }
+    }
+
+    @Override
+    public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorizationProvider) {
+        Map<String, String> config = new HashMap<>();
+        GroupPolicyRepresentation groupPolicy = toRepresentation(policy, new GroupPolicyRepresentation());
+        Set<GroupPolicyRepresentation.GroupDefinition> groups = groupPolicy.getGroups();
+
+        for (GroupPolicyRepresentation.GroupDefinition definition: groups) {
+            GroupModel group = authorizationProvider.getRealm().getGroupById(definition.getId());
+            definition.setId(null);
+            definition.setPath(ModelToRepresentation.buildGroupPath(group));
+        }
+
+        try {
+            config.put("groupsClaim", groupPolicy.getGroupsClaim());
+            config.put("groups", JsonSerialization.writeValueAsString(groups));
+        } catch (IOException cause) {
+            throw new RuntimeException("Failed to export group policy [" + policy.getName() + "]", cause);
+        }
+
+        representation.setConfig(config);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+        factory.register(event -> {
+        });
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    private void updatePolicy(Policy policy, String groupsClaim, Set<GroupPolicyRepresentation.GroupDefinition> groups, AuthorizationProvider authorization) {
+        if (groupsClaim == null) {
+            throw new RuntimeException("Group claims property not provided");
+        }
+
+        if (groups == null || groups.isEmpty()) {
+            throw new RuntimeException("You must provide at least one group");
+        }
+
+        Map<String, String> config = new HashMap<>(policy.getConfig());
+
+        config.put("groupsClaim", groupsClaim);
+
+        List<GroupModel> topLevelGroups = authorization.getRealm().getTopLevelGroups();
+
+        for (GroupPolicyRepresentation.GroupDefinition definition : groups) {
+            GroupModel group = null;
+
+            if (definition.getId() != null) {
+                group = authorization.getRealm().getGroupById(definition.getId());
+            }
+
+            if (group == null) {
+                String path = definition.getPath();
+                String canonicalPath = path.startsWith("/") ? path.substring(1, path.length()) : path;
+
+                if (canonicalPath != null) {
+                    String[] parts = canonicalPath.split("/");
+                    GroupModel parent = null;
+
+                    for (String part : parts) {
+                        if (parent == null) {
+                            parent = topLevelGroups.stream().filter(groupModel -> groupModel.getName().equals(part)).findFirst().orElseThrow(() -> new RuntimeException("Top level group with name [" + part + "] not found"));
+                        } else {
+                            group = parent.getSubGroups().stream().filter(groupModel -> groupModel.getName().equals(part)).findFirst().orElseThrow(() -> new RuntimeException("Group with name [" + part + "] not found"));
+                            parent = group;
+                        }
+                    }
+
+                    if (parts.length == 1) {
+                        group = parent;
+                    }
+                }
+            }
+
+            if (group == null) {
+                throw new RuntimeException("Group with id [" + definition.getId() + "] not found");
+            }
+
+            definition.setId(group.getId());
+            definition.setPath(null);
+        }
+
+        try {
+            config.put("groups", JsonSerialization.writeValueAsString(groups));
+        } catch (IOException cause) {
+            throw new RuntimeException("Failed to serialize groups", cause);
+        }
+
+        policy.setConfig(config);
+    }
+
+    private HashSet<GroupPolicyRepresentation.GroupDefinition> getGroupsDefinition(Map<String, String> config) throws IOException {
+        return new HashSet<>(Arrays.asList(JsonSerialization.readValue(config.get("groups"), GroupPolicyRepresentation.GroupDefinition[].class)));
+    }
+}
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java
index f875731..944ae02 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java
@@ -17,43 +17,44 @@
  */
 package org.keycloak.authorization.policy.provider.js;
 
-import java.util.function.Supplier;
-
-import javax.script.ScriptEngine;
-import javax.script.ScriptException;
+import java.util.function.BiFunction;
 
+import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.policy.evaluation.Evaluation;
 import org.keycloak.authorization.policy.provider.PolicyProvider;
+import org.keycloak.scripting.EvaluatableScriptAdapter;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
-public class JSPolicyProvider implements PolicyProvider {
+class JSPolicyProvider implements PolicyProvider {
 
-    private Supplier<ScriptEngine> engineProvider;
+    private final BiFunction<AuthorizationProvider, Policy, EvaluatableScriptAdapter> evaluatableScript;
 
-    public JSPolicyProvider(Supplier<ScriptEngine> engineProvider) {
-        this.engineProvider = engineProvider;
+    JSPolicyProvider(final BiFunction<AuthorizationProvider, Policy, EvaluatableScriptAdapter> evaluatableScript) {
+        this.evaluatableScript = evaluatableScript;
     }
 
     @Override
     public void evaluate(Evaluation evaluation) {
-        ScriptEngine engine = engineProvider.get();
-
-        engine.put("$evaluation", evaluation);
-
         Policy policy = evaluation.getPolicy();
+        AuthorizationProvider authorization = evaluation.getAuthorizationProvider();
+        final EvaluatableScriptAdapter adapter = evaluatableScript.apply(authorization, policy);
 
         try {
-            engine.eval(policy.getConfig().get("code"));
-        } catch (ScriptException e) {
+            //how to deal with long running scripts -> timeout?
+            adapter.eval(bindings -> {
+                bindings.put("script", adapter.getScriptModel());
+                bindings.put("$evaluation", evaluation);
+            });
+        }
+        catch (Exception e) {
             throw new RuntimeException("Error evaluating JS Policy [" + policy.getName() + "].", e);
         }
     }
 
     @Override
     public void close() {
-
     }
 }
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 3e68d7f..e3e82ce 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
@@ -1,9 +1,5 @@
 package org.keycloak.authorization.policy.provider.js;
 
-import java.util.Map;
-
-import javax.script.ScriptEngineManager;
-
 import org.keycloak.Config;
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.model.Policy;
@@ -11,17 +7,20 @@ import org.keycloak.authorization.policy.provider.PolicyProvider;
 import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.ScriptModel;
 import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
 import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.scripting.EvaluatableScriptAdapter;
+import org.keycloak.scripting.ScriptingProvider;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
 public class JSPolicyProviderFactory implements PolicyProviderFactory<JSPolicyRepresentation> {
 
-    private static final String ENGINE = "nashorn";
-
-    private JSPolicyProvider provider = new JSPolicyProvider(() -> new ScriptEngineManager().getEngineByName(ENGINE));
+    private final JSPolicyProvider provider = new JSPolicyProvider(this::getEvaluatableScript);
+    private ScriptCache scriptCache;
 
     @Override
     public String getName() {
@@ -69,13 +68,16 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory<JSPolicyRe
         updatePolicy(policy, representation.getConfig().get("code"));
     }
 
-    private void updatePolicy(Policy policy, String code) {
-        policy.putConfig("code", code);
+    @Override
+    public void onRemove(final Policy policy, final AuthorizationProvider authorization) {
+        scriptCache.remove(policy.getId());
     }
 
     @Override
     public void init(Config.Scope config) {
-
+        int maxEntries = Integer.parseInt(config.get("cache-max-entries", "100"));
+        int maxAge = Integer.parseInt(config.get("cache-entry-max-age", "-1"));
+        scriptCache = new ScriptCache(maxEntries, maxAge);
     }
 
     @Override
@@ -92,4 +94,26 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory<JSPolicyRe
     public String getId() {
         return "js";
     }
+
+    private EvaluatableScriptAdapter getEvaluatableScript(final AuthorizationProvider authz, final Policy policy) {
+        return scriptCache.computeIfAbsent(policy.getId(), id -> {
+            final ScriptingProvider scripting = authz.getKeycloakSession().getProvider(ScriptingProvider.class);
+            ScriptModel script = getScriptModel(policy, authz.getRealm(), scripting);
+            return scripting.prepareEvaluatableScript(script);
+        });
+    }
+
+    private ScriptModel getScriptModel(final Policy policy, final RealmModel realm, final ScriptingProvider scripting) {
+        String scriptName = policy.getName();
+        String scriptCode = policy.getConfig().get("code");
+        String scriptDescription = policy.getDescription();
+
+        //TODO lookup script by scriptId instead of creating it every time
+        return scripting.createScript(realm.getId(), ScriptModel.TEXT_JAVASCRIPT, scriptName, scriptCode, scriptDescription);
+    }
+
+    private void updatePolicy(Policy policy, String code) {
+        scriptCache.remove(policy.getId());
+        policy.putConfig("code", code);
+    }
 }
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/ScriptCache.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/ScriptCache.java
new file mode 100644
index 0000000..4180db6
--- /dev/null
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/ScriptCache.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2017 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.js;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.LockSupport;
+import java.util.function.Function;
+
+import org.keycloak.scripting.EvaluatableScriptAdapter;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ScriptCache {
+
+    /**
+     * The load factor.
+     */
+    private static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+    private final Map<String, CacheEntry> cache;
+
+    private final AtomicBoolean writing = new AtomicBoolean(false);
+
+    private final long maxAge;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param maxEntries the maximum number of entries to keep in the cache
+     */
+    public ScriptCache(int maxEntries) {
+        this(maxEntries, -1);
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * @param maxEntries the maximum number of entries to keep in the cache
+     * @param maxAge the time in milliseconds that an entry can stay in the cache. If {@code -1}, entries never expire
+     */
+    public ScriptCache(final int maxEntries, long maxAge) {
+        cache = new LinkedHashMap<String, CacheEntry>(16, DEFAULT_LOAD_FACTOR, true) {
+            @Override
+            protected boolean removeEldestEntry(Map.Entry eldest) {
+                return cache.size()  > maxEntries;
+            }
+        };
+        this.maxAge = maxAge;
+    }
+
+    public EvaluatableScriptAdapter computeIfAbsent(String id, Function<String, EvaluatableScriptAdapter> function) {
+        try {
+            if (parkForWriteAndCheckInterrupt()) {
+                return null;
+            }
+
+            CacheEntry entry = cache.computeIfAbsent(id, key -> new CacheEntry(key, function.apply(id), maxAge));
+
+            if (entry != null) {
+                return entry.value();
+            }
+
+            return null;
+        } finally {
+            writing.lazySet(false);
+        }
+    }
+
+    public EvaluatableScriptAdapter get(String uri) {
+        if (parkForReadAndCheckInterrupt()) {
+            return null;
+        }
+
+        CacheEntry cached = cache.get(uri);
+
+        if (cached != null) {
+            return removeIfExpired(cached);
+        }
+
+        return null;
+    }
+
+    public void remove(String key) {
+        try {
+            if (parkForWriteAndCheckInterrupt()) {
+                return;
+            }
+
+            cache.remove(key);
+        } finally {
+            writing.lazySet(false);
+        }
+    }
+
+    private EvaluatableScriptAdapter removeIfExpired(CacheEntry cached) {
+        if (cached == null) {
+            return null;
+        }
+
+        if (cached.isExpired()) {
+            remove(cached.key());
+            return null;
+        }
+
+        return cached.value();
+    }
+
+    private boolean parkForWriteAndCheckInterrupt() {
+        while (!writing.compareAndSet(false, true)) {
+            LockSupport.parkNanos(1L);
+            if (Thread.interrupted()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean parkForReadAndCheckInterrupt() {
+        while (writing.get()) {
+            LockSupport.parkNanos(1L);
+            if (Thread.interrupted()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static final class CacheEntry {
+
+        final String key;
+        final EvaluatableScriptAdapter value;
+        final long expiration;
+
+        CacheEntry(String key, EvaluatableScriptAdapter value, long maxAge) {
+            this.key = key;
+            this.value = value;
+            if(maxAge == -1) {
+                expiration = -1;
+            } else {
+                expiration = System.currentTimeMillis() + maxAge;
+            }
+        }
+
+        String key() {
+            return key;
+        }
+
+        EvaluatableScriptAdapter value() {
+            return value;
+        }
+
+        boolean isExpired() {
+            return expiration != -1 ? System.currentTimeMillis() > expiration : false;
+        }
+    }
+}
diff --git a/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory b/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory
index e4588f8..e6fa1cc 100644
--- a/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory
+++ b/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory
@@ -41,4 +41,5 @@ org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory
 org.keycloak.authorization.policy.provider.scope.ScopePolicyProviderFactory
 org.keycloak.authorization.policy.provider.time.TimePolicyProviderFactory
 org.keycloak.authorization.policy.provider.user.UserPolicyProviderFactory
-org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory
\ No newline at end of file
+org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory
+org.keycloak.authorization.policy.provider.group.GroupPolicyProviderFactory
\ No newline at end of file
diff --git a/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
index c092c6f..258f076 100755
--- a/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
+++ b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
@@ -31,9 +31,9 @@ public class ConcurrentMultivaluedHashMap<K, V> extends ConcurrentHashMap<K, Lis
 {
    public void putSingle(K key, V value)
    {
-      List<V> list = new CopyOnWriteArrayList<>();
+      List<V> list = createListInstance();
       list.add(value);
-      put(key, list);
+      put(key, list); // Just override with new List instance
    }
 
    public void addAll(K key, V... newValues)
@@ -84,8 +84,15 @@ public class ConcurrentMultivaluedHashMap<K, V> extends ConcurrentHashMap<K, Lis
    public final List<V> getList(K key)
    {
       List<V> list = get(key);
-      if (list == null)
-         put(key, list = new CopyOnWriteArrayList<V>());
+
+      if (list == null) {
+         list = createListInstance();
+         List<V> existing = putIfAbsent(key, list);
+         if (existing != null) {
+            list = existing;
+         }
+      }
+
       return list;
    }
 
@@ -97,4 +104,8 @@ public class ConcurrentMultivaluedHashMap<K, V> extends ConcurrentHashMap<K, Lis
       }
    }
 
+   protected List<V> createListInstance() {
+      return new CopyOnWriteArrayList<>();
+   }
+
 }
diff --git a/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java b/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java
index 1fdfff8..78fc4c7 100644
--- a/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java
+++ b/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.common.util;
 
+import java.util.Collections;
+import java.util.Map;
 import java.util.Properties;
 
 /**
@@ -24,9 +26,21 @@ import java.util.Properties;
  */
 public class SystemEnvProperties extends Properties {
 
+    private final Map<String, String> overrides;
+
+    public SystemEnvProperties(Map<String, String> overrides) {
+        this.overrides = overrides;
+    }
+
+    public SystemEnvProperties() {
+        this.overrides = Collections.EMPTY_MAP;
+    }
+
     @Override
     public String getProperty(String key) {
-        if (key.startsWith("env.")) {
+        if (overrides.containsKey(key)) {
+            return overrides.get(key);
+        } else if (key.startsWith("env.")) {
             return System.getenv().get(key.substring(4));
         } else {
             return System.getProperty(key);
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/GroupPolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/GroupPolicyRepresentation.java
new file mode 100644
index 0000000..c063f8f
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/GroupPolicyRepresentation.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2017 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;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class GroupPolicyRepresentation extends AbstractPolicyRepresentation {
+
+    private String groupsClaim;
+    private Set<GroupDefinition> groups;
+
+    @Override
+    public String getType() {
+        return "group";
+    }
+
+    public String getGroupsClaim() {
+        return groupsClaim;
+    }
+
+    public void setGroupsClaim(String groupsClaim) {
+        this.groupsClaim = groupsClaim;
+    }
+
+    public Set<GroupDefinition> getGroups() {
+        return groups;
+    }
+
+    public void setGroups(Set<GroupDefinition> groups) {
+        this.groups = groups;
+    }
+
+    public void addGroup(String... ids) {
+        for (String id : ids) {
+            addGroup(id, false);
+        }
+    }
+
+    public void addGroup(String id, boolean extendChildren) {
+        if (groups == null) {
+            groups = new HashSet<>();
+        }
+        groups.add(new GroupDefinition(id, extendChildren));
+    }
+
+    public void addGroupPath(String... paths) {
+        for (String path : paths) {
+            addGroupPath(path, false);
+        }
+    }
+
+    public void addGroupPath(String path, boolean extendChildren) {
+        if (groups == null) {
+            groups = new HashSet<>();
+        }
+        groups.add(new GroupDefinition(null, path, extendChildren));
+    }
+
+    public void removeGroup(String... ids) {
+        if (groups != null) {
+            for (final String id : ids) {
+                if (!groups.remove(id)) {
+                    for (GroupDefinition group : new HashSet<>(groups)) {
+                        if (group.getPath().startsWith(id)) {
+                            groups.remove(group);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public static class GroupDefinition {
+
+        private String id;
+        private String path;
+        private boolean extendChildren;
+
+        public GroupDefinition() {
+            this(null);
+        }
+
+        public GroupDefinition(String id) {
+            this(id, false);
+        }
+
+        public GroupDefinition(String id, boolean extendChildren) {
+            this(id, null, extendChildren);
+        }
+
+        public GroupDefinition(String id, String path, boolean extendChildren) {
+            this.id = id;
+            this.path = path;
+            this.extendChildren = extendChildren;
+        }
+
+        public String getId() {
+            return id;
+        }
+
+        public void setId(String id) {
+            this.id = id;
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        public void setPath(String path) {
+            this.path = path;
+        }
+
+        public boolean isExtendChildren() {
+            return extendChildren;
+        }
+
+        public void setExtendChildren(boolean extendChildren) {
+            this.extendChildren = extendChildren;
+        }
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
index cc9e54b..a7ca83e 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
@@ -65,6 +65,7 @@ public class ClientRepresentation {
     private Boolean useTemplateScope;
     private Boolean useTemplateMappers;
     private ResourceServerRepresentation authorizationSettings;
+    private Map<String, Boolean> access;
 
 
     public String getId() {
@@ -366,4 +367,12 @@ public class ClientRepresentation {
     public void setAuthorizationSettings(ResourceServerRepresentation authorizationSettings) {
         this.authorizationSettings = authorizationSettings;
     }
+
+    public Map<String, Boolean> getAccess() {
+        return access;
+    }
+
+    public void setAccess(Map<String, Boolean> access) {
+        this.access = access;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java
index 180db64..9c96970 100755
--- a/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java
@@ -34,6 +34,7 @@ public class GroupRepresentation {
     protected List<String> realmRoles;
     protected Map<String, List<String>> clientRoles;
     protected List<GroupRepresentation> subGroups;
+    private Map<String, Boolean> access;
 
     public String getId() {
         return id;
@@ -97,4 +98,12 @@ public class GroupRepresentation {
     public void setSubGroups(List<GroupRepresentation> subGroups) {
         this.subGroups = subGroups;
     }
+
+    public Map<String, Boolean> getAccess() {
+        return access;
+    }
+
+    public void setAccess(Map<String, Boolean> access) {
+        this.access = access;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/ManagementPermissionReference.java b/core/src/main/java/org/keycloak/representations/idm/ManagementPermissionReference.java
new file mode 100644
index 0000000..22550d6
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/ManagementPermissionReference.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ManagementPermissionReference {
+    private boolean enabled;
+    private String resource;
+    private Map<String, String> scopePermissions;
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public String getResource() {
+        return resource;
+    }
+
+    public void setResource(String resource) {
+        this.resource = resource;
+    }
+
+    public Map<String, String> getScopePermissions() {
+        return scopePermissions;
+    }
+
+    public void setScopePermissions(Map<String, String> scopePermissions) {
+        this.scopePermissions = scopePermissions;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index 2a4ec62..4dcea95 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -62,6 +62,7 @@ public class UserRepresentation {
     protected List<SocialLinkRepresentation> socialLinks;
 
     protected List<String> groups;
+    private Map<String, Boolean> access;
 
     public String getSelf() {
         return self;
@@ -264,4 +265,12 @@ public class UserRepresentation {
     public void setDisableableCredentialTypes(Set<String> disableableCredentialTypes) {
         this.disableableCredentialTypes = disableableCredentialTypes;
     }
+
+    public Map<String, Boolean> getAccess() {
+        return access;
+    }
+
+    public void setAccess(Map<String, Boolean> access) {
+        this.access = access;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/JsonWebToken.java b/core/src/main/java/org/keycloak/representations/JsonWebToken.java
index e499271..04071b6 100755
--- a/core/src/main/java/org/keycloak/representations/JsonWebToken.java
+++ b/core/src/main/java/org/keycloak/representations/JsonWebToken.java
@@ -141,6 +141,7 @@ public class JsonWebToken implements Serializable {
     }
 
     public boolean hasAudience(String audience) {
+        if (this.audience == null) return false;
         for (String a : this.audience) {
             if (a.equals(audience)) {
                 return true;
diff --git a/core/src/test/java/org/keycloak/RSAVerifierTest.java b/core/src/test/java/org/keycloak/RSAVerifierTest.java
index 58e1f38..e606e35 100755
--- a/core/src/test/java/org/keycloak/RSAVerifierTest.java
+++ b/core/src/test/java/org/keycloak/RSAVerifierTest.java
@@ -234,13 +234,16 @@ public class RSAVerifierTest {
     public void testTokenAuth() throws Exception {
         token = new AccessToken();
         token.subject("CN=Client")
-                .issuer("domain")
+                .issuer("http://localhost:8080/auth/realms/demo")
                 .addAccess("service").addRole("admin").verifyCaller(true);
+        token.setEmail("bill@jboss.org");
 
         String encoded = new JWSBuilder()
                 .jsonContent(token)
                 .rsa256(idpPair.getPrivate());
 
+        System.out.println("token size: " + encoded.length());
+
         AccessToken v = null;
         try {
             v = verifySkeletonKeyToken(encoded);
diff --git a/distribution/api-docs-dist/pom.xml b/distribution/api-docs-dist/pom.xml
index a50916a..af5e218 100755
--- a/distribution/api-docs-dist/pom.xml
+++ b/distribution/api-docs-dist/pom.xml
@@ -63,13 +63,6 @@
                 </executions>
             </plugin>
             <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-deploy-plugin</artifactId>
-                <configuration>
-                    <skip>true</skip>
-                </configuration>
-            </plugin>
-            <plugin>
                 <artifactId>maven-assembly-plugin</artifactId>
                 <executions>
                     <execution>
@@ -96,4 +89,27 @@
         </plugins>
     </build>
 
+
+    <profiles>
+        <profile>
+            <id>community</id>
+            <activation>
+                <property>
+                    <name>!product</name>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-deploy-plugin</artifactId>
+                        <configuration>
+                            <skip>true</skip>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
 </project>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli
index 0f47745..9344217 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli
@@ -206,13 +206,13 @@ if (outcome == failed) of /profile=$clusteredProfile/subsystem=infinispan/cache-
   echo
 end-if
 
-if (outcome == failed) of /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:read-resource
-  echo Adding local-cache=actionTokens to keycloak cache container...
-  /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:add(indexing=NONE,start=LAZY)
-  /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=strategy,value=NONE)
-  /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=max-entries,value=-1)
-  /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=interval,value=300000)
-  /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=max-idle,value=-1)
+if (outcome == failed) of /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/:read-resource
+  echo Adding distributed-cache=actionTokens to keycloak cache container...
+  /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/:add(indexing=NONE,mode=SYNC,owners=2)
+  /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/component=eviction/:write-attribute(name=strategy,value=NONE)
+  /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/component=eviction/:write-attribute(name=max-entries,value=-1)
+  /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/component=expiration/:write-attribute(name=interval,value=300000)
+  /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/component=expiration/:write-attribute(name=max-idle,value=-1)
   echo
 end-if
 
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli
index 4d5fac6..4f4e3e0 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli
@@ -211,13 +211,13 @@ if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/distrib
   echo
 end-if
 
-if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:read-resource
-  echo Adding local-cache=actionTokens to keycloak cache container...
-  /subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:add(indexing=NONE,start=LAZY)
-  /subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=strategy,value=NONE)
-  /subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=max-entries,value=-1)
-  /subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=interval,value=300000)
-  /subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=max-idle,value=-1)
+if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/:read-resource
+  echo Adding distributed-cache=actionTokens to keycloak cache container...
+  /subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/:add(indexing=NONE,mode=SYNC,owners=2)
+  /subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/component=eviction/:write-attribute(name=strategy,value=NONE)
+  /subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/component=eviction/:write-attribute(name=max-entries,value=-1)
+  /subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/component=expiration/:write-attribute(name=interval,value=300000)
+  /subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/component=expiration/:write-attribute(name=max-idle,value=-1)
   echo
 end-if
 
diff --git a/distribution/server-overlay/src/main/cli/keycloak-install-ha-base.cli b/distribution/server-overlay/src/main/cli/keycloak-install-ha-base.cli
index 4710eb8..6bb11d5 100644
--- a/distribution/server-overlay/src/main/cli/keycloak-install-ha-base.cli
+++ b/distribution/server-overlay/src/main/cli/keycloak-install-ha-base.cli
@@ -16,7 +16,7 @@ embed-server --server-config=standalone-ha.xml
 /subsystem=infinispan/cache-container=keycloak/local-cache=keys:add()
 /subsystem=infinispan/cache-container=keycloak/local-cache=keys/eviction=EVICTION:add(max-entries=1000,strategy=LRU)
 /subsystem=infinispan/cache-container=keycloak/local-cache=keys/expiration=EXPIRATION:add(max-idle=3600000)
-/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens:add()
-/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/eviction=EVICTION:add(max-entries=-1,strategy=NONE)
-/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/expiration=EXPIRATION:add(max-idle=-1,interval=300000)
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:add(indexing="NONE",mode="SYNC",owners="2")
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/eviction=EVICTION:add(max-entries=-1,strategy=NONE)
+/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/expiration=EXPIRATION:add(max-idle=-1,interval=300000)
 /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
diff --git a/examples/demo-template/pom.xml b/examples/demo-template/pom.xml
index a737bf8..bd239fb 100755
--- a/examples/demo-template/pom.xml
+++ b/examples/demo-template/pom.xml
@@ -51,7 +51,6 @@
         <module>example-ear</module>
         <module>admin-access-app</module>
         <module>angular-product-app</module>
-        <module>angular2-product-app</module>
         <module>database-service</module>
         <module>third-party</module>
         <module>third-party-cdi</module>
diff --git a/examples/demo-template/README.md b/examples/demo-template/README.md
index 52b3001..950b226 100755
--- a/examples/demo-template/README.md
+++ b/examples/demo-template/README.md
@@ -202,28 +202,7 @@ An Angular JS example using Keycloak to secure it.
 If you are already logged in, you will not be asked for a username and password, but you will be redirected to
 an oauth grant page.  This page asks you if you want to grant certain permissions to the third-part app.
 
-Step 10: Angular2 JS Example
-----------------------------------
-An Angular2 JS example using Keycloak to secure it. Angular2 is in beta version yet.
-
-To install angular2
-```
-$ cd keycloak/examples/demo-template/angular2-product-app/src/main/webapp/
-$ npm install
-```
-
-Transpile TypeScript to JavaScript before running the application.
-```
-$ npm run tsc
-```
-
-[http://localhost:8080/angular2-product](http://localhost:8080/angular2-product)
-
-If you are already logged in, you will not be asked for a username and password, but you will be redirected to
-an oauth grant page.  This page asks you if you want to grant certain permissions to the third-part app.
-
-
-Step 11: Pure HTML5/Javascript Example
+Step 10: Pure HTML5/Javascript Example
 ----------------------------------
 An pure HTML5/Javascript example using Keycloak to secure it.
 
@@ -232,7 +211,7 @@ An pure HTML5/Javascript example using Keycloak to secure it.
 If you are already logged in, you will not be asked for a username and password, but you will be redirected to
 an oauth grant page.  This page asks you if you want to grant certain permissions to the third-part app.
 
-Step 12: Service Account Example
+Step 11: Service Account Example
 ================================
 An example for retrieve service account dedicated to the Client Application itself (not to any user). 
 
@@ -240,7 +219,7 @@ An example for retrieve service account dedicated to the Client Application itse
 
 Client authentication is done with OAuth2 Client Credentials Grant in out-of-bound request (Not Keycloak login screen displayed) .
 
-Step 13: Offline Access Example
+Step 12: Offline Access Example
 ===============================
 An example for retrieve offline token, which is then saved to the database and can be used by application anytime later. Offline token
 is valid even if user is already logged out from SSO. Server restart also won't invalidate offline token. Offline token can be revoked by the user in 
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
index e061f5e..75e9d18 100755
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
@@ -156,4 +156,9 @@ public class KerberosFederationProviderFactory implements UserStorageProviderFac
                 AuthenticationExecutionModel.Requirement.ALTERNATIVE, AuthenticationExecutionModel.Requirement.DISABLED);
     }
 
+    @Override
+    public void preRemove(KeycloakSession session, RealmModel realm, ComponentModel model) {
+        CredentialHelper.setOrReplaceAuthenticationRequirement(session, realm, CredentialRepresentation.KERBEROS,
+                AuthenticationExecutionModel.Requirement.DISABLED, null);
+    }
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
index c962af8..0d4c07b 100755
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
@@ -384,8 +384,14 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
 
     }
 
-
-
+    @Override
+    public void preRemove(KeycloakSession session, RealmModel realm, ComponentModel model) {
+        String allowKerberosCfg = model.getConfig().getFirst(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION);
+        if (Boolean.valueOf(allowKerberosCfg)) {
+            CredentialHelper.setOrReplaceAuthenticationRequirement(session, realm, CredentialRepresentation.KERBEROS,
+                    AuthenticationExecutionModel.Requirement.DISABLED, null);
+        }
+    }
 
     @Override
     public SynchronizationResult sync(KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model) {
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientsResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientsResource.java
index 47b4db4..3c8552e 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientsResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientsResource.java
@@ -48,6 +48,10 @@ public interface ClientsResource {
 
     @GET
     @Produces(MediaType.APPLICATION_JSON)
+    public List<ClientRepresentation> findAll(@QueryParam("viewableOnly") boolean viewableOnly);
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
     public List<ClientRepresentation> findByClientId(@QueryParam("clientId") String clientId);
 
 
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupPoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupPoliciesResource.java
new file mode 100644
index 0000000..1cc51b0
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupPoliciesResource.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 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.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface GroupPoliciesResource {
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    Response create(GroupPolicyRepresentation representation);
+
+    @Path("{id}")
+    GroupPolicyResource findById(@PathParam("id") String id);
+
+    @Path("/search")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    GroupPolicyRepresentation findByName(@QueryParam("name") String name);
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupPolicyResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupPolicyResource.java
new file mode 100644
index 0000000..6171868
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupPolicyResource.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 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.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface GroupPolicyResource {
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    GroupPolicyRepresentation toRepresentation();
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    void update(GroupPolicyRepresentation representation);
+
+    @DELETE
+    void remove();
+
+    @Path("/associatedPolicies")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    List<PolicyRepresentation> associatedPolicies();
+
+    @Path("/dependentPolicies")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    List<PolicyRepresentation> dependentPolicies();
+
+    @Path("/resources")
+    @GET
+    @Produces("application/json")
+    @NoCache
+    List<ResourceRepresentation> resources();
+
+}
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 a0af5d4..9ced12c 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
@@ -89,4 +89,7 @@ public interface PoliciesResource {
 
     @Path("client")
     ClientPoliciesResource client();
+
+    @Path("group")
+    GroupPoliciesResource group();
 }
diff --git a/misc/CrossDataCenter.md b/misc/CrossDataCenter.md
index 4146eaa..3313f3f 100644
--- a/misc/CrossDataCenter.md
+++ b/misc/CrossDataCenter.md
@@ -3,6 +3,8 @@ Test Cross-Data-Center scenario (test with external JDG server)
 
 These are temporary notes. This docs should be removed once we have cross-DC support finished and properly documented. 
 
+Note that these steps are already automated, see Cross-DC tests section in [HOW-TO-RUN.md](../testsuite/integration-arquillian/HOW-TO-RUN.md) document.
+ 
 What is working right now is:
 - Propagating of invalidation messages for "realms" and "users" caches
 - All the other things provided by ClusterProvider, which is:
@@ -18,7 +20,7 @@ Basic setup
 
 This is setup with 2 keycloak nodes, which are NOT in cluster. They just share the same database and they will be configured with "work" infinispan cache with remoteStore, which will point
 to external JDG server.
- 
+
 JDG Server setup
 ----------------
 - Download JDG 7.0 server and unzip to some folder
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
index 17ae121..a9df047 100755
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
@@ -154,9 +154,14 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
 
         if (clustered) {
             String nodeName = config.get("nodeName", System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME));
-            configureTransport(gcb, nodeName);
+            String jgroupsUdpMcastAddr = config.get("jgroupsUdpMcastAddr", System.getProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR));
+            configureTransport(gcb, nodeName, jgroupsUdpMcastAddr);
+            gcb.globalJmxStatistics()
+              .jmxDomain(InfinispanConnectionProvider.JMX_DOMAIN + "-" + nodeName);
         }
-        gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
+        gcb.globalJmxStatistics()
+          .allowDuplicateDomains(allowDuplicateJMXDomains)
+          .enable();
 
         cacheManager = new DefaultCacheManager(gcb.build());
         containerManaged = false;
@@ -243,7 +248,14 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
         cacheManager.defineConfiguration(InfinispanConnectionProvider.KEYS_CACHE_NAME, getKeysCacheConfig());
         cacheManager.getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME, true);
 
-        cacheManager.defineConfiguration(InfinispanConnectionProvider.ACTION_TOKEN_CACHE, getActionTokenCacheConfig());
+        final ConfigurationBuilder actionTokenCacheConfigBuilder = getActionTokenCacheConfig();
+        if (clustered) {
+            actionTokenCacheConfigBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
+        }
+        if (jdgEnabled) {
+            configureRemoteActionTokenCacheStore(actionTokenCacheConfigBuilder, async);
+        }
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.ACTION_TOKEN_CACHE, actionTokenCacheConfigBuilder.build());
         cacheManager.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE, true);
 
         long authzRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME).getCacheConfiguration().eviction().maxEntries();
@@ -296,6 +308,30 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
 
     }
 
+    private void configureRemoteActionTokenCacheStore(ConfigurationBuilder builder, boolean async) {
+        String jdgServer = config.get("remoteStoreServer", "localhost");
+        Integer jdgPort = config.getInt("remoteStorePort", 11222);
+
+        builder.persistence()
+                .passivation(false)
+                .addStore(RemoteStoreConfigurationBuilder.class)
+                    .fetchPersistentState(false)
+                    .ignoreModifications(false)
+                    .purgeOnStartup(false)
+                    .preload(true)
+                    .shared(true)
+                    .remoteCacheName(InfinispanConnectionProvider.ACTION_TOKEN_CACHE)
+                    .rawValues(true)
+                    .forceReturnValues(false)
+                    .marshaller(KeycloakHotRodMarshallerFactory.class.getName())
+                    .addServer()
+                        .host(jdgServer)
+                        .port(jdgPort)
+                    .async()
+                        .enabled(async);
+
+    }
+
     protected Configuration getKeysCacheConfig() {
         ConfigurationBuilder cb = new ConfigurationBuilder();
         cb.eviction().strategy(EvictionStrategy.LRU).type(EvictionType.COUNT).size(InfinispanConnectionProvider.KEYS_CACHE_DEFAULT_MAX);
@@ -303,7 +339,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
         return cb.build();
     }
 
-    private Configuration getActionTokenCacheConfig() {
+    private ConfigurationBuilder getActionTokenCacheConfig() {
         ConfigurationBuilder cb = new ConfigurationBuilder();
 
         cb.eviction()
@@ -314,27 +350,48 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
                 .maxIdle(InfinispanConnectionProvider.ACTION_TOKEN_MAX_IDLE_SECONDS, TimeUnit.SECONDS)
                 .wakeUpInterval(InfinispanConnectionProvider.ACTION_TOKEN_WAKE_UP_INTERVAL_SECONDS, TimeUnit.SECONDS);
 
-        return cb.build();
+        return cb;
     }
 
-    protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName) {
+    private static final Object CHANNEL_INIT_SYNCHRONIZER = new Object();
+
+    protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName, String jgroupsUdpMcastAddr) {
         if (nodeName == null) {
             gcb.transport().defaultTransport();
         } else {
             FileLookup fileLookup = FileLookupFactory.newInstance();
 
-            try {
-                // Compatibility with Wildfly
-                JChannel channel = new JChannel(fileLookup.lookupFileLocation("default-configs/default-jgroups-udp.xml", this.getClass().getClassLoader()));
-                channel.setName(nodeName);
-                JGroupsTransport transport = new JGroupsTransport(channel);
-
-                gcb.transport().nodeName(nodeName);
-                gcb.transport().transport(transport);
-
-                logger.infof("Configured jgroups transport with the channel name: %s", nodeName);
-            } catch (Exception e) {
-                throw new RuntimeException(e);
+            synchronized (CHANNEL_INIT_SYNCHRONIZER) {
+                String originalMcastAddr = System.getProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
+                if (jgroupsUdpMcastAddr == null) {
+                    System.getProperties().remove(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
+                } else {
+                    System.setProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR, jgroupsUdpMcastAddr);
+                }
+                try {
+                    // Compatibility with Wildfly
+                    JChannel channel = new JChannel(fileLookup.lookupFileLocation("default-configs/default-jgroups-udp.xml", this.getClass().getClassLoader()));
+                    channel.setName(nodeName);
+                    JGroupsTransport transport = new JGroupsTransport(channel);
+
+                    gcb.transport()
+                      .nodeName(nodeName)
+                      .transport(transport)
+                      .globalJmxStatistics()
+                        .jmxDomain(InfinispanConnectionProvider.JMX_DOMAIN + "-" + nodeName)
+                        .enable()
+                      ;
+
+                    logger.infof("Configured jgroups transport with the channel name: %s", nodeName);
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                } finally {
+                    if (originalMcastAddr == null) {
+                        System.getProperties().remove(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
+                    } else {
+                        System.setProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR, originalMcastAddr);
+                    }
+                }
             }
         }
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
index 8618a69..e8cdbf6 100755
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
@@ -53,7 +53,9 @@ public interface InfinispanConnectionProvider extends Provider {
 
     // System property used on Wildfly to identify distributedCache address and sticky session route
     String JBOSS_NODE_NAME = "jboss.node.name";
+    String JGROUPS_UDP_MCAST_ADDR = "jgroups.udp.mcast_addr";
 
+    String JMX_DOMAIN = "jboss.datagrid-infinispan";
 
     <K, V> Cache<K, V> getCache(String name);
 
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java
index 759c284..4d46f81 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java
@@ -30,13 +30,17 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati
     private String id;
     private String name;
     private Set<String> resources;
+    private Set<String> resourceTypes;
+    private Set<String> scopes;
     private String serverId;
 
-    public static PolicyRemovedEvent create(String id, String name, Set<String> resources, String serverId) {
+    public static PolicyRemovedEvent create(String id, String name, Set<String> resources, Set<String> resourceTypes, Set<String> scopes, String serverId) {
         PolicyRemovedEvent event = new PolicyRemovedEvent();
         event.id = id;
         event.name = name;
         event.resources = resources;
+        event.resourceTypes = resourceTypes;
+        event.scopes = scopes;
         event.serverId = serverId;
         return event;
     }
@@ -53,6 +57,6 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati
 
     @Override
     public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
-        cache.policyRemoval(id, name, resources, serverId, invalidations);
+        cache.policyRemoval(id, name, resources, resourceTypes, scopes, serverId, invalidations);
     }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java
index b576bda..d613c57 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java
@@ -30,13 +30,17 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati
     private String id;
     private String name;
     private static Set<String> resources;
+    private Set<String> resourceTypes;
+    private Set<String> scopes;
     private String serverId;
 
-    public static PolicyUpdatedEvent create(String id, String name, Set<String> resources, String serverId) {
+    public static PolicyUpdatedEvent create(String id, String name, Set<String> resources, Set<String> resourceTypes, Set<String> scopes, String serverId) {
         PolicyUpdatedEvent event = new PolicyUpdatedEvent();
         event.id = id;
         event.name = name;
         event.resources = resources;
+        event.resourceTypes = resourceTypes;
+        event.scopes = scopes;
         event.serverId = serverId;
         return event;
     }
@@ -53,6 +57,6 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati
 
     @Override
     public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
-        cache.policyUpdated(id, name, resources, serverId, invalidations);
+        cache.policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
     }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java
index c5dc7fc..c74e4fe 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java
@@ -17,21 +17,19 @@
 
 package org.keycloak.models.cache.infinispan.authorization;
 
+import static org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory.REALM_CLEAR_CACHE_EVENTS;
+
 import org.infinispan.Cache;
 import org.jboss.logging.Logger;
 import org.keycloak.Config;
-import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.cluster.ClusterEvent;
 import org.keycloak.cluster.ClusterProvider;
 import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.CacheRealmProviderFactory;
 import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
 import org.keycloak.models.cache.authorization.CachedStoreProviderFactory;
-import org.keycloak.models.cache.infinispan.RealmCacheManager;
-import org.keycloak.models.cache.infinispan.RealmCacheSession;
+import org.keycloak.models.cache.infinispan.ClearCacheEvent;
 import org.keycloak.models.cache.infinispan.entities.Revisioned;
 import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
 
@@ -59,21 +57,17 @@ public class InfinispanCacheStoreFactoryProviderFactory implements CachedStorePr
                     Cache<String, Revisioned> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME);
                     Cache<String, Long> revisions = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME);
                     storeCache = new StoreFactoryCacheManager(cache, revisions);
-
                     ClusterProvider cluster = session.getProvider(ClusterProvider.class);
-                    cluster.registerListener(ClusterProvider.ALL, (ClusterEvent event) -> {
 
+                    cluster.registerListener(ClusterProvider.ALL, (ClusterEvent event) -> {
                         if (event instanceof InvalidationEvent) {
                             InvalidationEvent invalidationEvent = (InvalidationEvent) event;
                             storeCache.invalidationEventReceived(invalidationEvent);
                         }
                     });
 
-                    cluster.registerListener(AUTHORIZATION_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> {
-
-                        storeCache.clear();
-
-                    });
+                    cluster.registerListener(AUTHORIZATION_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> storeCache.clear());
+                    cluster.registerListener(REALM_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> storeCache.clear());
 
                     log.debug("Registered cluster listeners");
                 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
index 970d1b9..7660c96 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
@@ -47,7 +47,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
     @Override
     public Policy getDelegateForUpdate() {
         if (updated == null) {
-            cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getResourceServerId());
+            cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getScopesIds(), cached.getResourceServerId());
             updated = cacheSession.getPolicyStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
             if (updated == null) throw new IllegalStateException("Not found in database");
         }
@@ -96,7 +96,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
     @Override
     public void setName(String name) {
         getDelegateForUpdate();
-        cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getResourceServerId());
+        cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getScopesIds(), cached.getResourceServerId());
         updated.setName(name);
 
     }
@@ -235,7 +235,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
         getDelegateForUpdate();
         HashSet<String> resources = new HashSet<>();
         resources.add(resource.getId());
-        cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId());
+        cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(), cached.getResourceServerId());
         updated.addResource(resource);
 
     }
@@ -245,7 +245,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
         getDelegateForUpdate();
         HashSet<String> resources = new HashSet<>();
         resources.add(resource.getId());
-        cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId());
+        cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(), cached.getResourceServerId());
         updated.removeResource(resource);
 
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
index 7943589..63eb8a7 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
@@ -102,7 +102,7 @@ public class StoreFactoryCacheManager extends CacheManager {
         addInvalidations(InResourcePredicate.create().resource(id), invalidations);
     }
 
-    public void policyUpdated(String id, String name, Set<String> resources, String serverId, Set<String> invalidations) {
+    public void policyUpdated(String id, String name, Set<String> resources, Set<String> resourceTypes, Set<String> scopes, String serverId, Set<String> invalidations) {
         invalidations.add(id);
         invalidations.add(StoreFactoryCacheSession.getPolicyByNameCacheKey(name, serverId));
 
@@ -111,10 +111,22 @@ public class StoreFactoryCacheManager extends CacheManager {
                 invalidations.add(StoreFactoryCacheSession.getPolicyByResource(resource, serverId));
             }
         }
+
+        if (resourceTypes != null) {
+            for (String type : resourceTypes) {
+                invalidations.add(StoreFactoryCacheSession.getPolicyByResourceType(type, serverId));
+            }
+        }
+
+        if (scopes != null) {
+            for (String scope : scopes) {
+                invalidations.add(StoreFactoryCacheSession.getPolicyByScope(scope, serverId));
+            }
+        }
     }
 
-    public void policyRemoval(String id, String name, Set<String> resources, String serverId, Set<String> invalidations) {
-        policyUpdated(id, name, resources, serverId, invalidations);
+    public void policyRemoval(String id, String name, Set<String> resources, Set<String> resourceTypes, Set<String> scopes, String serverId, Set<String> invalidations) {
+        policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
     }
 
 
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
index 3e4c205..10be78d 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.BiFunction;
 import java.util.function.Supplier;
@@ -252,12 +253,30 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
         invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId));
     }
 
-    public void registerPolicyInvalidation(String id, String name, Set<String> resources, String serverId) {
-        cache.policyUpdated(id, name, resources, serverId, invalidations);
+    public void registerPolicyInvalidation(String id, String name, Set<String> resources, Set<String> scopes, String serverId) {
+        Set<String> resourceTypes = getResourceTypes(resources, serverId);
+        cache.policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
         PolicyAdapter adapter = managedPolicies.get(id);
         if (adapter != null) adapter.invalidateFlag();
 
-        invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, serverId));
+        invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, resourceTypes, scopes, serverId));
+    }
+
+    private Set<String> getResourceTypes(Set<String> resources, String serverId) {
+        if (resources == null) {
+            return Collections.emptySet();
+        }
+
+        return resources.stream().map(resourceId -> {
+            Resource resource = getResourceStore().findById(resourceId, serverId);
+            String type = resource.getType();
+
+            if (type != null) {
+                return type;
+            }
+
+            return null;
+        }).filter(Objects::nonNull).collect(Collectors.toSet());
     }
 
     public ResourceServerStore getResourceServerStoreDelegate() {
@@ -626,7 +645,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
         @Override
         public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) {
             Policy policy = getPolicyStoreDelegate().create(representation, resourceServer);
-            registerPolicyInvalidation(policy.getId(), policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), resourceServer.getId());
+            registerPolicyInvalidation(policy.getId(), representation.getName(), representation.getResources(), representation.getScopes(), resourceServer.getId());
             return policy;
         }
 
@@ -637,8 +656,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
             if (policy == null) return;
 
             cache.invalidateObject(id);
-            invalidationEvents.add(PolicyRemovedEvent.create(id, policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), policy.getResourceServer().getId()));
-            cache.policyRemoval(id, policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), policy.getResourceServer().getId(), invalidations);
+            Set<String> resources = policy.getResources().stream().map(resource -> resource.getId()).collect(Collectors.toSet());
+            ResourceServer resourceServer = policy.getResourceServer();
+            Set<String> resourceTypes = getResourceTypes(resources, resourceServer.getId());
+            Set<String> scopes = policy.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet());
+            invalidationEvents.add(PolicyRemovedEvent.create(id, policy.getName(), resources, resourceTypes, scopes, resourceServer.getId()));
+            cache.policyRemoval(id, policy.getName(), resources, resourceTypes, scopes, resourceServer.getId(), invalidations);
             getPolicyStoreDelegate().delete(id);
 
         }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
index c9832ff..4480f7a 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
@@ -220,7 +220,7 @@ public abstract class CacheManager {
 
         addInvalidationsFromEvent(event, invalidations);
 
-        getLogger().debugf("Invalidating %d cache items after received event %s", invalidations.size(), event);
+        getLogger().debugf("[%s] Invalidating %d cache items after received event %s", cache.getCacheManager().getAddress(), invalidations.size(), event);
 
         for (String invalidation : invalidations) {
             invalidateObject(invalidation);
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
index d32d35c..21bcc66 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
@@ -35,7 +35,7 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public class GroupAdapter implements GroupModel {
-    protected GroupModel updated;
+    protected volatile GroupModel updated;
     protected CachedGroup cached;
     protected RealmCacheSession cacheSession;
     protected KeycloakSession keycloakSession;
@@ -56,7 +56,7 @@ public class GroupAdapter implements GroupModel {
         }
     }
 
-    protected boolean invalidated;
+    protected volatile boolean invalidated;
     public void invalidate() {
         invalidated = true;
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 0bed826..d1945ad 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -55,7 +55,7 @@ import java.util.concurrent.ConcurrentHashMap;
 public class RealmAdapter implements CachedRealmModel {
     protected CachedRealm cached;
     protected RealmCacheSession cacheSession;
-    protected RealmModel updated;
+    protected volatile RealmModel updated;
     protected RealmCache cache;
     protected KeycloakSession session;
 
@@ -75,7 +75,7 @@ public class RealmAdapter implements CachedRealmModel {
         return updated;
     }
 
-    protected boolean invalidated;
+    protected volatile boolean invalidated;
 
     protected void invalidateFlag() {
         invalidated = true;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
index 52c1309..1d7ce64 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
@@ -19,6 +19,7 @@ package org.keycloak.models.cache.infinispan;
 
 import org.jboss.logging.Logger;
 import org.keycloak.cluster.ClusterProvider;
+import org.keycloak.models.ClientInitialAccessModel;
 import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
 import org.keycloak.migration.MigrationModel;
 import org.keycloak.models.ClientModel;
@@ -164,9 +165,8 @@ public class RealmCacheSession implements CacheRealmProvider {
 
     @Override
     public void clear() {
-        cache.clear();
         ClusterProvider cluster = session.getProvider(ClusterProvider.class);
-        cluster.notify(InfinispanCacheRealmProviderFactory.REALM_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), true);
+        cluster.notify(InfinispanCacheRealmProviderFactory.REALM_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), false);
     }
 
     @Override
@@ -1060,4 +1060,34 @@ public class RealmCacheSession implements CacheRealmProvider {
         return adapter;
     }
 
+    // Don't cache ClientInitialAccessModel for now
+    @Override
+    public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
+        return getDelegate().createClientInitialAccessModel(realm, expiration, count);
+    }
+
+    @Override
+    public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
+        return getDelegate().getClientInitialAccessModel(realm, id);
+    }
+
+    @Override
+    public void removeClientInitialAccessModel(RealmModel realm, String id) {
+        getDelegate().removeClientInitialAccessModel(realm, id);
+    }
+
+    @Override
+    public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
+        return getDelegate().listClientInitialAccess(realm);
+    }
+
+    @Override
+    public void removeExpiredClientInitialAccess() {
+        getDelegate().removeExpiredClientInitialAccess();
+    }
+
+    @Override
+    public void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess) {
+        getDelegate().decreaseRemainingCount(realm, clientInitialAccess);
+    }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index 3094521..0056edf 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -41,7 +41,7 @@ import java.util.concurrent.ConcurrentHashMap;
  * @version $Revision: 1 $
  */
 public class UserAdapter implements CachedUserModel {
-    protected UserModel updated;
+    protected volatile UserModel updated;
     protected CachedUser cached;
     protected UserCacheSession userProviderCache;
     protected KeycloakSession keycloakSession;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java
index 173c434..24742e5 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java
@@ -81,6 +81,11 @@ public class ActionTokenReducedKey implements Serializable {
           && Objects.equals(this.actionVerificationNonce, other.getActionVerificationNonce());
     }
 
+    @Override
+    public String toString() {
+        return "userId=" + userId + ", actionId=" + actionId + ", actionVerificationNonce=" + actionVerificationNonce;
+    }
+
     public static class ExternalizerImpl implements Externalizer<ActionTokenReducedKey> {
 
         @Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
index 7c0f663..7e3c76e 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
@@ -53,7 +53,7 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
         public void writeObject(ObjectOutput output, ActionTokenValueEntity t) throws IOException {
             output.writeByte(VERSION_1);
 
-            output.writeBoolean(! t.notes.isEmpty());
+            output.writeBoolean(t.notes.isEmpty());
             if (! t.notes.isEmpty()) {
                 output.writeObject(t.notes);
             }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
index 127879a..b4689aa 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
@@ -17,13 +17,14 @@
 package org.keycloak.models.sessions.infinispan;
 
 import org.keycloak.cluster.ClusterProvider;
+import org.keycloak.common.util.Time;
 import org.keycloak.models.*;
 
-import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
-import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
+import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
 import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
 import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import org.infinispan.Cache;
 
 /**
@@ -57,8 +58,11 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
         ActionTokenReducedKey tokenKey = new ActionTokenReducedKey(key.getUserId(), key.getActionId(), key.getActionVerificationNonce());
         ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
 
-        ClusterProvider cluster = session.getProvider(ClusterProvider.class);
-        this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new AddInvalidatedActionTokenEvent(tokenKey, key.getExpiration(), tokenValue), false);
+        this.tx.put(actionKeyCache, tokenKey, tokenValue, key.getExpiration() - Time.currentTime(), TimeUnit.SECONDS);
+    }
+
+    private static String generateActionTokenEventId() {
+        return InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS + "/" + UUID.randomUUID();
     }
 
     @Override
@@ -87,12 +91,13 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
         return value;
     }
 
+    @Override
     public void removeAll(String userId, String actionId) {
         if (userId == null || actionId == null) {
             return;
         }
 
         ClusterProvider cluster = session.getProvider(ClusterProvider.class);
-        this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new RemoveActionTokensSpecificEvent(userId, actionId), false);
+        this.tx.notify(cluster, generateActionTokenEventId(), new RemoveActionTokensSpecificEvent(userId, actionId), false);
     }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java
index a8c5e38..95ee903 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java
@@ -19,18 +19,20 @@ package org.keycloak.models.sessions.infinispan;
 import org.keycloak.Config;
 import org.keycloak.Config.Scope;
 import org.keycloak.cluster.ClusterProvider;
-import org.keycloak.common.util.Time;
 import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
 import org.keycloak.models.*;
 
-import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
-import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
+import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
 import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
 import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
+import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import org.infinispan.AdvancedCache;
 import org.infinispan.Cache;
 import org.infinispan.context.Flag;
+import org.infinispan.remoting.transport.Address;
+import org.jboss.logging.Logger;
 
 /**
  *
@@ -38,6 +40,10 @@ import org.infinispan.context.Flag;
  */
 public class InfinispanActionTokenStoreProviderFactory implements ActionTokenStoreProviderFactory {
 
+    private static final Logger LOG = Logger.getLogger(InfinispanActionTokenStoreProviderFactory.class);
+
+    private volatile Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache;
+
     public static final String ACTION_TOKEN_EVENTS = "ACTION_TOKEN_EVENTS";
 
     /**
@@ -49,43 +55,60 @@ public class InfinispanActionTokenStoreProviderFactory implements ActionTokenSto
 
     @Override
     public ActionTokenStoreProvider create(KeycloakSession session) {
+        return new InfinispanActionTokenStoreProvider(session, this.actionTokenCache);
+    }
+
+    @Override
+    public void init(Scope config) {
+        this.config = config;
+    }
+
+    private static Cache<ActionTokenReducedKey, ActionTokenValueEntity> initActionTokenCache(KeycloakSession session) {
         InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
-        Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
+        Cache<ActionTokenReducedKey, ActionTokenValueEntity> cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
+        final Address cacheAddress = cache.getCacheManager().getAddress();
 
         ClusterProvider cluster = session.getProvider(ClusterProvider.class);
 
-        cluster.registerListener(ACTION_TOKEN_EVENTS, event -> {
+        cluster.registerListener(ClusterProvider.ALL, event -> {
             if (event instanceof RemoveActionTokensSpecificEvent) {
                 RemoveActionTokensSpecificEvent e = (RemoveActionTokensSpecificEvent) event;
 
-                actionTokenCache
+                LOG.debugf("[%s] Removing token invalidation for user+action: userId=%s, actionId=%s", cacheAddress, e.getUserId(), e.getActionId());
+
+                AdvancedCache<ActionTokenReducedKey, ActionTokenValueEntity> localCache = cache
                   .getAdvancedCache()
-                  .withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD)
+                  .withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD);
+
+                List<ActionTokenReducedKey> toRemove = localCache
                   .keySet()
                   .stream()
                   .filter(k -> Objects.equals(k.getUserId(), e.getUserId()) && Objects.equals(k.getActionId(), e.getActionId()))
-                  .forEach(actionTokenCache::remove);
-            } else if (event instanceof AddInvalidatedActionTokenEvent) {
-                AddInvalidatedActionTokenEvent e = (AddInvalidatedActionTokenEvent) event;
-
-                if (e.getExpirationInSecs() == DEFAULT_CACHE_EXPIRATION) {
-                    actionTokenCache.put(e.getKey(), e.getTokenValue());
-                } else {
-                    actionTokenCache.put(e.getKey(), e.getTokenValue(), e.getExpirationInSecs() - Time.currentTime(), TimeUnit.SECONDS);
-                }
+                  .collect(Collectors.toList());
+
+                toRemove.forEach(localCache::remove);
             }
         });
 
-        return new InfinispanActionTokenStoreProvider(session, actionTokenCache);
-    }
+        LOG.debugf("[%s] Registered cluster listeners", cacheAddress);
 
-    @Override
-    public void init(Scope config) {
-        this.config = config;
+        return cache;
     }
 
     @Override
     public void postInit(KeycloakSessionFactory factory) {
+        Cache<ActionTokenReducedKey, ActionTokenValueEntity> cache = this.actionTokenCache;
+
+        // It is necessary to put the cache initialization here, otherwise the cache would be initialized lazily, that
+        // means also listeners will start only after first cache initialization - that would be too late
+        if (cache == null) {
+            synchronized (this) {
+                cache = this.actionTokenCache;
+                if (cache == null) {
+                    this.actionTokenCache = initActionTokenCache(factory.create());
+                }
+            }
+        }
     }
 
     @Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
index 83e970d..a9589cc 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
@@ -92,7 +92,7 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
                     ClusterProvider cluster = session.getProvider(ClusterProvider.class);
                     cluster.registerListener(AUTHENTICATION_SESSION_EVENTS, this::updateAuthNotes);
 
-                    log.debug("Registered cluster listeners");
+                    log.debugf("[%s] Registered cluster listeners", authSessionsCache.getCacheManager().getAddress());
                 }
             }
         }
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 0476698..202a051 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
@@ -22,7 +22,6 @@ import org.infinispan.CacheStream;
 import org.infinispan.context.Flag;
 import org.jboss.logging.Logger;
 import org.keycloak.common.util.Time;
-import org.keycloak.models.ClientInitialAccessModel;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
@@ -32,23 +31,19 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.session.UserSessionPersisterProvider;
-import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
 import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
 import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
 import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
 import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
-import org.keycloak.models.sessions.infinispan.stream.ClientInitialAccessPredicate;
 import org.keycloak.models.sessions.infinispan.stream.Comparators;
 import org.keycloak.models.sessions.infinispan.stream.Mappers;
 import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
 import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
 import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
-import org.keycloak.models.utils.KeycloakModelUtils;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -271,7 +266,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         log.debugf("Removing expired sessions");
         removeExpiredUserSessions(realm);
         removeExpiredOfflineUserSessions(realm);
-        removeExpiredClientInitialAccess(realm);
     }
 
     private void removeExpiredUserSessions(RealmModel realm) {
@@ -317,14 +311,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         log.debugf("Removed %d expired offline user sessions for realm '%s'", counter, realm.getName());
     }
 
-    private void removeExpiredClientInitialAccess(RealmModel realm) {
-        Iterator<String> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
-                .entrySet().stream().filter(ClientInitialAccessPredicate.create(realm.getId()).expired(Time.currentTime())).map(Mappers.sessionId()).iterator();
-        while (itr.hasNext()) {
-            tx.remove(sessionCache, itr.next());
-        }
-    }
-
     @Override
     public void removeUserSessions(RealmModel realm) {
         removeUserSessions(realm, false);
@@ -417,19 +403,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         return models;
     }
 
-    List<ClientInitialAccessModel> wrapClientInitialAccess(RealmModel realm, Collection<ClientInitialAccessEntity> entities) {
-        List<ClientInitialAccessModel> models = new LinkedList<>();
-        for (ClientInitialAccessEntity e : entities) {
-            models.add(wrap(realm, e));
-        }
-        return models;
-    }
-
-    ClientInitialAccessAdapter wrap(RealmModel realm, ClientInitialAccessEntity entity) {
-        Cache<String, SessionEntity> cache = getCache(false);
-        return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
-    }
-
     UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
         return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
     }
@@ -565,48 +538,4 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         return new AuthenticatedClientSessionAdapter(entity, clientSession.getClient(), importedUserSession, this, importedUserSession.getCache());
     }
 
-    @Override
-    public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
-        String id = KeycloakModelUtils.generateId();
-
-        ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
-        entity.setId(id);
-        entity.setRealm(realm.getId());
-        entity.setTimestamp(Time.currentTime());
-        entity.setExpiration(expiration);
-        entity.setCount(count);
-        entity.setRemainingCount(count);
-
-        tx.put(sessionCache, id, entity);
-
-        return wrap(realm, entity);
-    }
-
-    @Override
-    public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
-        Cache<String, SessionEntity> cache = getCache(false);
-        ClientInitialAccessEntity entity = (ClientInitialAccessEntity) tx.get(cache, id); // Chance created in this transaction
-
-        if (entity == null) {
-            entity = (ClientInitialAccessEntity) cache.get(id);
-        }
-
-        return wrap(realm, entity);
-    }
-
-    @Override
-    public void removeClientInitialAccessModel(RealmModel realm, String id) {
-        tx.remove(getCache(false), id);
-    }
-
-    @Override
-    public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
-        Iterator<Map.Entry<String, SessionEntity>> itr = sessionCache.entrySet().stream().filter(ClientInitialAccessPredicate.create(realm.getId())).iterator();
-        List<ClientInitialAccessModel> list = new LinkedList<>();
-        while (itr.hasNext()) {
-            list.add(wrap(realm, (ClientInitialAccessEntity) itr.next().getValue()));
-        }
-        return list;
-    }
-
 }
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceServerEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceServerEntity.java
index 9ee63b6..fabdd9c 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceServerEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceServerEntity.java
@@ -59,12 +59,6 @@ public class ResourceServerEntity {
     @Column(name = "POLICY_ENFORCE_MODE")
     private PolicyEnforcementMode policyEnforcementMode = PolicyEnforcementMode.ENFORCING;
 
-    @OneToMany(mappedBy = "resourceServer")
-    private List<ResourceEntity> resources;
-
-    @OneToMany (mappedBy = "resourceServer")
-    private List<ScopeEntity> scopes;
-
     public String getId() {
         return this.id;
     }
@@ -97,22 +91,6 @@ public class ResourceServerEntity {
         this.policyEnforcementMode = policyEnforcementMode;
     }
 
-    public List<ResourceEntity> getResources() {
-        return this.resources;
-    }
-
-    public void setResources(final List<ResourceEntity> resources) {
-        this.resources = resources;
-    }
-
-    public List<ScopeEntity> getScopes() {
-        return this.scopes;
-    }
-
-    public void setScopes(final List<ScopeEntity> scopes) {
-        this.scopes = scopes;
-    }
-
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceServerStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceServerStore.java
index 20404e5..8eb1037 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceServerStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceServerStore.java
@@ -19,9 +19,13 @@ package org.keycloak.authorization.jpa.store;
 
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.jpa.entities.PolicyEntity;
+import org.keycloak.authorization.jpa.entities.ResourceEntity;
 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.Resource;
 import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.store.ResourceServerStore;
 import org.keycloak.models.utils.KeycloakModelUtils;
 
@@ -65,24 +69,45 @@ public class JPAResourceServerStore implements ResourceServerStore {
         //entityManager.createNamedQuery("deletePolicyByResourceServer")
         //        .setParameter("serverId", id).executeUpdate();
 
-        TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByServerId", String.class);
-        query.setParameter("serverId", id);
-        List<String> result = query.getResultList();
-        List<Policy> list = new LinkedList<>();
-        for (String policyId : result) {
-            entityManager.remove(entityManager.getReference(PolicyEntity.class, policyId));
+        {
+            TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByServerId", String.class);
+            query.setParameter("serverId", id);
+            List<String> result = query.getResultList();
+            for (String policyId : result) {
+                entityManager.remove(entityManager.getReference(PolicyEntity.class, policyId));
+            }
         }
 
-        entityManager.flush();
-        entityManager.createNamedQuery("deleteResourceByResourceServer")
-                .setParameter("serverId", id).executeUpdate();
-        entityManager.flush();
-        entityManager.createNamedQuery("deleteScopeByResourceServer")
-                .setParameter("serverId", id).executeUpdate();
-        entityManager.flush();
+        //entityManager.createNamedQuery("deleteResourceByResourceServer")
+        //        .setParameter("serverId", id).executeUpdate();
+        {
+            TypedQuery<String> query = entityManager.createNamedQuery("findResourceIdByServerId", String.class);
+
+            query.setParameter("serverId", id);
+
+            List<String> result = query.getResultList();
+            List<Resource> list = new LinkedList<>();
+            for (String resourceId : result) {
+                entityManager.remove(entityManager.getReference(ResourceEntity.class, resourceId));
+            }
+        }
+
+        //entityManager.createNamedQuery("deleteScopeByResourceServer")
+        //        .setParameter("serverId", id).executeUpdate();
+        {
+            TypedQuery<String> query = entityManager.createNamedQuery("findScopeIdByResourceServer", String.class);
+
+            query.setParameter("serverId", id);
+
+            List<String> result = query.getResultList();
+            for (String scopeId : result) {
+                entityManager.remove(entityManager.getReference(ScopeEntity.class, scopeId));
+            }
+        }
 
         this.entityManager.remove(entity);
         entityManager.flush();
+        entityManager.detach(entity);
     }
 
     @Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientInitialAccessEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientInitialAccessEntity.java
new file mode 100644
index 0000000..bdb510e
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientInitialAccessEntity.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 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.models.jpa.entities;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Entity
+@Table(name="CLIENT_INITIAL_ACCESS")
+@NamedQueries({
+        @NamedQuery(name="findClientInitialAccessByRealm", query="select ia from ClientInitialAccessEntity ia where ia.realm = :realm order by timestamp"),
+        @NamedQuery(name="removeClientInitialAccessByRealm", query="delete from ClientInitialAccessEntity ia where ia.realm = :realm"),
+        @NamedQuery(name="removeExpiredClientInitialAccess", query="delete from ClientInitialAccessEntity ia where (ia.expiration > 0 and (ia.timestamp + ia.expiration) < :currentTime) or ia.remainingCount = 0"),
+        @NamedQuery(name="decreaseClientInitialAccessRemainingCount", query="update ClientInitialAccessEntity ia set ia.remainingCount = ia.remainingCount - 1 where ia.id = :id")
+})
+public class ClientInitialAccessEntity {
+
+    @Id
+    @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
+    protected String id;
+
+    @Column(name="TIMESTAMP")
+    private int timestamp;
+
+    @Column(name="EXPIRATION")
+    private int expiration;
+
+    @Column(name="COUNT")
+    private int count;
+
+    @Column(name="REMAINING_COUNT")
+    private int remainingCount;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "REALM_ID")
+    protected RealmEntity realm;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public int getExpiration() {
+        return expiration;
+    }
+
+    public void setExpiration(int expiration) {
+        this.expiration = expiration;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public void setCount(int count) {
+        this.count = count;
+    }
+
+    public int getRemainingCount() {
+        return remainingCount;
+    }
+
+    public void setRemainingCount(int remainingCount) {
+        this.remainingCount = remainingCount;
+    }
+
+    public RealmEntity getRealm() {
+        return realm;
+    }
+
+    public void setRealm(RealmEntity realm) {
+        this.realm = realm;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof ClientInitialAccessEntity)) return false;
+
+        ClientInitialAccessEntity that = (ClientInitialAccessEntity) o;
+
+        if (!id.equals(that.id)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 718d19a..219b9a5 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -18,8 +18,10 @@
 package org.keycloak.models.jpa;
 
 import org.jboss.logging.Logger;
+import org.keycloak.common.util.Time;
 import org.keycloak.connections.jpa.util.JpaUtils;
 import org.keycloak.migration.MigrationModel;
+import org.keycloak.models.ClientInitialAccessModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientTemplateModel;
 import org.keycloak.models.GroupModel;
@@ -29,6 +31,7 @@ import org.keycloak.models.RealmProvider;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.jpa.entities.ClientEntity;
+import org.keycloak.models.jpa.entities.ClientInitialAccessEntity;
 import org.keycloak.models.jpa.entities.ClientTemplateEntity;
 import org.keycloak.models.jpa.entities.GroupEntity;
 import org.keycloak.models.jpa.entities.RealmEntity;
@@ -152,6 +155,8 @@ public class JpaRealmProvider implements RealmProvider {
             removeRole(adapter, role);
         }
 
+        num = em.createNamedQuery("removeClientInitialAccessByRealm")
+                .setParameter("realm", realm).executeUpdate();
 
         em.remove(realm);
 
@@ -355,6 +360,24 @@ public class JpaRealmProvider implements RealmProvider {
             return false;
         }
 
+        GroupModel.GroupRemovedEvent event = new GroupModel.GroupRemovedEvent() {
+            @Override
+            public RealmModel getRealm() {
+                return realm;
+            }
+
+            @Override
+            public GroupModel getGroup() {
+                return group;
+            }
+
+            @Override
+            public KeycloakSession getKeycloakSession() {
+                return session;
+            }
+        };
+        session.getKeycloakSessionFactory().publish(event);
+
         session.users().preRemove(realm, group);
 
         realm.removeDefaultGroup(group);
@@ -519,4 +542,82 @@ public class JpaRealmProvider implements RealmProvider {
         ClientTemplateAdapter adapter = new ClientTemplateAdapter(realm, em, session, app);
         return adapter;
     }
+
+    @Override
+    public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
+        RealmEntity realmEntity = em.find(RealmEntity.class, realm.getId());
+
+        ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
+        entity.setId(KeycloakModelUtils.generateId());
+        entity.setRealm(realmEntity);
+
+        entity.setCount(count);
+        entity.setRemainingCount(count);
+
+        int currentTime = Time.currentTime();
+        entity.setTimestamp(currentTime);
+        entity.setExpiration(expiration);
+
+        em.persist(entity);
+
+        return entityToModel(entity);
+    }
+
+    @Override
+    public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
+        ClientInitialAccessEntity entity = em.find(ClientInitialAccessEntity.class, id);
+        if (entity == null) {
+            return null;
+        } else {
+            return entityToModel(entity);
+        }
+    }
+
+    @Override
+    public void removeClientInitialAccessModel(RealmModel realm, String id) {
+        ClientInitialAccessEntity entity = em.find(ClientInitialAccessEntity.class, id);
+        if (entity != null) {
+            em.remove(entity);
+            em.flush();
+        }
+    }
+
+    @Override
+    public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
+        RealmEntity realmEntity = em.find(RealmEntity.class, realm.getId());
+
+        TypedQuery<ClientInitialAccessEntity> query = em.createNamedQuery("findClientInitialAccessByRealm", ClientInitialAccessEntity.class);
+        query.setParameter("realm", realmEntity);
+        List<ClientInitialAccessEntity> entities = query.getResultList();
+
+        return entities.stream()
+                .map(entity -> entityToModel(entity))
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public void removeExpiredClientInitialAccess() {
+        int currentTime = Time.currentTime();
+
+        em.createNamedQuery("removeExpiredClientInitialAccess")
+                .setParameter("currentTime", currentTime)
+                .executeUpdate();
+    }
+
+    @Override
+    public void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess) {
+        em.createNamedQuery("decreaseClientInitialAccessRemainingCount")
+                .setParameter("id", clientInitialAccess.getId())
+                .executeUpdate();
+    }
+
+    private ClientInitialAccessModel entityToModel(ClientInitialAccessEntity entity) {
+        ClientInitialAccessModel model = new ClientInitialAccessModel();
+        model.setId(entity.getId());
+        model.setCount(entity.getCount());
+        model.setRemainingCount(entity.getRemainingCount());
+        model.setExpiration(entity.getExpiration());
+        model.setTimestamp(entity.getTimestamp());
+        return model;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 33ca943..eba62db 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1885,6 +1885,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
         ComponentEntity c = em.find(ComponentEntity.class, component.getId());
         if (c == null) return;
         session.users().preRemove(this, component);
+        ComponentUtil.notifyPreRemove(session, this, component);
         removeComponents(component.getId());
         getEntity().getComponents().remove(c);
     }
@@ -1896,7 +1897,10 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
         getEntity().getComponents().stream()
                 .filter(sameParent)
                 .map(this::entityToModel)
-                .forEach(c -> session.users().preRemove(this, c));
+                .forEach((ComponentModel c) -> {
+                    session.users().preRemove(this, c);
+                    ComponentUtil.notifyPreRemove(session, this, c);
+                });
 
         getEntity().getComponents().removeIf(sameParent);
     }
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
index b3872b8..bd55645 100644
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
@@ -22,6 +22,22 @@
         <dropPrimaryKey constraintName="CONSTRAINT_OFFL_CL_SES_PK2" tableName="OFFLINE_CLIENT_SESSION" />
         <dropColumn tableName="OFFLINE_CLIENT_SESSION" columnName="CLIENT_SESSION_ID" />
         <addPrimaryKey columnNames="USER_SESSION_ID,CLIENT_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK3" tableName="OFFLINE_CLIENT_SESSION"/>
+
+        <createTable tableName="CLIENT_INITIAL_ACCESS">
+            <column name="ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="REALM_ID" type="VARCHAR(36)"/>
+
+            <column name="TIMESTAMP" type="INT"/>
+            <column name="EXPIRATION" type="INT"/>
+            <column name="COUNT" type="INT"/>
+            <column name="REMAINING_COUNT" type="INT"/>
+        </createTable>
+
+        <addPrimaryKey columnNames="ID" constraintName="CNSTR_CLIENT_INIT_ACC_PK" tableName="CLIENT_INITIAL_ACCESS"/>
+        <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="CLIENT_INITIAL_ACCESS" constraintName="FK_CLIENT_INIT_ACC_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
+
     </changeSet>
 
     <changeSet author="glavoie@gmail.com" id="3.2.0.idx">
@@ -158,5 +174,9 @@
         <createIndex indexName="IDX_WEB_ORIG_CLIENT" tableName="WEB_ORIGINS">
             <column name="CLIENT_ID" type="VARCHAR(36)"/>
         </createIndex>
+
+        <createIndex indexName="IDX_CLIENT_INIT_ACC_REALM" tableName="CLIENT_INITIAL_ACCESS">
+            <column name="REALM_ID" type="VARCHAR(36)"/>
+        </createIndex>
      </changeSet>
 </databaseChangeLog>
diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml
index 5d3fa81..f23198c 100755
--- a/model/jpa/src/main/resources/META-INF/persistence.xml
+++ b/model/jpa/src/main/resources/META-INF/persistence.xml
@@ -56,6 +56,7 @@
         <class>org.keycloak.models.jpa.entities.UserGroupMembershipEntity</class>
         <class>org.keycloak.models.jpa.entities.ClientTemplateEntity</class>
         <class>org.keycloak.models.jpa.entities.TemplateScopeMappingEntity</class>
+        <class>org.keycloak.models.jpa.entities.ClientInitialAccessEntity</class>
 
         <!-- JpaAuditProviders -->
         <class>org.keycloak.events.jpa.EventEntity</class>
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
index b5f2232..f516124 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
@@ -52,6 +52,7 @@ import java.io.InputStream;
 import java.io.Reader;
 import java.io.StringReader;
 import java.io.StringWriter;
+import java.util.Objects;
 
 /**
  * Utility dealing with DOM
@@ -554,4 +555,33 @@ public class DocumentUtil {
 
         return documentBuilderFactory;
     }
+
+    /**
+     * Get a (direct) child {@linkplain Element} from the parent {@linkplain Element}. 
+     *
+     * @param parent parent element
+     * @param targetNamespace namespace URI
+     * @param targetLocalName local name
+     * @return a child element matching the target namespace and localname, where {@linkplain Element#getParentNode()} is the parent input parameter
+     * @return
+     */
+    
+    public static Element getDirectChildElement(Element parent, String targetNamespace, String targetLocalName) {
+        Node child = parent.getFirstChild();
+        
+        while(child != null) {
+            if(child instanceof Element) {
+                Element childElement = (Element)child;
+                
+                String ns = childElement.getNamespaceURI();
+                String localName = childElement.getLocalName();
+                
+                if(Objects.equals(targetNamespace, ns) && Objects.equals(targetLocalName, localName)) {
+                    return childElement;
+                }
+            }
+            child = child.getNextSibling();
+        }
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
index ef3e3bd..09ce9d6 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
@@ -49,8 +49,6 @@ public class SAML2Signature {
 
     private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
 
-    private static final String ID_ATTRIBUTE_NAME = "ID";
-
     private String signatureMethod = SignatureMethod.RSA_SHA1;
 
     private String digestMethod = DigestMethod.SHA1;
@@ -156,7 +154,7 @@ public class SAML2Signature {
      */
     public void signSAMLDocument(Document samlDocument, String keyName, KeyPair keypair, String canonicalizationMethodType) throws ProcessingException {
         // Get the ID from the root
-        String id = samlDocument.getDocumentElement().getAttribute(ID_ATTRIBUTE_NAME);
+        String id = samlDocument.getDocumentElement().getAttribute(JBossSAMLConstants.ID.get());
         try {
             sign(samlDocument, id, keyName, keypair, canonicalizationMethodType);
         } catch (ParserConfigurationException | GeneralSecurityException | MarshalException | XMLSignatureException e) {
@@ -210,18 +208,20 @@ public class SAML2Signature {
      *
      * @param document SAML document to have its ID attribute configured.
      */
-    private void configureIdAttribute(Document document) {
+    public static void configureIdAttribute(Document document) {
         // Estabilish the IDness of the ID attribute.
-        document.getDocumentElement().setIdAttribute(ID_ATTRIBUTE_NAME, true);
+        configureIdAttribute(document.getDocumentElement());
 
         NodeList nodes = document.getElementsByTagNameNS(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
                 JBossSAMLConstants.ASSERTION.get());
 
         for (int i = 0; i < nodes.getLength(); i++) {
-            Node n = nodes.item(i);
-            if (n instanceof Element) {
-                ((Element) n).setIdAttribute(ID_ATTRIBUTE_NAME, true);
-            }
+            configureIdAttribute((Element) nodes.item(i));
         }
     }
+    
+    public static void configureIdAttribute(Element element) {
+        element.setIdAttribute(JBossSAMLConstants.ID.get(), true);
+    }
+
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
index bb15d23..244fb7d 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
@@ -49,11 +49,12 @@ import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
 import org.keycloak.saml.processing.core.saml.v2.writers.SAMLAssertionWriter;
 import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
 import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
-
+import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
+import javax.xml.crypto.dsig.XMLSignature;
 import javax.xml.datatype.XMLGregorianCalendar;
 import javax.xml.namespace.QName;
 import java.io.ByteArrayInputStream;
@@ -267,42 +268,56 @@ public class AssertionUtil {
     }
 
     /**
-     * Given an assertion element, validate the signature
+     * Given an {@linkplain Element}, validate the Signature direct child element
      *
-     * @param assertionElement
+     * @param element parent {@linkplain Element}
      * @param publicKey the {@link PublicKey}
      *
-     * @return
+     * @return true if signature is present and valid
      */
-    public static boolean isSignatureValid(Element assertionElement, PublicKey publicKey) {
-        try {
-            Document doc = DocumentUtil.createDocument();
-            Node n = doc.importNode(assertionElement, true);
-            doc.appendChild(n);
-
-            return new SAML2Signature().validate(doc, new HardcodedKeyLocator(publicKey));
-        } catch (Exception e) {
-            logger.signatureAssertionValidationError(e);
-        }
-        return false;
+    public static boolean isSignatureValid(Element element, PublicKey publicKey) {
+        return isSignatureValid(element, new HardcodedKeyLocator(publicKey));
     }
 
     /**
-     * Given an assertion element, validate the signature.
+     * Given an {@linkplain Element}, validate the Signature direct child element
+     *
+     * @param element parent {@linkplain Element}
+     * @param keyLocator the {@link KeyLocator}
+     *
+     * @return true if signature is present and valid
      */
-    public static boolean isSignatureValid(Element assertionElement, KeyLocator keyLocator) {
+    
+    public static boolean isSignatureValid(Element element, KeyLocator keyLocator) {
         try {
-            Document doc = DocumentUtil.createDocument();
-            Node n = doc.importNode(assertionElement, true);
-            doc.appendChild(n);
-
-            return new SAML2Signature().validate(doc, keyLocator);
+            SAML2Signature.configureIdAttribute(element);
+            
+            Element signature = getSignature(element);
+            if(signature != null) {
+                return XMLSignatureUtil.validateSingleNode(signature, keyLocator);
+            }
         } catch (Exception e) {
             logger.signatureAssertionValidationError(e);
         }
         return false;
     }
+    
+    /**
+     * 
+     * Given an {@linkplain Element}, check if there is a Signature direct child element
+     * 
+     * @param element parent {@linkplain Element}
+     * @return true if signature is present
+     */
 
+    public static boolean isSignedElement(Element element) {
+        return getSignature(element) != null;
+    }
+    
+    protected static Element getSignature(Element element) {
+        return DocumentUtil.getDirectChildElement(element, XMLSignature.XMLNS, "Signature");
+    }
+    
     /**
      * Check whether the assertion has expired
      *
@@ -570,8 +585,8 @@ public class AssertionUtil {
 
     /**
      * This method modifies the given responseType, and replaces the encrypted assertion with a decrypted version.
-     *
-     * It returns the assertion element as it was decrypted. This can be used in sginature verification.
+     * @param responseType a response containg an encrypted assertion
+     * @return the assertion element as it was decrypted. This can be used in signature verification.
      */
     public static Element decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
         SAML2Response saml2Response = new SAML2Response();
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
index 53228c9..7093a20 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
@@ -468,7 +468,7 @@ public class XMLSignatureUtil {
         return true;
     }
 
-    private static boolean validateSingleNode(Node signatureNode, final KeyLocator locator) throws MarshalException, XMLSignatureException {
+    public static boolean validateSingleNode(Node signatureNode, final KeyLocator locator) throws MarshalException, XMLSignatureException {
         KeySelectorUtilizingKeyNameHint sel = new KeySelectorUtilizingKeyNameHint(locator);
         try {
             if (validateUsingKeySelector(signatureNode, sel)) {
diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java
new file mode 100644
index 0000000..b60bb4b
--- /dev/null
+++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java
@@ -0,0 +1,58 @@
+package org.keycloak.saml.processing.core.saml.v2.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.util.Arrays;
+import org.junit.Test;
+import org.keycloak.common.util.Base64;
+import org.keycloak.common.util.DerUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class AssertionUtilTest {
+
+    private static final String PRIVATE_KEY = "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==";
+
+    /**
+     * The public certificate that corresponds to {@link #PRIVATE_KEY}.
+     */
+    private static final String PUBLIC_CERT = "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin";
+
+    @Test
+    public void testSaml20Signed() throws Exception {
+        
+        X509Certificate decodeCertificate = DerUtils.decodeCertificate(new ByteArrayInputStream(Base64.decode(PUBLIC_CERT)));
+        
+        try (InputStream st = AssertionUtilTest.class.getResourceAsStream("saml20-signed-response.xml")) {
+            Document document = DocumentUtil.getDocument(st);
+            
+            Element assertion = DocumentUtil.getDirectChildElement(document.getDocumentElement(), "urn:oasis:names:tc:SAML:2.0:assertion", "Assertion");
+            
+            assertTrue(AssertionUtil.isSignatureValid(assertion, decodeCertificate.getPublicKey()));
+            
+            // test manipulation of signature
+            Element signatureElement = AssertionUtil.getSignature(assertion);
+            byte[] validSignature = Base64.decode(signatureElement.getTextContent());
+            
+            // change the signature value slightly
+            byte[] invalidSignature = Arrays.clone(validSignature);
+            invalidSignature[0] ^= invalidSignature[0];
+            signatureElement.setTextContent(Base64.encodeBytes(invalidSignature));
+            
+            // check that signature now is invalid
+            assertFalse(AssertionUtil.isSignatureValid(document.getDocumentElement(), decodeCertificate.getPublicKey()));
+            
+            // restore valid signature, but remove Signature element, check that still invalid
+            signatureElement.setTextContent(Base64.encodeBytes(validSignature));
+
+            assertion.removeChild(signatureElement);
+            assertFalse(AssertionUtil.isSignatureValid(document.getDocumentElement(), decodeCertificate.getPublicKey()));
+        }
+    }
+
+}
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/saml/v2/util/saml20-signed-response.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/saml/v2/util/saml20-signed-response.xml
new file mode 100644
index 0000000..998520a
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/saml/v2/util/saml20-signed-response.xml
@@ -0,0 +1 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint" ID="ID_9aca1381-6265-434c-98a5-d89236d32ea0" InResponseTo="ID_2d81575a-0bde-488a-b69d-0b78c0fcf521" IssueInstant="2017-05-04T22:18:13.550Z" Version="2.0"><saml:Issuer>http://localhost:8080/auth/realms/saml-broker-realm</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status><saml:Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="ID_e9d22a60-7954-48b2-ad97-700d70e332f5" IssueInstant="2017-05-04T22:18:13.546Z" Version="2.0"><saml:Issuer>http://localhost:8080/auth/realms/saml-broker-realm</saml:Issuer><dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:SignedInfo><dsig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><dsig:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><dsig:Reference URI="#ID_e9d22a60-7954-48b2-ad97-700d70e332f5"><dsig:Transforms><dsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><dsig:DigestValue>cCSNXxLmu411weW1kRpie4C9yaBg2In6V4oEuqya0Eo=</dsig:DigestValue></dsig:Reference></dsig:SignedInfo><dsig:SignatureValue>Qe6ZqgSwFH31UTu+zHqr1/UsafH0luxP5OH/cqyHm07Kf/Fp/fm9mnHJ0kGoUn0SUo7xWvwy8AzUfPXWMYS3kDyhUsPzgz0CnCzzfTz3koKFczgyIQ8sokIDv0cTp3z1qCUVWV0CEPzhtWlaIus2W89TEi/h9KjYrkeGl3+cpm8BPEAt4EP8Oht5czK2haIfPMDUm5Y7uw/FCSsvSfFyrlJ0jR/YMeP9PP0InYYegI9QQgvXKRm6DZSNZgKYFpprc12v6vv/zTaMm5fbuuy1wNDuDTB8EF6K1yrq21DatJXUKE1oOMBrkOvbFJNtgHlQviz1OssAqzHlf0NQPIAEig==</dsig:SignatureValue><dsig:KeyInfo><dsig:KeyName>IzH2UxfMxovYTEHn4Bh-EAj-Zrvldukl_5Snu0RA0B8</dsig:KeyName><dsig:X509Data><dsig:X509Certificate>MIICsTCCAZkCBgFb1GERYzANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFzYW1sLWJyb2tlci1yZWFsbTAeFw0xNzA1MDQxNjUxMjJaFw0yNzA1MDQxNjUzMDJaMBwxGjAYBgNVBAMMEXNhbWwtYnJva2VyLXJlYWxtMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF00U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlEGq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmRbX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHOyyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCBuWKmcVPX4sZtpvBp8BgQwH51l5tKPuQq66/4JH40hzkrLOGcVEOafIsGoOWi8HsZWZu+APwhSrnRNd7yMV+NJyw5W1DKjhdUPWnzPNy+UMAcoUBFhXDIWog0qxgGvTdoe1lfHryUQt1cd95SFVIerJA93nFbSOoMB+N7TmfQm+sNu2pJ2tr6mx3wGCXMnWf29gwhCI3wV19hh4KugnMIEStjvQoRyh2yna64BrR3eaUyhU/Bdrq2VXLNU/9WXg9gbRLUEWkMUPKOeQ5cGCgc4JFyFXRo5ExkzmvP9vwBRtjQulk5QKqfYo251mKvTQgO7K8d4CzVS/4+bpgKvZAM</dsig:X509Certificate></dsig:X509Data><dsig:KeyValue><dsig:RSAKeyValue><dsig:Modulus>gj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF00U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlEGq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmRbX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHOyyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQ==</dsig:Modulus><dsig:Exponent>AQAB</dsig:Exponent></dsig:RSAKeyValue></dsig:KeyValue></dsig:KeyInfo></dsig:Signature><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">G-ebb3a66f-686f-4bb9-8a8b-20b566ca747b</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData InResponseTo="ID_2d81575a-0bde-488a-b69d-0b78c0fcf521" NotOnOrAfter="2017-05-04T22:23:11.546Z" Recipient="http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2017-05-04T22:18:11.546Z" NotOnOrAfter="2017-05-04T22:19:11.546Z"><saml:AudienceRestriction><saml:Audience>http://localhost:8080/auth/realms/saml-broker-authentication-realm</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2017-05-04T22:18:13.551Z" SessionIndex="c0cc880f-83d7-461b-b299-9fb3354f598c"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name="Role" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">manager</saml:AttributeValue></saml:Attribute><saml:Attribute Name="Role" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
index 695637f..b4c6f44 100644
--- a/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
+++ b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
@@ -80,6 +80,18 @@ public interface ComponentFactory<CreatedType, ProviderType extends Provider> ex
     }
 
     /**
+     * Called before the component is removed.
+     *
+     * @param session
+     * @param realm
+     * @param model model of the component, which is going to be removed
+     */
+    default
+    void preRemove(KeycloakSession session, RealmModel realm, ComponentModel model) {
+
+    }
+
+    /**
      * These are config properties that are common across all implementation of this component type
      *
      * @return
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
index d3600ba..f466183 100755
--- a/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
@@ -56,7 +56,22 @@ public class CredentialModel implements Serializable {
     private int period;
     private MultivaluedHashMap<String, String> config;
 
-
+    public CredentialModel shallowClone() {
+        CredentialModel res = new CredentialModel();
+        res.id = id;
+        res.type = type;
+        res.value = value;
+        res.device = device;
+        res.salt = salt;
+        res.hashIterations = hashIterations;
+        res.createdDate = createdDate;
+        res.counter = counter;
+        res.algorithm = algorithm;
+        res.digits = digits;
+        res.period = period;
+        res.config = config;
+        return res;
+    }
 
     public String getId() {
         return id;
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
index 8434d9d..1d3f59f 100644
--- a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
@@ -18,7 +18,7 @@ package org.keycloak.models.cache;
 
 import org.keycloak.models.UserModel;
 
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 /**
  * Cached users will implement this interface
@@ -55,5 +55,5 @@ public interface CachedUserModel extends UserModel {
      *
      * @return
      */
-    ConcurrentHashMap getCachedWith();
+    ConcurrentMap getCachedWith();
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java b/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
old mode 100755
new mode 100644
index 149b6aa..24b5546
--- a/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
@@ -20,20 +20,55 @@ package org.keycloak.models;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public interface ClientInitialAccessModel {
+public class ClientInitialAccessModel {
 
-    String getId();
+    private String id;
 
-    RealmModel getRealm();
+    private int timestamp;
 
-    int getTimestamp();
+    private int expiration;
 
-    int getExpiration();
+    private int count;
 
-    int getCount();
+    private int remainingCount;
 
-    int getRemainingCount();
+    public String getId() {
+        return id;
+    }
 
-    void decreaseRemainingCount();
+    public void setId(String id) {
+        this.id = id;
+    }
 
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public int getExpiration() {
+        return expiration;
+    }
+
+    public void setExpiration(int expiration) {
+        this.expiration = expiration;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public void setCount(int count) {
+        this.count = count;
+    }
+
+    public int getRemainingCount() {
+        return remainingCount;
+    }
+
+    public void setRemainingCount(int remainingCount) {
+        this.remainingCount = remainingCount;
+    }
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/GroupModel.java b/server-spi/src/main/java/org/keycloak/models/GroupModel.java
index 5a0e006..aac0ddb 100755
--- a/server-spi/src/main/java/org/keycloak/models/GroupModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/GroupModel.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models;
 
+import org.keycloak.provider.ProviderEvent;
+
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -26,6 +28,11 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public interface GroupModel extends RoleMapperModel {
+    interface GroupRemovedEvent extends ProviderEvent {
+        RealmModel getRealm();
+        GroupModel getGroup();
+        KeycloakSession getKeycloakSession();
+    }
     String getId();
 
     String getName();
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
index 4e6070f..f3a26f1 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
@@ -90,4 +90,11 @@ public interface RealmProvider extends Provider {
     List<RealmModel> getRealms();
     boolean removeRealm(String id);
     void close();
+
+    ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
+    ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);
+    void removeClientInitialAccessModel(RealmModel realm, String id);
+    List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
+    void removeExpiredClientInitialAccess();
+    void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess); // Separate provider method to ensure we decrease remainingCount atomically instead of doing classic update
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
index d474e89..848c098 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -72,11 +72,6 @@ public interface UserSessionProvider extends Provider {
     /** Triggered by persister during pre-load. It optionally imports authenticatedClientSessions too if requested. Otherwise the imported UserSession will have empty list of AuthenticationSessionModel **/
     UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline, boolean importAuthenticatedClientSessions);
 
-    ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
-    ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);
-    void removeClientInitialAccessModel(RealmModel realm, String id);
-    List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
-
     void close();
 
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java b/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java
index c83d9f8..0719bab 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java
@@ -107,6 +107,10 @@ public interface Attributes {
             return values.length;
         }
 
+        public boolean isEmpty() {
+            return values.length == 0;
+        }
+
         public String asString(int idx) {
             if (idx >= values.length) {
                 throw new IllegalArgumentException("Invalid index [" + idx + "]. Values are [" + values + "].");
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 8a55ff1..644b90a 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,14 +21,17 @@ package org.keycloak.authorization;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 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.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.AuthorizationStoreFactory;
 import org.keycloak.authorization.store.PolicyStore;
 import org.keycloak.authorization.store.ResourceServerStore;
 import org.keycloak.authorization.store.ResourceStore;
@@ -37,7 +40,6 @@ import org.keycloak.authorization.store.StoreFactory;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
-import org.keycloak.models.cache.authorization.CachedStoreProviderFactory;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.provider.Provider;
 import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
@@ -143,6 +145,61 @@ public final class AuthorizationProvider implements Provider {
                 return new PolicyStore() {
                     @Override
                     public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) {
+                        Set<String> resources = representation.getResources();
+
+                        if (resources != null) {
+                            representation.setResources(resources.stream().map(id -> {
+                                Resource resource = getResourceStore().findById(id, resourceServer.getId());
+
+                                if (resource == null) {
+                                    resource = getResourceStore().findByName(id, resourceServer.getId());
+                                }
+
+                                if (resource == null) {
+                                    throw new RuntimeException("Resource [" + id + "] does not exist");
+                                }
+
+                                return resource.getId();
+                            }).collect(Collectors.toSet()));
+                        }
+
+                        Set<String> scopes = representation.getScopes();
+
+                        if (scopes != null) {
+                            representation.setScopes(scopes.stream().map(id -> {
+                                Scope scope = getScopeStore().findById(id, resourceServer.getId());
+
+                                if (scope == null) {
+                                    scope = getScopeStore().findByName(id, resourceServer.getId());
+                                }
+
+                                if (scope == null) {
+                                    throw new RuntimeException("Scope [" + id + "] does not exist");
+                                }
+
+                                return scope.getId();
+                            }).collect(Collectors.toSet()));
+                        }
+
+
+                        Set<String> policies = representation.getPolicies();
+
+                        if (policies != null) {
+                            representation.setPolicies(policies.stream().map(id -> {
+                                Policy policy = getPolicyStore().findById(id, resourceServer.getId());
+
+                                if (policy == null) {
+                                    policy = getPolicyStore().findByName(id, resourceServer.getId());
+                                }
+
+                                if (policy == null) {
+                                    throw new RuntimeException("Policy [" + id + "] does not exist");
+                                }
+
+                                return policy.getId();
+                            }).collect(Collectors.toSet()));
+                        }
+
                         return RepresentationToModel.toModel(representation, AuthorizationProvider.this, policyStore.create(representation, resourceServer));
                     }
 
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResult.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResult.java
new file mode 100644
index 0000000..2189c32
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResult.java
@@ -0,0 +1,51 @@
+/*
+ * 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.evaluation;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class DecisionResult extends DecisionResultCollector {
+    protected List<Result> results;
+    protected Throwable error;
+
+    @Override
+    protected void onComplete(List<Result> results) {
+        this.results = results;
+
+    }
+
+    @Override
+    public void onError(Throwable cause) {
+        this.error = cause;
+    }
+
+    public boolean completed() {
+        return results != null && error == null;
+    }
+
+    public List<Result> getResults() {
+        return results;
+    }
+
+    public Throwable getError() {
+        return error;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java
index ed24c53..971ae31 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java
@@ -40,9 +40,9 @@ public class RealmSynchronizer implements Synchronizer<RealmRemovedEvent> {
 
             if (resourceServer != null) {
                 String id = resourceServer.getId();
-                storeFactory.getResourceStore().findByResourceServer(id).forEach(resource -> storeFactory.getResourceStore().delete(resource.getId()));
-                storeFactory.getScopeStore().findByResourceServer(id).forEach(scope -> storeFactory.getScopeStore().delete(scope.getId()));
-                storeFactory.getPolicyStore().findByResourceServer(id).forEach(scope -> storeFactory.getPolicyStore().delete(scope.getId()));
+                //storeFactory.getResourceStore().findByResourceServer(id).forEach(resource -> storeFactory.getResourceStore().delete(resource.getId()));
+                //storeFactory.getScopeStore().findByResourceServer(id).forEach(scope -> storeFactory.getScopeStore().delete(scope.getId()));
+                //storeFactory.getPolicyStore().findByResourceServer(id).forEach(scope -> storeFactory.getPolicyStore().delete(scope.getId()));
                 storeFactory.getResourceServerStore().delete(id);
             }
         });
diff --git a/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java b/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java
index cba5ed6..128b344 100644
--- a/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java
+++ b/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java
@@ -156,5 +156,25 @@ public enum ResourceType {
     /**
      *
      */
-    , COMPONENT;
+    , COMPONENT
+
+    /**
+     *
+     */
+    , AUTHORIZATION_RESOURCE_SERVER
+
+    /**
+     *
+     */
+    , AUTHORIZATION_RESOURCE
+
+    /**
+     *
+     */
+    , AUTHORIZATION_SCOPE
+
+    /**
+     *
+     */
+    , AUTHORIZATION_POLICY;
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
index ac435e3..cb203c1 100755
--- a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
@@ -120,4 +120,6 @@ public interface LoginFormsProvider extends Provider {
     public LoginFormsProvider setStatus(Response.Status status);
 
     LoginFormsProvider setActionUri(URI requestUri);
+
+    LoginFormsProvider setExecution(String execution);
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java
index 85f2296..17cd0ac 100644
--- a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java
+++ b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java
@@ -14,18 +14,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.keycloak.migration.migrators;
 
-
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
 import org.keycloak.migration.ModelVersion;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
 public class MigrateTo3_2_0 implements Migration {
 
-    public static final ModelVersion VERSION = new ModelVersion("3.1.0");
+    public static final ModelVersion VERSION = new ModelVersion("3.2.0");
 
     @Override
     public void migrate(KeycloakSession session) {
@@ -34,6 +43,33 @@ public class MigrateTo3_2_0 implements Migration {
             if (!builder.contains(PasswordPolicy.HASH_ALGORITHM_ID) && "20000".equals(builder.get(PasswordPolicy.HASH_ITERATIONS_ID))) {
                 realm.setPasswordPolicy(builder.remove(PasswordPolicy.HASH_ITERATIONS_ID).build(session));
             }
+
+            ClientModel realmAccess = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID);
+            if (realmAccess != null) {
+                addRoles(realmAccess);
+            }
+            ClientModel masterAdminClient = realm.getMasterAdminClient();
+            if (masterAdminClient != null) {
+                addRoles(masterAdminClient);
+
+            }
+
+        }
+    }
+
+    public void addRoles(ClientModel realmAccess) {
+        RoleModel queryClients = realmAccess.addRole(AdminRoles.QUERY_CLIENTS);
+        RoleModel queryUsers = realmAccess.addRole(AdminRoles.QUERY_USERS);
+        RoleModel queryGroups = realmAccess.addRole(AdminRoles.QUERY_GROUPS);
+
+        RoleModel viewClients = realmAccess.getRole(AdminRoles.VIEW_CLIENTS);
+        if (viewClients != null) {
+            viewClients.addCompositeRole(queryClients);
+        }
+        RoleModel viewUsers = realmAccess.getRole(AdminRoles.VIEW_USERS);
+        if (viewUsers != null) {
+            viewUsers.addCompositeRole(queryUsers);
+            viewUsers.addCompositeRole(queryGroups);
         }
     }
 
@@ -41,5 +77,4 @@ public class MigrateTo3_2_0 implements Migration {
     public ModelVersion getVersion() {
         return VERSION;
     }
-
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java b/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java
index 24455b8..c2304e8 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java
@@ -17,6 +17,9 @@
 
 package org.keycloak.models;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -46,6 +49,21 @@ public class AdminRoles {
     public static String MANAGE_EVENTS = "manage-events";
     public static String MANAGE_AUTHORIZATION = "manage-authorization";
 
-    public static String[] ALL_REALM_ROLES = {CREATE_CLIENT, VIEW_REALM, VIEW_USERS, VIEW_CLIENTS, VIEW_EVENTS, VIEW_IDENTITY_PROVIDERS, VIEW_AUTHORIZATION, MANAGE_REALM, MANAGE_USERS, MANAGE_CLIENTS, MANAGE_EVENTS, MANAGE_IDENTITY_PROVIDERS, MANAGE_AUTHORIZATION};
+    public static String QUERY_USERS = "query-users";
+    public static String QUERY_CLIENTS = "query-clients";
+    public static String QUERY_REALMS = "query-realms";
+    public static String QUERY_GROUPS = "query-groups";
+
+    public static String[] ALL_REALM_ROLES = {CREATE_CLIENT, VIEW_REALM, VIEW_USERS, VIEW_CLIENTS, VIEW_EVENTS, VIEW_IDENTITY_PROVIDERS, VIEW_AUTHORIZATION, MANAGE_REALM, MANAGE_USERS, MANAGE_CLIENTS, MANAGE_EVENTS, MANAGE_IDENTITY_PROVIDERS, MANAGE_AUTHORIZATION, QUERY_USERS, QUERY_CLIENTS, QUERY_REALMS, QUERY_GROUPS};
 
+    public static Set<String> ALL_ROLES = new HashSet<>();
+    static {
+        for (String name : ALL_REALM_ROLES) {
+            ALL_ROLES.add(name);
+        }
+        ALL_ROLES.add(ImpersonationConstants.IMPERSONATION_ROLE);
+        ALL_ROLES.add(ADMIN);
+        ALL_ROLES.add(CREATE_REALM);
+        ALL_ROLES.add(CREATE_CLIENT);
+    }
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
index 1d41f0d..0c603c4 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.models.utils;
 
+import org.jboss.logging.Logger;
 import org.keycloak.component.ComponentFactory;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.models.KeycloakSession;
@@ -38,6 +39,8 @@ import java.util.Map;
  */
 public class ComponentUtil {
 
+    private static final Logger logger = Logger.getLogger(ComponentUtil.class);
+
     public static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, ComponentRepresentation component) {
         return getComponentConfigProperties(session, component.getProviderType(), component.getProviderId());
     }
@@ -102,5 +105,14 @@ public class ComponentUtil {
             ((OnUpdateComponent)session.userStorageManager()).onUpdate(session, realm, oldModel, newModel);
         }
     }
+    public static void notifyPreRemove(KeycloakSession session, RealmModel realm, ComponentModel model) {
+        try {
+            ComponentFactory factory = getComponentFactory(session, model);
+            factory.preRemove(session, realm, model);
+        } catch (IllegalArgumentException iae) {
+            // We allow to remove broken providers without throwing an exception
+            logger.warn(iae.getMessage());
+        }
+    }
 
 }
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 7431ed2..6a7aeaa 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
@@ -168,7 +168,6 @@ public class ModelToRepresentation {
         return rep;
     }
 
-
     public static UserRepresentation toRepresentation(KeycloakSession session, RealmModel realm, UserModel user) {
         UserRepresentation rep = new UserRepresentation();
         rep.setId(user.getId());
@@ -772,11 +771,7 @@ public class ModelToRepresentation {
         return rep;
     }
 
-    public static ScopeRepresentation toRepresentation(Scope model, AuthorizationProvider authorizationProvider) {
-        return toRepresentation(model, authorizationProvider, true);
-    }
-
-    public static ScopeRepresentation toRepresentation(Scope model, AuthorizationProvider authorizationProvider, boolean deep) {
+    public static ScopeRepresentation toRepresentation(Scope model) {
         ScopeRepresentation scope = new ScopeRepresentation();
 
         scope.setId(model.getId());
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 e102d3d..4a4b4fb 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
@@ -2171,13 +2171,13 @@ public class RepresentationToModel {
     private static void updateResources(Set<String> resourceIds, Policy policy, StoreFactory storeFactory) {
         if (resourceIds != null) {
             if (resourceIds.isEmpty()) {
-                for (Scope scope : new HashSet<Scope>(policy.getScopes())) {
-                    policy.removeScope(scope);
+                for (Resource resource : new HashSet<>(policy.getResources())) {
+                    policy.removeResource(resource);
                 }
             }
             for (String resourceId : resourceIds) {
                 boolean hasResource = false;
-                for (Resource resourceModel : new HashSet<Resource>(policy.getResources())) {
+                for (Resource resourceModel : new HashSet<>(policy.getResources())) {
                     if (resourceModel.getId().equals(resourceId) || resourceModel.getName().equals(resourceId)) {
                         hasResource = true;
                     }
@@ -2196,7 +2196,7 @@ public class RepresentationToModel {
                 }
             }
 
-            for (Resource resourceModel : new HashSet<Resource>(policy.getResources())) {
+            for (Resource resourceModel : new HashSet<>(policy.getResources())) {
                 boolean hasResource = false;
 
                 for (String resourceId : resourceIds) {
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/EvaluatableScriptAdapter.java b/server-spi-private/src/main/java/org/keycloak/scripting/EvaluatableScriptAdapter.java
new file mode 100644
index 0000000..2a76add
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/EvaluatableScriptAdapter.java
@@ -0,0 +1,14 @@
+package org.keycloak.scripting;
+
+import org.keycloak.models.ScriptModel;
+
+/**
+ * Wraps a {@link ScriptModel} so it can be evaluated with custom bindings.
+ *
+ * @author <a href="mailto:jay@anslow.me.uk">Jay Anslow</a>
+ */
+public interface EvaluatableScriptAdapter {
+    ScriptModel getScriptModel();
+
+    Object eval(ScriptBindingsConfigurer bindingsConfigurer) throws ScriptExecutionException;
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java b/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java
index c3859ab..17bb4a1 100644
--- a/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java
@@ -56,7 +56,7 @@ public class InvocableScriptAdapter implements Invocable {
         }
 
         this.scriptModel = scriptModel;
-        this.scriptEngine = loadScriptIntoEngine(scriptModel, scriptEngine);
+        this.scriptEngine = scriptEngine;
     }
 
     @Override
@@ -101,17 +101,6 @@ public class InvocableScriptAdapter implements Invocable {
         return candidate != null;
     }
 
-    private ScriptEngine loadScriptIntoEngine(ScriptModel script, ScriptEngine engine) {
-
-        try {
-            engine.eval(script.getCode());
-        } catch (ScriptException se) {
-            throw new ScriptExecutionException(script, se);
-        }
-
-        return engine;
-    }
-
     private Invocable getInvocableEngine() {
         return (Invocable) scriptEngine;
     }
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java
index 67bad5a..ef2990f 100644
--- a/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java
@@ -39,6 +39,14 @@ public interface ScriptingProvider extends Provider {
     InvocableScriptAdapter prepareInvocableScript(ScriptModel scriptModel, ScriptBindingsConfigurer bindingsConfigurer);
 
     /**
+     * Returns an {@link EvaluatableScriptAdapter} based on the given {@link ScriptModel}.
+     * <p>The {@code EvaluatableScriptAdapter} wraps a dedicated {@link ScriptEngine} that was populated with empty bindings.</p>
+     *
+     * @param scriptModel the scriptModel to wrap
+     */
+    EvaluatableScriptAdapter prepareEvaluatableScript(ScriptModel scriptModel);
+
+    /**
      * Creates a new {@link ScriptModel} instance.
      *
      * @param realmId
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 23d06e3..af7d2f7 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -471,6 +471,7 @@ public class AuthenticationProcessor {
             LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
                     .setUser(getUser())
                     .setActionUri(action)
+                    .setExecution(getExecution().getId())
                     .setFormData(request.getDecodedFormParameters())
                     .setClientSessionCode(accessCode);
             if (getForwardedErrorMessage() != null) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
index 7189b95..ca841d0 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -169,6 +169,7 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator 
                 .setStatus(Response.Status.OK)
                 .setAttribute(LoginFormsProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
                 .setActionUri(action)
+                .setExecution(context.getExecution().getId())
                 .createIdpLinkEmailPage();
         context.forceChallenge(challenge);
     }
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index 3a9c53c..89471e7 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -121,7 +121,20 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
             if (model.isAuthenticatorFlow()) {
                 logger.debug("execution is flow");
                 AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
-                Response flowChallenge = authenticationFlow.processFlow();
+
+                Response flowChallenge = null;
+                try {
+                    flowChallenge = authenticationFlow.processFlow();
+                } catch (AuthenticationFlowException afe) {
+                    if (model.isAlternative()) {
+                        logger.debug("Thrown exception in alternative Subflow. Ignoring Subflow");
+                        processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.ATTEMPTED);
+                        continue;
+                    } else {
+                        throw afe;
+                    }
+                }
+
                 if (flowChallenge == null) {
                     processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
                     if (model.isAlternative()) alternativeSuccessful = true;
@@ -183,7 +196,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
 //            if (redirect != null) return redirect;
 
             AuthenticationProcessor.Result context = processor.createAuthenticatorContext(model, authenticator, executions);
-            logger.debug("invoke authenticator.authenticate");
+            logger.debugv("invoke authenticator.authenticate: {0}", factory.getId());
             authenticator.authenticate(context);
             Response response = processResult(context, false);
             if (response != null) return response;
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index 82c12ec..575677d 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -270,6 +270,7 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
         URI actionUrl = getActionUrl(executionId, code);
         LoginFormsProvider form = processor.getSession().getProvider(LoginFormsProvider.class)
                 .setActionUri(actionUrl)
+                .setExecution(executionId)
                 .setClientSessionCode(code)
                 .setFormData(formData)
                 .setErrors(errors);
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index 1d9475a..3afb34c 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -137,11 +137,15 @@ public class RequiredActionContextResult implements RequiredActionContext {
         ClientModel client = authenticationSession.getClient();
         return LoginActionsService.requiredActionProcessor(getUriInfo())
                 .queryParam(OAuth2Constants.CODE, code)
-                .queryParam(Constants.EXECUTION, factory.getId())
+                .queryParam(Constants.EXECUTION, getExecution())
                 .queryParam(Constants.CLIENT_ID, client.getClientId())
                 .build(getRealm().getName());
     }
 
+    private String getExecution() {
+        return factory.getId();
+    }
+
     @Override
     public String generateCode() {
         ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, getRealm(), getAuthenticationSession());
@@ -164,6 +168,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
         LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
                 .setUser(getUser())
                 .setActionUri(action)
+                .setExecution(getExecution())
                 .setClientSessionCode(accessCode);
         return provider;
     }
diff --git a/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java
index a4ff868..535634b 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java
@@ -23,7 +23,8 @@ import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.services.resources.admin.RealmAuth;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
 
 import javax.ws.rs.Path;
 
@@ -32,36 +33,34 @@ import javax.ws.rs.Path;
  */
 public class AuthorizationService {
 
-    private final RealmAuth auth;
+    private final AdminPermissionEvaluator auth;
     private final ClientModel client;
     private final KeycloakSession session;
     private final ResourceServer resourceServer;
     private final AuthorizationProvider authorization;
+    private final AdminEventBuilder adminEvent;
 
-    public AuthorizationService(KeycloakSession session, ClientModel client, RealmAuth auth) {
+    public AuthorizationService(KeycloakSession session, ClientModel client, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.session = session;
         this.client = client;
         this.authorization = session.getProvider(AuthorizationProvider.class);
+        this.adminEvent = adminEvent;
         this.resourceServer = this.authorization.getStoreFactory().getResourceServerStore().findByClient(this.client.getId());
         this.auth = auth;
-
-        if (auth != null) {
-            this.auth.init(RealmAuth.Resource.AUTHORIZATION);
-        }
     }
 
     @Path("/resource-server")
     public ResourceServerService resourceServer() {
-        ResourceServerService resource = new ResourceServerService(this.authorization, this.resourceServer, this.client, this.auth);
+        ResourceServerService resource = new ResourceServerService(this.authorization, this.resourceServer, this.client, this.auth, adminEvent);
 
         ResteasyProviderFactory.getInstance().injectProperties(resource);
 
         return resource;
     }
 
-    public void enable() {
+    public void enable(boolean newClient) {
         if (!isEnabled()) {
-            resourceServer().create();
+            resourceServer().create(newClient);
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java
index ba4fae2..6973b4d 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java
@@ -22,25 +22,26 @@ 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;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
 public class PermissionService extends PolicyService {
 
-    public PermissionService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) {
-        super(resourceServer, authorization, auth);
+    public PermissionService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
+        super(resourceServer, authorization, auth, adminEvent);
     }
 
     @Override
     protected PolicyResourceService doCreatePolicyResource(Policy policy) {
-        return new PolicyTypeResourceService(policy, resourceServer, authorization, auth);
+        return new PolicyTypeResourceService(policy, resourceServer, authorization, auth, adminEvent);
     }
 
     @Override
     protected PolicyTypeService doCreatePolicyTypeResource(String type) {
-        return new PolicyTypeService(type, resourceServer, authorization, auth) {
+        return new PolicyTypeService(type, resourceServer, authorization, auth, adminEvent) {
             @Override
             protected List<Object> doSearch(Integer firstResult, Integer maxResult, Map<String, String[]> filters) {
                 filters.put("permission", new String[] {Boolean.TRUE.toString()});
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
index 5d9114f..ecebaae 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -71,7 +71,7 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
 import org.keycloak.services.ErrorResponseException;
 import org.keycloak.services.Urls;
 import org.keycloak.services.managers.AuthenticationManager;
-import org.keycloak.services.resources.admin.RealmAuth;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 import org.keycloak.sessions.AuthenticationSessionModel;
 
 /**
@@ -80,13 +80,13 @@ import org.keycloak.sessions.AuthenticationSessionModel;
 public class PolicyEvaluationService {
 
     private final AuthorizationProvider authorization;
-    private final RealmAuth auth;
+    private final AdminPermissionEvaluator auth;
     @Context
     private HttpRequest httpRequest;
 
     private final ResourceServer resourceServer;
 
-    PolicyEvaluationService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) {
+    PolicyEvaluationService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) {
         this.resourceServer = resourceServer;
         this.authorization = authorization;
         this.auth = auth;
@@ -118,7 +118,7 @@ public class PolicyEvaluationService {
     @Consumes("application/json")
     @Produces("application/json")
     public Response evaluate(PolicyEvaluationRequest evaluationRequest) throws Throwable {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
         CloseableKeycloakIdentity identity = createIdentity(evaluationRequest);
         try {
             return Response.ok(PolicyEvaluationResponseBuilder.build(evaluate(evaluationRequest, createEvaluationContext(evaluationRequest, identity)), resourceServer, authorization, identity)).build();
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java
index 85e0943..b32899f 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java
@@ -26,8 +26,10 @@ 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.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
 
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.keycloak.authorization.AuthorizationProvider;
@@ -36,13 +38,16 @@ 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.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
 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.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
 import org.keycloak.util.JsonSerialization;
 
 /**
@@ -53,21 +58,23 @@ public class PolicyResourceService {
     private final Policy policy;
     protected final ResourceServer resourceServer;
     protected final AuthorizationProvider authorization;
-    protected final RealmAuth auth;
+    protected final AdminPermissionEvaluator auth;
+    private final AdminEventBuilder adminEvent;
 
-    public PolicyResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) {
+    public PolicyResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.policy = policy;
         this.resourceServer = resourceServer;
         this.authorization = authorization;
         this.auth = auth;
+        this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_POLICY);
     }
 
     @PUT
     @Consumes("application/json")
     @Produces("application/json")
     @NoCache
-    public Response update(String payload) {
-        this.auth.requireManage();
+    public Response update(@Context UriInfo uriInfo,String payload) {
+        this.auth.realm().requireManageAuthorization();
 
         AbstractPolicyRepresentation representation = doCreateRepresentation(payload);
 
@@ -79,12 +86,15 @@ public class PolicyResourceService {
 
         RepresentationToModel.toModel(representation, authorization, policy);
 
+
+        audit(uriInfo, representation, OperationType.UPDATE);
+
         return Response.status(Status.CREATED).build();
     }
 
     @DELETE
-    public Response delete() {
-        this.auth.requireManage();
+    public Response delete(@Context UriInfo uriInfo) {
+        this.auth.realm().requireManageAuthorization();
 
         if (policy == null) {
             return Response.status(Status.NOT_FOUND).build();
@@ -98,6 +108,10 @@ public class PolicyResourceService {
 
         policyStore.delete(policy.getId());
 
+        if (authorization.getRealm().isAdminEventsEnabled()) {
+            audit(uriInfo, toRepresentation(policy, authorization), OperationType.DELETE);
+        }
+
         return Response.noContent().build();
     }
 
@@ -105,7 +119,7 @@ public class PolicyResourceService {
     @Produces("application/json")
     @NoCache
     public Response findById() {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
 
         if (policy == null) {
             return Response.status(Status.NOT_FOUND).build();
@@ -123,7 +137,7 @@ public class PolicyResourceService {
     @Produces("application/json")
     @NoCache
     public Response getDependentPolicies() {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
 
         if (policy == null) {
             return Response.status(Status.NOT_FOUND).build();
@@ -147,7 +161,7 @@ public class PolicyResourceService {
     @Produces("application/json")
     @NoCache
     public Response getScopes() {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
 
         if (policy == null) {
             return Response.status(Status.NOT_FOUND).build();
@@ -168,7 +182,7 @@ public class PolicyResourceService {
     @Produces("application/json")
     @NoCache
     public Response getResources() {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
 
         if (policy == null) {
             return Response.status(Status.NOT_FOUND).build();
@@ -189,7 +203,7 @@ public class PolicyResourceService {
     @Produces("application/json")
     @NoCache
     public Response getAssociatedPolicies() {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
 
         if (policy == null) {
             return Response.status(Status.NOT_FOUND).build();
@@ -225,4 +239,10 @@ public class PolicyResourceService {
     protected Policy getPolicy() {
         return policy;
     }
+
+    private void audit(@Context UriInfo uriInfo, AbstractPolicyRepresentation policy, OperationType operation) {
+        if (authorization.getRealm().isAdminEventsEnabled()) {
+            adminEvent.operation(operation).resourcePath(uriInfo).representation(policy).success();
+        }
+    }
 }
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 e670042..33e6299 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
@@ -33,9 +33,11 @@ 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.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
 
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
@@ -46,13 +48,17 @@ 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.StoreFactory;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
 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.ScopeRepresentation;
 import org.keycloak.services.ErrorResponseException;
-import org.keycloak.services.resources.admin.RealmAuth;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
 import org.keycloak.util.JsonSerialization;
 
 /**
@@ -62,12 +68,14 @@ public class PolicyService {
 
     protected final ResourceServer resourceServer;
     protected final AuthorizationProvider authorization;
-    protected final RealmAuth auth;
+    protected final AdminPermissionEvaluator auth;
+    protected final AdminEventBuilder adminEvent;
 
-    public PolicyService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) {
+    public PolicyService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.resourceServer = resourceServer;
         this.authorization = authorization;
         this.auth = auth;
+        this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_POLICY);
     }
 
     @Path("{type}")
@@ -84,25 +92,27 @@ public class PolicyService {
     }
 
     protected PolicyTypeService doCreatePolicyTypeResource(String type) {
-        return new PolicyTypeService(type, resourceServer, authorization, auth);
+        return new PolicyTypeService(type, resourceServer, authorization, auth, adminEvent);
     }
 
     protected Object doCreatePolicyResource(Policy policy) {
-        return new PolicyResourceService(policy, resourceServer, authorization, auth);
+        return new PolicyResourceService(policy, resourceServer, authorization, auth, adminEvent);
     }
 
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
-    public Response create(String payload) {
-        this.auth.requireManage();
+    public Response create(@Context UriInfo uriInfo, String payload) {
+        this.auth.realm().requireManageAuthorization();
 
         AbstractPolicyRepresentation representation = doCreateRepresentation(payload);
         Policy policy = create(representation);
 
         representation.setId(policy.getId());
 
+        audit(uriInfo, representation, representation.getId(), OperationType.CREATE);
+
         return Response.status(Status.CREATED).entity(representation).build();
     }
 
@@ -134,7 +144,7 @@ public class PolicyService {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public Response findByName(@QueryParam("name") String name) {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
         StoreFactory storeFactory = authorization.getStoreFactory();
 
         if (name == null) {
@@ -161,7 +171,7 @@ public class PolicyService {
                             @QueryParam("permission") Boolean permission,
                             @QueryParam("first") Integer firstResult,
                             @QueryParam("max") Integer maxResult) {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
 
         Map<String, String[]> search = new HashMap<>();
 
@@ -240,7 +250,7 @@ public class PolicyService {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public Response findPolicyProviders() {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
         return Response.ok(
                 authorization.getProviderFactories().stream()
                         .map(provider -> {
@@ -258,7 +268,7 @@ public class PolicyService {
 
     @Path("evaluate")
     public PolicyEvaluationService getPolicyEvaluateResource() {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
         PolicyEvaluationService resource = new PolicyEvaluationService(this.resourceServer, this.authorization, this.auth);
 
         ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -280,4 +290,14 @@ public class PolicyService {
             findAssociatedPolicies(associated, policies);
         });
     }
+
+    private void audit(@Context UriInfo uriInfo, AbstractPolicyRepresentation resource, String id, OperationType operation) {
+        if (authorization.getRealm().isAdminEventsEnabled()) {
+            if (id != null) {
+                adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success();
+            } else {
+                adminEvent.operation(operation).resourcePath(uriInfo).representation(resource).success();
+            }
+        }
+    }
 }
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java
index 8756721..1297b6a 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java
@@ -24,7 +24,8 @@ 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.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
 import org.keycloak.util.JsonSerialization;
 
 /**
@@ -32,8 +33,8 @@ import org.keycloak.util.JsonSerialization;
  */
 public class PolicyTypeResourceService extends PolicyResourceService {
 
-    public PolicyTypeResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) {
-        super(policy, resourceServer, authorization, auth);
+    public PolicyTypeResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
+        super(policy, resourceServer, authorization, auth, adminEvent);
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java
index 5f03dad..4b41e94 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java
@@ -30,7 +30,8 @@ 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.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
 import org.keycloak.util.JsonSerialization;
 
 /**
@@ -40,8 +41,8 @@ public class PolicyTypeService extends PolicyService {
 
     private final String type;
 
-    PolicyTypeService(String type, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) {
-        super(resourceServer, authorization, auth);
+    PolicyTypeService(String type, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
+        super(resourceServer, authorization, auth, adminEvent);
         this.type = type;
     }
 
@@ -60,7 +61,7 @@ public class PolicyTypeService extends PolicyService {
 
     @Override
     protected Object doCreatePolicyResource(Policy policy) {
-        return new PolicyTypeResourceService(policy, resourceServer,authorization, auth);
+        return new PolicyTypeResourceService(policy, resourceServer,authorization, auth, adminEvent);
     }
 
     @Override
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 15f1db7..e52da9a 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
@@ -40,12 +40,15 @@ 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.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
 import org.keycloak.exportimport.util.ExportUtils;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.authorization.DecisionStrategy;
 import org.keycloak.representations.idm.authorization.Logic;
@@ -53,7 +56,8 @@ 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 org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -61,21 +65,26 @@ import org.keycloak.services.resources.admin.RealmAuth;
 public class ResourceServerService {
 
     private final AuthorizationProvider authorization;
-    private final RealmAuth auth;
+    private final AdminPermissionEvaluator auth;
+    private final AdminEventBuilder adminEvent;
     private final KeycloakSession session;
     private ResourceServer resourceServer;
     private final ClientModel client;
 
-    public ResourceServerService(AuthorizationProvider authorization, ResourceServer resourceServer, ClientModel client, RealmAuth auth) {
+    @Context
+    private UriInfo uriInfo;
+
+    public ResourceServerService(AuthorizationProvider authorization, ResourceServer resourceServer, ClientModel client, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.authorization = authorization;
         this.session = authorization.getKeycloakSession();
         this.client = client;
         this.resourceServer = resourceServer;
         this.auth = auth;
+        this.adminEvent = adminEvent;
     }
 
-    public void create() {
-        this.auth.requireManage();
+    public void create(boolean newClient) {
+        this.auth.realm().requireManageAuthorization();
 
         UserModel serviceAccount = this.session.users().getServiceAccount(client);
 
@@ -86,42 +95,45 @@ public class ResourceServerService {
         this.resourceServer = this.authorization.getStoreFactory().getResourceServerStore().create(this.client.getId());
         createDefaultRoles(serviceAccount);
         createDefaultPermission(createDefaultResource(), createDefaultPolicy());
+        audit(OperationType.CREATE, uriInfo, newClient);
     }
 
     @PUT
     @Consumes("application/json")
     @Produces("application/json")
     public Response update(ResourceServerRepresentation server) {
-        this.auth.requireManage();
+        this.auth.realm().requireManageAuthorization();
         this.resourceServer.setAllowRemoteResourceManagement(server.isAllowRemoteResourceManagement());
         this.resourceServer.setPolicyEnforcementMode(server.getPolicyEnforcementMode());
-
+        audit(OperationType.UPDATE, uriInfo, false);
         return Response.noContent().build();
     }
 
     public void delete() {
-        this.auth.requireManage();
+        this.auth.realm().requireManageAuthorization();
         StoreFactory storeFactory = authorization.getStoreFactory();
         ResourceStore resourceStore = storeFactory.getResourceStore();
         String id = resourceServer.getId();
 
+        PolicyStore policyStore = storeFactory.getPolicyStore();
+
+        policyStore.findByResourceServer(id).forEach(scope -> policyStore.delete(scope.getId()));
+
         resourceStore.findByResourceServer(id).forEach(resource -> resourceStore.delete(resource.getId()));
 
         ScopeStore scopeStore = storeFactory.getScopeStore();
 
         scopeStore.findByResourceServer(id).forEach(scope -> scopeStore.delete(scope.getId()));
 
-        PolicyStore policyStore = storeFactory.getPolicyStore();
-
-        policyStore.findByResourceServer(id).forEach(scope -> policyStore.delete(scope.getId()));
-
         storeFactory.getResourceServerStore().delete(id);
+
+        audit(OperationType.DELETE, uriInfo, false);
     }
 
     @GET
     @Produces("application/json")
     public Response findById() {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
         return Response.ok(toRepresentation(this.resourceServer, this.client)).build();
     }
 
@@ -129,7 +141,7 @@ public class ResourceServerService {
     @GET
     @Produces("application/json")
     public Response exportSettings() {
-        this.auth.requireManage();
+        this.auth.realm().requireManageAuthorization();
         return Response.ok(ExportUtils.exportAuthorizationSettings(session, client)).build();
     }
 
@@ -137,18 +149,20 @@ public class ResourceServerService {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public Response importSettings(@Context final UriInfo uriInfo, ResourceServerRepresentation rep) throws IOException {
-        this.auth.requireManage();
+        this.auth.realm().requireManageAuthorization();
 
         rep.setClientId(client.getId());
 
         RepresentationToModel.toModel(rep, authorization);
 
+        audit(OperationType.UPDATE, uriInfo, false);
+
         return Response.noContent().build();
     }
 
     @Path("/resource")
     public ResourceSetService getResourceSetResource() {
-        ResourceSetService resource = new ResourceSetService(this.resourceServer, this.authorization, this.auth);
+        ResourceSetService resource = new ResourceSetService(this.resourceServer, this.authorization, this.auth, adminEvent);
 
         ResteasyProviderFactory.getInstance().injectProperties(resource);
 
@@ -157,7 +171,7 @@ public class ResourceServerService {
 
     @Path("/scope")
     public ScopeService getScopeResource() {
-        ScopeService resource = new ScopeService(this.resourceServer, this.authorization, this.auth);
+        ScopeService resource = new ScopeService(this.resourceServer, this.authorization, this.auth, adminEvent);
 
         ResteasyProviderFactory.getInstance().injectProperties(resource);
 
@@ -166,7 +180,7 @@ public class ResourceServerService {
 
     @Path("/policy")
     public PolicyService getPolicyResource() {
-        PolicyService resource = new PolicyService(this.resourceServer, this.authorization, this.auth);
+        PolicyService resource = new PolicyService(this.resourceServer, this.authorization, this.auth, adminEvent);
 
         ResteasyProviderFactory.getInstance().injectProperties(resource);
 
@@ -175,8 +189,8 @@ public class ResourceServerService {
 
     @Path("/permission")
     public Object getPermissionTypeResource() {
-        this.auth.requireView();
-        PermissionService resource = new PermissionService(this.resourceServer, this.authorization, this.auth);
+        this.auth.realm().requireViewAuthorization();
+        PermissionService resource = new PermissionService(this.resourceServer, this.authorization, this.auth, adminEvent);
 
         ResteasyProviderFactory.getInstance().injectProperties(resource);
 
@@ -239,4 +253,14 @@ public class ResourceServerService {
             serviceAccount.grantRole(umaProtectionRole);
         }
     }
+
+    private void audit(OperationType operation, UriInfo uriInfo, boolean newClient) {
+        if (newClient) {
+            adminEvent.resource(ResourceType.AUTHORIZATION_RESOURCE_SERVER).operation(operation).resourcePath(uriInfo, client.getId())
+                    .representation(ModelToRepresentation.toRepresentation(resourceServer, client)).success();
+        } else {
+            adminEvent.resource(ResourceType.AUTHORIZATION_RESOURCE_SERVER).operation(operation).resourcePath(uriInfo)
+                    .representation(ModelToRepresentation.toRepresentation(resourceServer, client)).success();
+        }
+    }
 }
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 5d65923..7c95281 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
@@ -26,6 +26,8 @@ import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.store.PolicyStore;
 import org.keycloak.authorization.store.ResourceStore;
 import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
@@ -37,7 +39,8 @@ import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentatio
 import org.keycloak.representations.idm.authorization.ResourceRepresentation;
 import org.keycloak.representations.idm.authorization.ScopeRepresentation;
 import org.keycloak.services.ErrorResponse;
-import org.keycloak.services.resources.admin.RealmAuth;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -48,8 +51,10 @@ 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.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -69,18 +74,27 @@ import static org.keycloak.models.utils.RepresentationToModel.toModel;
 public class ResourceSetService {
 
     private final AuthorizationProvider authorization;
-    private final RealmAuth auth;
+    private final AdminPermissionEvaluator auth;
+    private final AdminEventBuilder adminEvent;
     private ResourceServer resourceServer;
 
-    public ResourceSetService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) {
+    public ResourceSetService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.resourceServer = resourceServer;
         this.authorization = authorization;
         this.auth = auth;
+        this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_RESOURCE);
     }
 
     @POST
+    @NoCache
     @Consumes("application/json")
     @Produces("application/json")
+    public Response create(@Context UriInfo uriInfo, ResourceRepresentation resource) {
+        Response response = create(resource);
+        audit(uriInfo, resource, resource.getId(), OperationType.CREATE);
+        return response;
+    }
+
     public Response create(ResourceRepresentation resource) {
         requireManage();
         StoreFactory storeFactory = this.authorization.getStoreFactory();
@@ -128,7 +142,7 @@ public class ResourceSetService {
     @PUT
     @Consumes("application/json")
     @Produces("application/json")
-    public Response update(@PathParam("id") String id, ResourceRepresentation resource) {
+    public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, ResourceRepresentation resource) {
         requireManage();
         resource.setId(id);
         StoreFactory storeFactory = this.authorization.getStoreFactory();
@@ -141,12 +155,14 @@ public class ResourceSetService {
 
         toModel(resource, resourceServer, authorization);
 
+        audit(uriInfo, resource, OperationType.UPDATE);
+
         return Response.noContent().build();
     }
 
     @Path("{id}")
     @DELETE
-    public Response delete(@PathParam("id") String id) {
+    public Response delete(@Context UriInfo uriInfo, @PathParam("id") String id) {
         requireManage();
         StoreFactory storeFactory = authorization.getStoreFactory();
         Resource resource = storeFactory.getResourceStore().findById(id, resourceServer.getId());
@@ -168,6 +184,10 @@ public class ResourceSetService {
 
         storeFactory.getResourceStore().delete(id);
 
+        if (authorization.getRealm().isAdminEventsEnabled()) {
+            audit(uriInfo, toRepresentation(resource, resourceServer, authorization), OperationType.DELETE);
+        }
+
         return Response.noContent().build();
     }
 
@@ -269,10 +289,10 @@ public class ResourceSetService {
 
     @Path("/search")
     @GET
-    @Produces("application/json")
     @NoCache
+    @Produces("application/json")
     public Response find(@QueryParam("name") String name) {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
         StoreFactory storeFactory = authorization.getStoreFactory();
 
         if (name == null) {
@@ -367,13 +387,27 @@ public class ResourceSetService {
 
     private void requireView() {
         if (this.auth != null) {
-            this.auth.requireView();
+            this.auth.realm().requireViewAuthorization();
         }
     }
 
     private void requireManage() {
         if (this.auth != null) {
-            this.auth.requireManage();
+            this.auth.realm().requireManageAuthorization();
+        }
+    }
+
+    private void audit(@Context UriInfo uriInfo, ResourceRepresentation resource, OperationType operation) {
+        audit(uriInfo, resource, null, operation);
+    }
+
+    private void audit(@Context UriInfo uriInfo, ResourceRepresentation resource, String id, OperationType operation) {
+        if (authorization.getRealm().isAdminEventsEnabled()) {
+            if (id != null) {
+                adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success();
+            } else {
+                adminEvent.operation(operation).resourcePath(uriInfo).representation(resource).success();
+            }
         }
     }
 }
\ No newline at end of file
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 1ec9a13..1ab3546 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java
@@ -25,12 +25,15 @@ import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.store.PolicyStore;
 import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.Constants;
 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.ErrorResponse;
-import org.keycloak.services.resources.admin.RealmAuth;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -41,9 +44,11 @@ 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.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
 
 import java.util.Arrays;
 import java.util.HashMap;
@@ -60,24 +65,29 @@ import static org.keycloak.models.utils.RepresentationToModel.toModel;
 public class ScopeService {
 
     private final AuthorizationProvider authorization;
-    private final RealmAuth auth;
+    private final AdminPermissionEvaluator auth;
+    private final AdminEventBuilder adminEvent;
     private ResourceServer resourceServer;
 
-    public ScopeService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) {
+    public ScopeService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.resourceServer = resourceServer;
         this.authorization = authorization;
         this.auth = auth;
+        this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_SCOPE);
     }
 
     @POST
+    @NoCache
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
-    public Response create(ScopeRepresentation scope) {
-        this.auth.requireManage();
+    public Response create(@Context UriInfo uriInfo,  ScopeRepresentation scope) {
+            this.auth.realm().requireManageAuthorization();
         Scope model = toModel(scope, this.resourceServer, authorization);
 
         scope.setId(model.getId());
 
+        audit(uriInfo, scope, scope.getId(), OperationType.CREATE);
+
         return Response.status(Status.CREATED).entity(scope).build();
     }
 
@@ -85,8 +95,8 @@ public class ScopeService {
     @PUT
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
-    public Response update(@PathParam("id") String id, ScopeRepresentation scope) {
-        this.auth.requireManage();
+    public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, ScopeRepresentation scope) {
+            this.auth.realm().requireManageAuthorization();
         scope.setId(id);
         StoreFactory storeFactory = authorization.getStoreFactory();
         Scope model = storeFactory.getScopeStore().findById(scope.getId(), resourceServer.getId());
@@ -97,13 +107,15 @@ public class ScopeService {
 
         toModel(scope, resourceServer, authorization);
 
+        audit(uriInfo, scope, OperationType.UPDATE);
+
         return Response.noContent().build();
     }
 
     @Path("{id}")
     @DELETE
-    public Response delete(@PathParam("id") String id) {
-        this.auth.requireManage();
+    public Response delete(@Context UriInfo uriInfo, @PathParam("id") String id) {
+        this.auth.realm().requireManageAuthorization();
         StoreFactory storeFactory = authorization.getStoreFactory();
         List<Resource> resources = storeFactory.getResourceStore().findByScope(Arrays.asList(id), resourceServer.getId());
 
@@ -130,28 +142,34 @@ public class ScopeService {
 
         storeFactory.getScopeStore().delete(id);
 
+        if (authorization.getRealm().isAdminEventsEnabled()) {
+            audit(uriInfo, toRepresentation(scope), OperationType.DELETE);
+        }
+
         return Response.noContent().build();
     }
 
     @Path("{id}")
     @GET
+    @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Response findById(@PathParam("id") String id) {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
         Scope model = this.authorization.getStoreFactory().getScopeStore().findById(id, resourceServer.getId());
 
         if (model == null) {
             return Response.status(Status.NOT_FOUND).build();
         }
 
-        return Response.ok(toRepresentation(model, this.authorization)).build();
+        return Response.ok(toRepresentation(model)).build();
     }
 
     @Path("{id}/resources")
     @GET
+    @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Response getResources(@PathParam("id") String id) {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
         StoreFactory storeFactory = this.authorization.getStoreFactory();
         Scope model = storeFactory.getScopeStore().findById(id, resourceServer.getId());
 
@@ -171,9 +189,10 @@ public class ScopeService {
 
     @Path("{id}/permissions")
     @GET
+    @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Response getPermissions(@PathParam("id") String id) {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
         StoreFactory storeFactory = this.authorization.getStoreFactory();
         Scope model = storeFactory.getScopeStore().findById(id, resourceServer.getId());
 
@@ -199,7 +218,7 @@ public class ScopeService {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public Response find(@QueryParam("name") String name) {
-        this.auth.requireView();
+        this.auth.realm().requireViewAuthorization();
         StoreFactory storeFactory = authorization.getStoreFactory();
 
         if (name == null) {
@@ -212,21 +231,17 @@ public class ScopeService {
             return Response.status(Status.OK).build();
         }
 
-        return Response.ok(toRepresentation(model, authorization)).build();
+        return Response.ok(toRepresentation(model)).build();
     }
 
     @GET
+    @NoCache
     @Produces("application/json")
     public Response findAll(@QueryParam("scopeId") String id,
                             @QueryParam("name") String name,
-                            @QueryParam("deep") Boolean deep,
                             @QueryParam("first") Integer firstResult,
                             @QueryParam("max") Integer maxResult) {
-        this.auth.requireView();
-
-        if (deep == null) {
-            deep = true;
-        }
+        this.auth.realm().requireViewAuthorization();
 
         Map<String, String[]> search = new HashMap<>();
 
@@ -238,11 +253,24 @@ public class ScopeService {
             search.put("name", new String[] {name});
         }
 
-        Boolean finalDeep = deep;
         return Response.ok(
                 this.authorization.getStoreFactory().getScopeStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS).stream()
-                        .map(scope -> toRepresentation(scope, this.authorization, finalDeep))
+                        .map(scope -> toRepresentation(scope))
                         .collect(Collectors.toList()))
                 .build();
     }
+
+    private void audit(@Context UriInfo uriInfo, ScopeRepresentation resource, OperationType operation) {
+        audit(uriInfo, resource, null, operation);
+    }
+
+    private void audit(@Context UriInfo uriInfo, ScopeRepresentation resource, String id, OperationType operation) {
+        if (authorization.getRealm().isAdminEventsEnabled()) {
+            if (id != null) {
+                adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success();
+            } else {
+                adminEvent.operation(operation).resourcePath(uriInfo).representation(resource).success();
+            }
+        }
+    }
 }
diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java
index fc929ec..da3cb0f 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java
@@ -18,34 +18,28 @@
 
 package org.keycloak.authorization.common;
 
-import org.keycloak.authorization.attribute.Attributes;
 import org.keycloak.authorization.identity.Identity;
-import org.keycloak.authorization.policy.evaluation.EvaluationContext;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.representations.AccessToken;
 
-import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
-public class KeycloakEvaluationContext implements EvaluationContext {
+public class KeycloakEvaluationContext extends DefaultEvaluationContext {
 
     private final KeycloakIdentity identity;
-    private final KeycloakSession keycloakSession;
 
     public KeycloakEvaluationContext(KeycloakSession keycloakSession) {
         this(new KeycloakIdentity(keycloakSession), keycloakSession);
     }
 
     public KeycloakEvaluationContext(KeycloakIdentity identity, KeycloakSession keycloakSession) {
+        super(identity, keycloakSession);
         this.identity = identity;
-        this.keycloakSession = keycloakSession;
     }
 
     @Override
@@ -54,27 +48,13 @@ public class KeycloakEvaluationContext implements EvaluationContext {
     }
 
     @Override
-    public Attributes getAttributes() {
-        HashMap<String, Collection<String>> attributes = new HashMap<>();
-
-        attributes.put("kc.time.date_time", Arrays.asList(new SimpleDateFormat("MM/dd/yyyy hh:mm:ss").format(new Date())));
-        attributes.put("kc.client.network.ip_address", Arrays.asList(this.keycloakSession.getContext().getConnection().getRemoteAddr()));
-        attributes.put("kc.client.network.host", Arrays.asList(this.keycloakSession.getContext().getConnection().getRemoteHost()));
-
+    public Map<String, Collection<String>> getBaseAttributes() {
+        Map<String, Collection<String>> attributes = super.getBaseAttributes();
         AccessToken accessToken = this.identity.getAccessToken();
 
         if (accessToken != null) {
             attributes.put("kc.client.id", Arrays.asList(accessToken.getIssuedFor()));
         }
-
-        List<String> userAgents = this.keycloakSession.getContext().getRequestHeaders().getRequestHeader("User-Agent");
-
-        if (userAgents != null) {
-            attributes.put("kc.client.user_agent", userAgents);
-        }
-
-        attributes.put("kc.realm.name", Arrays.asList(this.keycloakSession.getContext().getRealm().getName()));
-
-        return Attributes.from(attributes);
+        return attributes;
     }
 }
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 9ea53e2..59963ba 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
@@ -53,6 +53,74 @@ public class KeycloakIdentity implements Identity {
         this(Tokens.getAccessToken(keycloakSession), keycloakSession);
     }
 
+    public KeycloakIdentity(KeycloakSession keycloakSession, AccessToken accessToken) {
+        this(accessToken, keycloakSession, keycloakSession.getContext().getRealm());
+    }
+
+    public KeycloakIdentity(AccessToken accessToken, KeycloakSession keycloakSession, RealmModel realm) {
+        if (accessToken == null) {
+            throw new ErrorResponseException("invalid_bearer_token", "Could not obtain bearer access_token from request.", Status.FORBIDDEN);
+        }
+        if (keycloakSession == null) {
+            throw new ErrorResponseException("no_keycloak_session", "No keycloak session", Status.FORBIDDEN);
+        }
+        if (realm == null) {
+            throw new ErrorResponseException("no_keycloak_session", "No realm set", Status.FORBIDDEN);
+        }
+        this.accessToken = accessToken;
+        this.keycloakSession = keycloakSession;
+        this.realm = realm;
+
+        Map<String, Collection<String>> attributes = new HashMap<>();
+
+        try {
+            ObjectNode objectNode = JsonSerialization.createObjectNode(this.accessToken);
+            Iterator<String> iterator = objectNode.fieldNames();
+
+            while (iterator.hasNext()) {
+                String fieldName = iterator.next();
+                JsonNode fieldValue = objectNode.get(fieldName);
+                List<String> values = new ArrayList<>();
+
+                if (fieldValue.isArray()) {
+                    Iterator<JsonNode> valueIterator = fieldValue.iterator();
+
+                    while (valueIterator.hasNext()) {
+                        values.add(valueIterator.next().asText());
+                    }
+                } else {
+                    String value = fieldValue.asText();
+
+                    if (StringUtil.isNullOrEmpty(value)) {
+                        continue;
+                    }
+
+                    values.add(value);
+                }
+
+                if (!values.isEmpty()) {
+                    attributes.put(fieldName, values);
+                }
+            }
+
+            AccessToken.Access realmAccess = accessToken.getRealmAccess();
+
+            if (realmAccess != null) {
+                attributes.put("kc.realm.roles", realmAccess.getRoles());
+            }
+
+            Map<String, AccessToken.Access> resourceAccess = accessToken.getResourceAccess();
+
+            if (resourceAccess != null) {
+                resourceAccess.forEach((clientId, access) -> attributes.put("kc.client." + clientId + ".roles", access.getRoles()));
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Error while reading attributes from security token.", e);
+        }
+
+        this.attributes = Attributes.from(attributes);
+    }
+
     public KeycloakIdentity(AccessToken accessToken, KeycloakSession keycloakSession) {
         if (accessToken == null) {
             throw new ErrorResponseException("invalid_bearer_token", "Could not obtain bearer access_token from request.", Status.FORBIDDEN);
diff --git a/services/src/main/java/org/keycloak/authorization/common/UserModelIdentity.java b/services/src/main/java/org/keycloak/authorization/common/UserModelIdentity.java
index 67e6bb4..c54e4c0 100644
--- a/services/src/main/java/org/keycloak/authorization/common/UserModelIdentity.java
+++ b/services/src/main/java/org/keycloak/authorization/common/UserModelIdentity.java
@@ -34,7 +34,8 @@ public class UserModelIdentity implements Identity {
     protected RealmModel realm;
     protected UserModel user;
 
-    public UserModelIdentity(UserModel user) {
+    public UserModelIdentity(RealmModel realm, UserModel user) {
+        this.realm = realm;
         this.user = user;
     }
 
diff --git a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
index 45fb6fe..3779279 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
@@ -27,12 +27,17 @@ import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.protection.permission.PermissionService;
 import org.keycloak.authorization.protection.permission.PermissionsService;
 import org.keycloak.authorization.protection.resource.ResourceService;
+import org.keycloak.common.ClientConnection;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.resources.admin.AdminAuth;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
 
 import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response.Status;
 
 /**
@@ -41,6 +46,8 @@ import javax.ws.rs.core.Response.Status;
 public class ProtectionService {
 
     private final AuthorizationProvider authorization;
+    @Context
+    protected ClientConnection clientConnection;
 
     public ProtectionService(AuthorizationProvider authorization) {
         this.authorization = authorization;
@@ -50,7 +57,12 @@ public class ProtectionService {
     public Object resource() {
         KeycloakIdentity identity = createIdentity();
         ResourceServer resourceServer = getResourceServer(identity);
-        ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null);
+        RealmModel realm = authorization.getRealm();
+        ClientModel client = realm.getClientById(identity.getId());
+        KeycloakSession keycloakSession = authorization.getKeycloakSession();
+        UserModel serviceAccount = keycloakSession.users().getServiceAccount(client);
+        AdminEventBuilder adminEvent = new AdminEventBuilder(realm, new AdminAuth(realm, identity.getAccessToken(), serviceAccount, client), keycloakSession, clientConnection);
+        ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null, adminEvent.realm(realm).authClient(client).authUser(serviceAccount));
 
         ResteasyProviderFactory.getInstance().injectProperties(resourceManager);
 
diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
index 1695ba5..c2e11dc 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
@@ -31,8 +31,10 @@ 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.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
 
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.admin.ResourceSetService;
@@ -68,10 +70,10 @@ public class ResourceService {
     @POST
     @Consumes("application/json")
     @Produces("application/json")
-    public Response create(UmaResourceRepresentation umaResource) {
+    public Response create(@Context  UriInfo uriInfo, UmaResourceRepresentation umaResource) {
         checkResourceServerSettings();
         ResourceRepresentation resource = toResourceRepresentation(umaResource);
-        Response response = this.resourceManager.create(resource);
+        Response response = this.resourceManager.create(uriInfo, resource);
 
         if (response.getEntity() instanceof ResourceRepresentation) {
             return Response.status(Status.CREATED).entity(toUmaRepresentation((ResourceRepresentation) response.getEntity())).build();
@@ -84,9 +86,9 @@ public class ResourceService {
     @PUT
     @Consumes("application/json")
     @Produces("application/json")
-    public Response update(@PathParam("id") String id, UmaResourceRepresentation representation) {
+    public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, UmaResourceRepresentation representation) {
         ResourceRepresentation resource = toResourceRepresentation(representation);
-        Response response = this.resourceManager.update(id, resource);
+        Response response = this.resourceManager.update(uriInfo, id, resource);
 
         if (response.getEntity() instanceof ResourceRepresentation) {
             return Response.noContent().build();
@@ -97,9 +99,9 @@ public class ResourceService {
 
     @Path("/{id}")
     @DELETE
-    public Response delete(@PathParam("id") String id) {
+    public Response delete(@Context UriInfo uriInfo, @PathParam("id") String id) {
         checkResourceServerSettings();
-        return this.resourceManager.delete(id);
+        return this.resourceManager.delete(uriInfo, id);
     }
 
     @Path("/{id}")
diff --git a/services/src/main/java/org/keycloak/authorization/util/Permissions.java b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
index 325c056..b0e5daa 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -19,6 +19,8 @@
 package org.keycloak.authorization.util;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -48,6 +50,10 @@ import org.keycloak.representations.idm.authorization.Permission;
  */
 public final class Permissions {
 
+    public static List<ResourcePermission> permission(ResourceServer server, Resource resource, Scope scope) {
+       return Arrays.asList(new ResourcePermission(resource, Arrays.asList(scope), server));
+    }
+
     /**
      * Returns a list of permissions for all resources and scopes that belong to the given <code>resourceServer</code> and
      * <code>identity</code>.
diff --git a/services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeStatementMapper.java b/services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeStatementMapper.java
new file mode 100644
index 0000000..4856fb6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeStatementMapper.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) eHealth
+ */
+package org.keycloak.broker.saml.mappers;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.saml.SAMLEndpoint;
+import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
+import org.keycloak.common.util.CollectionUtil;
+import org.keycloak.dom.saml.v2.assertion.AssertionType;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType.ASTChoiceType;
+import org.keycloak.dom.saml.v2.assertion.AttributeType;
+import org.keycloak.models.IdentityProviderMapperModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:frelibert@yahoo.com">Frederik Libert</a>
+ *
+ */
+public class UserAttributeStatementMapper extends AbstractIdentityProviderMapper {
+
+    private static final String USER_ATTR_LOCALE = "locale";
+
+    private static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
+
+    private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();
+
+    public static final String ATTRIBUTE_NAME_PATTERN = "attribute.name.pattern";
+
+    public static final String USER_ATTRIBUTE_FIRST_NAME = "user.attribute.firstName";
+
+    public static final String USER_ATTRIBUTE_LAST_NAME = "user.attribute.lastName";
+
+    public static final String USER_ATTRIBUTE_EMAIL = "user.attribute.email";
+
+    public static final String USER_ATTRIBUTE_LANGUAGE = "user.attribute.language";
+    
+    private static final String USE_FRIENDLY_NAMES = "use.friendly.names";
+
+    static {
+        ProviderConfigProperty property;
+        property = new ProviderConfigProperty();
+        property.setName(ATTRIBUTE_NAME_PATTERN);
+        property.setLabel("Attribute Name Pattern");
+        property.setHelpText("Pattern of attribute names in assertion that must be mapped. Leave blank to map all attributes.");
+        property.setType(ProviderConfigProperty.STRING_TYPE);
+        CONFIG_PROPERTIES.add(property);
+        property = new ProviderConfigProperty();
+        property.setName(USER_ATTRIBUTE_FIRST_NAME);
+        property.setLabel("User Attribute FirstName");
+        property.setHelpText("Define which saml Attribute must be mapped to the User property firstName.");
+        property.setType(ProviderConfigProperty.STRING_TYPE);
+        CONFIG_PROPERTIES.add(property);
+        property = new ProviderConfigProperty();
+        property.setName(USER_ATTRIBUTE_LAST_NAME);
+        property.setLabel("User Attribute LastName");
+        property.setHelpText("Define which saml Attribute must be mapped to the User property lastName.");
+        property.setType(ProviderConfigProperty.STRING_TYPE);
+        CONFIG_PROPERTIES.add(property);
+        property = new ProviderConfigProperty();
+        property.setName(USER_ATTRIBUTE_EMAIL);
+        property.setLabel("User Attribute Email");
+        property.setHelpText("Define which saml Attribute must be mapped to the User property email.");
+        property.setType(ProviderConfigProperty.STRING_TYPE);
+        CONFIG_PROPERTIES.add(property);
+        property = new ProviderConfigProperty();
+        property.setName(USER_ATTRIBUTE_LANGUAGE);
+        property.setLabel("User Attribute Language");
+        property.setHelpText("Define which saml Attribute must be mapped to the User attribute locale.");
+        property.setType(ProviderConfigProperty.STRING_TYPE);
+        CONFIG_PROPERTIES.add(property);
+        property = new ProviderConfigProperty();
+        property.setName(USE_FRIENDLY_NAMES);
+        property.setLabel("Use Attribute Friendly Name");
+        property.setHelpText("Define which name to give to each mapped user attribute: name or friendlyName.");
+        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+        CONFIG_PROPERTIES.add(property);
+    }
+
+    public static final String PROVIDER_ID = "saml-user-attributestatement-idp-mapper";
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return CONFIG_PROPERTIES;
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String[] getCompatibleProviders() {
+        return COMPATIBLE_PROVIDERS.clone();
+    }
+
+    @Override
+    public String getDisplayCategory() {
+        return "AttributeStatement Importer";
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "AttributeStatement Importer";
+    }
+
+    @Override
+    public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
+        String firstNameAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_FIRST_NAME);
+        String lastNameAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_LAST_NAME);
+        String emailAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_EMAIL);
+        String langAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_LANGUAGE);
+        Boolean useFriendlyNames = Boolean.valueOf(mapperModel.getConfig().get(USE_FRIENDLY_NAMES));
+        List<AttributeType> attributesInContext = findAttributesInContext(context, getAttributePattern(mapperModel));
+        for (AttributeType a : attributesInContext) {
+            String attribute = useFriendlyNames ? a.getFriendlyName() : a.getName();
+            List<String> attributeValuesInContext = a.getAttributeValue().stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.toList());
+            if (!attributeValuesInContext.isEmpty()) {
+                // set as attribute anyway
+                context.setUserAttribute(attribute, attributeValuesInContext);
+                // set as special field ?
+                if (Objects.equals(attribute, emailAttribute)) {
+                    setIfNotEmpty(context::setEmail, attributeValuesInContext);
+                } else if (Objects.equals(attribute, firstNameAttribute)) {
+                    setIfNotEmpty(context::setFirstName, attributeValuesInContext);
+                } else if (Objects.equals(attribute, lastNameAttribute)) {
+                    setIfNotEmpty(context::setLastName, attributeValuesInContext);
+                } else if (Objects.equals(attribute, langAttribute)) {
+                    context.setUserAttribute(USER_ATTR_LOCALE, attributeValuesInContext);
+                } 
+            }
+        }
+    }
+
+    @Override
+    public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
+        String firstNameAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_FIRST_NAME);
+        String lastNameAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_LAST_NAME);
+        String emailAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_EMAIL);
+        String langAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_LANGUAGE);
+        Boolean useFriendlyNames = Boolean.valueOf(mapperModel.getConfig().get(USE_FRIENDLY_NAMES));
+        List<AttributeType> attributesInContext = findAttributesInContext(context, getAttributePattern(mapperModel));
+
+        Set<String> assertedUserAttributes = new HashSet<String>();
+        for (AttributeType a : attributesInContext) {
+            String attribute = useFriendlyNames ? a.getFriendlyName() : a.getName();
+            List<String> attributeValuesInContext = a.getAttributeValue().stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.toList());
+            List<String> currentAttributeValues = user.getAttributes().get(attribute);
+            if (attributeValuesInContext == null) {
+                // attribute no longer sent by brokered idp, remove it
+                user.removeAttribute(attribute);
+            } else if (currentAttributeValues == null) {
+                // new attribute sent by brokered idp, add it
+                user.setAttribute(attribute, attributeValuesInContext);
+            } else if (!CollectionUtil.collectionEquals(attributeValuesInContext, currentAttributeValues)) {
+                // attribute sent by brokered idp has different values as before, update it
+                user.setAttribute(attribute, attributeValuesInContext);
+            }
+            if (Objects.equals(attribute, emailAttribute)) {
+                setIfNotEmpty(context::setEmail, attributeValuesInContext);
+            } else if (Objects.equals(attribute, firstNameAttribute)) {
+                setIfNotEmpty(context::setFirstName, attributeValuesInContext);
+            } else if (Objects.equals(attribute, lastNameAttribute)) {
+                setIfNotEmpty(context::setLastName, attributeValuesInContext);
+            } else if (Objects.equals(attribute, langAttribute)) {
+                if(attributeValuesInContext == null) {
+                    user.removeAttribute(USER_ATTR_LOCALE);
+                } else {
+                    user.setAttribute(USER_ATTR_LOCALE, attributeValuesInContext);
+                }
+                assertedUserAttributes.add(USER_ATTR_LOCALE);
+            } 
+            // Mark attribute as handled
+            assertedUserAttributes.add(attribute);
+        }
+        // Remove user attributes that were not referenced in assertion.
+        user.getAttributes().keySet().stream().filter(a -> !assertedUserAttributes.contains(a)).forEach(a -> user.removeAttribute(a));
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Import all saml attributes found in attributestatements in assertion into user properties or attributes.";
+    }
+
+    private Optional<Pattern> getAttributePattern(IdentityProviderMapperModel mapperModel) {
+        String attributePatternConfig = mapperModel.getConfig().get(ATTRIBUTE_NAME_PATTERN);
+        return Optional.ofNullable(attributePatternConfig != null ? Pattern.compile(attributePatternConfig) : null);
+    }
+
+    private List<AttributeType> findAttributesInContext(BrokeredIdentityContext context, Optional<Pattern> attributePattern) {
+        AssertionType assertion = (AssertionType) context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
+
+        return assertion.getAttributeStatements().stream()//
+            .flatMap(statement -> statement.getAttributes().stream())//
+            .filter(item -> !attributePattern.isPresent() || attributePattern.get().matcher(item.getAttribute().getName()).matches())//
+            .map(ASTChoiceType::getAttribute)//
+            .collect(Collectors.toList());
+    }
+
+    private void setIfNotEmpty(Consumer<String> consumer, List<String> values) {
+        if (values != null && !values.isEmpty()) {
+            consumer.accept(values.get(0));
+        }
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index dc38d59..4451b8c 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -372,13 +372,13 @@ public class SAMLEndpoint {
                     assertionElement = DocumentUtil.getElement(holder.getSamlDocument(), new QName(JBossSAMLConstants.ASSERTION.get()));
                 }
 
-                if (config.isWantAssertionsSigned() && config.isValidateSignature()) {
-                    if (!AssertionUtil.isSignatureValid(assertionElement, getIDPKeyLocator())) {
-                        logger.error("validation failed");
-                        event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
-                        event.error(Errors.INVALID_SIGNATURE);
-                        return ErrorPage.error(session, Messages.INVALID_REQUESTER);
-                    }
+                boolean signed = AssertionUtil.isSignedElement(assertionElement);
+                if ((config.isWantAssertionsSigned() && !signed)
+                        || (signed && config.isValidateSignature() && !AssertionUtil.isSignatureValid(assertionElement, getIDPKeyLocator()))) {
+                    logger.error("validation failed");
+                    event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
+                    event.error(Errors.INVALID_SIGNATURE);
+                    return ErrorPage.error(session, Messages.INVALID_REQUESTER);
                 }
 
                 AssertionType assertion = responseType.getAssertions().get(0).getAssertion();
diff --git a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
index a3f468e..2be3471 100644
--- a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
@@ -56,12 +56,14 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
     }
 
     public CredentialModel getPassword(RealmModel realm, UserModel user) {
-        List<CredentialModel> passwords;
+        List<CredentialModel> passwords = null;
         if (user instanceof CachedUserModel && !((CachedUserModel)user).isMarkedForEviction()) {
             CachedUserModel cached = (CachedUserModel)user;
             passwords = (List<CredentialModel>)cached.getCachedWith().get(PASSWORD_CACHE_KEY);
 
-        } else {
+        }
+        // if the model was marked for eviction while passwords were initialized, override it from credentialStore
+        if (! (user instanceof CachedUserModel) || ((CachedUserModel) user).isMarkedForEviction()) {
             passwords = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD);
         }
         if (passwords == null || passwords.isEmpty()) return null;
@@ -207,8 +209,10 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
             return true;
         }
 
-        hash.encode(cred.getValue(), policy.getHashIterations(), password);
-        getCredentialStore().updateCredential(realm, user, password);
+        CredentialModel newPassword = password.shallowClone();
+        hash.encode(cred.getValue(), policy.getHashIterations(), newPassword);
+        getCredentialStore().updateCredential(realm, user, newPassword);
+
         UserCache userCache = session.userCache();
         if (userCache != null) {
             userCache.evict(realm, user);
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 8f2e153..facce6c 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -343,7 +343,7 @@ public class ExportUtils {
         representation.setPolicies(policies);
 
         List<ScopeRepresentation> scopes = storeFactory.getScopeStore().findByResourceServer(settingsModel.getId()).stream().map(scope -> {
-            ScopeRepresentation rep = toRepresentation(scope, authorization);
+            ScopeRepresentation rep = toRepresentation(scope);
 
             rep.setId(null);
             rep.setPolicies(null);
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
index affaf20..d7eb01c 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -76,6 +76,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
     private String accessRequestMessage;
     private URI actionUri;
+    private String execution;
 
     private List<FormMessage> messages = null;
     private MessageType messageType = MessageType.ERROR;
@@ -230,6 +231,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                         b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath());
                         break;
                 }
+
+                if (execution != null) {
+                    b.queryParam(Constants.EXECUTION, execution);
+                }
+
                 attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
             }
         }
@@ -366,7 +372,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
             attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
 
             if (realm.isInternationalizationEnabled()) {
-                UriBuilder b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath());
+                UriBuilder b = UriBuilder.fromUri(baseUri)
+                        .path(uriInfo.getPath());
+
+                if (execution != null) {
+                    b.queryParam(Constants.EXECUTION, execution);
+                }
+
                 attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
             }
         }
@@ -591,6 +603,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
+    public LoginFormsProvider setExecution(String execution) {
+        this.execution = execution;
+        return this;
+    }
+
+    @Override
     public LoginFormsProvider setResponseHeader(String headerName, String headerValue) {
         this.httpResponseHeaders.put(headerName, headerValue);
         return this;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index 26d012b..3a7e4c0 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -269,6 +269,12 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
     }
 
     private Response checkOIDCParams() {
+        // If request is not OIDC request, but pure OAuth2 request and response_type is just 'token', then 'nonce' is not mandatory
+        boolean isOIDCRequest = TokenUtil.isOIDCRequest(request.getScope());
+        if (!isOIDCRequest && parsedResponseType.toString().equals(OIDCResponseType.TOKEN)) {
+            return null;
+        }
+
         if (parsedResponseType.isImplicitOrHybridFlow() && request.getNonce() == null) {
             ServicesLogger.LOGGER.missingParameter(OIDCLoginProtocol.NONCE_PARAM);
             event.error(Errors.INVALID_REQUEST);
@@ -354,10 +360,12 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
 
     private void checkRedirectUri() {
         String redirectUriParam = request.getRedirectUriParam();
+        boolean isOIDCRequest = TokenUtil.isOIDCRequest(request.getScope());
 
         event.detail(Details.REDIRECT_URI, redirectUriParam);
 
-        redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client);
+        // redirect_uri parameter is required per OpenID Connect, but optional per OAuth2
+        redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client, isOIDCRequest);
         if (redirectUri == null) {
             event.error(Errors.INVALID_REDIRECT_URI);
             throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 6aa13e2..4870415 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -560,13 +560,9 @@ public class TokenEndpoint {
     // https://tools.ietf.org/html/rfc7636#section-4.6
     private String generateS256CodeChallenge(String codeVerifier) throws Exception {
         MessageDigest md = MessageDigest.getInstance("SHA-256");
-        md.update(codeVerifier.getBytes());
-        StringBuilder sb = new StringBuilder();
-        for (byte b : md.digest()) {
-            String hex = String.format("%02x", b);
-            sb.append(hex);
-        }
-        String codeVerifierEncoded = Base64Url.encode(sb.toString().getBytes());
+        md.update(codeVerifier.getBytes("ISO_8859_1"));
+        byte[] digestBytes = md.digest();
+        String codeVerifierEncoded = Base64Url.encode(digestBytes);
         return codeVerifierEncoded;
     }
  
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
index dfae565..4fc73f7 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
@@ -151,7 +151,7 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide
     }
 
     private void configureAuthorizationSettings(KeycloakSession session, ClientModel client, ClientManager.InstallationAdapterConfig rep) {
-        if (new AuthorizationService(session, client, null).isEnabled()) {
+        if (new AuthorizationService(session, client, null, null).isEnabled()) {
             PolicyEnforcerConfig enforcerConfig = new PolicyEnforcerConfig();
 
             enforcerConfig.setEnforcementMode(null);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
index 09b39c4..374581d 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
@@ -64,24 +64,32 @@ public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapp
     }
 
     @Override
-    public final IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
-        setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+    public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+        setIDTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
         return token;
     }
 
     @Override
-    public final AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
-        setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+        setAccessTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
         return token;
     }
 
     @Override
-    public final AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
-        setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+    public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+        setUserInfoTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
         return token;
     }
 
-    private void setSubject(IDToken token, String pairwiseSub) {
+    protected void setIDTokenSubject(IDToken token, String pairwiseSub) {
+        token.setSubject(pairwiseSub);
+    }
+
+    protected void setAccessTokenSubject(IDToken token, String pairwiseSub) {
+        token.setSubject(pairwiseSub);
+    }
+
+    protected void setUserInfoTokenSubject(IDToken token, String pairwiseSub) {
         token.getOtherClaims().put("sub", pairwiseSub);
     }
 
@@ -115,6 +123,4 @@ public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapp
     public final String getId() {
         return "oidc-" + getIdPrefix() + PROVIDER_ID_SUFFIX;
     }
-}
-
-
+}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA256PairwiseSubMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA256PairwiseSubMapper.java
index 28a0b87..f69f2b1 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA256PairwiseSubMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA256PairwiseSubMapper.java
@@ -79,7 +79,7 @@ public class SHA256PairwiseSubMapper extends AbstractPairwiseSubMapper {
         Charset charset = Charset.forName("UTF-8");
         byte[] salt = saltStr.getBytes(charset);
         String pairwiseSub = generateSub(sectorIdentifier, localSub, salt);
-        logger.infof("local sub = '%s', pairwise sub = '%s'", localSub, pairwiseSub);
+        logger.tracef("local sub = '%s', pairwise sub = '%s'", localSub, pairwiseSub);
         return pairwiseSub;
     }
 
@@ -109,4 +109,4 @@ public class SHA256PairwiseSubMapper extends AbstractPairwiseSubMapper {
     public String getIdPrefix() {
         return PROVIDER_ID;
     }
-}
+}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
index 60f5493..c61bdd0 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
@@ -26,6 +26,7 @@ import org.keycloak.services.Urls;
 
 import javax.ws.rs.core.UriInfo;
 import java.net.URI;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -38,12 +39,16 @@ public class RedirectUtils {
 
     public static String verifyRealmRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm) {
         Set<String> validRedirects = getValidateRedirectUris(uriInfo, realm);
-        return verifyRedirectUri(uriInfo, null, redirectUri, realm, validRedirects);
+        return verifyRedirectUri(uriInfo, null, redirectUri, realm, validRedirects, true);
     }
 
     public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
+        return verifyRedirectUri(uriInfo, redirectUri, realm, client, true);
+    }
+
+    public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client, boolean requireRedirectUri) {
         if (client != null)
-            return verifyRedirectUri(uriInfo, client.getRootUrl(), redirectUri, realm, client.getRedirectUris());
+            return verifyRedirectUri(uriInfo, client.getRootUrl(), redirectUri, realm, client.getRedirectUris(), requireRedirectUri);
         return null;
     }
 
@@ -69,10 +74,16 @@ public class RedirectUtils {
         return redirects;
     }
 
-    private static String verifyRedirectUri(UriInfo uriInfo, String rootUrl, String redirectUri, RealmModel realm, Set<String> validRedirects) {
+    private static String verifyRedirectUri(UriInfo uriInfo, String rootUrl, String redirectUri, RealmModel realm, Set<String> validRedirects, boolean requireRedirectUri) {
         if (redirectUri == null) {
-            logger.debug("No Redirect URI parameter specified");
-            return null;
+            if (!requireRedirectUri) {
+                redirectUri = getSingleValidRedirectUri(validRedirects);
+            }
+
+            if (redirectUri == null) {
+                logger.debug("No Redirect URI parameter specified");
+                return null;
+            }
         } else if (validRedirects.isEmpty()) {
             logger.debug("No Redirect URIs supplied");
             redirectUri = null;
@@ -149,4 +160,14 @@ public class RedirectUtils {
         return false;
     }
 
+    private static String getSingleValidRedirectUri(Collection<String> validRedirects) {
+        if (validRedirects.size() != 1) return null;
+        String validRedirect = validRedirects.iterator().next();
+        int idx = validRedirect.indexOf("/*");
+        if (idx > -1) {
+            validRedirect = validRedirect.substring(0, idx);
+        }
+        return validRedirect;
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/scripting/AbstractEvaluatableScriptAdapter.java b/services/src/main/java/org/keycloak/scripting/AbstractEvaluatableScriptAdapter.java
new file mode 100644
index 0000000..534883a
--- /dev/null
+++ b/services/src/main/java/org/keycloak/scripting/AbstractEvaluatableScriptAdapter.java
@@ -0,0 +1,76 @@
+package org.keycloak.scripting;
+
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+
+import org.keycloak.models.ScriptModel;
+
+/**
+ * Abstract class for wrapping a {@link ScriptModel} to make it evaluatable.
+ *
+ * @author <a href="mailto:jay@anslow.me.uk">Jay Anslow</a>
+ */
+abstract class AbstractEvaluatableScriptAdapter implements EvaluatableScriptAdapter {
+    /**
+     * Holds the {@link ScriptModel}.
+     */
+    private final ScriptModel scriptModel;
+
+    AbstractEvaluatableScriptAdapter(final ScriptModel scriptModel) {
+        if (scriptModel == null) {
+            throw new IllegalArgumentException("scriptModel must not be null");
+        }
+        this.scriptModel = scriptModel;
+    }
+
+    @Override
+    public Object eval(final ScriptBindingsConfigurer bindingsConfigurer) throws ScriptExecutionException {
+        return evalUnchecked(createBindings(bindingsConfigurer));
+    }
+
+    @Override
+    public ScriptModel getScriptModel() {
+        return scriptModel;
+    }
+
+    /**
+     * Note, calling this method modifies the underlying {@link ScriptEngine},
+     * preventing concurrent use of the ScriptEngine (Nashorn's {@link ScriptEngine} and
+     * {@link javax.script.CompiledScript} is thread-safe, but {@link Bindings} isn't).
+     */
+    InvocableScriptAdapter prepareInvokableScript(final ScriptBindingsConfigurer bindingsConfigurer) {
+        final Bindings bindings = createBindings(bindingsConfigurer);
+        evalUnchecked(bindings);
+        final ScriptEngine engine = getEngine();
+        engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
+        return new InvocableScriptAdapter(scriptModel, engine);
+    }
+
+    protected String getCode() {
+        return scriptModel.getCode();
+    }
+
+    protected abstract ScriptEngine getEngine();
+
+    protected abstract Object eval(Bindings bindings) throws ScriptException;
+
+    private Object evalUnchecked(final Bindings bindings) {
+        try {
+            return eval(bindings);
+        }
+        catch (ScriptException e) {
+            throw new ScriptExecutionException(scriptModel, e);
+        }
+    }
+
+    private Bindings createBindings(final ScriptBindingsConfigurer bindingsConfigurer) {
+        if (bindingsConfigurer == null) {
+            throw new IllegalArgumentException("bindingsConfigurer must not be null");
+        }
+        final Bindings bindings = getEngine().createBindings();
+        bindingsConfigurer.configureBindings(bindings);
+        return bindings;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/scripting/CompiledEvaluatableScriptAdapter.java b/services/src/main/java/org/keycloak/scripting/CompiledEvaluatableScriptAdapter.java
new file mode 100644
index 0000000..7359dc9
--- /dev/null
+++ b/services/src/main/java/org/keycloak/scripting/CompiledEvaluatableScriptAdapter.java
@@ -0,0 +1,40 @@
+package org.keycloak.scripting;
+
+import javax.script.Bindings;
+import javax.script.CompiledScript;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+
+import org.keycloak.models.ScriptModel;
+
+/**
+ * Wraps a compiled {@link ScriptModel} so it can be evaluated.
+ *
+ * @author <a href="mailto:jay@anslow.me.uk">Jay Anslow</a>
+ */
+class CompiledEvaluatableScriptAdapter extends AbstractEvaluatableScriptAdapter {
+    /**
+     * Holds the {@link CompiledScript} for the {@link ScriptModel}.
+     */
+    private final CompiledScript compiledScript;
+
+    CompiledEvaluatableScriptAdapter(final ScriptModel scriptModel, final CompiledScript compiledScript) {
+        super(scriptModel);
+
+        if (compiledScript == null) {
+            throw new IllegalArgumentException("compiledScript must not be null");
+        }
+
+        this.compiledScript = compiledScript;
+    }
+
+    @Override
+    protected ScriptEngine getEngine() {
+        return compiledScript.getEngine();
+    }
+
+    @Override
+    protected Object eval(final Bindings bindings) throws ScriptException {
+        return compiledScript.eval(bindings);
+    }
+}
diff --git a/services/src/main/java/org/keycloak/scripting/DefaultScriptingProvider.java b/services/src/main/java/org/keycloak/scripting/DefaultScriptingProvider.java
index 601da8e..d781460 100644
--- a/services/src/main/java/org/keycloak/scripting/DefaultScriptingProvider.java
+++ b/services/src/main/java/org/keycloak/scripting/DefaultScriptingProvider.java
@@ -16,12 +16,14 @@
  */
 package org.keycloak.scripting;
 
-import org.keycloak.models.ScriptModel;
-
 import javax.script.Bindings;
-import javax.script.ScriptContext;
+import javax.script.Compilable;
+import javax.script.CompiledScript;
 import javax.script.ScriptEngine;
 import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+
+import org.keycloak.models.ScriptModel;
 
 /**
  * A {@link ScriptingProvider} that uses a {@link ScriptEngineManager} to evaluate scripts with a {@link ScriptEngine}.
@@ -32,8 +34,7 @@ public class DefaultScriptingProvider implements ScriptingProvider {
 
     private final ScriptEngineManager scriptEngineManager;
 
-    public DefaultScriptingProvider(ScriptEngineManager scriptEngineManager) {
-
+    DefaultScriptingProvider(ScriptEngineManager scriptEngineManager) {
         if (scriptEngineManager == null) {
             throw new IllegalStateException("scriptEngineManager must not be null!");
         }
@@ -44,13 +45,22 @@ public class DefaultScriptingProvider implements ScriptingProvider {
     /**
      * Wraps the provided {@link ScriptModel} in a {@link javax.script.Invocable} instance with bindings configured through the {@link ScriptBindingsConfigurer}.
      *
-     * @param scriptModel  must not be {@literal null}
+     * @param scriptModel        must not be {@literal null}
      * @param bindingsConfigurer must not be {@literal null}
-     * @return
      */
     @Override
     public InvocableScriptAdapter prepareInvocableScript(ScriptModel scriptModel, ScriptBindingsConfigurer bindingsConfigurer) {
+        final AbstractEvaluatableScriptAdapter evaluatable = prepareEvaluatableScript(scriptModel);
+        return evaluatable.prepareInvokableScript(bindingsConfigurer);
+    }
 
+    /**
+     * Wraps the provided {@link ScriptModel} in a {@link javax.script.Invocable} instance with bindings configured through the {@link ScriptBindingsConfigurer}.
+     *
+     * @param scriptModel must not be {@literal null}
+     */
+    @Override
+    public AbstractEvaluatableScriptAdapter prepareEvaluatableScript(ScriptModel scriptModel) {
         if (scriptModel == null) {
             throw new IllegalArgumentException("script must not be null");
         }
@@ -59,13 +69,18 @@ public class DefaultScriptingProvider implements ScriptingProvider {
             throw new IllegalArgumentException("script must not be null or empty");
         }
 
-        if (bindingsConfigurer == null) {
-            throw new IllegalArgumentException("bindingsConfigurer must not be null");
-        }
+        ScriptEngine engine = createPreparedScriptEngine(scriptModel);
 
-        ScriptEngine engine = createPreparedScriptEngine(scriptModel, bindingsConfigurer);
-
-        return new InvocableScriptAdapter(scriptModel, engine);
+        if (engine instanceof Compilable) {
+            try {
+                final CompiledScript compiledScript = ((Compilable) engine).compile(scriptModel.getCode());
+                return new CompiledEvaluatableScriptAdapter(scriptModel, compiledScript);
+            }
+            catch (ScriptException e) {
+                throw new ScriptExecutionException(scriptModel, e);
+            }
+        }
+        return new UncompiledEvaluatableScriptAdapter(scriptModel, engine);
     }
 
     //TODO allow scripts to be maintained independently of other components, e.g. with dedicated persistence
@@ -74,38 +89,27 @@ public class DefaultScriptingProvider implements ScriptingProvider {
 
     @Override
     public ScriptModel createScript(String realmId, String mimeType, String scriptName, String scriptCode, String scriptDescription) {
+        return new Script(null /* scriptId */, realmId, scriptName, mimeType, scriptCode, scriptDescription);
+    }
 
-        ScriptModel script = new Script(null /* scriptId */, realmId, scriptName, mimeType, scriptCode, scriptDescription);
-        return script;
+    @Override
+    public void close() {
+        //NOOP
     }
 
     /**
      * Looks-up a {@link ScriptEngine} with prepared {@link Bindings} for the given {@link ScriptModel Script}.
-     *
-     * @param script
-     * @param bindingsConfigurer
-     * @return
      */
-    private ScriptEngine createPreparedScriptEngine(ScriptModel script, ScriptBindingsConfigurer bindingsConfigurer) {
-
+    private ScriptEngine createPreparedScriptEngine(ScriptModel script) {
         ScriptEngine scriptEngine = lookupScriptEngineFor(script);
 
         if (scriptEngine == null) {
             throw new IllegalStateException("Could not find ScriptEngine for script: " + script);
         }
 
-        configureBindings(bindingsConfigurer, scriptEngine);
-
         return scriptEngine;
     }
 
-    private void configureBindings(ScriptBindingsConfigurer bindingsConfigurer, ScriptEngine engine) {
-
-        Bindings bindings = engine.createBindings();
-        bindingsConfigurer.configureBindings(bindings);
-        engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
-    }
-
     /**
      * Looks-up a {@link ScriptEngine} based on the MIME-type provided by the given {@link Script}.
      */
@@ -114,13 +118,9 @@ public class DefaultScriptingProvider implements ScriptingProvider {
         try {
             Thread.currentThread().setContextClassLoader(DefaultScriptingProvider.class.getClassLoader());
             return scriptEngineManager.getEngineByMimeType(script.getMimeType());
-        } finally {
+        }
+        finally {
             Thread.currentThread().setContextClassLoader(cl);
         }
     }
-
-    @Override
-    public void close() {
-        //NOOP
-    }
 }
diff --git a/services/src/main/java/org/keycloak/scripting/UncompiledEvaluatableScriptAdapter.java b/services/src/main/java/org/keycloak/scripting/UncompiledEvaluatableScriptAdapter.java
new file mode 100644
index 0000000..8464fdf
--- /dev/null
+++ b/services/src/main/java/org/keycloak/scripting/UncompiledEvaluatableScriptAdapter.java
@@ -0,0 +1,39 @@
+package org.keycloak.scripting;
+
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+
+import org.keycloak.models.ScriptModel;
+
+/**
+ * Wraps an uncompiled {@link ScriptModel} so it can be evaluated.
+ *
+ * @author <a href="mailto:jay@anslow.me.uk">Jay Anslow</a>
+ */
+class UncompiledEvaluatableScriptAdapter extends AbstractEvaluatableScriptAdapter {
+    /**
+     * Holds the {@link ScriptEngine} instance.
+     */
+    private final ScriptEngine scriptEngine;
+
+    UncompiledEvaluatableScriptAdapter(final ScriptModel scriptModel, final ScriptEngine scriptEngine) {
+        super(scriptModel);
+        if (scriptEngine == null) {
+            throw new IllegalArgumentException("scriptEngine must not be null");
+        }
+
+        this.scriptEngine = scriptEngine;
+    }
+
+    @Override
+    protected ScriptEngine getEngine() {
+        return scriptEngine;
+    }
+
+    @Override
+    protected Object eval(final Bindings bindings) throws ScriptException {
+        return getEngine().eval(getCode(), bindings);
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
index 2974b6c..da693aa 100755
--- a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
@@ -23,6 +23,7 @@ import org.keycloak.models.ClientInitialAccessModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.ClientRepresentation;
@@ -65,7 +66,8 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
         }
 
         try {
-            ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
+            RealmModel realm = session.getContext().getRealm();
+            ClientModel clientModel = RepresentationToModel.createClient(session, realm, client, true);
 
             ClientRegistrationPolicyManager.triggerAfterRegister(context, registrationAuth, clientModel);
 
@@ -78,7 +80,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
 
             if (auth.isInitialAccessToken()) {
                 ClientInitialAccessModel initialAccessModel = auth.getInitialAccessModel();
-                initialAccessModel.decreaseRemainingCount();
+                session.realms().decreaseRemainingCount(realm, initialAccessModel);
             }
 
             event.client(client.getClientId()).success();
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
index 8382155..dfed5aa 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
@@ -31,12 +31,15 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
 import org.keycloak.representations.JsonWebToken;
 import org.keycloak.services.ErrorResponseException;
 import org.keycloak.services.clientregistration.policy.RegistrationAuth;
 import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyException;
 import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyManager;
+import org.keycloak.services.managers.RealmManager;
 import org.keycloak.util.TokenUtil;
 
 import javax.ws.rs.core.HttpHeaders;
@@ -85,7 +88,7 @@ public class ClientRegistrationAuth {
         jwt = tokenVerification.getJwt();
 
         if (isInitialAccessToken()) {
-            initialAccessModel = session.sessions().getClientInitialAccessModel(session.getContext().getRealm(), jwt.getId());
+            initialAccessModel = session.realms().getClientInitialAccessModel(session.getContext().getRealm(), jwt.getId());
             if (initialAccessModel == null) {
                 throw unauthorized("Initial Access Token not found");
             }
@@ -231,42 +234,70 @@ public class ClientRegistrationAuth {
         return initialAccessModel;
     }
 
-    private boolean hasRole(String... role) {
+    private boolean hasRole(String... roles) {
         try {
-            Map<String, Object> otherClaims = jwt.getOtherClaims();
-            if (otherClaims != null) {
-                Map<String, Map<String, List<String>>> resourceAccess = (Map<String, Map<String, List<String>>>) jwt.getOtherClaims().get("resource_access");
-                if (resourceAccess == null) {
-                    return false;
-                }
+            if (jwt.getIssuedFor().equals(Constants.ADMIN_CLI_CLIENT_ID)
+                    || jwt.getIssuedFor().equals(Constants.ADMIN_CONSOLE_CLIENT_ID)) {
+                return hasRoleInModel(roles);
 
-                List<String> roles = null;
+            } else {
+                return hasRoleInToken(roles);
+            }
+        } catch (Throwable t) {
+            return false;
+        }
+    }
 
-                Map<String, List<String>> map;
-                if (realm.getName().equals(Config.getAdminRealm())) {
-                    map = resourceAccess.get(realm.getMasterAdminClient().getClientId());
-                } else {
-                    map = resourceAccess.get(Constants.REALM_MANAGEMENT_CLIENT_ID);
-                }
+    private boolean hasRoleInModel(String[] roles) {
+        ClientModel roleNamespace;
+        UserModel user = session.users().getUserById(jwt.getSubject(), realm);
+        if (user == null) {
+            return false;
+        }
+        if (realm.getName().equals(Config.getAdminRealm())) {
+            roleNamespace = realm.getMasterAdminClient();
+        } else {
+            roleNamespace = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID);
+        }
+        for (String role : roles) {
+            RoleModel roleModel = roleNamespace.getRole(role);
+            if (user.hasRole(roleModel)) return true;
+        }
+        return false;
+    }
 
-                if (map != null) {
-                    roles = map.get("roles");
-                }
+    private boolean hasRoleInToken(String[] role) {
+        Map<String, Object> otherClaims = jwt.getOtherClaims();
+        if (otherClaims != null) {
+            Map<String, Map<String, List<String>>> resourceAccess = (Map<String, Map<String, List<String>>>) jwt.getOtherClaims().get("resource_access");
+            if (resourceAccess == null) {
+                return false;
+            }
 
-                if (roles == null) {
-                    return false;
-                }
+            List<String> roles = null;
+
+            Map<String, List<String>> map;
+            if (realm.getName().equals(Config.getAdminRealm())) {
+                map = resourceAccess.get(realm.getMasterAdminClient().getClientId());
+            } else {
+                map = resourceAccess.get(Constants.REALM_MANAGEMENT_CLIENT_ID);
+            }
 
-                for (String r : role) {
-                    if (roles.contains(r)) {
-                        return true;
-                    }
+            if (map != null) {
+                roles = map.get("roles");
+            }
+
+            if (roles == null) {
+                return false;
+            }
+
+            for (String r : role) {
+                if (roles.contains(r)) {
+                    return true;
                 }
             }
-            return false;
-        } catch (Throwable t) {
-            return false;
         }
+        return false;
     }
 
     private boolean authenticateClient(ClientModel client) {
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 0134229..0869c5d 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -30,6 +30,7 @@ import org.keycloak.provider.ProviderManager;
 import org.keycloak.provider.ProviderManagerDeployer;
 import org.keycloak.provider.ProviderManagerRegistry;
 import org.keycloak.provider.Spi;
+import org.keycloak.services.resources.admin.permissions.AdminPermissions;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -93,6 +94,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
         }
         // make the session factory ready for hot deployment
         ProviderManagerRegistry.SINGLETON.setDeployer(this);
+        AdminPermissions.registerListener(this);
 
     }
     protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 07bd1f6..6c91759 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -644,12 +644,15 @@ public class AuthenticationManager {
 
             // Skip grant screen if everything was already approved by this user
             if (realmRoles.size() > 0 || resourceRoles.size() > 0 || protocolMappers.size() > 0) {
+                String execution = AuthenticatedClientSessionModel.Action.OAUTH_GRANT.name();
+
                 accessCode.
 
                         setAction(AuthenticatedClientSessionModel.Action.REQUIRED_ACTIONS.name());
-                authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, AuthenticatedClientSessionModel.Action.OAUTH_GRANT.name());
+                authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution);
 
                 return session.getProvider(LoginFormsProvider.class)
+                        .setExecution(execution)
                         .setClientSessionCode(accessCode.getCode())
                         .setAccessRequest(realmRoles, resourceRoles, protocolMappers)
                         .createOAuthGrant();
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index a83d80d..bab0440 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -159,7 +159,6 @@ public class RealmManager {
             ClientModel realmAdminApp = realm.getClientByClientId(realmAdminApplicationClientId);
             adminRole = realmAdminApp.getRole(AdminRoles.REALM_ADMIN);
         }
-        adminConsole.addScopeMapping(adminRole);
     }
 
     protected void setupAdminConsoleLocaleMapper(RealmModel realm) {
@@ -194,10 +193,21 @@ public class RealmManager {
                 ClientModel realmAdminApp = realm.getClientByClientId(realmAdminApplicationClientId);
                 adminRole = realmAdminApp.getRole(AdminRoles.REALM_ADMIN);
             }
-            adminCli.addScopeMapping(adminRole);
         }
 
     }
+    public void addQueryCompositeRoles(ClientModel realmAccess) {
+        RoleModel queryClients = realmAccess.getRole(AdminRoles.QUERY_CLIENTS);
+        RoleModel queryUsers = realmAccess.getRole(AdminRoles.QUERY_USERS);
+        RoleModel queryGroups = realmAccess.getRole(AdminRoles.QUERY_GROUPS);
+
+        RoleModel viewClients = realmAccess.getRole(AdminRoles.VIEW_CLIENTS);
+        viewClients.addCompositeRole(queryClients);
+        RoleModel viewUsers = realmAccess.getRole(AdminRoles.VIEW_USERS);
+        viewUsers.addCompositeRole(queryUsers);
+        viewUsers.addCompositeRole(queryGroups);
+    }
+
 
     public String getRealmAdminClientId(RealmModel realm) {
         return Constants.REALM_MANAGEMENT_CLIENT_ID;
@@ -325,6 +335,7 @@ public class RealmManager {
             role.setScopeParamRequired(false);
             adminRole.addCompositeRole(role);
         }
+        addQueryCompositeRoles(realmAdminApp);
     }
 
     private void checkMasterAdminManagementRoles(RealmModel realm) {
@@ -338,6 +349,7 @@ public class RealmManager {
                 addAndSetAdminRole(r, masterAdminClient, adminRole);
             }
         }
+        addQueryCompositeRoles(masterAdminClient);
     }
 
 
@@ -360,6 +372,7 @@ public class RealmManager {
         for (String r : AdminRoles.ALL_REALM_ROLES) {
             addAndSetAdminRole(r, realmAdminClient, adminRole);
         }
+        addQueryCompositeRoles(realmAdminClient);
     }
 
     private void addAndSetAdminRole(String roleName, ClientModel parentClient, RoleModel parentRole) {
@@ -383,6 +396,7 @@ public class RealmManager {
                 addAndSetAdminRole(r, realmAdminClient, adminRole);
             }
         }
+        addQueryCompositeRoles(realmAdminClient);
     }
 
 
@@ -500,7 +514,8 @@ public class RealmManager {
         // I need to postpone impersonation because it needs "realm-management" client and its roles set
         if (postponeImpersonationSetup) {
             setupImpersonationService(realm);
-        }
+            String realmAdminClientId = getRealmAdminClientId(realm);
+         }
 
         if (postponeAdminCliSetup) {
             setupAdminCli(realm);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminAuth.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminAuth.java
index 924b35e..98d4538 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminAuth.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminAuth.java
@@ -98,4 +98,7 @@ public class AdminAuth {
         return false;
     }
 
+    public enum Resource {
+        CLIENT, USER, REALM, EVENTS, IDENTITY_PROVIDER, IMPERSONATION, AUTHORIZATION
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
index 5db1ea4..650ac75 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
@@ -38,6 +38,8 @@ import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.resources.Cors;
 import org.keycloak.services.resources.admin.info.ServerInfoAdminResource;
+import org.keycloak.services.resources.admin.permissions.AdminPermissions;
+import org.keycloak.services.resources.admin.permissions.RealmsPermissionEvaluator;
 import org.keycloak.theme.Theme;
 import org.keycloak.theme.ThemeProvider;
 
@@ -229,7 +231,7 @@ public class AdminRoot {
         handlePreflightRequest();
 
         AdminAuth auth = authenticateRealmAdminRequest(headers);
-        if (!isAdmin(auth)) {
+        if (!AdminPermissions.realms(session, auth).isAdmin()) {
             throw new ForbiddenException();
         }
 
@@ -244,26 +246,6 @@ public class AdminRoot {
         return adminResource;
     }
 
-    protected boolean isAdmin(AdminAuth auth) {
-
-        RealmManager realmManager = new RealmManager(session);
-        if (auth.getRealm().equals(realmManager.getKeycloakAdminstrationRealm())) {
-            if (auth.hasOneOfRealmRole(AdminRoles.ADMIN, AdminRoles.CREATE_REALM)) {
-                return true;
-            }
-            for (RealmModel realm : session.realms().getRealms()) {
-                ClientModel client = realm.getMasterAdminClient();
-                if (auth.hasOneOfAppRole(client, AdminRoles.ALL_REALM_ROLES)) {
-                    return true;
-                }
-            }
-            return false;
-        } else {
-            ClientModel client = auth.getRealm().getClientByClientId(realmManager.getRealmAdminClientId(auth.getRealm()));
-            return auth.hasOneOfAppRole(client, AdminRoles.ALL_REALM_ROLES);
-        }
-    }
-
     protected void handlePreflightRequest() {
         if (request.getHttpMethod().equalsIgnoreCase("OPTIONS")) {
             logger.debug("Cors admin pre-flight");
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java
index 5ac7593..c6064bb 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java
@@ -18,6 +18,7 @@ package org.keycloak.services.resources.admin;
 
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.NotFoundException;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
@@ -26,6 +27,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserLoginFailureModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.services.managers.BruteForceProtector;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
@@ -48,7 +50,7 @@ import java.util.Map;
  */
 public class AttackDetectionResource {
     protected static final Logger logger = Logger.getLogger(AttackDetectionResource.class);
-    protected RealmAuth auth;
+    protected AdminPermissionEvaluator auth;
     protected RealmModel realm;
     private AdminEventBuilder adminEvent;
 
@@ -64,12 +66,10 @@ public class AttackDetectionResource {
     @Context
     protected HttpHeaders headers;
 
-    public AttackDetectionResource(RealmAuth auth, RealmModel realm, AdminEventBuilder adminEvent) {
+    public AttackDetectionResource(AdminPermissionEvaluator auth, RealmModel realm, AdminEventBuilder adminEvent) {
         this.auth = auth;
         this.realm = realm;
         this.adminEvent = adminEvent.realm(realm).resource(ResourceType.USER_LOGIN_FAILURE);
-
-        auth.init(RealmAuth.Resource.USER);
     }
 
     /**
@@ -83,7 +83,12 @@ public class AttackDetectionResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Map<String, Object> bruteForceUserStatus(@PathParam("userId") String userId) {
-        auth.requireView();
+        UserModel user = session.users().getUserById(userId, realm);
+        if (user == null) {
+            auth.users().requireView();
+        } else {
+            auth.users().requireView(user);
+        }
 
         Map<String, Object> data = new HashMap<>();
         data.put("disabled", false);
@@ -92,7 +97,6 @@ public class AttackDetectionResource {
         data.put("lastIPFailure", "n/a");
         if (!realm.isBruteForceProtected()) return data;
 
-        UserModel user = session.users().getUserById(userId, realm);
 
         UserLoginFailureModel model = session.sessions().getUserLoginFailure(realm, userId);
         if (model == null) return data;
@@ -115,8 +119,12 @@ public class AttackDetectionResource {
     @Path("brute-force/users/{userId}")
     @DELETE
     public void clearBruteForceForUser(@PathParam("userId") String userId) {
-        auth.requireManage();
-
+        UserModel user = session.users().getUserById(userId, realm);
+        if (user == null) {
+            auth.users().requireManage();
+        } else {
+            auth.users().requireManage(user);
+        }
         UserLoginFailureModel model = session.sessions().getUserLoginFailure(realm, userId);
         if (model != null) {
             session.sessions().removeUserLoginFailure(realm, userId);
@@ -133,7 +141,7 @@ public class AttackDetectionResource {
     @Path("brute-force/users")
     @DELETE
     public void clearAllBruteForce() {
-        auth.requireManage();
+        auth.users().requireManage();
 
         session.sessions().removeAllUserLoginFailures(realm);
         adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
index 2f7362b..61f6254 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
@@ -50,6 +50,7 @@ import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
 import org.keycloak.representations.idm.ConfigPropertyRepresentation;
 import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
 import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 import org.keycloak.utils.CredentialHelper;
 
 import javax.ws.rs.Consumes;
@@ -80,18 +81,17 @@ public class AuthenticationManagementResource {
 
     private final RealmModel realm;
     private final KeycloakSession session;
-    private RealmAuth auth;
+    private AdminPermissionEvaluator auth;
     private AdminEventBuilder adminEvent;
     @Context
     private UriInfo uriInfo;
 
     protected static final Logger logger = Logger.getLogger(AuthenticationManagementResource.class);
 
-    public AuthenticationManagementResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public AuthenticationManagementResource(RealmModel realm, KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.realm = realm;
         this.session = session;
         this.auth = auth;
-        this.auth.init(RealmAuth.Resource.REALM);
         this.adminEvent = adminEvent.resource(ResourceType.AUTH_FLOW);
     }
 
@@ -105,7 +105,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<Map<String, Object>> getFormProviders() {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         List<ProviderFactory> factories = session.getKeycloakSessionFactory().getProviderFactories(FormAuthenticator.class);
         return buildProviderMetadata(factories);
@@ -121,7 +121,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<Map<String, Object>> getAuthenticatorProviders() {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         List<ProviderFactory> factories = session.getKeycloakSessionFactory().getProviderFactories(Authenticator.class);
         return buildProviderMetadata(factories);
@@ -137,7 +137,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<Map<String, Object>> getClientAuthenticatorProviders() {
-        auth.requireAny();
+        auth.requireAnyAdminRole();
 
         List<ProviderFactory> factories = session.getKeycloakSessionFactory().getProviderFactories(ClientAuthenticator.class);
         return buildProviderMetadata(factories);
@@ -167,7 +167,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<Map<String, Object>> getFormActionProviders() {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         List<ProviderFactory> factories = session.getKeycloakSessionFactory().getProviderFactories(FormAction.class);
         return buildProviderMetadata(factories);
@@ -184,7 +184,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<AuthenticationFlowRepresentation> getFlows() {
-        auth.requireAny();
+        auth.realm().requireViewRealm();
 
         List<AuthenticationFlowRepresentation> flows = new LinkedList<>();
         for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) {
@@ -207,7 +207,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Consumes(MediaType.APPLICATION_JSON)
     public Response createFlow(AuthenticationFlowRepresentation flow) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         if (flow.getAlias() == null || flow.getAlias().isEmpty()) {
             return ErrorResponse.exists("Failed to create flow with empty alias name");
@@ -235,7 +235,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public AuthenticationFlowRepresentation getFlow(@PathParam("id") String id) {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         AuthenticationFlowModel flow = realm.getAuthenticationFlowById(id);
         if (flow == null) {
@@ -253,7 +253,7 @@ public class AuthenticationManagementResource {
     @DELETE
     @NoCache
     public void deleteFlow(@PathParam("id") String id) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
         
         deleteFlow(id, true);
     }
@@ -292,7 +292,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Consumes(MediaType.APPLICATION_JSON)
     public Response copy(@PathParam("flowAlias") String flowAlias, Map<String, String> data) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         String newName = data.get("newName");
         if (realm.getFlowByAlias(newName) != null) {
@@ -351,7 +351,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Consumes(MediaType.APPLICATION_JSON)
     public Response addExecutionFlow(@PathParam("flowAlias") String flowAlias, Map<String, String> data) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         AuthenticationFlowModel parentFlow = realm.getFlowByAlias(flowAlias);
         if (parentFlow == null) {
@@ -403,7 +403,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Consumes(MediaType.APPLICATION_JSON)
     public void addExecution(@PathParam("flowAlias") String flowAlias, Map<String, String> data) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         AuthenticationFlowModel parentFlow = realm.getFlowByAlias(flowAlias);
         if (parentFlow == null) {
@@ -450,7 +450,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Response getExecutions(@PathParam("flowAlias") String flowAlias) {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
         if (flow == null) {
@@ -535,7 +535,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Consumes(MediaType.APPLICATION_JSON)
     public void updateExecutions(@PathParam("flowAlias") String flowAlias, AuthenticationExecutionInfoRepresentation rep) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
         if (flow == null) {
@@ -566,7 +566,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Consumes(MediaType.APPLICATION_JSON)
     public Response addExecution(AuthenticationExecutionRepresentation execution) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         AuthenticationExecutionModel model = RepresentationToModel.toModel(realm, execution);
         AuthenticationFlowModel parentFlow = getParentFlow(model);
@@ -601,7 +601,7 @@ public class AuthenticationManagementResource {
     @POST
     @NoCache
     public void raisePriority(@PathParam("executionId") String execution) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution);
         if (model == null) {
@@ -647,7 +647,7 @@ public class AuthenticationManagementResource {
     @POST
     @NoCache
     public void lowerPriority(@PathParam("executionId") String execution) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution);
         if (model == null) {
@@ -687,7 +687,7 @@ public class AuthenticationManagementResource {
     @DELETE
     @NoCache
     public void removeExecution(@PathParam("executionId") String execution) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution);
         if (model == null) {
@@ -723,7 +723,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Consumes(MediaType.APPLICATION_JSON)
     public Response newExecutionConfig(@PathParam("executionId") String execution, AuthenticatorConfigRepresentation json) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution);
         if (model == null) {
@@ -753,7 +753,7 @@ public class AuthenticationManagementResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("executionId") String execution,@PathParam("id") String id) {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id);
         if (config == null) {
@@ -773,7 +773,7 @@ public class AuthenticationManagementResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<Map<String, String>> getUnregisteredRequiredActions() {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         List<ProviderFactory> factories = session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class);
         List<Map<String, String>> unregisteredList = new LinkedList<>();
@@ -807,7 +807,7 @@ public class AuthenticationManagementResource {
     @Consumes(MediaType.APPLICATION_JSON)
     @NoCache
     public void registerRequiredAction(Map<String, String> data) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         String providerId = data.get("providerId");
         String name = data.get("name");
@@ -834,7 +834,7 @@ public class AuthenticationManagementResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RequiredActionProviderRepresentation> getRequiredActions() {
-        auth.requireAny();
+        auth.requireAnyAdminRole();
 
         List<RequiredActionProviderRepresentation> list = new LinkedList<>();
         for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
@@ -863,7 +863,7 @@ public class AuthenticationManagementResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public RequiredActionProviderRepresentation getRequiredAction(@PathParam("alias") String alias) {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias);
         if (model == null) {
@@ -883,7 +883,7 @@ public class AuthenticationManagementResource {
     @PUT
     @Consumes(MediaType.APPLICATION_JSON)
     public void updateRequiredAction(@PathParam("alias") String alias, RequiredActionProviderRepresentation rep) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias);
         if (model == null) {
@@ -909,7 +909,7 @@ public class AuthenticationManagementResource {
     @Path("required-actions/{alias}")
     @DELETE
     public void removeRequiredAction(@PathParam("alias") String alias) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias);
         if (model == null) {
@@ -928,7 +928,7 @@ public class AuthenticationManagementResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public AuthenticatorConfigInfoRepresentation getAuthenticatorConfigDescription(@PathParam("providerId") String providerId) {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         ConfigurableAuthenticatorFactory factory = CredentialHelper.getConfigurableAuthenticatorFactory(session, providerId);
         if (factory == null) {
@@ -959,7 +959,7 @@ public class AuthenticationManagementResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public Map<String, List<ConfigPropertyRepresentation>> getPerClientConfigDescription() {
-        auth.requireAny();
+        auth.requireAnyAdminRole();
 
         List<ProviderFactory> factories = session.getKeycloakSessionFactory().getProviderFactories(ClientAuthenticator.class);
 
@@ -991,7 +991,7 @@ public class AuthenticationManagementResource {
     @NoCache
     @Consumes(MediaType.APPLICATION_JSON)
     public Response createAuthenticatorConfig(AuthenticatorConfigRepresentation rep) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         AuthenticatorConfigModel config = realm.addAuthenticatorConfig(RepresentationToModel.toModel(rep));
         adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTHENTICATOR_CONFIG).resourcePath(uriInfo, config.getId()).representation(rep).success();
@@ -1007,7 +1007,7 @@ public class AuthenticationManagementResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("id") String id) {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id);
         if (config == null) {
@@ -1025,7 +1025,7 @@ public class AuthenticationManagementResource {
     @DELETE
     @NoCache
     public void removeAuthenticatorConfig(@PathParam("id") String id) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id);
         if (config == null) {
@@ -1057,7 +1057,7 @@ public class AuthenticationManagementResource {
     @Consumes(MediaType.APPLICATION_JSON)
     @NoCache
     public void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigRepresentation rep) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         AuthenticatorConfigModel exists = realm.getAuthenticatorConfigById(id);
         if (exists == null) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
index 1f4277b..a798414 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
@@ -37,10 +37,12 @@ import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.KeyStoreConfig;
 import org.keycloak.representations.idm.CertificateRepresentation;
 import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 import org.keycloak.services.util.CertificateInfoHelper;
 import org.keycloak.util.JWKSUtils;
 import org.keycloak.util.JsonSerialization;
 
+import javax.ws.rs.BadRequestException;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
@@ -73,13 +75,13 @@ public class ClientAttributeCertificateResource {
     public static final String JSON_WEB_KEY_SET = "JSON Web Key Set";
 
     protected RealmModel realm;
-    private RealmAuth auth;
+    private AdminPermissionEvaluator auth;
     protected ClientModel client;
     protected KeycloakSession session;
     protected AdminEventBuilder adminEvent;
     protected String attributePrefix;
 
-    public ClientAttributeCertificateResource(RealmModel realm, RealmAuth auth, ClientModel client, KeycloakSession session, String attributePrefix, AdminEventBuilder adminEvent) {
+    public ClientAttributeCertificateResource(RealmModel realm, AdminPermissionEvaluator auth, ClientModel client, KeycloakSession session, String attributePrefix, AdminEventBuilder adminEvent) {
         this.realm = realm;
         this.auth = auth;
         this.client = client;
@@ -97,11 +99,7 @@ public class ClientAttributeCertificateResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public CertificateRepresentation getKeyInfo() {
-        auth.requireView();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireView(client);
 
         CertificateRepresentation info = CertificateInfoHelper.getCertificateFromClient(client, attributePrefix);
         return info;
@@ -117,11 +115,7 @@ public class ClientAttributeCertificateResource {
     @Path("generate")
     @Produces(MediaType.APPLICATION_JSON)
     public CertificateRepresentation generate() {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireConfigure(client);
 
         CertificateRepresentation info = KeycloakModelUtils.generateKeyPairCertificate(client.getClientId());
 
@@ -145,11 +139,7 @@ public class ClientAttributeCertificateResource {
     @Consumes(MediaType.MULTIPART_FORM_DATA)
     @Produces(MediaType.APPLICATION_JSON)
     public CertificateRepresentation uploadJks(@Context final UriInfo uriInfo, MultipartFormDataInput input) throws IOException {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireConfigure(client);
 
         try {
             CertificateRepresentation info = getCertFromRequest(input);
@@ -175,11 +165,7 @@ public class ClientAttributeCertificateResource {
     @Consumes(MediaType.MULTIPART_FORM_DATA)
     @Produces(MediaType.APPLICATION_JSON)
     public CertificateRepresentation uploadJksCertificate(@Context final UriInfo uriInfo, MultipartFormDataInput input) throws IOException {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireConfigure(client);
 
         try {
             CertificateRepresentation info = getCertFromRequest(input);
@@ -194,10 +180,12 @@ public class ClientAttributeCertificateResource {
     }
 
     private CertificateRepresentation getCertFromRequest(MultipartFormDataInput input) throws IOException {
-        auth.requireManage();
+        auth.clients().requireManage(client);
         CertificateRepresentation info = new CertificateRepresentation();
         Map<String, List<InputPart>> uploadForm = input.getFormDataMap();
-        String keystoreFormat = uploadForm.get("keystoreFormat").get(0).getBodyAsString();
+        List<InputPart> keystoreFormatPart = uploadForm.get("keystoreFormat");
+        if (keystoreFormatPart == null) throw new BadRequestException();
+        String keystoreFormat = keystoreFormatPart.get(0).getBodyAsString();
         List<InputPart> inputParts = uploadForm.get("file");
         if (keystoreFormat.equals(CERTIFICATE_PEM)) {
             String pem = StreamUtil.readString(inputParts.get(0).getBody(InputStream.class, null));
@@ -279,11 +267,7 @@ public class ClientAttributeCertificateResource {
     @Produces(MediaType.APPLICATION_OCTET_STREAM)
     @Consumes(MediaType.APPLICATION_JSON)
     public byte[] getKeystore(final KeyStoreConfig config) {
-        auth.requireView();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireView(client);
 
         if (config.getFormat() != null && !config.getFormat().equals("JKS") && !config.getFormat().equals("PKCS12")) {
             throw new NotAcceptableException("Only support jks or pkcs12 format.");
@@ -322,11 +306,7 @@ public class ClientAttributeCertificateResource {
     @Produces(MediaType.APPLICATION_OCTET_STREAM)
     @Consumes(MediaType.APPLICATION_JSON)
     public byte[] generateAndGetKeystore(final KeyStoreConfig config) {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireConfigure(client);
 
         if (config.getFormat() != null && !config.getFormat().equals("JKS") && !config.getFormat().equals("PKCS12")) {
             throw new NotAcceptableException("Only support jks or pkcs12 format.");
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
index dfd28cc..609b9b0 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
@@ -25,6 +25,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
 import org.keycloak.representations.idm.ClientInitialAccessPresentation;
 import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.Consumes;
@@ -48,7 +49,7 @@ import java.util.List;
  */
 public class ClientInitialAccessResource {
 
-    private final RealmAuth auth;
+    private final AdminPermissionEvaluator auth;
     private final RealmModel realm;
     private final AdminEventBuilder adminEvent;
 
@@ -58,12 +59,11 @@ public class ClientInitialAccessResource {
     @Context
     protected UriInfo uriInfo;
 
-    public ClientInitialAccessResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public ClientInitialAccessResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.auth = auth;
         this.realm = realm;
         this.adminEvent = adminEvent.resource(ResourceType.CLIENT_INITIAL_ACCESS_MODEL);
 
-        auth.init(RealmAuth.Resource.CLIENT);
     }
 
     /**
@@ -76,12 +76,12 @@ public class ClientInitialAccessResource {
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
     public ClientInitialAccessPresentation create(ClientInitialAccessCreatePresentation config, @Context final HttpServletResponse response) {
-        auth.requireManage();
+        auth.clients().requireManage();
 
         int expiration = config.getExpiration() != null ? config.getExpiration() : 0;
         int count = config.getCount() != null ? config.getCount() : 1;
 
-        ClientInitialAccessModel clientInitialAccessModel = session.sessions().createClientInitialAccessModel(realm, expiration, count);
+        ClientInitialAccessModel clientInitialAccessModel = session.realms().createClientInitialAccessModel(realm, expiration, count);
 
         adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientInitialAccessModel.getId()).representation(config).success();
 
@@ -99,9 +99,9 @@ public class ClientInitialAccessResource {
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     public List<ClientInitialAccessPresentation> list() {
-        auth.requireView();
+        auth.clients().requireView();
 
-        List<ClientInitialAccessModel> models = session.sessions().listClientInitialAccess(realm);
+        List<ClientInitialAccessModel> models = session.realms().listClientInitialAccess(realm);
         List<ClientInitialAccessPresentation> reps = new LinkedList<>();
         for (ClientInitialAccessModel m : models) {
             ClientInitialAccessPresentation r = wrap(m);
@@ -113,9 +113,9 @@ public class ClientInitialAccessResource {
     @DELETE
     @Path("{id}")
     public void delete(final @PathParam("id") String id) {
-        auth.requireManage();
+        auth.clients().requireManage();
 
-        session.sessions().removeClientInitialAccessModel(realm, id);
+        session.realms().removeClientInitialAccessModel(realm, id);
         adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationPolicyResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationPolicyResource.java
index f8c57e2..9250326 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationPolicyResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationPolicyResource.java
@@ -37,6 +37,7 @@ import org.keycloak.provider.ProviderFactory;
 import org.keycloak.representations.idm.ComponentTypeRepresentation;
 import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy;
 import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyFactory;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 /**
  * @resource Client Registration Policy
@@ -44,7 +45,7 @@ import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyF
  */
 public class ClientRegistrationPolicyResource {
 
-    private final RealmAuth auth;
+    private final AdminPermissionEvaluator auth;
     private final RealmModel realm;
     private final AdminEventBuilder adminEvent;
 
@@ -54,12 +55,11 @@ public class ClientRegistrationPolicyResource {
     @Context
     protected UriInfo uriInfo;
 
-    public ClientRegistrationPolicyResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public ClientRegistrationPolicyResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.auth = auth;
         this.realm = realm;
         this.adminEvent = adminEvent.resource(ResourceType.CLIENT_INITIAL_ACCESS_MODEL);
 
-        auth.init(RealmAuth.Resource.CLIENT);
     }
 
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index a92729e..e7d611e 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -27,10 +27,12 @@ import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
@@ -40,7 +42,9 @@ import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.protocol.ClientInstallationProvider;
 import org.keycloak.representations.adapters.action.GlobalRequestResult;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ClientTemplateRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.ManagementPermissionReference;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.representations.idm.UserSessionRepresentation;
 import org.keycloak.services.ErrorResponse;
@@ -51,6 +55,9 @@ import org.keycloak.services.managers.ClientManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.services.resources.KeycloakApplication;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
+import org.keycloak.services.resources.admin.permissions.AdminPermissions;
 import org.keycloak.services.validation.ClientValidator;
 import org.keycloak.services.validation.PairwiseClientValidator;
 import org.keycloak.services.validation.ValidationMessages;
@@ -89,7 +96,7 @@ import static java.lang.Boolean.TRUE;
 public class ClientResource {
     protected static final Logger logger = Logger.getLogger(ClientResource.class);
     protected RealmModel realm;
-    private RealmAuth auth;
+    private AdminPermissionEvaluator auth;
     private AdminEventBuilder adminEvent;
     protected ClientModel client;
     protected KeycloakSession session;
@@ -104,19 +111,19 @@ public class ClientResource {
         return keycloak;
     }
 
-    public ClientResource(RealmModel realm, RealmAuth auth, ClientModel clientModel, KeycloakSession session, AdminEventBuilder adminEvent) {
+    public ClientResource(RealmModel realm, AdminPermissionEvaluator auth, ClientModel clientModel, KeycloakSession session, AdminEventBuilder adminEvent) {
         this.realm = realm;
         this.auth = auth;
         this.client = clientModel;
         this.session = session;
         this.adminEvent = adminEvent.resource(ResourceType.CLIENT);
-
-        auth.init(RealmAuth.Resource.CLIENT);
     }
 
     @Path("protocol-mappers")
     public ProtocolMappersResource getProtocolMappers() {
-        ProtocolMappersResource mappers = new ProtocolMappersResource(realm, client, auth, adminEvent);
+        AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.clients().requireManage(client);
+        AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.clients().requireView(client);
+        ProtocolMappersResource mappers = new ProtocolMappersResource(realm, client, auth, adminEvent, manageCheck, viewCheck);
         ResteasyProviderFactory.getInstance().injectProperties(mappers);
         return mappers;
     }
@@ -129,15 +136,11 @@ public class ClientResource {
     @PUT
     @Consumes(MediaType.APPLICATION_JSON)
     public Response update(final ClientRepresentation rep) {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireConfigure(client);
 
         ValidationMessages validationMessages = new ValidationMessages();
         if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) {
-            Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
+            Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale());
             throw new ErrorResponseException(
                     validationMessages.getStringMessages(),
                     validationMessages.getStringMessages(messages),
@@ -148,36 +151,13 @@ public class ClientResource {
         try {
             updateClientFromRep(rep, client, session);
             adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
+            updateAuthorizationSettings(rep);
             return Response.noContent().build();
         } catch (ModelDuplicateException e) {
             return ErrorResponse.exists("Client " + rep.getClientId() + " already exists");
         }
     }
 
-    public void updateClientFromRep(ClientRepresentation rep, ClientModel client, KeycloakSession session) throws ModelDuplicateException {
-        if (TRUE.equals(rep.isServiceAccountsEnabled())) {
-            UserModel serviceAccount = this.session.users().getServiceAccount(client);
-
-            if (serviceAccount == null) {
-                new ClientManager(new RealmManager(session)).enableServiceAccount(client);
-            }
-        }
-
-        if (!rep.getClientId().equals(client.getClientId())) {
-            new ClientManager(new RealmManager(session)).clientIdChanged(client, rep.getClientId());
-        }
-
-        RepresentationToModel.updateClient(rep, client);
-
-        if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
-            if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
-                authorization().enable();
-            } else {
-                authorization().disable();
-            }
-        }
-    }
-
     /**
      * Get representation of the client
      *
@@ -187,17 +167,14 @@ public class ClientResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public ClientRepresentation getClient() {
-        auth.requireView();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireView(client);
 
         ClientRepresentation representation = ModelToRepresentation.toRepresentation(client);
 
         if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
             representation.setAuthorizationServicesEnabled(authorization().isEnabled());
         }
+        representation.setAccess(auth.clients().getAccess(client));
 
         return representation;
     }
@@ -217,11 +194,7 @@ public class ClientResource {
     @NoCache
     @Path("installation/providers/{providerId}")
     public Response getInstallationProvider(@PathParam("providerId") String providerId) {
-        auth.requireView();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireView(client);
 
         ClientInstallationProvider provider = session.getProvider(ClientInstallationProvider.class, providerId);
         if (provider == null) throw new NotFoundException("Unknown Provider");
@@ -235,7 +208,7 @@ public class ClientResource {
     @DELETE
     @NoCache
     public void deleteClient() {
-        auth.requireManage();
+        auth.clients().requireManage(client);
 
         if (client == null) {
             throw new NotFoundException("Could not find client");
@@ -256,11 +229,7 @@ public class ClientResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
     public CredentialRepresentation regenerateSecret() {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireConfigure(client);
 
         logger.debug("regenerateSecret");
         UserCredentialModel cred = KeycloakModelUtils.generateSecret(client);
@@ -279,11 +248,7 @@ public class ClientResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
     public ClientRepresentation regenerateRegistrationAccessToken() {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireManage(client);
 
         String token = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, realm, uriInfo, client, RegistrationAuth.AUTHENTICATED);
 
@@ -304,11 +269,7 @@ public class ClientResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public CredentialRepresentation getClientSecret() {
-        auth.requireView();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireView(client);
 
         logger.debug("getClientSecret");
         UserCredentialModel model = UserCredentialModel.secret(client.getSecret());
@@ -323,12 +284,14 @@ public class ClientResource {
      */
     @Path("scope-mappings")
     public ScopeMappedResource getScopeMappedResource() {
-        return new ScopeMappedResource(realm, auth, client, session, adminEvent);
+        AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.clients().requireManage(client);
+        AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.clients().requireView(client);
+        return new ScopeMappedResource(realm, auth, client, session, adminEvent, manageCheck, viewCheck);
     }
 
     @Path("roles")
     public RoleContainerResource getRoleContainerResource() {
-        return new RoleContainerResource(uriInfo, realm, auth, client, adminEvent);
+        return new RoleContainerResource(session, uriInfo, realm, auth, client, adminEvent);
     }
 
     /**
@@ -341,11 +304,7 @@ public class ClientResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public UserRepresentation getServiceAccountUser() {
-        auth.requireView();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireView(client);
 
         UserModel user = session.users().getServiceAccount(client);
         if (user == null) {
@@ -369,11 +328,7 @@ public class ClientResource {
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     public GlobalRequestResult pushRevocation() {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireConfigure(client);
 
         adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).resource(ResourceType.CLIENT).success();
         return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client);
@@ -396,11 +351,7 @@ public class ClientResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Map<String, Long> getApplicationSessionCount() {
-        auth.requireView();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireView(client);
 
         Map<String, Long> map = new HashMap<>();
         map.put("count", session.sessions().getActiveUserSessions(client.getRealm(), client));
@@ -421,11 +372,7 @@ public class ClientResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<UserSessionRepresentation> getUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) {
-        auth.requireView();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireView(client);
 
         firstResult = firstResult != null ? firstResult : -1;
         maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
@@ -453,11 +400,7 @@ public class ClientResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Map<String, Long> getOfflineSessionCount() {
-        auth.requireView();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireView(client);
 
         Map<String, Long> map = new HashMap<>();
         map.put("count", session.sessions().getOfflineSessionsCount(client.getRealm(), client));
@@ -478,11 +421,7 @@ public class ClientResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<UserSessionRepresentation> getOfflineUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) {
-        auth.requireView();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireView(client);
 
         firstResult = firstResult != null ? firstResult : -1;
         maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
@@ -519,11 +458,7 @@ public class ClientResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public void registerNode(Map<String, String> formParams) {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireConfigure(client);
 
         String node = formParams.get("node");
         if (node == null) {
@@ -543,11 +478,7 @@ public class ClientResource {
     @DELETE
     @NoCache
     public void unregisterNode(final @PathParam("node") String node) {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireConfigure(client);
 
         if (logger.isDebugEnabled()) logger.debug("Unregister node: " + node);
 
@@ -571,11 +502,7 @@ public class ClientResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public GlobalRequestResult testNodesAvailable() {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.clients().requireConfigure(client);
 
         logger.debug("Test availability of cluster nodes");
         GlobalRequestResult result = new ResourceAdminManager(session).testNodesAvailability(uriInfo.getRequestUri(), realm, client);
@@ -587,10 +514,111 @@ public class ClientResource {
     public AuthorizationService authorization() {
         ProfileHelper.requireFeature(Profile.Feature.AUTHORIZATION);
 
-        AuthorizationService resource = new AuthorizationService(this.session, this.client, this.auth);
+        AuthorizationService resource = new AuthorizationService(this.session, this.client, this.auth, adminEvent);
 
         ResteasyProviderFactory.getInstance().injectProperties(resource);
 
         return resource;
     }
+
+    /**
+     * Return object stating whether client Authorization permissions have been initialized or not and a reference
+     *
+     * @return
+     */
+    @Path("management/permissions")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    public ManagementPermissionReference getManagementPermissions() {
+        auth.roles().requireView(client);
+
+        AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+        if (!permissions.clients().isPermissionsEnabled(client)) {
+            return new ManagementPermissionReference();
+        }
+        return toMgmtRef(client, permissions);
+    }
+
+    public static ManagementPermissionReference toMgmtRef(ClientModel client, AdminPermissionManagement permissions) {
+        ManagementPermissionReference ref = new ManagementPermissionReference();
+        ref.setEnabled(true);
+        ref.setResource(permissions.clients().resource(client).getId());
+        ref.setScopePermissions(permissions.clients().getPermissions(client));
+        return ref;
+    }
+
+
+    /**
+     * Return object stating whether client Authorization permissions have been initialized or not and a reference
+     *
+     *
+     * @return initialized manage permissions reference
+     */
+    @Path("management/permissions")
+    @PUT
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @NoCache
+    public ManagementPermissionReference setManagementPermissionsEnabled(ManagementPermissionReference ref) {
+        auth.clients().requireManage(client);
+         if (ref.isEnabled()) {
+            AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+            permissions.clients().setPermissionsEnabled(client, ref.isEnabled());
+            return toMgmtRef(client, permissions);
+        } else {
+            return new ManagementPermissionReference();
+        }
+    }
+
+
+    private void updateClientFromRep(ClientRepresentation rep, ClientModel client, KeycloakSession session) throws ModelDuplicateException {
+        if (TRUE.equals(rep.isServiceAccountsEnabled())) {
+            UserModel serviceAccount = this.session.users().getServiceAccount(client);
+
+            if (serviceAccount == null) {
+                new ClientManager(new RealmManager(session)).enableServiceAccount(client);
+            }
+        }
+
+        if (!rep.getClientId().equals(client.getClientId())) {
+            new ClientManager(new RealmManager(session)).clientIdChanged(client, rep.getClientId());
+        }
+
+        if (rep.isFullScopeAllowed() != null && rep.isFullScopeAllowed().booleanValue() != client.isFullScopeAllowed()) {
+            auth.clients().requireManage(client);
+        }
+
+        if (rep.getClientTemplate() != null) {
+            ClientTemplateModel currTemplate = client.getClientTemplate();
+            if (currTemplate == null) {
+                if (!rep.getClientTemplate().equals(ClientTemplateRepresentation.NONE)) {
+                    auth.clients().requireManage(client);
+                }
+            }  else if (!rep.getClientTemplate().equals(currTemplate.getName())){
+                auth.clients().requireManage(client);
+            }
+            if ((rep.isUseTemplateConfig() != null && rep.isUseTemplateConfig().booleanValue() != client.useTemplateConfig())
+                    || (rep.isUseTemplateScope() != null && rep.isUseTemplateScope().booleanValue() != client.useTemplateScope())
+                    || (rep.isUseTemplateMappers() != null && rep.isUseTemplateMappers().booleanValue() != client.useTemplateMappers())
+
+                    ) {
+                auth.clients().requireManage(client);
+            }
+        }
+
+
+
+        RepresentationToModel.updateClient(rep, client);
+    }
+
+    private void updateAuthorizationSettings(ClientRepresentation rep) {
+        if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
+            if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
+                authorization().enable(false);
+            } else {
+                authorization().disable();
+            }
+        }
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java
index 3d7d4cd..ae7d519 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java
@@ -19,6 +19,7 @@ package org.keycloak.services.resources.admin;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.ClientModel;
@@ -30,6 +31,7 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.ForbiddenException;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -47,6 +49,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Properties;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * @resource Client Role Mappings
@@ -58,19 +61,26 @@ public class ClientRoleMappingsResource {
 
     protected KeycloakSession session;
     protected RealmModel realm;
-    protected RealmAuth auth;
+    protected AdminPermissionEvaluator auth;
     protected RoleMapperModel user;
     protected ClientModel client;
     protected AdminEventBuilder adminEvent;
     private UriInfo uriInfo;
+    protected AdminPermissionEvaluator.RequirePermissionCheck managePermission;
+    protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission;
 
-    public ClientRoleMappingsResource(UriInfo uriInfo, KeycloakSession session, RealmModel realm, RealmAuth auth, RoleMapperModel user, ClientModel client, AdminEventBuilder adminEvent) {
+
+    public ClientRoleMappingsResource(UriInfo uriInfo, KeycloakSession session, RealmModel realm, AdminPermissionEvaluator auth,
+                                      RoleMapperModel user, ClientModel client, AdminEventBuilder adminEvent,
+                                      AdminPermissionEvaluator.RequirePermissionCheck manageCheck, AdminPermissionEvaluator.RequirePermissionCheck viewCheck ) {
         this.uriInfo = uriInfo;
         this.session = session;
         this.realm = realm;
         this.auth = auth;
         this.user = user;
         this.client = client;
+        this.managePermission = manageCheck;
+        this.viewPermission = viewCheck;
         this.adminEvent = adminEvent.resource(ResourceType.CLIENT_ROLE_MAPPING);
     }
 
@@ -83,11 +93,7 @@ public class ClientRoleMappingsResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getClientRoleMappings() {
-        auth.requireView();
-
-        if (user == null || client == null) {
-            throw new NotFoundException("Not found");
-        }
+        viewPermission.require();
 
         Set<RoleModel> mappings = user.getClientRoleMappings(client);
         List<RoleRepresentation> mapRep = new ArrayList<RoleRepresentation>();
@@ -109,11 +115,8 @@ public class ClientRoleMappingsResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getCompositeClientRoleMappings() {
-        auth.requireView();
+        viewPermission.require();
 
-        if (user == null || client == null) {
-            throw new NotFoundException("Not found");
-        }
 
         Set<RoleModel> roles = client.getRoles();
         List<RoleRepresentation> mapRep = new ArrayList<RoleRepresentation>();
@@ -133,13 +136,12 @@ public class ClientRoleMappingsResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getAvailableClientRoleMappings() {
-        auth.requireView();
-
-        if (user == null || client == null) {
-            throw new NotFoundException("Not found");
-        }
+        viewPermission.require();
 
         Set<RoleModel> available = client.getRoles();
+        available = available.stream().filter(r ->
+                auth.roles().canMapRole(r)
+        ).collect(Collectors.toSet());
         return getAvailableRoles(user, available);
     }
 
@@ -165,17 +167,14 @@ public class ClientRoleMappingsResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public void addClientRoleMapping(List<RoleRepresentation> roles) {
-        auth.requireManage();
-
-        if (user == null || client == null) {
-            throw new NotFoundException("Not found");
-        }
+        managePermission.require();
 
         for (RoleRepresentation role : roles) {
             RoleModel roleModel = client.getRole(role.getName());
             if (roleModel == null || !roleModel.getId().equals(role.getId())) {
                 throw new NotFoundException("Role not found");
             }
+            auth.roles().requireMapRole(roleModel);
             user.grantRole(roleModel);
         }
         adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(roles).success();
@@ -183,18 +182,14 @@ public class ClientRoleMappingsResource {
     }
 
     /**
-     * Delete client-level roles from user role mapping
-     *
-     * @param roles
-     */
+         * Delete client-level roles from user role mapping
+         *
+         * @param roles
+         */
     @DELETE
     @Consumes(MediaType.APPLICATION_JSON)
     public void deleteClientRoleMapping(List<RoleRepresentation> roles) {
-        auth.requireManage();
-
-        if (user == null || client == null) {
-            throw new NotFoundException("Not found");
-        }
+        managePermission.require();
 
         if (roles == null) {
             Set<RoleModel> roleModels = user.getClientRoleMappings(client);
@@ -205,6 +200,7 @@ public class ClientRoleMappingsResource {
                     ClientModel client = (ClientModel) roleModel.getContainer();
                     if (!client.getId().equals(this.client.getId())) continue;
                 }
+                auth.roles().requireMapRole(roleModel);
                 user.deleteRoleMapping(roleModel);
                 roles.add(ModelToRepresentation.toRepresentation(roleModel));
             }
@@ -216,10 +212,11 @@ public class ClientRoleMappingsResource {
                     throw new NotFoundException("Role not found");
                 }
 
+                auth.roles().requireMapRole(roleModel);
                 try {
                     user.deleteRoleMapping(roleModel);
                 } catch (ModelException me) {
-                    Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
+                    Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale());
                     throw new ErrorResponseException(me.getMessage(), MessageFormat.format(messages.getProperty(me.getMessage(), me.getMessage()), me.getParameters()),
                             Response.Status.BAD_REQUEST);
                 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
index 3fa3c75..decb4da 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
@@ -26,6 +26,7 @@ import org.keycloak.common.Profile;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.RealmModel;
@@ -34,14 +35,18 @@ import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.services.ErrorResponse;
 import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.ForbiddenException;
 import org.keycloak.services.managers.ClientManager;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 import org.keycloak.services.validation.ClientValidator;
 import org.keycloak.services.validation.PairwiseClientValidator;
 import org.keycloak.services.validation.ValidationMessages;
 
 import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
+import javax.ws.rs.NotFoundException;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
@@ -65,18 +70,17 @@ import java.util.Properties;
 public class ClientsResource {
     protected static final Logger logger = Logger.getLogger(ClientsResource.class);
     protected RealmModel realm;
-    private RealmAuth auth;
+    private AdminPermissionEvaluator auth;
     private AdminEventBuilder adminEvent;
 
     @Context
     protected KeycloakSession session;
 
-    public ClientsResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public ClientsResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.realm = realm;
         this.auth = auth;
         this.adminEvent = adminEvent.resource(ResourceType.CLIENT);
 
-        auth.init(RealmAuth.Resource.CLIENT);
     }
 
     /**
@@ -85,21 +89,20 @@ public class ClientsResource {
      * Returns a list of clients belonging to the realm
      *
      * @param clientId filter by clientId
+     * @param viewableOnly filter clients that cannot be viewed in full by admin
      */
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
-    public List<ClientRepresentation> getClients(@QueryParam("clientId") String clientId) {
-        auth.requireAny();
-
+    public List<ClientRepresentation> getClients(@QueryParam("clientId") String clientId, @QueryParam("viewableOnly") @DefaultValue("false") boolean viewableOnly) {
         List<ClientRepresentation> rep = new ArrayList<>();
 
         if (clientId == null) {
             List<ClientModel> clientModels = realm.getClients();
-
-            boolean view = auth.hasView();
+            auth.clients().requireList();
+            boolean view = auth.clients().canView();
             for (ClientModel clientModel : clientModels) {
-                if (view) {
+                if (view || auth.clients().canView(clientModel)) {
                     ClientRepresentation representation = ModelToRepresentation.toRepresentation(clientModel);
 
                     if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
@@ -111,7 +114,8 @@ public class ClientsResource {
                     }
 
                     rep.add(representation);
-                } else {
+                    representation.setAccess(auth.clients().getAccess(clientModel));
+                } else if (!viewableOnly) {
                     ClientRepresentation client = new ClientRepresentation();
                     client.setId(clientModel.getId());
                     client.setClientId(clientModel.getClientId());
@@ -120,16 +124,29 @@ public class ClientsResource {
                 }
             }
         } else {
-            ClientModel client = realm.getClientByClientId(clientId);
-            if (client != null) {
-                rep.add(ModelToRepresentation.toRepresentation(client));
+            ClientModel clientModel = realm.getClientByClientId(clientId);
+            if (clientModel != null) {
+                if (auth.clients().canView(clientModel)) {
+                    ClientRepresentation representation = ModelToRepresentation.toRepresentation(clientModel);
+                    representation.setAccess(auth.clients().getAccess(clientModel));
+                    rep.add(representation);
+                } else if (!viewableOnly && auth.clients().canList()){
+                    ClientRepresentation client = new ClientRepresentation();
+                    client.setId(clientModel.getId());
+                    client.setClientId(clientModel.getClientId());
+                    client.setDescription(clientModel.getDescription());
+                    rep.add(client);
+
+                } else {
+                    throw new ForbiddenException();
+                }
             }
         }
         return rep;
     }
 
     private AuthorizationService getAuthorizationService(ClientModel clientModel) {
-        return new AuthorizationService(session, clientModel, auth);
+        return new AuthorizationService(session, clientModel, auth, adminEvent);
     }
 
     /**
@@ -144,11 +161,11 @@ public class ClientsResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public Response createClient(final @Context UriInfo uriInfo, final ClientRepresentation rep) {
-        auth.requireManage();
+        auth.clients().requireManage();
 
         ValidationMessages validationMessages = new ValidationMessages();
         if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) {
-            Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
+            Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale());
             throw new ErrorResponseException(
                     validationMessages.getStringMessages(),
                     validationMessages.getStringMessages(messages),
@@ -167,14 +184,14 @@ public class ClientsResource {
                 }
             }
 
+            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientModel.getId()).representation(rep).success();
+
             if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
                 if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
-                    getAuthorizationService(clientModel).enable();
+                    getAuthorizationService(clientModel).enable(true);
                 }
             }
 
-            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientModel.getId()).representation(rep).success();
-
             return Response.created(uriInfo.getAbsolutePathBuilder().path(clientModel.getId()).build()).build();
         } catch (ModelDuplicateException e) {
             return ErrorResponse.exists("Client " + rep.getClientId() + " already exists");
@@ -189,7 +206,13 @@ public class ClientsResource {
      */
     @Path("{id}")
     public ClientResource getClient(final @PathParam("id") String id) {
+
         ClientModel clientModel = realm.getClientById(id);
+        if (clientModel == null) {
+            // we do this to make sure somebody can't phish ids
+            if (auth.clients().canList()) throw new NotFoundException("Could not find client");
+            else throw new ForbiddenException();
+        }
 
         session.getContext().setClient(clientModel);
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java
index f760a41..f076850 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java
@@ -31,6 +31,7 @@ import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.ClientTemplateRepresentation;
 import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -54,7 +55,7 @@ import javax.ws.rs.core.UriInfo;
 public class ClientTemplateResource {
     protected static final Logger logger = Logger.getLogger(ClientTemplateResource.class);
     protected RealmModel realm;
-    private RealmAuth auth;
+    private AdminPermissionEvaluator auth;
     private AdminEventBuilder adminEvent;
     protected ClientTemplateModel template;
     protected KeycloakSession session;
@@ -62,19 +63,20 @@ public class ClientTemplateResource {
     @Context
     protected UriInfo uriInfo;
 
-    public ClientTemplateResource(RealmModel realm, RealmAuth auth, ClientTemplateModel template, KeycloakSession session, AdminEventBuilder adminEvent) {
+    public ClientTemplateResource(RealmModel realm, AdminPermissionEvaluator auth, ClientTemplateModel template, KeycloakSession session, AdminEventBuilder adminEvent) {
         this.realm = realm;
         this.auth = auth;
         this.template = template;
         this.session = session;
         this.adminEvent = adminEvent.resource(ResourceType.CLIENT_TEMPLATE);
 
-        auth.init(RealmAuth.Resource.CLIENT);
     }
 
     @Path("protocol-mappers")
     public ProtocolMappersResource getProtocolMappers() {
-        ProtocolMappersResource mappers = new ProtocolMappersResource(realm, template, auth, adminEvent);
+        AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.clients().requireManage(template);
+        AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.clients().requireView(template);
+        ProtocolMappersResource mappers = new ProtocolMappersResource(realm, template, auth, adminEvent, manageCheck, viewCheck);
         ResteasyProviderFactory.getInstance().injectProperties(mappers);
         return mappers;
     }
@@ -86,7 +88,9 @@ public class ClientTemplateResource {
      */
     @Path("scope-mappings")
     public ScopeMappedResource getScopeMappedResource() {
-        return new ScopeMappedResource(realm, auth, template, session, adminEvent);
+        AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.clients().requireManage(template);
+        AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.clients().requireView(template);
+        return new ScopeMappedResource(realm, auth, template, session, adminEvent, manageCheck, viewCheck);
     }
 
     /**
@@ -97,11 +101,7 @@ public class ClientTemplateResource {
     @PUT
     @Consumes(MediaType.APPLICATION_JSON)
     public Response update(final ClientTemplateRepresentation rep) {
-        auth.requireManage();
-
-        if (template == null) {
-            throw new NotFoundException("Could not find client template");
-        }
+        auth.clients().requireManageTemplates();
 
         try {
             RepresentationToModel.updateClientTemplate(rep, template);
@@ -125,11 +125,8 @@ public class ClientTemplateResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public ClientTemplateRepresentation getClient() {
-        auth.requireView();
+        auth.clients().requireView(template);
 
-        if (template == null) {
-            throw new NotFoundException("Could not find client template");
-        }
 
         return ModelToRepresentation.toRepresentation(template);
     }
@@ -141,11 +138,7 @@ public class ClientTemplateResource {
     @DELETE
     @NoCache
     public Response deleteClientTemplate() {
-        auth.requireManage();
-
-        if (template == null) {
-            throw new NotFoundException("Could not find client template");
-        }
+        auth.clients().requireManage(template);
 
         try {
             realm.removeClientTemplate(template.getId());
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java
index 5e27712..1e8105d 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java
@@ -18,6 +18,7 @@ package org.keycloak.services.resources.admin;
 
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.NotFoundException;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
@@ -29,6 +30,7 @@ import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.ClientTemplateRepresentation;
 import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -53,18 +55,16 @@ import java.util.List;
 public class ClientTemplatesResource {
     protected static final Logger logger = Logger.getLogger(ClientTemplatesResource.class);
     protected RealmModel realm;
-    private RealmAuth auth;
+    private AdminPermissionEvaluator auth;
     private AdminEventBuilder adminEvent;
 
     @Context
     protected KeycloakSession session;
 
-    public ClientTemplatesResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public ClientTemplatesResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.realm = realm;
         this.auth = auth;
         this.adminEvent = adminEvent.resource(ResourceType.CLIENT_TEMPLATE);
-
-        auth.init(RealmAuth.Resource.CLIENT);
     }
 
     /**
@@ -76,22 +76,19 @@ public class ClientTemplatesResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<ClientTemplateRepresentation> getClientTemplates() {
-        auth.requireView();
+        auth.clients().requireListTemplates();
 
         List<ClientTemplateRepresentation> rep = new ArrayList<>();
         List<ClientTemplateModel> clientModels = realm.getClientTemplates();
 
-        boolean view = auth.hasView();
+        boolean viewable = auth.clients().canViewTemplates();
         for (ClientTemplateModel clientModel : clientModels) {
-            if (view) {
-                rep.add(ModelToRepresentation.toRepresentation(clientModel));
-            } else {
-                ClientTemplateRepresentation client = new ClientTemplateRepresentation();
-                client.setId(clientModel.getId());
-                client.setName(clientModel.getName());
-                client.setDescription(clientModel.getDescription());
-                client.setProtocol(clientModel.getProtocol());
-                rep.add(client);
+            if (viewable) rep.add(ModelToRepresentation.toRepresentation(clientModel));
+            else {
+                ClientTemplateRepresentation tempRep = new ClientTemplateRepresentation();
+                tempRep.setName(clientModel.getName());
+                tempRep.setId(clientModel.getId());
+                tempRep.setProtocol(clientModel.getProtocol());
             }
         }
         return rep;
@@ -109,7 +106,7 @@ public class ClientTemplatesResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public Response createClientTemplate(final @Context UriInfo uriInfo, final ClientTemplateRepresentation rep) {
-        auth.requireManage();
+        auth.clients().requireManageTemplates();
 
         try {
             ClientTemplateModel clientModel = RepresentationToModel.createClientTemplate(session, realm, rep);
@@ -130,7 +127,11 @@ public class ClientTemplatesResource {
      */
     @Path("{id}")
     public ClientTemplateResource getClient(final @PathParam("id") String id) {
+        auth.clients().requireListTemplates();
         ClientTemplateModel clientModel = realm.getClientTemplateById(id);
+        if (clientModel == null) {
+            throw new NotFoundException("Could not find client template");
+        }
         ClientTemplateResource clientResource = new ClientTemplateResource(realm, auth, clientModel, session, adminEvent);
         ResteasyProviderFactory.getInstance().injectProperties(clientResource);
         return clientResource;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java
index b39b773..c532245 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java
@@ -38,7 +38,7 @@ import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.representations.idm.ComponentTypeRepresentation;
 import org.keycloak.representations.idm.ConfigPropertyRepresentation;
 import org.keycloak.services.ErrorResponse;
-import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.ws.rs.BadRequestException;
 import javax.ws.rs.Consumes;
@@ -63,7 +63,6 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
-import java.util.stream.Collectors;
 
 /**
  * @resource Component
@@ -75,7 +74,7 @@ public class ComponentResource {
 
     protected RealmModel realm;
 
-    private RealmAuth auth;
+    private AdminPermissionEvaluator auth;
 
     private AdminEventBuilder adminEvent;
 
@@ -91,12 +90,10 @@ public class ComponentResource {
     @Context
     protected HttpHeaders headers;
 
-    public ComponentResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public ComponentResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.auth = auth;
         this.realm = realm;
         this.adminEvent = adminEvent.resource(ResourceType.COMPONENT);
-
-        auth.init(RealmAuth.Resource.REALM);
     }
 
     @GET
@@ -105,7 +102,7 @@ public class ComponentResource {
     public List<ComponentRepresentation> getComponents(@QueryParam("parent") String parent,
                                                        @QueryParam("type") String type,
                                                        @QueryParam("name") String name) {
-        auth.requireView();
+        auth.realm().requireViewRealm();
         List<ComponentModel> components = Collections.EMPTY_LIST;
         if (parent == null && type == null) {
             components = realm.getComponents();
@@ -135,7 +132,7 @@ public class ComponentResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public Response create(ComponentRepresentation rep) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
         try {
             ComponentModel model = RepresentationToModel.toModel(session, rep);
             if (model.getParentId() == null) model.setParentId(realm.getId());
@@ -156,7 +153,7 @@ public class ComponentResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public ComponentRepresentation getComponent(@PathParam("id") String id) {
-        auth.requireView();
+        auth.realm().requireViewRealm();
         ComponentModel model = realm.getComponent(id);
         if (model == null) {
             throw new NotFoundException("Could not find component");
@@ -169,7 +166,7 @@ public class ComponentResource {
     @Path("{id}")
     @Consumes(MediaType.APPLICATION_JSON)
     public Response updateComponent(@PathParam("id") String id, ComponentRepresentation rep) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
         try {
             ComponentModel model = realm.getComponent(id);
             if (model == null) {
@@ -188,7 +185,7 @@ public class ComponentResource {
     @DELETE
     @Path("{id}")
     public void removeComponent(@PathParam("id") String id) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
         ComponentModel model = realm.getComponent(id);
         if (model == null) {
             throw new NotFoundException("Could not find component");
@@ -199,7 +196,7 @@ public class ComponentResource {
     }
 
     private Response localizedErrorResponse(ComponentValidationException cve) {
-        Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale(), "admin-messages", "messages");
+        Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale(), "admin-messages", "messages");
 
         Object[] localizedParameters = cve.getParameters()==null ? null : Arrays.asList(cve.getParameters()).stream().map((Object parameter) -> {
 
@@ -228,7 +225,7 @@ public class ComponentResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<ComponentTypeRepresentation> getSubcomponentConfig(@PathParam("id") String parentId, @QueryParam("type") String subtype) {
-        auth.requireView();
+        auth.realm().requireViewRealm();
         ComponentModel parent = realm.getComponent(parentId);
         if (parent == null) {
             throw new NotFoundException("Could not find parent component");
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java
index c9fe194..3de46b0 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java
@@ -21,6 +21,7 @@ import org.jboss.resteasy.spi.NotFoundException;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
@@ -28,6 +29,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.ManagementPermissionReference;
 import org.keycloak.representations.idm.UserRepresentation;
 
 import javax.ws.rs.Consumes;
@@ -49,6 +51,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
+import org.keycloak.services.resources.admin.permissions.AdminPermissions;
 
 /**
  * @resource Groups
@@ -58,11 +63,11 @@ public class GroupResource {
 
     private final RealmModel realm;
     private final KeycloakSession session;
-    private final RealmAuth auth;
+    private final AdminPermissionEvaluator auth;
     private final AdminEventBuilder adminEvent;
     private final GroupModel group;
 
-    public GroupResource(RealmModel realm, GroupModel group, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public GroupResource(RealmModel realm, GroupModel group, KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.realm = realm;
         this.session = session;
         this.auth = auth;
@@ -72,7 +77,7 @@ public class GroupResource {
 
     @Context private UriInfo uriInfo;
 
-    /**
+     /**
      *
      *
      * @return
@@ -81,13 +86,13 @@ public class GroupResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public GroupRepresentation getGroup() {
-        this.auth.requireView();
+        this.auth.groups().requireView(group);
 
-        if (group == null) {
-            throw new NotFoundException("Could not find group by id");
-        }
+        GroupRepresentation rep = ModelToRepresentation.toGroupHierarchy(group, true);
+
+        rep.setAccess(auth.groups().getAccess(group));
 
-        return ModelToRepresentation.toGroupHierarchy(group, true);
+        return rep;
     }
 
     /**
@@ -98,11 +103,7 @@ public class GroupResource {
     @PUT
     @Consumes(MediaType.APPLICATION_JSON)
     public void updateGroup(GroupRepresentation rep) {
-        this.auth.requireManage();
-
-        if (group == null) {
-            throw new NotFoundException("Could not find group by id");
-        }
+        this.auth.groups().requireManage(group);
 
         updateGroup(rep, group);
         adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
@@ -112,11 +113,7 @@ public class GroupResource {
 
     @DELETE
     public void deleteGroup() {
-        this.auth.requireManage();
-
-        if (group == null) {
-            throw new NotFoundException("Could not find group by id");
-        }
+        this.auth.groups().requireManage(group);
 
         realm.removeGroup(group);
         adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
@@ -135,12 +132,8 @@ public class GroupResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
     public Response addChild(GroupRepresentation rep) {
-        this.auth.requireManage();
+        this.auth.groups().requireManage(group);
 
-        if (group == null) {
-            throw new NotFoundException("Could not find group by id");
-        }
-        
         for (GroupModel group : group.getSubGroups()) {
             if (group.getName().equals(rep.getName())) {
                 return ErrorResponse.exists("Parent already contains subgroup named '" + rep.getName() + "'");
@@ -191,9 +184,9 @@ public class GroupResource {
 
     @Path("role-mappings")
     public RoleMapperResource getRoleMappings() {
-        auth.init(RealmAuth.Resource.USER);
-
-        RoleMapperResource resource =  new RoleMapperResource(realm, auth, group, adminEvent);
+        AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.groups().requireManage(group);
+        AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.groups().requireView(group);
+        RoleMapperResource resource =  new RoleMapperResource(realm, auth, group, adminEvent, manageCheck, viewCheck);
         ResteasyProviderFactory.getInstance().injectProperties(resource);
         return resource;
 
@@ -214,11 +207,8 @@ public class GroupResource {
     @Produces(MediaType.APPLICATION_JSON)
     public List<UserRepresentation> getMembers(@QueryParam("first") Integer firstResult,
                                                @QueryParam("max") Integer maxResults) {
-        auth.requireView();
+        this.auth.groups().requireViewMembers(group);
 
-        if (group == null) {
-            throw new NotFoundException("Could not find group by id");
-        }
 
         firstResult = firstResult != null ? firstResult : 0;
         maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
@@ -232,4 +222,55 @@ public class GroupResource {
         return results;
     }
 
+    /**
+     * Return object stating whether client Authorization permissions have been initialized or not and a reference
+     *
+     * @return
+     */
+    @Path("management/permissions")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    public ManagementPermissionReference getManagementPermissions() {
+        auth.groups().requireView(group);
+
+        AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+        if (!permissions.groups().isPermissionsEnabled(group)) {
+            return new ManagementPermissionReference();
+        }
+        return toMgmtRef(group, permissions);
+    }
+
+    public static ManagementPermissionReference toMgmtRef(GroupModel group, AdminPermissionManagement permissions) {
+        ManagementPermissionReference ref = new ManagementPermissionReference();
+        ref.setEnabled(true);
+        ref.setResource(permissions.groups().resource(group).getId());
+        ref.setScopePermissions(permissions.groups().getPermissions(group));
+        return ref;
+    }
+
+
+    /**
+     * Return object stating whether client Authorization permissions have been initialized or not and a reference
+     *
+     *
+     * @return initialized manage permissions reference
+     */
+    @Path("management/permissions")
+    @PUT
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @NoCache
+    public ManagementPermissionReference setManagementPermissionsEnabled(ManagementPermissionReference ref) {
+        auth.groups().requireManage(group);
+        if (ref.isEnabled()) {
+            AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+            permissions.groups().setPermissionsEnabled(group, ref.isEnabled());
+            return toMgmtRef(group, permissions);
+        } else {
+            return new ManagementPermissionReference();
+        }
+    }
+
 }
+
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java
index 5be1c0d..2a1909b 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java
@@ -40,6 +40,7 @@ import javax.ws.rs.core.UriInfo;
 import java.net.URI;
 import java.util.List;
 import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 /**
  * @resource Groups
@@ -49,15 +50,14 @@ public class GroupsResource {
 
     private final RealmModel realm;
     private final KeycloakSession session;
-    private final RealmAuth auth;
+    private final AdminPermissionEvaluator auth;
     private final AdminEventBuilder adminEvent;
 
-    public GroupsResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public GroupsResource(RealmModel realm, KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.realm = realm;
         this.session = session;
         this.auth = auth;
         this.adminEvent = adminEvent.resource(ResourceType.GROUP);
-        auth.init(RealmAuth.Resource.USER);
 
     }
 
@@ -72,7 +72,7 @@ public class GroupsResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<GroupRepresentation> getGroups() {
-        auth.requireView();
+        auth.groups().requireList();
 
         return ModelToRepresentation.toGroupHierarchy(realm, false);
     }
@@ -85,9 +85,10 @@ public class GroupsResource {
      */
     @Path("{id}")
     public GroupResource getGroupById(@PathParam("id") String id) {
-        auth.requireView();
-
         GroupModel group = realm.getGroupById(id);
+        if (group == null) {
+            throw new NotFoundException("Could not find group by id");
+        }
         GroupResource resource =  new GroupResource(realm, group, session, this.auth, adminEvent);
         ResteasyProviderFactory.getInstance().injectProperties(resource);
         return resource;
@@ -102,7 +103,7 @@ public class GroupsResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public Response addTopLevelGroup(GroupRepresentation rep) {
-        auth.requireManage();
+        auth.groups().requireManage();
 
         for (GroupModel group : realm.getGroups()) {
             if (group.getName().equals(rep.getName())) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
index aa4a054..4c08c20 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
@@ -44,6 +44,7 @@ import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
 import org.keycloak.representations.idm.IdentityProviderMapperTypeRepresentation;
 import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -72,7 +73,7 @@ public class IdentityProviderResource {
 
     protected static final Logger logger = Logger.getLogger(IdentityProviderResource.class);
 
-    private final RealmAuth auth;
+    private final AdminPermissionEvaluator auth;
     private final RealmModel realm;
     private final KeycloakSession session;
     private final IdentityProviderModel identityProviderModel;
@@ -80,7 +81,7 @@ public class IdentityProviderResource {
 
     @Context private UriInfo uriInfo;
 
-    public IdentityProviderResource(RealmAuth auth, RealmModel realm, KeycloakSession session, IdentityProviderModel identityProviderModel, AdminEventBuilder adminEvent) {
+    public IdentityProviderResource(AdminPermissionEvaluator auth, RealmModel realm, KeycloakSession session, IdentityProviderModel identityProviderModel, AdminEventBuilder adminEvent) {
         this.realm = realm;
         this.session = session;
         this.identityProviderModel = identityProviderModel;
@@ -97,7 +98,7 @@ public class IdentityProviderResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public IdentityProviderRepresentation getIdentityProvider() {
-        this.auth.requireView();
+        this.auth.realm().requireViewIdentityProviders();
 
         if (identityProviderModel == null) {
             throw new javax.ws.rs.NotFoundException();
@@ -115,7 +116,7 @@ public class IdentityProviderResource {
     @DELETE
     @NoCache
     public Response delete() {
-        this.auth.requireManage();
+        this.auth.realm().requireManageIdentityProviders();
 
         if (identityProviderModel == null) {
             throw new javax.ws.rs.NotFoundException();
@@ -138,7 +139,7 @@ public class IdentityProviderResource {
     @Consumes(MediaType.APPLICATION_JSON)
     @NoCache
     public Response update(IdentityProviderRepresentation providerRep) {
-        this.auth.requireManage();
+        this.auth.realm().requireManageIdentityProviders();
 
         if (identityProviderModel == null) {
             throw new javax.ws.rs.NotFoundException();
@@ -229,7 +230,7 @@ public class IdentityProviderResource {
     @Path("export")
     @NoCache
     public Response export(@Context UriInfo uriInfo, @QueryParam("format") String format) {
-        this.auth.requireView();
+        this.auth.realm().requireViewIdentityProviders();
 
         if (identityProviderModel == null) {
             throw new javax.ws.rs.NotFoundException();
@@ -250,7 +251,7 @@ public class IdentityProviderResource {
     @Path("mapper-types")
     @NoCache
     public Map<String, IdentityProviderMapperTypeRepresentation> getMapperTypes() {
-        this.auth.requireView();
+        this.auth.realm().requireViewIdentityProviders();
 
         if (identityProviderModel == null) {
             throw new javax.ws.rs.NotFoundException();
@@ -289,7 +290,7 @@ public class IdentityProviderResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<IdentityProviderMapperRepresentation> getMappers() {
-        this.auth.requireView();
+        this.auth.realm().requireViewIdentityProviders();
 
         if (identityProviderModel == null) {
             throw new javax.ws.rs.NotFoundException();
@@ -312,7 +313,7 @@ public class IdentityProviderResource {
     @Path("mappers")
     @Consumes(MediaType.APPLICATION_JSON)
     public Response addMapper(IdentityProviderMapperRepresentation mapper) {
-        auth.requireManage();
+        this.auth.realm().requireManageIdentityProviders();
 
         if (identityProviderModel == null) {
             throw new javax.ws.rs.NotFoundException();
@@ -343,7 +344,7 @@ public class IdentityProviderResource {
     @Path("mappers/{id}")
     @Produces(MediaType.APPLICATION_JSON)
     public IdentityProviderMapperRepresentation getMapperById(@PathParam("id") String id) {
-        auth.requireView();
+        this.auth.realm().requireViewIdentityProviders();
 
         if (identityProviderModel == null) {
             throw new javax.ws.rs.NotFoundException();
@@ -365,7 +366,7 @@ public class IdentityProviderResource {
     @Path("mappers/{id}")
     @Consumes(MediaType.APPLICATION_JSON)
     public void update(@PathParam("id") String id, IdentityProviderMapperRepresentation rep) {
-        auth.requireManage();
+        this.auth.realm().requireManageIdentityProviders();
 
         if (identityProviderModel == null) {
             throw new javax.ws.rs.NotFoundException();
@@ -388,7 +389,7 @@ public class IdentityProviderResource {
     @NoCache
     @Path("mappers/{id}")
     public void delete(@PathParam("id") String id) {
-        auth.requireManage();
+        this.auth.realm().requireManageIdentityProviders();
 
         if (identityProviderModel == null) {
             throw new javax.ws.rs.NotFoundException();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
index c5250ac..646b463 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
@@ -37,6 +37,7 @@ import org.keycloak.models.utils.StripSecretsUtils;
 import org.keycloak.provider.ProviderFactory;
 import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.ws.rs.BadRequestException;
 import javax.ws.rs.Consumes;
@@ -65,14 +66,13 @@ public class IdentityProvidersResource {
 
     private final RealmModel realm;
     private final KeycloakSession session;
-    private RealmAuth auth;
+    private AdminPermissionEvaluator auth;
     private AdminEventBuilder adminEvent;
 
-    public IdentityProvidersResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public IdentityProvidersResource(RealmModel realm, KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.realm = realm;
         this.session = session;
         this.auth = auth;
-        this.auth.init(RealmAuth.Resource.IDENTITY_PROVIDER);
         this.adminEvent = adminEvent.resource(ResourceType.IDENTITY_PROVIDER);
     }
 
@@ -87,7 +87,7 @@ public class IdentityProvidersResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Response getIdentityProviders(@PathParam("provider_id") String providerId) {
-        this.auth.requireView();
+        this.auth.realm().requireViewIdentityProviders();
         IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
         if (providerFactory != null) {
             return Response.ok(providerFactory).build();
@@ -108,7 +108,7 @@ public class IdentityProvidersResource {
     @Consumes(MediaType.MULTIPART_FORM_DATA)
     @Produces(MediaType.APPLICATION_JSON)
     public Map<String, String> importFrom(@Context UriInfo uriInfo, MultipartFormDataInput input) throws IOException {
-        this.auth.requireManage();
+        this.auth.realm().requireManageIdentityProviders();
         Map<String, List<InputPart>> formDataMap = input.getFormDataMap();
         if (!(formDataMap.containsKey("providerId") && formDataMap.containsKey("file"))) {
             throw new BadRequestException();
@@ -134,7 +134,7 @@ public class IdentityProvidersResource {
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
     public Map<String, String> importFrom(@Context UriInfo uriInfo, Map<String, Object> data) throws IOException {
-        this.auth.requireManage();
+        this.auth.realm().requireManageIdentityProviders();
         if (!(data.containsKey("providerId") && data.containsKey("fromUrl"))) {
             throw new BadRequestException();
         }
@@ -164,7 +164,7 @@ public class IdentityProvidersResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<IdentityProviderRepresentation> getIdentityProviders() {
-        this.auth.requireView();
+        this.auth.realm().requireViewIdentityProviders();
 
         List<IdentityProviderRepresentation> representations = new ArrayList<IdentityProviderRepresentation>();
 
@@ -185,7 +185,7 @@ public class IdentityProvidersResource {
     @Path("instances")
     @Consumes(MediaType.APPLICATION_JSON)
     public Response create(@Context UriInfo uriInfo, IdentityProviderRepresentation representation) {
-        this.auth.requireManage();
+        this.auth.realm().requireManageIdentityProviders();
 
         try {
             IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, representation);
@@ -203,7 +203,7 @@ public class IdentityProvidersResource {
 
     @Path("instances/{alias}")
     public IdentityProviderResource getIdentityProvider(@PathParam("alias") String alias) {
-        this.auth.requireView();
+        this.auth.realm().requireViewIdentityProviders();
         IdentityProviderModel identityProviderModel = null;
 
         for (IdentityProviderModel storedIdentityProvider : this.realm.getIdentityProviders()) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java b/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java
index f2c9401..d990fd1 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java
@@ -26,6 +26,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeyManager;
 import org.keycloak.models.RealmModel;
 import org.keycloak.representations.idm.KeysMetadataRepresentation;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.ws.rs.GET;
 import javax.ws.rs.Produces;
@@ -43,9 +44,9 @@ public class KeyResource {
 
     private RealmModel realm;
     private KeycloakSession session;
-    private RealmAuth auth;
+    private AdminPermissionEvaluator auth;
 
-    public KeyResource(RealmModel realm, KeycloakSession session, RealmAuth auth) {
+    public KeyResource(RealmModel realm, KeycloakSession session, AdminPermissionEvaluator auth) {
         this.realm = realm;
         this.session = session;
         this.auth = auth;
@@ -55,7 +56,7 @@ public class KeyResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public KeysMetadataRepresentation getKeyMetadata() {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         KeyManager keystore = session.keys();
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionEvaluator.java
new file mode 100644
index 0000000..19abaf4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionEvaluator.java
@@ -0,0 +1,57 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.services.resources.admin.AdminAuth;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface AdminPermissionEvaluator {
+    RealmPermissionEvaluator realm();
+
+    void requireAnyAdminRole();
+
+    AdminAuth adminAuth();
+
+    RolePermissionEvaluator roles();
+    UserPermissionEvaluator users();
+    ClientPermissionEvaluator clients();
+    GroupPermissionEvaluator groups();
+
+    /**
+     * Useful as a function pointer, i.e. RoleMapperResource is reused bewteen GroupResource and UserResource to manage role mappings.
+     * We don't know what type of resource we're managing here (user or group), so we don't know how to query the policy engine to determine
+     * if an action is allowed.
+     *
+     */
+    interface PermissionCheck {
+        boolean evaluate();
+    }
+    /**
+     * Useful as a function pointer, i.e. RoleMapperResource is reused bewteen GroupResource and UserResource to manage role mappings.
+     * We don't know what type of resource we're managing here (user or group), so we don't know how to query the policy engine to determine
+     * if an action is allowed.
+     *
+     * throws appropriate exception if permission is deny
+     *
+     */
+    interface RequirePermissionCheck {
+        void require();
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java
new file mode 100644
index 0000000..2a94132
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.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.services.resources.admin.permissions;
+
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.ResourceServer;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface AdminPermissionManagement {
+    public static final String MANAGE_SCOPE = "manage";
+    public static final String VIEW_SCOPE = "view";
+
+    AuthorizationProvider authz();
+
+    RolePermissionManagement roles();
+    UserPermissionManagement users();
+    GroupPermissionManagement groups();
+    ClientPermissionManagement clients();
+
+    ResourceServer realmResourceServer();
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
new file mode 100644
index 0000000..f809e1d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
@@ -0,0 +1,80 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.ProviderEvent;
+import org.keycloak.provider.ProviderEventListener;
+import org.keycloak.provider.ProviderEventManager;
+import org.keycloak.services.resources.admin.AdminAuth;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class AdminPermissions {
+
+
+    public static AdminPermissionEvaluator evaluator(KeycloakSession session, RealmModel realm, AdminAuth auth) {
+        return new MgmtPermissions(session, realm, auth);
+    }
+    public static AdminPermissionEvaluator evaluator(KeycloakSession session, RealmModel realm, RealmModel adminsRealm, UserModel admin) {
+        return new MgmtPermissions(session, realm, adminsRealm, admin);
+    }
+
+    public static RealmsPermissionEvaluator realms(KeycloakSession session, AdminAuth auth) {
+        return new MgmtPermissions(session, auth);
+    }
+
+    public static AdminPermissionManagement management(KeycloakSession session, RealmModel realm) {
+        return new MgmtPermissions(session, realm);
+    }
+
+    public static void registerListener(ProviderEventManager manager) {
+        manager.register(new ProviderEventListener() {
+            @Override
+            public void onEvent(ProviderEvent event) {
+                if (event instanceof RoleContainerModel.RoleRemovedEvent) {
+                    RoleContainerModel.RoleRemovedEvent cast = (RoleContainerModel.RoleRemovedEvent)event;
+                    RoleModel role = cast.getRole();
+                    RealmModel realm;
+                    if (role.getContainer() instanceof ClientModel) {
+                        realm = ((ClientModel)role.getContainer()).getRealm();
+
+                    } else {
+                        realm = (RealmModel)role.getContainer();
+                    }
+                    management(cast.getKeycloakSession(), realm).roles().setPermissionsEnabled(role, false);
+                } else if (event instanceof RealmModel.ClientRemovedEvent) {
+                    RealmModel.ClientRemovedEvent cast = (RealmModel.ClientRemovedEvent)event;
+                    management(cast.getKeycloakSession(), cast.getClient().getRealm()).clients().setPermissionsEnabled(cast.getClient(), false);
+                } else if (event instanceof GroupModel.GroupRemovedEvent) {
+                    GroupModel.GroupRemovedEvent cast = (GroupModel.GroupRemovedEvent)event;
+                    management(cast.getKeycloakSession(), cast.getRealm()).groups().setPermissionsEnabled(cast.getGroup(), false);
+                }
+            }
+        });
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java
new file mode 100644
index 0000000..3b64a27
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java
@@ -0,0 +1,84 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface ClientPermissionEvaluator {
+    boolean isPermissionsEnabled(ClientModel client);
+
+    void setPermissionsEnabled(ClientModel client, boolean enable);
+
+    void requireListTemplates();
+
+    boolean canManage();
+
+    void requireManage();
+
+    boolean canManageTemplates();
+
+    void requireManageTemplates();
+
+    boolean canView();
+
+    boolean canList();
+
+    boolean canViewTemplates();
+
+    void requireList();
+
+    boolean canListTemplates();
+
+    void requireView();
+
+    void requireViewTemplates();
+
+    boolean canManage(ClientModel client);
+
+    boolean canConfigure(ClientModel client);
+
+    void requireConfigure(ClientModel client);
+
+    void requireManage(ClientModel client);
+
+    boolean canView(ClientModel client);
+
+    void requireView(ClientModel client);
+
+    boolean canManage(ClientTemplateModel template);
+
+    void requireManage(ClientTemplateModel template);
+
+    boolean canView(ClientTemplateModel template);
+
+    void requireView(ClientTemplateModel template);
+
+    boolean canMapRoles(ClientModel client);
+
+    boolean canMapCompositeRoles(ClientModel client);
+
+    boolean canMapClientScopeRoles(ClientModel client);
+
+    Map<String, Boolean> getAccess(ClientModel client);
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java
new file mode 100644
index 0000000..8a6b76d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java
@@ -0,0 +1,57 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.models.ClientModel;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface ClientPermissionManagement {
+    public static final String MAP_ROLES_SCOPE = "map-roles";
+    public static final String MAP_ROLES_CLIENT_SCOPE = "map-roles-client-scope";
+    public static final String MAP_ROLES_COMPOSITE_SCOPE = "map-roles-composite";
+    public static final String CONFIGURE_SCOPE = "configure";
+
+    boolean isPermissionsEnabled(ClientModel client);
+
+    void setPermissionsEnabled(ClientModel client, boolean enable);
+
+    Resource resource(ClientModel client);
+
+    Map<String, String> getPermissions(ClientModel client);
+
+    Policy mapRolesPermission(ClientModel client);
+
+    Policy mapRolesClientScopePermission(ClientModel client);
+
+    Policy mapRolesCompositePermission(ClientModel client);
+
+    Policy managePermission(ClientModel client);
+
+    Policy configurePermission(ClientModel client);
+
+    Policy viewPermission(ClientModel client);
+
+    ResourceServer resourceServer(ClientModel client);
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
new file mode 100644
index 0000000..2b1e234
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
@@ -0,0 +1,568 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authorization.AuthorizationProvider;
+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.models.AdminRoles;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.services.ForbiddenException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages default policies for all users.
+ *
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionManagement {
+    private static final Logger logger = Logger.getLogger(ClientPermissions.class);
+    protected final KeycloakSession session;
+    protected final RealmModel realm;
+    protected final AuthorizationProvider authz;
+    protected final MgmtPermissions root;
+
+    public ClientPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) {
+        this.session = session;
+        this.realm = realm;
+        this.authz = authz;
+        this.root = root;
+    }
+
+    private String getResourceName(ClientModel client) {
+        return "client.resource." + client.getId();
+    }
+
+    private String getManagePermissionName(ClientModel client) {
+        return "manage.permission.client." + client.getId();
+    }
+    private String getConfigurePermissionName(ClientModel client) {
+        return "configure.permission.client." + client.getId();
+    }
+    private String getViewPermissionName(ClientModel client) {
+        return "view.permission.client." + client.getId();
+    }
+    private String getMapRolesPermissionName(ClientModel client) {
+        return MAP_ROLES_SCOPE + ".permission.client." + client.getId();
+    }
+    private String getMapRolesClientScopePermissionName(ClientModel client) {
+        return MAP_ROLES_CLIENT_SCOPE + ".permission.client." + client.getId();
+    }
+    private String getMapRolesCompositePermissionName(ClientModel client) {
+        return MAP_ROLES_COMPOSITE_SCOPE + ".permission.client." + client.getId();
+    }
+
+    private void initialize(ClientModel client) {
+        ResourceServer server = root.findOrCreateResourceServer(client);
+        Scope manageScope = manageScope(server);
+        if (manageScope == null) {
+            manageScope = authz.getStoreFactory().getScopeStore().create(AdminPermissionManagement.MANAGE_SCOPE, server);
+        }
+        Scope viewScope = viewScope(server);
+        if (viewScope == null) {
+            viewScope = authz.getStoreFactory().getScopeStore().create(AdminPermissionManagement.VIEW_SCOPE, server);
+        }
+        Scope mapRoleScope = mapRolesScope(server);
+        if (mapRoleScope == null) {
+            mapRoleScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_SCOPE, server);
+        }
+        Scope mapRoleClientScope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_CLIENT_SCOPE, server.getId());
+        if (mapRoleClientScope == null) {
+            mapRoleClientScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_CLIENT_SCOPE, server);
+        }
+        Scope mapRoleCompositeScope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_COMPOSITE_SCOPE, server.getId());
+        if (mapRoleCompositeScope == null) {
+            mapRoleCompositeScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_COMPOSITE_SCOPE, server);
+        }
+        Scope configureScope = authz.getStoreFactory().getScopeStore().findByName(CONFIGURE_SCOPE, server.getId());
+        if (configureScope == null) {
+            configureScope = authz.getStoreFactory().getScopeStore().create(CONFIGURE_SCOPE, server);
+        }
+
+        String resourceName = getResourceName(client);
+        Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId());
+        if (resource == null) {
+            resource = authz.getStoreFactory().getResourceStore().create(resourceName, server, server.getClientId());
+            resource.setType("Client");
+            Set<Scope> scopeset = new HashSet<>();
+            scopeset.add(configureScope);
+            scopeset.add(manageScope);
+            scopeset.add(viewScope);
+            scopeset.add(mapRoleScope);
+            scopeset.add(mapRoleClientScope);
+            scopeset.add(mapRoleCompositeScope);
+            resource.updateScopes(scopeset);
+        }
+        String managePermissionName = getManagePermissionName(client);
+        Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(managePermissionName, server.getId());
+        if (managePermission == null) {
+            Helper.addEmptyScopePermission(authz, server, managePermissionName, resource, manageScope);
+        }
+        String configurePermissionName = getConfigurePermissionName(client);
+        Policy configurePermission = authz.getStoreFactory().getPolicyStore().findByName(configurePermissionName, server.getId());
+        if (configurePermission == null) {
+            Helper.addEmptyScopePermission(authz, server, configurePermissionName, resource, configureScope);
+        }
+        String viewPermissionName = getViewPermissionName(client);
+        Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId());
+        if (viewPermission == null) {
+            Helper.addEmptyScopePermission(authz, server, viewPermissionName, resource, viewScope);
+        }
+        String mapRolePermissionName = getMapRolesPermissionName(client);
+        Policy mapRolePermission = authz.getStoreFactory().getPolicyStore().findByName(mapRolePermissionName, server.getId());
+        if (mapRolePermission == null) {
+            Helper.addEmptyScopePermission(authz, server, mapRolePermissionName, resource, mapRoleScope);
+        }
+        String mapRoleClientScopePermissionName = getMapRolesClientScopePermissionName(client);
+        Policy mapRoleClientScopePermission = authz.getStoreFactory().getPolicyStore().findByName(mapRoleClientScopePermissionName, server.getId());
+        if (mapRoleClientScopePermission == null) {
+            Helper.addEmptyScopePermission(authz, server, mapRoleClientScopePermissionName, resource, mapRoleClientScope);
+        }
+        String mapRoleCompositePermissionName = getMapRolesCompositePermissionName(client);
+        Policy mapRoleCompositePermission = authz.getStoreFactory().getPolicyStore().findByName(mapRoleCompositePermissionName, server.getId());
+        if (mapRoleCompositePermission == null) {
+            Helper.addEmptyScopePermission(authz, server, mapRoleCompositePermissionName, resource, mapRoleCompositeScope);
+        }
+    }
+
+    private void deletePolicy(String name, ResourceServer server) {
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(name, server.getId());
+        if (policy != null) {
+            authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+        }
+
+    }
+
+    private void deletePermissions(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return;
+        deletePolicy(getManagePermissionName(client), server);
+        deletePolicy(getViewPermissionName(client), server);
+        deletePolicy(getMapRolesPermissionName(client), server);
+        deletePolicy(getMapRolesClientScopePermissionName(client), server);
+        deletePolicy(getMapRolesCompositePermissionName(client), server);
+        deletePolicy(getConfigurePermissionName(client), server);
+        Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());;
+        if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId());
+    }
+
+    @Override
+    public boolean isPermissionsEnabled(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return false;
+
+        return authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()) != null;
+    }
+
+    @Override
+    public void setPermissionsEnabled(ClientModel client, boolean enable) {
+        if (enable) {
+            initialize(client);
+        } else {
+            deletePermissions(client);
+        }
+    }
+
+
+
+    private Scope manageScope(ResourceServer server) {
+        return authz.getStoreFactory().getScopeStore().findByName(AdminPermissionManagement.MANAGE_SCOPE, server.getId());
+    }
+
+    private Scope configureScope(ResourceServer server) {
+        return authz.getStoreFactory().getScopeStore().findByName(CONFIGURE_SCOPE, server.getId());
+    }
+
+    private Scope viewScope(ResourceServer server) {
+        return authz.getStoreFactory().getScopeStore().findByName(AdminPermissionManagement.VIEW_SCOPE, server.getId());
+    }
+    private Scope mapRolesScope(ResourceServer server) {
+        return authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_SCOPE, server.getId());
+    }
+
+    @Override
+    public boolean canList() {
+        return root.hasAnyAdminRole();
+    }
+
+    @Override
+    public void requireList() {
+        if (!canList()) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canListTemplates() {
+        return root.hasAnyAdminRole();
+    }
+
+    @Override
+    public void requireListTemplates() {
+        if (!canListTemplates()) {
+            throw new ForbiddenException();
+        }
+    }
+    public boolean canManageClientsDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_CLIENTS);
+    }
+    public boolean canViewClientDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_CLIENTS, AdminRoles.VIEW_CLIENTS);
+    }
+
+    @Override
+    public boolean canManage() {
+        return canManageClientsDefault();
+    }
+
+    @Override
+    public void requireManage() {
+        if (!canManage()) {
+            throw new ForbiddenException();
+        }
+    }
+    @Override
+    public boolean canView() {
+        return canManageClientsDefault() || canViewClientDefault();
+    }
+
+    @Override
+    public void requireView() {
+        if (!canView()) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public Resource resource(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return null;
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());
+        if (resource == null) return null;
+        return resource;
+    }
+
+    @Override
+    public Map<String, String> getPermissions(ClientModel client) {
+        Map<String, String> scopes = new HashMap<>();
+        scopes.put(MAP_ROLES_SCOPE,  mapRolesPermission(client).getId());
+        scopes.put(MAP_ROLES_CLIENT_SCOPE, mapRolesClientScopePermission(client).getId());
+        scopes.put(MAP_ROLES_COMPOSITE_SCOPE, mapRolesCompositePermission(client).getId());
+        scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(client).getId());
+        scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(client).getId());
+        scopes.put(CONFIGURE_SCOPE, configurePermission(client).getId());
+        return scopes;
+    }
+
+
+    @Override
+    public boolean canManage(ClientModel client) {
+        if (canManageClientsDefault()) return true;
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = resourceServer(client);
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());
+        if (resource == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getManagePermissionName(client), server.getId());
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = manageScope(server);
+        return root.evaluatePermission(resource, scope, server);
+    }
+
+    @Override
+    public boolean canConfigure(ClientModel client) {
+        if (canManage(client)) return true;
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = resourceServer(client);
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());
+        if (resource == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getConfigurePermissionName(client), server.getId());
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = configureScope(server);
+        return root.evaluatePermission(resource, scope, server);
+    }
+    @Override
+    public void requireConfigure(ClientModel client) {
+        if (!canConfigure(client)) {
+            throw new ForbiddenException();
+        }
+    }
+
+
+    @Override
+    public void requireManage(ClientModel client) {
+        if (!canManage(client)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canView(ClientModel client) {
+        return hasView(client) || canConfigure(client);
+    }
+
+    private boolean hasView(ClientModel client) {
+        if (canView()) return true;
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = resourceServer(client);
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());
+        if (resource == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getViewPermissionName(client), server.getId());
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = viewScope(server);
+        return root.evaluatePermission(resource, scope, server);
+    }
+
+    @Override
+    public void requireView(ClientModel client) {
+        if (!canView(client)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    // templates
+
+    @Override
+    public boolean canViewTemplates() {
+        return canView();
+    }
+
+    @Override
+    public boolean canManageTemplates() {
+        return canManageClientsDefault();
+    }
+
+    @Override
+    public void requireManageTemplates() {
+        if (!canManageTemplates()) {
+            throw new ForbiddenException();
+        }
+    }
+    @Override
+    public void requireViewTemplates() {
+        if (!canViewTemplates()) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canManage(ClientTemplateModel template) {
+        return canManageClientsDefault();
+    }
+
+    @Override
+    public void requireManage(ClientTemplateModel template) {
+        if (!canManage(template)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canView(ClientTemplateModel template) {
+        return canViewClientDefault();
+    }
+
+    @Override
+    public void requireView(ClientTemplateModel template) {
+        if (!canView(template)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canMapRoles(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());
+        if (resource == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRolesPermissionName(client), server.getId());
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = mapRolesScope(server);
+        return root.evaluatePermission(resource, scope, server);
+    }
+
+    @Override
+    public Policy mapRolesPermission(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return null;
+        return authz.getStoreFactory().getPolicyStore().findByName(getMapRolesPermissionName(client), server.getId());
+    }
+
+    @Override
+    public Policy mapRolesClientScopePermission(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return null;
+        return authz.getStoreFactory().getPolicyStore().findByName(getMapRolesClientScopePermissionName(client), server.getId());
+    }
+
+    @Override
+    public Policy mapRolesCompositePermission(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return null;
+        return authz.getStoreFactory().getPolicyStore().findByName(getMapRolesCompositePermissionName(client), server.getId());
+    }
+
+    @Override
+    public Policy managePermission(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return null;
+        return authz.getStoreFactory().getPolicyStore().findByName(getManagePermissionName(client), server.getId());
+    }
+
+    @Override
+    public Policy configurePermission(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return null;
+        return authz.getStoreFactory().getPolicyStore().findByName(getConfigurePermissionName(client), server.getId());
+    }
+
+    @Override
+    public Policy viewPermission(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return null;
+        return authz.getStoreFactory().getPolicyStore().findByName(getViewPermissionName(client), server.getId());
+    }
+
+    @Override
+    public ResourceServer resourceServer(ClientModel client) {
+        return root.resourceServer(client);
+    }
+
+    @Override
+    public boolean canMapCompositeRoles(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());
+        if (resource == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRolesCompositePermissionName(client), server.getId());
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_COMPOSITE_SCOPE, server.getId());
+        return root.evaluatePermission(resource, scope, server);
+    }
+    @Override
+    public boolean canMapClientScopeRoles(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());
+        if (resource == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRolesClientScopePermissionName(client), server.getId());
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_CLIENT_SCOPE, server.getId());
+        return root.evaluatePermission(resource, scope, server);
+    }
+
+    @Override
+    public Map<String, Boolean> getAccess(ClientModel client) {
+        Map<String, Boolean> map = new HashMap<>();
+        map.put("view", canView(client));
+        map.put("manage", canManage(client));
+        map.put("configure", canConfigure(client));
+        return map;
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java
new file mode 100644
index 0000000..d3a5213
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java
@@ -0,0 +1,61 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.models.GroupModel;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface GroupPermissionEvaluator {
+    boolean canList();
+
+    void requireList();
+
+    boolean canManage(GroupModel group);
+
+    void requireManage(GroupModel group);
+
+    boolean canView(GroupModel group);
+
+    void requireView(GroupModel group);
+
+    boolean canManage();
+
+    void requireManage();
+
+    boolean canView();
+
+    void requireView();
+
+    boolean canViewMembers(GroupModel group);
+
+    void requireViewMembers(GroupModel group);
+
+    boolean canManageMembers(GroupModel group);
+
+    void requireManageMembers(GroupModel group);
+
+    boolean canManageMembership(GroupModel group);
+
+    void requireManageMembership(GroupModel group);
+
+    Map<String, Boolean> getAccess(GroupModel group);
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java
new file mode 100644
index 0000000..2ec602c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.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.services.resources.admin.permissions;
+
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RoleModel;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface GroupPermissionManagement {
+    boolean isPermissionsEnabled(GroupModel group);
+    void setPermissionsEnabled(GroupModel group, boolean enable);
+
+    Policy viewMembersPermission(GroupModel group);
+    Policy manageMembersPermission(GroupModel group);
+
+    Policy manageMembershipPermission(GroupModel group);
+
+    Policy viewPermission(GroupModel group);
+    Policy managePermission(GroupModel group);
+
+    Resource resource(GroupModel group);
+
+    Map<String, String> getPermissions(GroupModel group);
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
new file mode 100644
index 0000000..a7e9f37
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
@@ -0,0 +1,483 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authorization.AuthorizationProvider;
+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.models.AdminRoles;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.services.ForbiddenException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManagement {
+    private static final Logger logger = Logger.getLogger(GroupPermissions.class);
+    public static final String MAP_ROLE_SCOPE = "map-role";
+    public static final String MANAGE_MEMBERSHIP_SCOPE = "manage-membership";
+    public static final String MANAGE_MEMBERS_SCOPE = "manage-members";
+    public static final String VIEW_MEMBERS_SCOPE = "view-members";
+    protected final KeycloakSession session;
+    protected final RealmModel realm;
+    protected final AuthorizationProvider authz;
+    protected final MgmtPermissions root;
+
+    public GroupPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) {
+        this.session = session;
+        this.realm = realm;
+        this.authz = authz;
+        this.root = root;
+    }
+
+    private static String getGroupResourceName(GroupModel group) {
+        return "group.resource." + group.getId();
+    }
+
+
+    public static String getManagePermissionGroup(GroupModel group) {
+        return "manage.permission.group." + group.getId();
+    }
+
+    public static String getManageMembersPermissionGroup(GroupModel group) {
+        return "manage.members.permission.group." + group.getId();
+    }
+
+    public static String getManageMembershipPermissionGroup(GroupModel group) {
+        return "manage.membership.permission.group." + group.getId();
+    }
+
+    public static String getViewPermissionGroup(GroupModel group) {
+        return "view.permission.group." + group.getId();
+    }
+
+    public static String getViewMembersPermissionGroup(GroupModel group) {
+        return "view.members.permission.group." + group.getId();
+    }
+
+    private void initialize(GroupModel group) {
+        root.initializeRealmResourceServer();
+        root.initializeRealmDefaultScopes();
+        ResourceServer server = root.realmResourceServer();
+        Scope manageScope = root.realmManageScope();
+        Scope viewScope = root.realmViewScope();
+        Scope manageMembersScope = root.initializeRealmScope(MANAGE_MEMBERS_SCOPE);
+        Scope viewMembersScope = root.initializeRealmScope(VIEW_MEMBERS_SCOPE);
+        Scope manageMembershipScope = root.initializeRealmScope(MANAGE_MEMBERSHIP_SCOPE);
+
+        String groupResourceName = getGroupResourceName(group);
+        Resource groupResource = authz.getStoreFactory().getResourceStore().findByName(groupResourceName, server.getId());
+        if (groupResource == null) {
+            groupResource = authz.getStoreFactory().getResourceStore().create(groupResourceName, server, server.getClientId());
+            Set<Scope> scopeset = new HashSet<>();
+            scopeset.add(manageScope);
+            scopeset.add(viewScope);
+            scopeset.add(manageMembershipScope);
+            scopeset.add(manageMembersScope);
+            groupResource.updateScopes(scopeset);
+        }
+        String managePermissionName = getManagePermissionGroup(group);
+        Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(managePermissionName, server.getId());
+        if (managePermission == null) {
+            Helper.addEmptyScopePermission(authz, server, managePermissionName, groupResource, manageScope);
+        }
+        String viewPermissionName = getViewPermissionGroup(group);
+        Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId());
+        if (viewPermission == null) {
+            Helper.addEmptyScopePermission(authz, server, viewPermissionName, groupResource, viewScope);
+        }
+        String manageMembersPermissionName = getManageMembersPermissionGroup(group);
+        Policy manageMembersPermission = authz.getStoreFactory().getPolicyStore().findByName(manageMembersPermissionName, server.getId());
+        if (manageMembersPermission == null) {
+            Helper.addEmptyScopePermission(authz, server, manageMembersPermissionName, groupResource, manageMembersScope);
+        }
+        String viewMembersPermissionName = getViewMembersPermissionGroup(group);
+        Policy viewMembersPermission = authz.getStoreFactory().getPolicyStore().findByName(viewMembersPermissionName, server.getId());
+        if (viewMembersPermission == null) {
+            Helper.addEmptyScopePermission(authz, server, viewMembersPermissionName, groupResource, viewMembersScope);
+        }
+        String manageMembershipPermissionName = getManageMembershipPermissionGroup(group);
+        Policy manageMembershipPermission = authz.getStoreFactory().getPolicyStore().findByName(manageMembershipPermissionName, server.getId());
+        if (manageMembershipPermission == null) {
+            Helper.addEmptyScopePermission(authz, server, manageMembershipPermissionName, groupResource, manageMembershipScope);
+        }
+
+    }
+
+    @Override
+    public boolean canList() {
+        return root.hasOneAdminRole(AdminRoles.VIEW_USERS, AdminRoles.MANAGE_USERS, AdminRoles.QUERY_GROUPS);
+    }
+
+    @Override
+    public void requireList() {
+        if (!canList()) {
+            throw new ForbiddenException();
+        }
+    }
+
+
+
+    @Override
+    public boolean isPermissionsEnabled(GroupModel group) {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return false;
+
+        return authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()) != null;
+    }
+
+    private Resource groupResource(GroupModel group) {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return null;
+        String groupResourceName = getGroupResourceName(group);
+        return authz.getStoreFactory().getResourceStore().findByName(groupResourceName, server.getId());
+    }
+
+    @Override
+    public void setPermissionsEnabled(GroupModel group, boolean enable) {
+       if (enable) {
+           initialize(group);
+       } else {
+           deletePermissions(group);
+       }
+    }
+
+    private void deletePermissions(GroupModel group) {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return;
+        Policy managePermission = managePermission(group);
+        if (managePermission != null) {
+            authz.getStoreFactory().getPolicyStore().delete(managePermission.getId());
+        }
+        Policy viewPermission = viewPermission(group);
+        if (viewPermission != null) {
+            authz.getStoreFactory().getPolicyStore().delete(viewPermission.getId());
+        }
+        Policy manageMembersPermission = manageMembersPermission(group);
+        if (manageMembersPermission != null) {
+            authz.getStoreFactory().getPolicyStore().delete(manageMembersPermission.getId());
+        }
+        Policy viewMembersPermission = viewMembersPermission(group);
+        if (manageMembersPermission == null) {
+            authz.getStoreFactory().getPolicyStore().delete(viewMembersPermission.getId());
+        }
+        Resource resource = groupResource(group);
+        if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId());
+    }
+
+    @Override
+    public Policy viewMembersPermission(GroupModel group) {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return null;
+        String viewMembersPermissionName = getViewMembersPermissionGroup(group);
+        return authz.getStoreFactory().getPolicyStore().findByName(viewMembersPermissionName, server.getId());
+    }
+
+    @Override
+    public Policy manageMembersPermission(GroupModel group) {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return null;
+        String manageMembersPermissionName = getManageMembersPermissionGroup(group);
+        return authz.getStoreFactory().getPolicyStore().findByName(manageMembersPermissionName, server.getId());
+    }
+
+    @Override
+    public Policy manageMembershipPermission(GroupModel group) {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return null;
+        String manageMembershipPermissionName = getManageMembershipPermissionGroup(group);
+        return authz.getStoreFactory().getPolicyStore().findByName(manageMembershipPermissionName, server.getId());
+    }
+
+    @Override
+    public Policy viewPermission(GroupModel group) {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return null;
+        String viewPermissionName = getViewPermissionGroup(group);
+        return authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId());
+    }
+
+    @Override
+    public Policy managePermission(GroupModel group) {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return null;
+        String managePermissionName = getManagePermissionGroup(group);
+        return authz.getStoreFactory().getPolicyStore().findByName(managePermissionName, server.getId());
+    }
+
+    @Override
+    public Resource resource(GroupModel group) {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return null;
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId());
+        if (resource == null) return null;
+        return resource;
+    }
+
+    @Override
+    public Map<String, String> getPermissions(GroupModel group) {
+        Map<String, String> scopes = new HashMap<>();
+        scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(group).getId());
+        scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(group).getId());
+        scopes.put(MANAGE_MEMBERS_SCOPE, manageMembersPermission(group).getId());
+        scopes.put(VIEW_MEMBERS_SCOPE, viewMembersPermission(group).getId());
+        scopes.put(MANAGE_MEMBERSHIP_SCOPE, manageMembershipPermission(group).getId());
+        return scopes;
+    }
+
+
+
+
+    @Override
+    public boolean canManage(GroupModel group) {
+        if (canManage()) return true;
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId());
+        if (resource == null) return false;
+
+        Policy policy = managePermission(group);
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = root.realmManageScope();
+        return root.evaluatePermission(resource, scope, server);
+    }
+
+    @Override
+    public void requireManage(GroupModel group) {
+        if (!canManage(group)) {
+            throw new ForbiddenException();
+        }
+    }
+    @Override
+    public boolean canView(GroupModel group) {
+        return hasView(group) || canManage(group);
+    }
+
+    private boolean hasView(GroupModel group) {
+        if (canView()) return true;
+
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId());
+        if (resource == null) return false;
+
+        Policy policy = viewPermission(group);
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then abort
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = root.realmViewScope();
+        return root.evaluatePermission(resource, scope, server);
+    }
+
+    @Override
+    public void requireView(GroupModel group) {
+        if (!canView(group)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canManage() {
+        return root.users().canManageDefault();
+    }
+
+    @Override
+    public void requireManage() {
+        if (!canManage()) {
+            throw new ForbiddenException();
+        }
+    }
+    @Override
+    public boolean canView() {
+        return root.users().canViewDefault();
+    }
+
+    @Override
+    public void requireView() {
+        if (!canView()) {
+            throw new ForbiddenException();
+        }
+    }
+
+
+
+    @Override
+    public boolean canViewMembers(GroupModel group) {
+        return canViewMembersEvaluation(group) || canManageMembers(group);
+    }
+
+    private boolean canViewMembersEvaluation(GroupModel group) {
+        if (root.users().canView()) return true;
+
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId());
+        if (resource == null) return false;
+
+        Policy policy = viewMembersPermission(group);
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = authz.getStoreFactory().getScopeStore().findByName(VIEW_MEMBERS_SCOPE, server.getId());
+
+        return root.evaluatePermission(resource, scope, server);
+    }
+
+
+    @Override
+    public void requireViewMembers(GroupModel group) {
+        if (!canViewMembers(group)) {
+            throw new ForbiddenException();
+        }
+    }
+
+
+    @Override
+    public boolean canManageMembers(GroupModel group) {
+        if (root.users().canManage()) return true;
+
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId());
+        if (resource == null) return false;
+
+        Policy policy = manageMembersPermission(group);
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = authz.getStoreFactory().getScopeStore().findByName(MANAGE_MEMBERS_SCOPE, server.getId());
+        return root.evaluatePermission(resource, scope, server);
+    }
+
+    @Override
+    public void requireManageMembers(GroupModel group) {
+        if (!canManageMembers(group)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canManageMembership(GroupModel group) {
+        if (canManage(group)) return true;
+
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId());
+        if (resource == null) return false;
+
+        Policy policy = manageMembershipPermission(group);
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = authz.getStoreFactory().getScopeStore().findByName(MANAGE_MEMBERSHIP_SCOPE, server.getId());
+        return root.evaluatePermission(resource, scope, server);
+    }
+
+    @Override
+    public void requireManageMembership(GroupModel group) {
+        if (!canManageMembership(group)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public Map<String, Boolean> getAccess(GroupModel group) {
+        Map<String, Boolean> map = new HashMap<>();
+        map.put("view", canView(group));
+        map.put("manage", canManage(group));
+        map.put("manageMembership", canManageMembership(group));
+        return map;
+    }
+
+
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/Helper.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/Helper.java
new file mode 100644
index 0000000..2e7942d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/Helper.java
@@ -0,0 +1,95 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.authorization.AuthorizationProvider;
+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.models.ClientModel;
+import org.keycloak.models.RoleModel;
+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.ScopePermissionRepresentation;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+class Helper {
+    public static Policy addScopePermission(AuthorizationProvider authz, ResourceServer resourceServer, String name, Resource resource, Scope scope, Policy policy) {
+        ScopePermissionRepresentation representation = new ScopePermissionRepresentation();
+
+        representation.setName(name);
+        representation.setDecisionStrategy(DecisionStrategy.UNANIMOUS);
+        representation.setLogic(Logic.POSITIVE);
+        representation.addResource(resource.getName());
+        representation.addScope(scope.getName());
+        representation.addPolicy(policy.getName());
+
+        return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer);
+    }
+
+    public static Policy addEmptyScopePermission(AuthorizationProvider authz, ResourceServer resourceServer, String name, Resource resource, Scope scope) {
+        ScopePermissionRepresentation representation = new ScopePermissionRepresentation();
+
+        representation.setName(name);
+        representation.setDecisionStrategy(DecisionStrategy.UNANIMOUS);
+        representation.setLogic(Logic.POSITIVE);
+        representation.addResource(resource.getName());
+        representation.addScope(scope.getName());
+
+        return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer);
+    }
+
+    public static Policy createRolePolicy(AuthorizationProvider authz, ResourceServer resourceServer, RoleModel role) {
+        String roleName = getRolePolicyName(role);
+        return createRolePolicy(authz, resourceServer, role, roleName);
+    }
+
+    public static Policy createRolePolicy(AuthorizationProvider authz, ResourceServer resourceServer, RoleModel role, String policyName) {
+        PolicyRepresentation representation = new PolicyRepresentation();
+
+        representation.setName(policyName);
+        representation.setType("role");
+        representation.setDecisionStrategy(DecisionStrategy.UNANIMOUS);
+        representation.setLogic(Logic.POSITIVE);
+        String roleValues = "[{\"id\":\"" + role.getId() + "\",\"required\": true}]";
+        Map<String, String> config = new HashMap<>();
+        config.put("roles", roleValues);
+        representation.setConfig(config);
+
+        return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer);
+    }
+
+    public static String getRolePolicyName(RoleModel role) {
+        String roleName = "";
+        if (role.getContainer() instanceof ClientModel) {
+            ClientModel client = (ClientModel) role.getContainer();
+            roleName = client.getClientId() + "." + role.getName();
+        } else {
+            roleName = role.getName();
+        }
+        roleName = "role.policy." + roleName;
+        return roleName;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java
new file mode 100644
index 0000000..2df4953
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java
@@ -0,0 +1,364 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.AuthorizationProviderFactory;
+import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.common.DefaultEvaluationContext;
+import org.keycloak.authorization.common.KeycloakIdentity;
+import org.keycloak.authorization.common.UserModelIdentity;
+import org.keycloak.authorization.identity.Identity;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.permission.ResourcePermission;
+import org.keycloak.authorization.permission.evaluator.PermissionEvaluator;
+import org.keycloak.authorization.policy.evaluation.DecisionResult;
+import org.keycloak.authorization.policy.evaluation.EvaluationContext;
+import org.keycloak.authorization.store.ResourceServerStore;
+import org.keycloak.authorization.util.Permissions;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.resources.admin.AdminAuth;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManagement, RealmsPermissionEvaluator {
+    private static final Logger logger = Logger.getLogger(MgmtPermissions.class);
+
+    protected RealmModel realm;
+    protected KeycloakSession session;
+    protected AuthorizationProvider authz;
+    protected AdminAuth auth;
+    protected Identity identity;
+    protected UserModel admin;
+    protected RealmModel adminsRealm;
+    protected ResourceServer realmResourceServer;
+    protected UserPermissions users;
+    protected GroupPermissions groups;
+    protected RealmPermissions realmPermissions;
+    protected ClientPermissions clientPermissions;
+
+
+    MgmtPermissions(KeycloakSession session, RealmModel realm) {
+        this.session = session;
+        this.realm = realm;
+        KeycloakSessionFactory keycloakSessionFactory = session.getKeycloakSessionFactory();
+        AuthorizationProviderFactory factory = (AuthorizationProviderFactory) keycloakSessionFactory.getProviderFactory(AuthorizationProvider.class);
+        this.authz = factory.create(session, realm);
+    }
+
+    MgmtPermissions(KeycloakSession session, RealmModel realm, AdminAuth auth) {
+        this(session, realm);
+        this.auth = auth;
+        this.admin = auth.getUser();
+        this.adminsRealm = auth.getRealm();
+        if (!auth.getRealm().equals(realm)
+                && !auth.getRealm().equals(new RealmManager(session).getKeycloakAdminstrationRealm())) {
+            throw new ForbiddenException();
+        }
+        if (auth.getClient().getClientId().equals(Constants.ADMIN_CLI_CLIENT_ID)
+                || auth.getClient().getClientId().equals(Constants.ADMIN_CONSOLE_CLIENT_ID)) {
+            this.identity = new UserModelIdentity(auth.getRealm(), auth.getUser());
+
+        } else {
+            this.identity = new KeycloakIdentity(auth.getToken(), session);
+        }
+    }
+    MgmtPermissions(KeycloakSession session, AdminAuth auth) {
+        this.session = session;
+        this.auth = auth;
+        this.admin = auth.getUser();
+        this.adminsRealm = auth.getRealm();
+        if (auth.getClient().getClientId().equals(Constants.ADMIN_CLI_CLIENT_ID)
+                || auth.getClient().getClientId().equals(Constants.ADMIN_CONSOLE_CLIENT_ID)) {
+            this.identity = new UserModelIdentity(auth.getRealm(), auth.getUser());
+
+        } else {
+            this.identity = new KeycloakIdentity(auth.getToken(), session);
+        }
+    }
+    MgmtPermissions(KeycloakSession session, RealmModel realm, RealmModel adminsRealm, UserModel admin) {
+        this(session, realm);
+        this.admin = admin;
+        this.adminsRealm = adminsRealm;
+        this.identity = new UserModelIdentity(realm, admin);
+    }
+
+    public ClientModel getRealmManagementClient() {
+        ClientModel client = null;
+        if (realm.getName().equals(Config.getAdminRealm())) {
+            client = realm.getClientByClientId(Config.getAdminRealm() + "-realm");
+        } else {
+            client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID);
+
+        }
+        return client;
+    }
+
+    @Override
+    public AuthorizationProvider authz() {
+        return authz;
+    }
+
+
+
+    @Override
+    public void requireAnyAdminRole() {
+        if (!hasAnyAdminRole()) {
+            throw new ForbiddenException();
+        }
+    }
+
+    public boolean hasAnyAdminRole() {
+        return hasOneAdminRole(AdminRoles.ALL_REALM_ROLES);
+    }
+
+    public boolean hasAnyAdminRole(RealmModel realm) {
+        return hasOneAdminRole(realm, AdminRoles.ALL_REALM_ROLES);
+    }
+
+    public boolean hasOneAdminRole(String... adminRoles) {
+        String clientId;
+        RealmModel realm = this.realm;
+        return hasOneAdminRole(realm, adminRoles);
+    }
+
+    public boolean hasOneAdminRole(RealmModel realm, String... adminRoles) {
+        String clientId;
+        RealmManager realmManager = new RealmManager(session);
+        if (adminsRealm.equals(realmManager.getKeycloakAdminstrationRealm())) {
+            clientId = realm.getMasterAdminClient().getClientId();
+        } else if (adminsRealm.equals(realm)) {
+            clientId = realm.getClientByClientId(realmManager.getRealmAdminClientId(realm)).getClientId();
+        } else {
+            return false;
+        }
+        for (String adminRole : adminRoles) {
+            if (identity.hasClientRole(clientId, adminRole)) return true;
+        }
+        return false;
+    }
+
+
+    public boolean isAdminSameRealm() {
+        return auth == null || realm.getId().equals(auth.getRealm().getId());
+    }
+
+    @Override
+    public AdminAuth adminAuth() {
+        return auth;
+    }
+
+    public Identity identity() {
+        return identity;
+    }
+
+    public UserModel admin() {
+        return admin;
+    }
+
+
+    @Override
+    public RolePermissions roles() {
+        return new RolePermissions(session, realm, authz, this);
+    }
+
+    @Override
+    public UserPermissions users() {
+        if (users != null) return users;
+        users = new UserPermissions(session, realm, authz, this);
+        return users;
+    }
+
+    @Override
+    public RealmPermissions realm() {
+        if (realmPermissions != null) return realmPermissions;
+        realmPermissions = new RealmPermissions(session, realm, authz, this);
+        return realmPermissions;
+    }
+
+    @Override
+    public ClientPermissions clients() {
+        if (clientPermissions != null) return clientPermissions;
+        clientPermissions = new ClientPermissions(session, realm, authz, this);
+        return clientPermissions;
+    }
+
+    @Override
+    public GroupPermissions groups() {
+        if (groups != null) return groups;
+        groups = new GroupPermissions(session, realm, authz, this);
+        return groups;
+    }
+
+    public ResourceServer findOrCreateResourceServer(ClientModel client) {
+         return initializeRealmResourceServer();
+    }
+
+    public ResourceServer resourceServer(ClientModel client) {
+        return realmResourceServer();
+    }
+
+    @Override
+    public ResourceServer realmResourceServer() {
+        if (realmResourceServer != null) return realmResourceServer;
+        ResourceServerStore resourceServerStore = authz.getStoreFactory().getResourceServerStore();
+        ClientModel client = getRealmManagementClient();
+        if (client == null) return null;
+        realmResourceServer = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId());
+        return realmResourceServer;
+
+    }
+
+    public ResourceServer initializeRealmResourceServer() {
+        if (realmResourceServer != null) return realmResourceServer;
+        ClientModel client = getRealmManagementClient();
+        realmResourceServer = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId());
+        if (realmResourceServer == null) {
+            realmResourceServer = authz.getStoreFactory().getResourceServerStore().create(client.getId());
+        }
+        return realmResourceServer;
+    }
+
+    protected Scope manageScope;
+    protected Scope viewScope;
+
+    public void initializeRealmDefaultScopes() {
+        ResourceServer server = initializeRealmResourceServer();
+        manageScope = initializeRealmScope(MgmtPermissions.MANAGE_SCOPE);
+        viewScope = initializeRealmScope(MgmtPermissions.VIEW_SCOPE);
+    }
+
+    public Scope initializeRealmScope(String name) {
+        ResourceServer server = initializeRealmResourceServer();
+        Scope scope  = authz.getStoreFactory().getScopeStore().findByName(name, server.getId());
+        if (scope == null) {
+            scope = authz.getStoreFactory().getScopeStore().create(name, server);
+        }
+        return scope;
+    }
+
+
+
+    public Scope realmManageScope() {
+        if (manageScope != null) return manageScope;
+        manageScope = realmScope(MgmtPermissions.MANAGE_SCOPE);
+        return manageScope;
+    }
+
+
+    public Scope realmViewScope() {
+        if (viewScope != null) return viewScope;
+        viewScope = realmScope(MgmtPermissions.VIEW_SCOPE);
+        return viewScope;
+    }
+
+    public Scope realmScope(String scope) {
+        ResourceServer server = realmResourceServer();
+        if (server == null) return null;
+        return authz.getStoreFactory().getScopeStore().findByName(scope, server.getId());
+    }
+
+    public boolean evaluatePermission(Resource resource, Scope scope, ResourceServer resourceServer) {
+        Identity identity = identity();
+        if (identity == null) {
+            throw new RuntimeException("Identity of admin is not set for permission query");
+        }
+        return evaluatePermission(resource, scope, resourceServer, identity);
+    }
+
+    public boolean evaluatePermission(Resource resource, Scope scope, ResourceServer resourceServer, Identity identity) {
+        RealmModel oldRealm = session.getContext().getRealm();
+        try {
+            session.getContext().setRealm(realm);
+            EvaluationContext context = new DefaultEvaluationContext(identity, session);
+            DecisionResult decisionCollector = new DecisionResult();
+            List<ResourcePermission> permissions = Permissions.permission(resourceServer, resource, scope);
+            PermissionEvaluator from = authz.evaluators().from(permissions, context);
+            from.evaluate(decisionCollector);
+            if (!decisionCollector.completed()) {
+                logger.error("Failed to run permission check", decisionCollector.getError());
+                return false;
+            }
+            return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT;
+        } finally {
+            session.getContext().setRealm(oldRealm);
+        }
+    }
+
+    @Override
+    public boolean canView(RealmModel realm) {
+        return hasOneAdminRole(realm, AdminRoles.VIEW_REALM, AdminRoles.MANAGE_REALM);
+    }
+
+    @Override
+    public boolean isAdmin(RealmModel realm) {
+        return hasAnyAdminRole(realm);
+    }
+
+    @Override
+    public boolean isAdmin() {
+        RealmManager realmManager = new RealmManager(session);
+        if (adminsRealm.equals(realmManager.getKeycloakAdminstrationRealm())) {
+            if (identity.hasRealmRole(AdminRoles.ADMIN) || identity.hasRealmRole(AdminRoles.CREATE_REALM)) {
+                return true;
+            }
+            for (RealmModel realm : session.realms().getRealms()) {
+                if (isAdmin(realm)) return true;
+            }
+            return false;
+        } else {
+            return isAdmin(adminsRealm);
+        }
+    }
+
+    @Override
+    public boolean canCreateRealm() {
+        RealmManager realmManager = new RealmManager(session);
+        if (!auth.getRealm().equals(realmManager.getKeycloakAdminstrationRealm())) {
+           return false;
+        }
+        return identity.hasRealmRole(AdminRoles.CREATE_REALM);
+    }
+
+    @Override
+    public void requireCreateRealm() {
+        if (!canCreateRealm()) {
+            throw new ForbiddenException();
+        }
+    }
+
+
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissionEvaluator.java
new file mode 100644
index 0000000..7020667
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissionEvaluator.java
@@ -0,0 +1,61 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface RealmPermissionEvaluator {
+    boolean canListRealms();
+
+    void requireViewRealmNameList();
+
+    boolean canManageRealm();
+
+    void requireManageRealm();
+
+    boolean canViewRealm();
+
+    void requireViewRealm();
+
+    boolean canManageIdentityProviders();
+
+    boolean canViewIdentityProviders();
+
+    void requireViewIdentityProviders();
+
+    void requireManageIdentityProviders();
+
+    boolean canManageAuthorization();
+
+    boolean canViewAuthorization();
+
+    void requireManageAuthorization();
+
+    void requireViewAuthorization();
+
+    boolean canManageEvents();
+
+    void requireManageEvents();
+
+    boolean canViewEvents();
+
+    void requireViewEvents();
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java
new file mode 100644
index 0000000..3fc752f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java
@@ -0,0 +1,189 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.ForbiddenException;
+
+/**
+ * Manages default policies for all users.
+ *
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+class RealmPermissions implements RealmPermissionEvaluator {
+    private static final Logger logger = Logger.getLogger(RealmPermissions.class);
+    protected final KeycloakSession session;
+    protected final RealmModel realm;
+    protected final AuthorizationProvider authz;
+    protected final MgmtPermissions root;
+
+    public RealmPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) {
+        this.session = session;
+        this.realm = realm;
+        this.authz = authz;
+        this.root = root;
+    }
+
+
+    public boolean canManageRealmDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_REALM);
+
+    }
+    public boolean canViewRealmDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_REALM, AdminRoles.VIEW_REALM);
+    }
+
+    public boolean canManageIdentityProvidersDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_IDENTITY_PROVIDERS);
+
+    }
+    public boolean canViewIdentityProvidersDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_IDENTITY_PROVIDERS, AdminRoles.VIEW_IDENTITY_PROVIDERS);
+    }
+
+    public boolean canManageAuthorizationDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_AUTHORIZATION);
+
+    }
+    public boolean canViewAuthorizationDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_AUTHORIZATION, AdminRoles.VIEW_AUTHORIZATION);
+    }
+    public boolean canManageEventsDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_EVENTS);
+    }
+    public boolean canViewEventsDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_EVENTS, AdminRoles.VIEW_EVENTS);
+    }
+
+    @Override
+    public boolean canListRealms() {
+        return root.hasAnyAdminRole();
+    }
+
+    @Override
+    public void requireViewRealmNameList() {
+        if (!canListRealms()) {
+            throw new ForbiddenException();
+        }
+    }
+
+
+    @Override
+    public boolean canManageRealm() {
+        return canManageRealmDefault();
+    }
+
+    @Override
+    public void requireManageRealm() {
+        if (!canManageRealm()) {
+            throw new ForbiddenException();
+        }
+    }
+    @Override
+    public boolean canViewRealm() {
+        return canViewRealmDefault();
+    }
+
+    @Override
+    public void requireViewRealm() {
+        if (!canViewRealm()) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canManageIdentityProviders() {
+        return canManageIdentityProvidersDefault();
+    }
+
+    @Override
+    public boolean canViewIdentityProviders() {
+        return canViewIdentityProvidersDefault();
+    }
+
+    @Override
+    public void requireViewIdentityProviders() {
+        if (!canViewIdentityProviders()) {
+            throw new ForbiddenException();
+        }
+    }
+
+
+    @Override
+    public void requireManageIdentityProviders() {
+        if (!canManageIdentityProviders()) {
+            throw new ForbiddenException();
+        }
+    }
+
+
+    @Override
+    public boolean canManageAuthorization() {
+        return canManageAuthorizationDefault();
+    }
+
+    @Override
+    public boolean canViewAuthorization() {
+        return canViewAuthorizationDefault();
+    }
+
+    @Override
+    public void requireManageAuthorization() {
+        if (!canManageAuthorization()) {
+            throw new ForbiddenException();
+        }
+    }
+    @Override
+    public void requireViewAuthorization() {
+        if (!canViewAuthorization()) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canManageEvents() {
+        return canManageEventsDefault();
+    }
+
+    @Override
+    public void requireManageEvents() {
+        if (!canManageEvents()) {
+            throw new ForbiddenException();
+        }
+    }
+    @Override
+    public boolean canViewEvents() {
+        return canViewEventsDefault();
+    }
+
+    @Override
+    public void requireViewEvents() {
+        if (!canViewEvents()) {
+            throw new ForbiddenException();
+        }
+    }
+
+
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmsPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmsPermissionEvaluator.java
new file mode 100644
index 0000000..5286d10
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmsPermissionEvaluator.java
@@ -0,0 +1,35 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface RealmsPermissionEvaluator {
+    boolean canView(RealmModel realm);
+
+    boolean isAdmin(RealmModel realm);
+
+    boolean isAdmin();
+
+    boolean canCreateRealm();
+
+    void requireCreateRealm();
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java
new file mode 100644
index 0000000..a4a8b71
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java
@@ -0,0 +1,56 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface RolePermissionEvaluator {
+    boolean canList(RoleContainerModel container);
+
+    void requireList(RoleContainerModel container);
+
+    boolean canMapRole(RoleModel role);
+    void requireMapRole(RoleModel role);
+
+    boolean canManage(RoleModel role);
+
+    void requireManage(RoleModel role);
+
+    boolean canView(RoleModel role);
+
+    void requireView(RoleModel role);
+
+    boolean canMapClientScope(RoleModel role);
+    void requireMapClientScope(RoleModel role);
+
+    boolean canMapComposite(RoleModel role);
+    void requireMapComposite(RoleModel role);
+
+    boolean canManage(RoleContainerModel container);
+
+    void requireManage(RoleContainerModel container);
+
+    boolean canView(RoleContainerModel container);
+
+    void requireView(RoleContainerModel container);
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionManagement.java
new file mode 100644
index 0000000..977e109
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionManagement.java
@@ -0,0 +1,56 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface RolePermissionManagement {
+    public static final String MAP_ROLE_SCOPE = "map-role";
+    public static final String MAP_ROLE_CLIENT_SCOPE_SCOPE = "map-role-client-scope";
+    public static final String MAP_ROLE_COMPOSITE_SCOPE = "map-role-composite";
+
+    boolean isPermissionsEnabled(RoleModel role);
+    void setPermissionsEnabled(RoleModel role, boolean enable);
+
+    Map<String, String> getPermissions(RoleModel role);
+
+    Policy mapRolePermission(RoleModel role);
+
+    Policy mapCompositePermission(RoleModel role);
+
+    Policy mapClientScopePermission(RoleModel role);
+
+    Resource resource(RoleModel role);
+
+    ResourceServer resourceServer(RoleModel role);
+
+    Policy manageUsersPolicy(ResourceServer server);
+
+    Policy viewUsersPolicy(ResourceServer server);
+
+    Policy rolePolicy(ResourceServer server, RoleModel role);
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
new file mode 100644
index 0000000..091d7a5
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
@@ -0,0 +1,585 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.authorization.AuthorizationProvider;
+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.store.ResourceStore;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ImpersonationConstants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.representations.idm.authorization.DecisionStrategy;
+import org.keycloak.services.ForbiddenException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+class RolePermissions implements RolePermissionEvaluator, RolePermissionManagement {
+    private static final Logger logger = Logger.getLogger(RolePermissions.class);
+    protected final KeycloakSession session;
+    protected final RealmModel realm;
+    protected final AuthorizationProvider authz;
+    protected final MgmtPermissions root;
+
+    public RolePermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) {
+        this.session = session;
+        this.realm = realm;
+        this.authz = authz;
+        this.root = root;
+    }
+
+    @Override
+    public boolean isPermissionsEnabled(RoleModel role) {
+        return mapRolePermission(role) != null;
+    }
+
+    @Override
+    public void setPermissionsEnabled(RoleModel role, boolean enable) {
+       if (enable) {
+           initialize(role);
+       } else {
+           disablePermissions(role);
+       }
+    }
+
+    private void disablePermissions(RoleModel role) {
+        ResourceServer server = resourceServer(role);
+        if (server == null) return;
+        Policy policy = mapRolePermission(role);
+        if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+        policy = mapClientScopePermission(role);
+        if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+        policy = mapCompositePermission(role);
+        if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+        Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId());
+        if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId());
+    }
+
+    @Override
+    public Map<String, String> getPermissions(RoleModel role) {
+        Map<String, String> scopes = new HashMap<>();
+        scopes.put(RolePermissionManagement.MAP_ROLE_SCOPE, mapRolePermission(role).getId());
+        scopes.put(RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE, mapClientScopePermission(role).getId());
+        scopes.put(RolePermissionManagement.MAP_ROLE_COMPOSITE_SCOPE, mapCompositePermission(role).getId());
+        return scopes;
+    }
+
+    @Override
+    public Policy mapRolePermission(RoleModel role) {
+        ResourceServer server = resourceServer(role);
+        if (server == null) return null;
+        return  authz.getStoreFactory().getPolicyStore().findByName(getMapRolePermissionName(role), server.getId());
+    }
+
+    @Override
+    public Policy mapCompositePermission(RoleModel role) {
+        ResourceServer server = resourceServer(role);
+        if (server == null) return null;
+
+        return  authz.getStoreFactory().getPolicyStore().findByName(getMapCompositePermissionName(role), server.getId());
+    }
+
+    @Override
+    public Policy mapClientScopePermission(RoleModel role) {
+        ResourceServer server = resourceServer(role);
+        if (server == null) return null;
+
+        return  authz.getStoreFactory().getPolicyStore().findByName(getMapClientScopePermissionName(role), server.getId());
+    }
+
+    @Override
+    public Resource resource(RoleModel role) {
+        ResourceStore resourceStore = authz.getStoreFactory().getResourceStore();
+        ResourceServer server = resourceServer(role);
+        if (server == null) return null;
+        return  resourceStore.findByName(getRoleResourceName(role), server.getId());
+    }
+
+    @Override
+    public ResourceServer resourceServer(RoleModel role) {
+        ClientModel client = getRoleClient(role);
+        return root.resourceServer(client);
+    }
+
+    private boolean checkAdminRoles(RoleModel role) {
+        if (AdminRoles.ALL_ROLES.contains(role.getName())) {
+            if (root.admin().hasRole(role)) return true;
+
+            ClientModel adminClient = root.getRealmManagementClient();
+            if (adminClient.equals(role.getContainer())) {
+                // if this is realm admin role, then check to see if admin has similar permissions
+                // we do this so that the authz service is invoked
+                if (role.getName().equals(AdminRoles.MANAGE_CLIENTS)) {
+                    if (!root.clients().canManage()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(AdminRoles.VIEW_CLIENTS)) {
+                    if (!root.clients().canView()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(AdminRoles.QUERY_CLIENTS)) {
+                    return true;
+                } else if (role.getName().equals(AdminRoles.QUERY_USERS)) {
+                    return true;
+                } else if (role.getName().equals(AdminRoles.QUERY_GROUPS)) {
+                    return true;
+                } else if (role.getName().equals(AdminRoles.MANAGE_AUTHORIZATION)) {
+                    if (!root.realm().canManageAuthorization()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(AdminRoles.VIEW_AUTHORIZATION)) {
+                    if (!root.realm().canViewAuthorization()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(AdminRoles.MANAGE_EVENTS)) {
+                    if (!root.realm().canManageEvents()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(AdminRoles.VIEW_EVENTS)) {
+                    if (!root.realm().canViewEvents()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(AdminRoles.MANAGE_USERS)) {
+                    if (!root.users().canManage()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(AdminRoles.VIEW_USERS)) {
+                    if (!root.users().canView()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(AdminRoles.MANAGE_IDENTITY_PROVIDERS)) {
+                    if (!root.realm().canManageIdentityProviders()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(AdminRoles.VIEW_IDENTITY_PROVIDERS)) {
+                    if (!root.realm().canViewIdentityProviders()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(AdminRoles.MANAGE_REALM)) {
+                    if (!root.realm().canManageRealm()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(AdminRoles.VIEW_REALM)) {
+                    if (!root.realm().canViewRealm()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else if (role.getName().equals(ImpersonationConstants.IMPERSONATION_ROLE)) {
+                    if (!root.users().canImpersonate()) {
+                        return adminConflictMessage(role);
+                    } else {
+                        return true;
+                    }
+                } else {
+                    return adminConflictMessage(role);
+                }
+
+            } else {
+                // now we need to check to see if this is a master admin role
+                if (role.getContainer() instanceof RealmModel) {
+                    RealmModel realm = (RealmModel)role.getContainer();
+                    // If realm role is master admin role then abort
+                    if (realm.getName().equals(Config.getAdminRealm())) {
+                        return adminConflictMessage(role);
+                    }
+                } else {
+                    ClientModel container = (ClientModel)role.getContainer();
+                    // abort if this is an role in master realm and role is an admin role of any realm
+                    if (container.getRealm().getName().equals(Config.getAdminRealm())
+                            && container.getClientId().endsWith("-realm")) {
+                        return adminConflictMessage(role);
+                    }
+                }
+                return true;
+            }
+
+        }
+        return true;
+
+    }
+
+    private boolean adminConflictMessage(RoleModel role) {
+        logger.debug("Trying to assign admin privileges of role: " + role.getName() + " but admin doesn't have same privilege");
+        return false;
+    }
+
+    /**
+     * Is admin allowed to map this role?
+     *
+     * @param role
+     * @return
+     */
+    @Override
+    public boolean canMapRole(RoleModel role) {
+        if (root.users().canManageDefault()) return checkAdminRoles(role);
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        if (role.getContainer() instanceof ClientModel) {
+            if (root.clients().canMapRoles((ClientModel)role.getContainer())) return true;
+        }
+        if (!isPermissionsEnabled(role)){
+            return false;
+        }
+
+        ResourceServer resourceServer = resourceServer(role);
+        if (resourceServer == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRolePermissionName(role), resourceServer.getId());
+        if (policy == null || policy.getAssociatedPolicies().isEmpty()) {
+            return false;
+        }
+
+        Resource roleResource = resource(role);
+        Scope mapRoleScope = mapRoleScope(resourceServer);
+        if (root.evaluatePermission(roleResource, mapRoleScope, resourceServer)) {
+            return checkAdminRoles(role);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void requireMapRole(RoleModel role) {
+        if (!canMapRole(role)) {
+            throw new ForbiddenException();
+        }
+
+    }
+
+    @Override
+    public boolean canList(RoleContainerModel container) {
+        return root.hasAnyAdminRole();
+    }
+
+    @Override
+    public void requireList(RoleContainerModel container) {
+        if (!canList(container)) {
+            throw new ForbiddenException();
+        }
+
+    }
+
+    @Override
+    public boolean canManage(RoleContainerModel container) {
+        if (container instanceof RealmModel) {
+            return root.realm().canManageRealm();
+        } else {
+            return root.clients().canConfigure((ClientModel)container);
+        }
+    }
+
+    @Override
+    public void requireManage(RoleContainerModel container) {
+        if (!canManage(container)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canView(RoleContainerModel container) {
+        if (container instanceof RealmModel) {
+            return root.realm().canViewRealm();
+        } else {
+            return root.clients().canView((ClientModel)container);
+        }
+    }
+
+    @Override
+    public void requireView(RoleContainerModel container) {
+        if (!canView(container)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canMapComposite(RoleModel role) {
+        if (canManageDefault(role)) return checkAdminRoles(role);
+
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+        if (role.getContainer() instanceof ClientModel) {
+            if (root.clients().canMapCompositeRoles((ClientModel)role.getContainer())) return true;
+        }
+        if (!isPermissionsEnabled(role)){
+            return false;
+        }
+
+        ResourceServer resourceServer = resourceServer(role);
+        if (resourceServer == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapCompositePermissionName(role), resourceServer.getId());
+        if (policy == null || policy.getAssociatedPolicies().isEmpty()) {
+            return false;
+        }
+
+        Resource roleResource = resource(role);
+        Scope scope = mapCompositeScope(resourceServer);
+        if (root.evaluatePermission(roleResource, scope, resourceServer)) {
+            return checkAdminRoles(role);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void requireMapComposite(RoleModel role) {
+        if (!canMapComposite(role)) {
+            throw new ForbiddenException();
+        }
+
+    }
+
+
+    @Override
+    public boolean canMapClientScope(RoleModel role) {
+        if (root.clients().canManageClientsDefault()) return true;
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+        if (role.getContainer() instanceof ClientModel) {
+            if (root.clients().canMapClientScopeRoles((ClientModel)role.getContainer())) return true;
+        }
+        if (!isPermissionsEnabled(role)){
+            return false;
+        }
+
+        ResourceServer resourceServer = resourceServer(role);
+        if (resourceServer == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapClientScopePermissionName(role), resourceServer.getId());
+        if (policy == null || policy.getAssociatedPolicies().isEmpty()) {
+            return false;
+        }
+
+        Resource roleResource = resource(role);
+        Scope scope = mapClientScope(resourceServer);
+        return root.evaluatePermission(roleResource, scope, resourceServer);
+    }
+
+    @Override
+    public void requireMapClientScope(RoleModel role) {
+        if (!canMapClientScope(role)) {
+            throw new ForbiddenException();
+        }
+    }
+
+
+    @Override
+    public boolean canManage(RoleModel role) {
+        if (role.getContainer() instanceof RealmModel) {
+            return root.realm().canManageRealm();
+        } else if (role.getContainer() instanceof ClientModel) {
+            ClientModel client = (ClientModel)role.getContainer();
+            return root.clients().canManage(client);
+        }
+        return false;
+    }
+
+    public boolean canManageDefault(RoleModel role) {
+        if (role.getContainer() instanceof RealmModel) {
+            return root.realm().canManageRealmDefault();
+        } else if (role.getContainer() instanceof ClientModel) {
+            ClientModel client = (ClientModel)role.getContainer();
+            return root.clients().canManageClientsDefault();
+        }
+        return false;
+    }
+
+    @Override
+    public void requireManage(RoleModel role) {
+        if (!canManage(role)) {
+            throw new ForbiddenException();
+        }
+
+    }
+
+    @Override
+    public boolean canView(RoleModel role) {
+        if (role.getContainer() instanceof RealmModel) {
+            return root.realm().canViewRealm();
+        } else if (role.getContainer() instanceof ClientModel) {
+            ClientModel client = (ClientModel)role.getContainer();
+            return root.clients().canView(client);
+        }
+        return false;
+    }
+
+    @Override
+    public void requireView(RoleModel role) {
+        if (!canView(role)) {
+            throw new ForbiddenException();
+        }
+
+    }
+
+    private ClientModel getRoleClient(RoleModel role) {
+        ClientModel client = null;
+        if (role.getContainer() instanceof ClientModel) {
+            client = (ClientModel)role.getContainer();
+        } else {
+            client = root.getRealmManagementClient();
+        }
+        return client;
+    }
+
+    @Override
+    public Policy manageUsersPolicy(ResourceServer server) {
+        RoleModel role = root.getRealmManagementClient().getRole(AdminRoles.MANAGE_USERS);
+        return rolePolicy(server, role);
+    }
+
+    @Override
+    public Policy viewUsersPolicy(ResourceServer server) {
+        RoleModel role = root.getRealmManagementClient().getRole(AdminRoles.VIEW_USERS);
+        return rolePolicy(server, role);
+    }
+
+    @Override
+    public Policy rolePolicy(ResourceServer server, RoleModel role) {
+        String policyName = Helper.getRolePolicyName(role);
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(policyName, server.getId());
+        if (policy != null) return policy;
+        return Helper.createRolePolicy(authz, server, role, policyName);
+    }
+
+    private Scope mapRoleScope(ResourceServer server) {
+        return authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_SCOPE, server.getId());
+    }
+
+    private Scope mapClientScope(ResourceServer server) {
+        return authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_CLIENT_SCOPE_SCOPE, server.getId());
+    }
+
+    private Scope mapCompositeScope(ResourceServer server) {
+        return authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_COMPOSITE_SCOPE, server.getId());
+    }
+
+
+    private void initialize(RoleModel role) {
+        ResourceServer server = resourceServer(role);
+        if (server == null) {
+            ClientModel client = getRoleClient(role);
+            server = root.findOrCreateResourceServer(client);
+        }
+        Scope mapRoleScope = mapRoleScope(server);
+        if (mapRoleScope == null) {
+            mapRoleScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_SCOPE, server);
+        }
+        Scope mapClientScope = mapClientScope(server);
+        if (mapClientScope == null) {
+            mapClientScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_CLIENT_SCOPE_SCOPE, server);
+        }
+        Scope mapCompositeScope = mapCompositeScope(server);
+        if (mapCompositeScope == null) {
+            mapCompositeScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_COMPOSITE_SCOPE, server);
+        }
+
+        String roleResourceName = getRoleResourceName(role);
+        Resource resource = authz.getStoreFactory().getResourceStore().findByName(roleResourceName, server.getId());
+        if (resource == null) {
+            resource = authz.getStoreFactory().getResourceStore().create(roleResourceName, server, server.getClientId());
+            Set<Scope> scopeset = new HashSet<>();
+            scopeset.add(mapClientScope);
+            scopeset.add(mapCompositeScope);
+            scopeset.add(mapRoleScope);
+            resource.updateScopes(scopeset);
+            resource.setType("Role");
+        }
+        Policy mapRolePermission = mapRolePermission(role);
+        if (mapRolePermission == null) {
+            mapRolePermission = Helper.addEmptyScopePermission(authz, server, getMapRolePermissionName(role), resource, mapRoleScope);
+            mapRolePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
+        }
+
+        Policy mapClientScopePermission = mapClientScopePermission(role);
+        if (mapClientScopePermission == null) {
+            mapClientScopePermission = Helper.addEmptyScopePermission(authz, server, getMapClientScopePermissionName(role), resource, mapClientScope);
+            mapClientScopePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
+        }
+
+        Policy mapCompositePermission = mapCompositePermission(role);
+        if (mapCompositePermission == null) {
+            mapCompositePermission = Helper.addEmptyScopePermission(authz, server, getMapCompositePermissionName(role), resource, mapCompositeScope);
+            mapCompositePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
+        }
+    }
+
+    private String getMapRolePermissionName(RoleModel role) {
+        return MAP_ROLE_SCOPE + ".permission." + role.getId();
+    }
+
+    private String getMapClientScopePermissionName(RoleModel role) {
+        return MAP_ROLE_CLIENT_SCOPE_SCOPE + ".permission." + role.getId();
+    }
+
+    private String getMapCompositePermissionName(RoleModel role) {
+        return MAP_ROLE_COMPOSITE_SCOPE + ".permission." + role.getId();
+    }
+
+    private ResourceServer sdfgetResourceServer(RoleModel role) {
+        ClientModel client = getRoleClient(role);
+        return root.findOrCreateResourceServer(client);
+    }
+
+    private static String getRoleResourceName(RoleModel role) {
+        return "role.resource." + role.getId();
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java
new file mode 100644
index 0000000..40b1f8f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java
@@ -0,0 +1,65 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.models.UserModel;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserPermissionEvaluator {
+     boolean canManage();
+
+    void requireManage();
+
+    boolean canManage(UserModel user);
+    void requireManage(UserModel user);
+
+    boolean canQuery();
+
+    void requireQuery();
+
+    boolean canQuery(UserModel user);
+
+    void requireQuery(UserModel user);
+
+    boolean canView();
+    boolean canView(UserModel user);
+    void requireView(UserModel user);
+
+    void requireView();
+
+    boolean canImpersonate(UserModel user);
+
+    boolean canImpersonate();
+
+    void requireImpersonate(UserModel user);
+
+    Map<String, Boolean> getAccess(UserModel user);
+
+    boolean canMapRoles(UserModel user);
+
+    void requireMapRoles(UserModel user);
+
+    boolean canManageGroupMembership(UserModel user);
+
+    void requireManageGroupMembership(UserModel user);
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionManagement.java
new file mode 100644
index 0000000..d465378
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionManagement.java
@@ -0,0 +1,49 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.models.RoleModel;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserPermissionManagement {
+    boolean isPermissionsEnabled();
+
+    void setPermissionsEnabled(boolean enable);
+
+    Map<String, String> getPermissions();
+
+    Resource resource();
+
+    Policy managePermission();
+
+    Policy viewPermission();
+
+    Policy manageGroupMembershipPermission();
+
+    Policy mapRolesPermission();
+
+    Policy adminImpersonatingPermission();
+
+    Policy userImpersonatedPermission();
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
new file mode 100644
index 0000000..149e526
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
@@ -0,0 +1,635 @@
+/*
+ * 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.services.resources.admin.permissions;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.common.UserModelIdentity;
+import org.keycloak.authorization.identity.Identity;
+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.models.AdminRoles;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.ImpersonationConstants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.ForbiddenException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages default policies for all users.
+ *
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+class UserPermissions implements UserPermissionEvaluator, UserPermissionManagement {
+    private static final Logger logger = Logger.getLogger(UserPermissions.class);
+    public static final String MAP_ROLES_SCOPE="map-roles";
+    public static final String IMPERSONATE_SCOPE="impersonate";
+    public static final String USER_IMPERSONATED_SCOPE="user-impersonated";
+    public static final String MANAGE_GROUP_MEMBERSHIP_SCOPE="manage-group-membership";
+    public static final String MAP_ROLES_PERMISSION_USERS = "map-roles.permission.users";
+    public static final String ADMIN_IMPERSONATING_PERMISSION = "admin-impersonating.permission.users";
+    public static final String USER_IMPERSONATED_PERMISSION = "user-impersonated.permission.users";
+    public static final String MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS = "manage-group-membership.permission.users";
+    public static final String MANAGE_PERMISSION_USERS = "manage.permission.users";
+    public static final String VIEW_PERMISSION_USERS = "view.permission.users";
+    public static final String USERS_RESOURCE = "Users";
+    protected final KeycloakSession session;
+    protected final RealmModel realm;
+    protected final AuthorizationProvider authz;
+    protected final MgmtPermissions root;
+
+    public UserPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) {
+        this.session = session;
+        this.realm = realm;
+        this.authz = authz;
+        this.root = root;
+    }
+
+
+    private void initialize() {
+        root.initializeRealmResourceServer();
+        root.initializeRealmDefaultScopes();
+        ResourceServer server = root.realmResourceServer();
+        Scope manageScope = root.realmManageScope();
+        Scope viewScope = root.realmViewScope();
+        Scope mapRolesScope = root.initializeRealmScope(MAP_ROLES_SCOPE);
+        Scope impersonateScope = root.initializeRealmScope(IMPERSONATE_SCOPE);
+        Scope userImpersonatedScope = root.initializeRealmScope(USER_IMPERSONATED_SCOPE);
+        Scope manageGroupMembershipScope = root.initializeRealmScope(MANAGE_GROUP_MEMBERSHIP_SCOPE);
+
+        Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+        if (usersResource == null) {
+            usersResource = authz.getStoreFactory().getResourceStore().create(USERS_RESOURCE, server, server.getClientId());
+            Set<Scope> scopeset = new HashSet<>();
+            scopeset.add(manageScope);
+            scopeset.add(viewScope);
+            scopeset.add(mapRolesScope);
+            scopeset.add(impersonateScope);
+            scopeset.add(manageGroupMembershipScope);
+            scopeset.add(userImpersonatedScope);
+            usersResource.updateScopes(scopeset);
+        }
+        Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId());
+        if (managePermission == null) {
+            Helper.addEmptyScopePermission(authz, server, MANAGE_PERMISSION_USERS, usersResource, manageScope);
+        }
+        Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(VIEW_PERMISSION_USERS, server.getId());
+        if (viewPermission == null) {
+            Helper.addEmptyScopePermission(authz, server, VIEW_PERMISSION_USERS, usersResource, viewScope);
+        }
+        Policy mapRolesPermission = authz.getStoreFactory().getPolicyStore().findByName(MAP_ROLES_PERMISSION_USERS, server.getId());
+        if (mapRolesPermission == null) {
+            Helper.addEmptyScopePermission(authz, server, MAP_ROLES_PERMISSION_USERS, usersResource, mapRolesScope);
+        }
+        Policy membershipPermission = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, server.getId());
+        if (membershipPermission == null) {
+            Helper.addEmptyScopePermission(authz, server, MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, usersResource, manageGroupMembershipScope);
+        }
+        Policy impersonatePermission = authz.getStoreFactory().getPolicyStore().findByName(ADMIN_IMPERSONATING_PERMISSION, server.getId());
+        if (impersonatePermission == null) {
+            Helper.addEmptyScopePermission(authz, server, ADMIN_IMPERSONATING_PERMISSION, usersResource, impersonateScope);
+        }
+        impersonatePermission = authz.getStoreFactory().getPolicyStore().findByName(USER_IMPERSONATED_PERMISSION, server.getId());
+        if (impersonatePermission == null) {
+            Helper.addEmptyScopePermission(authz, server, USER_IMPERSONATED_PERMISSION, usersResource, userImpersonatedScope);
+        }
+    }
+
+    @Override
+    public Map<String, String> getPermissions() {
+        Map<String, String> scopes = new HashMap<>();
+        scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission().getId());
+        scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission().getId());
+        scopes.put(MAP_ROLES_SCOPE, mapRolesPermission().getId());
+        scopes.put(MANAGE_GROUP_MEMBERSHIP_SCOPE, manageGroupMembershipPermission().getId());
+        scopes.put(IMPERSONATE_SCOPE, adminImpersonatingPermission().getId());
+        scopes.put(USER_IMPERSONATED_SCOPE, userImpersonatedPermission().getId());
+        return scopes;
+    }
+
+    @Override
+    public boolean isPermissionsEnabled() {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+        if (resource == null) return false;
+
+        Policy policy = managePermission();
+
+        return policy != null;
+    }
+
+    @Override
+    public void setPermissionsEnabled(boolean enable) {
+        if (enable) {
+            initialize();
+        } else {
+            deletePermissionSetup();
+        }
+    }
+
+    private void deletePermissionSetup() {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return;
+        Policy policy = managePermission();
+        if (policy == null) {
+            authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+        }
+        policy = viewPermission();
+        if (policy == null) {
+            authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+        }
+        policy = mapRolesPermission();
+        if (policy == null) {
+            authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+        }
+        policy = manageGroupMembershipPermission();
+        if (policy == null) {
+            authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+        }
+        policy = adminImpersonatingPermission();
+        if (policy == null) {
+            authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+        }
+        policy = userImpersonatedPermission();
+        if (policy == null) {
+            authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+        }
+        Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+        if (usersResource == null) {
+            authz.getStoreFactory().getResourceStore().delete(usersResource.getId());
+        }
+    }
+
+    public boolean canManageDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_USERS);
+    }
+
+    @Override
+    public Resource resource() {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return null;
+
+        return  authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+    }
+
+    @Override
+    public Policy managePermission() {
+        ResourceServer server = root.realmResourceServer();
+        return authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId());
+    }
+
+    @Override
+    public Policy viewPermission() {
+        ResourceServer server = root.realmResourceServer();
+        return authz.getStoreFactory().getPolicyStore().findByName(VIEW_PERMISSION_USERS, server.getId());
+    }
+
+    @Override
+    public Policy manageGroupMembershipPermission() {
+        ResourceServer server = root.realmResourceServer();
+        return authz.getStoreFactory().getPolicyStore().findByName(MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, server.getId());
+    }
+
+    @Override
+    public Policy mapRolesPermission() {
+        ResourceServer server = root.realmResourceServer();
+        return authz.getStoreFactory().getPolicyStore().findByName(MAP_ROLES_PERMISSION_USERS, server.getId());
+    }
+
+
+    @Override
+    public Policy adminImpersonatingPermission() {
+        ResourceServer server = root.realmResourceServer();
+        return authz.getStoreFactory().getPolicyStore().findByName(ADMIN_IMPERSONATING_PERMISSION, server.getId());
+    }
+
+    @Override
+    public Policy userImpersonatedPermission() {
+        ResourceServer server = root.realmResourceServer();
+        return authz.getStoreFactory().getPolicyStore().findByName(USER_IMPERSONATED_PERMISSION, server.getId());
+    }
+
+
+
+    /**
+     * Is admin allowed to manage all users?  In Authz terms, does the admin have the "manage" scope for the Users Authz resource?
+     *
+     * This method will follow the old default behavior (does the admin have the manage-users role) if any of these conditions
+     * are met.:
+     * - The admin is from the master realm managing a different realm
+     * - If the Authz objects are not set up correctly for the Users resource in Authz
+     * - The "manage" permission for the Users resource has an empty associatedPolicy list.
+     *
+     * Otherwise, it will use the Authz policy engine to resolve this answer.
+     *
+     * @return
+     */
+    @Override
+    public boolean canManage() {
+        if (canManageDefault()) return true;
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+        if (resource == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId());
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = root.realmManageScope();
+        return root.evaluatePermission(resource, scope, server);
+
+    }
+
+    @Override
+    public void requireManage() {
+        if (!canManage()) {
+            throw new ForbiddenException();
+        }
+    }
+
+
+    /**
+     * Does current admin have manage permissions for this particular user?
+     *
+     * @param user
+     * @return
+     */
+    @Override
+    public boolean canManage(UserModel user) {
+        return canManage() || canManageByGroup(user);
+    }
+
+    @Override
+    public void requireManage(UserModel user) {
+        if (!canManage(user)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    private interface EvaluateGroup {
+        boolean evaluate(GroupModel group);
+    }
+
+    private boolean evaluateGroups(UserModel user, EvaluateGroup eval) {
+        for (GroupModel group : user.getGroups()) {
+            if (eval.evaluate(group)) return true;
+        }
+        return false;
+    }
+
+    private boolean evaluateHierarchy(UserModel user, EvaluateGroup eval) {
+        Set<GroupModel> visited = new HashSet<>();
+        for (GroupModel group : user.getGroups()) {
+            if (evaluateHierarchy(eval, group, visited)) return true;
+        }
+        return false;
+    }
+
+    private boolean evaluateHierarchy(EvaluateGroup eval, GroupModel group, Set<GroupModel> visited) {
+        if (visited.contains(group)) return false;
+        if (eval.evaluate(group)) {
+            return true;
+        }
+        visited.add(group);
+        if (group.getParent() == null) return false;
+        return evaluateHierarchy(eval, group.getParent(), visited);
+    }
+
+    private boolean canManageByGroup(UserModel user) {
+        /* no inheritance
+        return evaluateGroups(user,
+                (group) -> root.groups().canViewMembers(group)
+        );
+        */
+
+        /* inheritance
+        */
+        return evaluateHierarchy(user, (group) -> root.groups().canManageMembers(group));
+
+    }
+    private boolean canViewByGroup(UserModel user) {
+        /* no inheritance
+        return evaluateGroups(user,
+                (group) -> root.groups().canViewMembers(group)
+        );
+        */
+
+        /* inheritance
+        */
+        return evaluateHierarchy(user, (group) -> root.groups().canViewMembers(group));
+    }
+
+    public boolean canViewDefault() {
+        return root.hasOneAdminRole(AdminRoles.MANAGE_USERS, AdminRoles.VIEW_USERS);
+    }
+
+    @Override
+    public boolean canQuery() {
+        return canView() || root.hasOneAdminRole(AdminRoles.QUERY_USERS);
+    }
+
+    @Override
+    public void requireQuery() {
+        if (!canQuery()) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canQuery(UserModel user) {
+        return canView(user);
+    }
+
+    @Override
+    public void requireQuery(UserModel user) {
+        if (!canQuery(user)) {
+            throw new ForbiddenException();
+        }
+
+    }
+
+
+
+    /**
+     * Is admin allowed to view all users?  In Authz terms, does the admin have the "view" scope for the Users Authz resource?
+     *
+     * This method will follow the old default behavior (does the admin have the view-users role) if any of these conditions
+     * are met.:
+     * - The admin is from the master realm managing a different realm
+     * - If the Authz objects are not set up correctly for the Users resource in Authz
+     * - The "view" permission for the Users resource has an empty associatedPolicy list.
+     *
+     * Otherwise, it will use the Authz policy engine to resolve this answer.
+     *
+     * @return
+     */
+    @Override
+    public boolean canView() {
+        if (canViewDefault()) return true;
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        return hasViewPermission() || canManage();
+    }
+
+    private boolean hasViewPermission() {
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return canViewDefault();
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+        if (resource == null) return canViewDefault();
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(VIEW_PERMISSION_USERS, server.getId());
+        if (policy == null) {
+            return canViewDefault();
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return canViewDefault();
+        }
+
+        Scope scope = root.realmViewScope();
+        return root.evaluatePermission(resource, scope, server);
+    }
+
+    /**
+     * Does current admin have view permissions for this particular user?
+     *
+     * Evaluates in this order. If any true, return true:
+     * - canViewUsers
+     * - canManageUsers
+     *
+     *
+     * @param user
+     * @return
+     */
+    @Override
+    public boolean canView(UserModel user) {
+        return canView() || canViewByGroup(user);
+    }
+
+    @Override
+    public void requireView(UserModel user) {
+        if (!canView(user)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public void requireView() {
+        if (!(canView())) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public boolean canImpersonate(UserModel user) {
+        if (!canImpersonate()) {
+            return false;
+        }
+
+        Identity userIdentity = new UserModelIdentity(root.realm, user);
+        if (!root.isAdminSameRealm()) {
+            return true;
+        }
+
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return true;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+        if (resource == null) return true;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(USER_IMPERSONATED_PERMISSION, server.getId());
+        if (policy == null) {
+            return true;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return true;
+        }
+
+        Scope scope = root.realmScope(USER_IMPERSONATED_SCOPE);
+        return root.evaluatePermission(resource, scope, server, userIdentity);
+
+    }
+
+    @Override
+    public boolean canImpersonate() {
+        if (root.hasOneAdminRole(ImpersonationConstants.IMPERSONATION_ROLE)) return true;
+
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+        if (resource == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(ADMIN_IMPERSONATING_PERMISSION, server.getId());
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = root.realmScope(IMPERSONATE_SCOPE);
+        return root.evaluatePermission(resource, scope, server);
+    }
+
+    @Override
+    public void requireImpersonate(UserModel user) {
+        if (!canImpersonate(user)) {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public Map<String, Boolean> getAccess(UserModel user) {
+        Map<String, Boolean> map = new HashMap<>();
+        map.put("view", canView(user));
+        map.put("manage", canManage(user));
+        map.put("mapRoles", canMapRoles(user));
+        map.put("manageGroupMembership", canManageGroupMembership(user));
+        map.put("impersonate", canImpersonate(user));
+        return map;
+    }
+
+    @Override
+    public boolean canMapRoles(UserModel user) {
+        if (canManage(user)) return true;
+
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+        if (resource == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MAP_ROLES_PERMISSION_USERS, server.getId());
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = root.realmScope(MAP_ROLES_SCOPE);
+        return root.evaluatePermission(resource, scope, server);
+
+    }
+
+    @Override
+    public void requireMapRoles(UserModel user) {
+        if (!canMapRoles(user)) {
+            throw new ForbiddenException();
+        }
+
+    }
+
+
+    @Override
+    public boolean canManageGroupMembership(UserModel user) {
+        if (canManage(user)) return true;
+
+        if (!root.isAdminSameRealm()) {
+            return false;
+        }
+
+        ResourceServer server = root.realmResourceServer();
+        if (server == null) return false;
+
+        Resource resource =  authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+        if (resource == null) return false;
+
+        Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, server.getId());
+        if (policy == null) {
+            return false;
+        }
+
+        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+        // if no policies attached to permission then just do default behavior
+        if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+            return false;
+        }
+
+        Scope scope = root.realmScope(MANAGE_GROUP_MEMBERSHIP_SCOPE);
+        return root.evaluatePermission(resource, scope, server);
+
+    }
+
+    @Override
+    public void requireManageGroupMembership(UserModel user) {
+        if (!canManageGroupMembership(user)) {
+            throw new ForbiddenException();
+        }
+
+    }
+
+
+
+
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
index ad473b9..709197d 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
@@ -33,7 +33,7 @@ import org.keycloak.protocol.ProtocolMapperConfigException;
 import org.keycloak.representations.idm.ProtocolMapperRepresentation;
 import org.keycloak.services.ErrorResponse;
 import org.keycloak.services.ErrorResponseException;
-import org.keycloak.services.resources.admin.RealmAuth.Resource;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -66,7 +66,9 @@ public class ProtocolMappersResource {
 
     protected ProtocolMapperContainerModel client;
 
-    protected RealmAuth auth;
+    protected AdminPermissionEvaluator auth;
+    protected AdminPermissionEvaluator.RequirePermissionCheck managePermission;
+    protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission;
 
     protected AdminEventBuilder adminEvent;
 
@@ -76,13 +78,17 @@ public class ProtocolMappersResource {
     @Context
     protected KeycloakSession session;
 
-    public ProtocolMappersResource(RealmModel realm, ProtocolMapperContainerModel client, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public ProtocolMappersResource(RealmModel realm, ProtocolMapperContainerModel client, AdminPermissionEvaluator auth,
+                                   AdminEventBuilder adminEvent,
+                                   AdminPermissionEvaluator.RequirePermissionCheck managePermission,
+                                   AdminPermissionEvaluator.RequirePermissionCheck viewPermission) {
         this.realm = realm;
         this.auth = auth;
         this.client = client;
         this.adminEvent = adminEvent.resource(ResourceType.PROTOCOL_MAPPER);
+        this.managePermission = managePermission;
+        this.viewPermission = viewPermission;
 
-        auth.init(Resource.CLIENT);
     }
 
     /**
@@ -96,11 +102,7 @@ public class ProtocolMappersResource {
     @Path("protocol/{protocol}")
     @Produces(MediaType.APPLICATION_JSON)
     public List<ProtocolMapperRepresentation> getMappersPerProtocol(@PathParam("protocol") String protocol) {
-        auth.requireAny();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        viewPermission.require();
 
         List<ProtocolMapperRepresentation> mappers = new LinkedList<ProtocolMapperRepresentation>();
         for (ProtocolMapperModel mapper : client.getProtocolMappers()) {
@@ -119,11 +121,7 @@ public class ProtocolMappersResource {
     @NoCache
     @Consumes(MediaType.APPLICATION_JSON)
     public Response createMapper(ProtocolMapperRepresentation rep) {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        managePermission.require();
 
         ProtocolMapperModel model = null;
         try {
@@ -147,11 +145,7 @@ public class ProtocolMappersResource {
     @NoCache
     @Consumes(MediaType.APPLICATION_JSON)
     public void createMapper(List<ProtocolMapperRepresentation> reps) {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        managePermission.require();
 
         ProtocolMapperModel model = null;
         for (ProtocolMapperRepresentation rep : reps) {
@@ -172,11 +166,7 @@ public class ProtocolMappersResource {
     @Path("models")
     @Produces(MediaType.APPLICATION_JSON)
     public List<ProtocolMapperRepresentation> getMappers() {
-        auth.requireAny();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        viewPermission.require();
 
         List<ProtocolMapperRepresentation> mappers = new LinkedList<ProtocolMapperRepresentation>();
         for (ProtocolMapperModel mapper : client.getProtocolMappers()) {
@@ -196,11 +186,7 @@ public class ProtocolMappersResource {
     @Path("models/{id}")
     @Produces(MediaType.APPLICATION_JSON)
     public ProtocolMapperRepresentation getMapperById(@PathParam("id") String id) {
-        auth.requireAny();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        viewPermission.require();
 
         ProtocolMapperModel model = client.getProtocolMapperById(id);
         if (model == null) throw new NotFoundException("Model not found");
@@ -218,11 +204,7 @@ public class ProtocolMappersResource {
     @Path("models/{id}")
     @Consumes(MediaType.APPLICATION_JSON)
     public void update(@PathParam("id") String id, ProtocolMapperRepresentation rep) {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        managePermission.require();
 
         ProtocolMapperModel model = client.getProtocolMapperById(id);
         if (model == null) throw new NotFoundException("Model not found");
@@ -243,11 +225,7 @@ public class ProtocolMappersResource {
     @NoCache
     @Path("models/{id}")
     public void delete(@PathParam("id") String id) {
-        auth.requireManage();
-
-        if (client == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        managePermission.require();
 
         ProtocolMapperModel model = client.getProtocolMapperById(id);
         if (model == null) throw new NotFoundException("Model not found");
@@ -261,10 +239,12 @@ public class ProtocolMappersResource {
             ProtocolMapper mapper = (ProtocolMapper)session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, model.getProtocolMapper());
             if (mapper != null) {
                 mapper.validateConfig(session, realm, client, model);
+            } else {
+                throw new NotFoundException("ProtocolMapper provider not found");
             }
         } catch (ProtocolMapperConfigException ex) {
             logger.error(ex.getMessage());
-            Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
+            Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale());
             throw new ErrorResponseException(ex.getMessage(), MessageFormat.format(messages.getProperty(ex.getMessageKey(), ex.getMessage()), ex.getParameters()),
                     Response.Status.BAD_REQUEST);
         }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index f1469d0..28392f7 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -23,6 +23,9 @@ import org.jboss.resteasy.spi.NotFoundException;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.Config;
 import org.keycloak.KeyPairVerifier;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
+import org.keycloak.services.resources.admin.permissions.AdminPermissions;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.PemUtils;
@@ -63,6 +66,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.ManagementPermissionReference;
 import org.keycloak.representations.idm.PartialImportRepresentation;
 import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
@@ -72,7 +76,6 @@ import org.keycloak.services.managers.LDAPConnectionTestManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.services.managers.UserStorageSyncManager;
-import org.keycloak.services.resources.admin.RealmAuth.Resource;
 import org.keycloak.storage.UserStorageProviderModel;
 
 import javax.ws.rs.Consumes;
@@ -112,7 +115,7 @@ import static org.keycloak.models.utils.StripSecretsUtils.stripForExport;
  */
 public class RealmAdminResource {
     protected static final Logger logger = Logger.getLogger(RealmAdminResource.class);
-    protected RealmAuth auth;
+    protected AdminPermissionEvaluator auth;
     protected RealmModel realm;
     private TokenManager tokenManager;
     private AdminEventBuilder adminEvent;
@@ -129,14 +132,11 @@ public class RealmAdminResource {
     @Context
     protected HttpHeaders headers;
 
-    public RealmAdminResource(RealmAuth auth, RealmModel realm, TokenManager tokenManager, AdminEventBuilder adminEvent) {
+    public RealmAdminResource(AdminPermissionEvaluator auth, RealmModel realm, TokenManager tokenManager, AdminEventBuilder adminEvent) {
         this.auth = auth;
         this.realm = realm;
         this.tokenManager = tokenManager;
         this.adminEvent = adminEvent.realm(realm).resource(ResourceType.REALM);
-
-        auth.init(RealmAuth.Resource.REALM);
-        auth.requireAny();
     }
 
     /**
@@ -149,7 +149,7 @@ public class RealmAdminResource {
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     public ClientRepresentation convertClientDescription(String description) {
-        auth.init(Resource.CLIENT).requireManage();
+        auth.clients().requireManage();
 
         if (realm == null) {
             throw new NotFoundException("Realm not found.");
@@ -238,7 +238,7 @@ public class RealmAdminResource {
      */
     @Path("roles")
     public RoleContainerResource getRoleContainerResource() {
-        return new RoleContainerResource(uriInfo, realm, auth, realm, adminEvent);
+        return new RoleContainerResource(session, uriInfo, realm, auth, realm, adminEvent);
     }
 
     /**
@@ -252,15 +252,15 @@ public class RealmAdminResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public RealmRepresentation getRealm() {
-        if (auth.hasView()) {
+        if (auth.realm().canViewRealm()) {
             return ModelToRepresentation.toRepresentation(realm, false);
         } else {
-            auth.requireAny();
+            auth.realm().requireViewRealmNameList();
 
             RealmRepresentation rep = new RealmRepresentation();
             rep.setRealm(realm.getName());
 
-            if (auth.init(Resource.IDENTITY_PROVIDER).hasView()) {
+            if (auth.realm().canViewIdentityProviders()) {
                 RealmRepresentation r = ModelToRepresentation.toRepresentation(realm, false);
                 rep.setIdentityProviders(r.getIdentityProviders());
                 rep.setIdentityProviderMappers(r.getIdentityProviderMappers());
@@ -282,7 +282,7 @@ public class RealmAdminResource {
     @PUT
     @Consumes(MediaType.APPLICATION_JSON)
     public Response updateRealm(final RealmRepresentation rep) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         logger.debug("updating realm: " + realm.getName());
 
@@ -344,7 +344,7 @@ public class RealmAdminResource {
      */
     @DELETE
     public void deleteRealm() {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         if (!new RealmManager(session).removeRealm(realm)) {
             throw new NotFoundException("Realm doesn't exist");
@@ -364,6 +364,50 @@ public class RealmAdminResource {
         return users;
     }
 
+    @NoCache
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("users-management-permissions")
+    public ManagementPermissionReference getUserMgmtPermissions() {
+        auth.realm().requireViewRealm();
+
+        AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+        if (permissions.users().isPermissionsEnabled()) {
+            return toUsersMgmtRef(permissions);
+        } else {
+            return new ManagementPermissionReference();
+        }
+
+    }
+
+    @PUT
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @NoCache
+    @Path("users-management-permissions")
+    public ManagementPermissionReference setUsersManagementPermissionsEnabled(ManagementPermissionReference ref) {
+        auth.realm().requireManageRealm();
+
+        AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+        permissions.users().setPermissionsEnabled(ref.isEnabled());
+        if (ref.isEnabled()) {
+            return toUsersMgmtRef(permissions);
+        } else {
+            return new ManagementPermissionReference();
+        }
+    }
+
+
+    public static ManagementPermissionReference toUsersMgmtRef(AdminPermissionManagement permissions) {
+        ManagementPermissionReference ref = new ManagementPermissionReference();
+        ref.setEnabled(true);
+        ref.setResource(permissions.users().resource().getId());
+        Map<String, String> scopes = permissions.users().getPermissions();
+        ref.setScopePermissions(scopes);
+        return ref;
+    }
+
+
     @Path("user-storage")
     public UserStorageProviderResource userStorage() {
         UserStorageProviderResource fed = new UserStorageProviderResource(realm, auth, adminEvent);
@@ -388,7 +432,7 @@ public class RealmAdminResource {
      */
     @Path("roles-by-id")
     public RoleByIdResource rolesById() {
-        RoleByIdResource resource = new RoleByIdResource(realm, auth, adminEvent);
+         RoleByIdResource resource = new RoleByIdResource(realm, auth, adminEvent);
         ResteasyProviderFactory.getInstance().injectProperties(resource);
         //resourceContext.initResource(resource);
         return resource;
@@ -401,7 +445,7 @@ public class RealmAdminResource {
     @Path("push-revocation")
     @POST
     public GlobalRequestResult pushRevocation() {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         GlobalRequestResult result = new ResourceAdminManager(session).pushRealmRevocationPolicy(uriInfo.getRequestUri(), realm);
         adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).representation(result).success();
@@ -416,7 +460,7 @@ public class RealmAdminResource {
     @Path("logout-all")
     @POST
     public GlobalRequestResult logoutAll() {
-        auth.init(RealmAuth.Resource.USER).requireManage();
+        auth.users().requireManage();
 
         session.sessions().removeUserSessions(realm);
         GlobalRequestResult result = new ResourceAdminManager(session).logoutAll(uriInfo.getRequestUri(), realm);
@@ -433,7 +477,7 @@ public class RealmAdminResource {
     @Path("sessions/{session}")
     @DELETE
     public void deleteSession(@PathParam("session") String sessionId) {
-        auth.init(RealmAuth.Resource.USER).requireManage();
+        auth.users().requireManage();
 
         UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
         if (userSession == null) throw new NotFoundException("Sesssion not found");
@@ -455,7 +499,7 @@ public class RealmAdminResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<Map<String, String>> getClientSessionStats() {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         List<Map<String, String>> data = new LinkedList<Map<String, String>>();
         for (ClientModel client : realm.getClients()) {
@@ -482,7 +526,7 @@ public class RealmAdminResource {
     @Path("events/config")
     @Produces(MediaType.APPLICATION_JSON)
     public RealmEventsConfigRepresentation getRealmEventsConfig() {
-        auth.init(RealmAuth.Resource.EVENTS).requireView();
+        auth.realm().requireViewEvents();
 
         RealmEventsConfigRepresentation config = ModelToRepresentation.toEventsConfigReprensetation(realm);
         if (config.getEnabledEventTypes() == null || config.getEnabledEventTypes().isEmpty()) {
@@ -507,7 +551,7 @@ public class RealmAdminResource {
     @Path("events/config")
     @Consumes(MediaType.APPLICATION_JSON)
     public void updateRealmEventsConfig(final RealmEventsConfigRepresentation rep) {
-        auth.init(RealmAuth.Resource.EVENTS).requireManage();
+        auth.realm().requireManageEvents();
 
         logger.debug("updating realm events config: " + realm.getName());
         new RealmManager(session).updateRealmEventsConfig(rep, realm);
@@ -536,7 +580,7 @@ public class RealmAdminResource {
                                                @QueryParam("user") String user, @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo,
                                                @QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
                                                @QueryParam("max") Integer maxResults) {
-        auth.init(RealmAuth.Resource.EVENTS).requireView();
+        auth.realm().requireViewEvents();
 
         EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
 
@@ -629,7 +673,7 @@ public class RealmAdminResource {
                                                     @QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
                                                     @QueryParam("max") Integer maxResults,
                                                     @QueryParam("resourceTypes") List<String> resourceTypes) {
-        auth.init(RealmAuth.Resource.EVENTS).requireView();
+        auth.realm().requireViewEvents();
 
         EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
         AdminEventQuery query = eventStore.createAdminQuery().realm(realm.getId());;
@@ -722,7 +766,7 @@ public class RealmAdminResource {
     @Path("events")
     @DELETE
     public void clearEvents() {
-        auth.init(RealmAuth.Resource.EVENTS).requireManage();
+        auth.realm().requireManageEvents();
 
         EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
         eventStore.clear(realm.getId());
@@ -735,7 +779,7 @@ public class RealmAdminResource {
     @Path("admin-events")
     @DELETE
     public void clearAdminEvents() {
-        auth.init(RealmAuth.Resource.EVENTS).requireManage();
+        auth.realm().requireManageEvents();
 
         EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
         eventStore.clearAdmin(realm.getId());
@@ -757,7 +801,7 @@ public class RealmAdminResource {
                                        @QueryParam("bindDn") String bindDn, @QueryParam("bindCredential") String bindCredential,
                                        @QueryParam("useTruststoreSpi") String useTruststoreSpi, @QueryParam("connectionTimeout") String connectionTimeout,
                                        @QueryParam("componentId") String componentId) {
-        auth.init(RealmAuth.Resource.REALM).requireManage();
+        auth.realm().requireManageRealm();
 
         if (componentId != null && bindCredential.equals(ComponentRepresentation.SECRET_VALUE)) {
             bindCredential = realm.getComponent(componentId).getConfig().getFirst(LDAPConstants.BIND_CREDENTIAL);
@@ -782,7 +826,7 @@ public class RealmAdminResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Path("default-groups")
     public List<GroupRepresentation> getDefaultGroups() {
-        auth.requireView();
+        auth.realm().requireViewRealm();
 
         List<GroupRepresentation> defaults = new LinkedList<>();
         for (GroupModel group : realm.getDefaultGroups()) {
@@ -794,7 +838,7 @@ public class RealmAdminResource {
     @NoCache
     @Path("default-groups/{groupId}")
     public void addDefaultGroup(@PathParam("groupId") String groupId) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         GroupModel group = realm.getGroupById(groupId);
         if (group == null) {
@@ -809,7 +853,7 @@ public class RealmAdminResource {
     @NoCache
     @Path("default-groups/{groupId}")
     public void removeDefaultGroup(@PathParam("groupId") String groupId) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         GroupModel group = realm.getGroupById(groupId);
         if (group == null) {
@@ -834,13 +878,12 @@ public class RealmAdminResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public GroupRepresentation getGroupByPath(@PathParam("path") String path) {
-        auth.requireView();
-
         GroupModel found = KeycloakModelUtils.findGroupByPath(realm, path);
         if (found == null) {
             throw new NotFoundException("Group path does not exist");
 
         }
+        auth.groups().requireView(found);
         return ModelToRepresentation.toGroupHierarchy(found, true);
     }
 
@@ -854,7 +897,7 @@ public class RealmAdminResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public Response partialImport(PartialImportRepresentation rep) {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         PartialImportManager partialImport = new PartialImportManager(rep, session, realm, adminEvent);
         return partialImport.saveResources();
@@ -888,7 +931,7 @@ public class RealmAdminResource {
     @Path("clear-realm-cache")
     @POST
     public void clearRealmCache() {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         CacheRealmProvider cache = session.getProvider(CacheRealmProvider.class);
         if (cache != null) {
@@ -905,7 +948,7 @@ public class RealmAdminResource {
     @Path("clear-user-cache")
     @POST
     public void clearUserCache() {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         UserCache cache = session.getProvider(UserCache.class);
         if (cache != null) {
@@ -922,7 +965,7 @@ public class RealmAdminResource {
     @Path("clear-keys-cache")
     @POST
     public void clearKeysCache() {
-        auth.requireManage();
+        auth.realm().requireManageRealm();
 
         PublicKeyStorageProvider cache = session.getProvider(PublicKeyStorageProvider.class);
         if (cache != null) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
index 7a948d9..b00f2e4 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
@@ -34,6 +34,8 @@ import org.keycloak.services.ErrorResponse;
 import org.keycloak.services.ForbiddenException;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.resources.KeycloakApplication;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.permissions.AdminPermissions;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -94,18 +96,11 @@ public class RealmsAdminResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<RealmRepresentation> getRealms() {
-        RealmManager realmManager = new RealmManager(session);
         List<RealmRepresentation> reps = new ArrayList<RealmRepresentation>();
-        if (auth.getRealm().equals(realmManager.getKeycloakAdminstrationRealm())) {
-            List<RealmModel> realms = session.realms().getRealms();
-            for (RealmModel realm : realms) {
-                addRealmRep(reps, realm, realm.getMasterAdminClient());
-            }
-        } else {
-            ClientModel adminApp = auth.getRealm().getClientByClientId(realmManager.getRealmAdminClientId(auth.getRealm()));
-            addRealmRep(reps, auth.getRealm(), adminApp);
+        List<RealmModel> realms = session.realms().getRealms();
+        for (RealmModel realm : realms) {
+            addRealmRep(reps, realm);
         }
-
         if (reps.isEmpty()) {
             throw new ForbiddenException();
         }
@@ -114,10 +109,10 @@ public class RealmsAdminResource {
         return reps;
     }
 
-    protected void addRealmRep(List<RealmRepresentation> reps, RealmModel realm, ClientModel realmManagementClient) {
-        if (auth.hasAppRole(realmManagementClient, AdminRoles.VIEW_REALM)) {
+    protected void addRealmRep(List<RealmRepresentation> reps, RealmModel realm) {
+        if (AdminPermissions.realms(session, auth).canView(realm)) {
             reps.add(ModelToRepresentation.toRepresentation(realm, false));
-        } else if (auth.hasOneOfAppRole(realmManagementClient, AdminRoles.ALL_REALM_ROLES)) {
+        } else if (AdminPermissions.realms(session, auth).isAdmin(realm)) {
             RealmRepresentation rep = new RealmRepresentation();
             rep.setRealm(realm.getName());
             reps.add(rep);
@@ -138,12 +133,7 @@ public class RealmsAdminResource {
     public Response importRealm(@Context final UriInfo uriInfo, final RealmRepresentation rep) {
         RealmManager realmManager = new RealmManager(session);
         realmManager.setContextPath(keycloak.getContextPath());
-        if (!auth.getRealm().equals(realmManager.getKeycloakAdminstrationRealm())) {
-            throw new ForbiddenException();
-        }
-        if (!auth.hasRealmRole(AdminRoles.CREATE_REALM)) {
-            throw new ForbiddenException();
-        }
+        AdminPermissions.realms(session, auth).requireCreateRealm();
 
         logger.debugv("importRealm: {0}", rep.getRealm());
 
@@ -191,13 +181,7 @@ public class RealmsAdminResource {
                 && !auth.getRealm().equals(realm)) {
             throw new ForbiddenException();
         }
-        RealmAuth realmAuth;
-
-        if (auth.getRealm().equals(realmManager.getKeycloakAdminstrationRealm())) {
-            realmAuth = new RealmAuth(auth, realm.getMasterAdminClient());
-        } else {
-            realmAuth = new RealmAuth(auth, realm.getClientByClientId(realmManager.getRealmAdminClientId(auth.getRealm())));
-        }
+        AdminPermissionEvaluator realmAuth = AdminPermissions.evaluator(session, realm, auth);
 
         AdminEventBuilder adminEvent = new AdminEventBuilder(realm, auth, session, clientConnection);
         session.getContext().setRealm(realm);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java
index 1f19b64..b2ae6ad 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java
@@ -19,6 +19,10 @@ package org.keycloak.services.resources.admin;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
+import org.keycloak.services.resources.admin.permissions.AdminPermissions;
+import org.keycloak.services.resources.admin.permissions.RolePermissionManagement;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.ClientModel;
@@ -26,6 +30,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.ManagementPermissionReference;
 import org.keycloak.representations.idm.RoleRepresentation;
 
 import javax.ws.rs.Consumes;
@@ -39,7 +44,9 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.UriInfo;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -52,7 +59,7 @@ import java.util.Set;
 public class RoleByIdResource extends RoleResource {
     protected static final Logger logger = Logger.getLogger(RoleByIdResource.class);
     private final RealmModel realm;
-    private final RealmAuth auth;
+    private AdminPermissionEvaluator auth;
     private AdminEventBuilder adminEvent;
 
     @Context
@@ -61,7 +68,7 @@ public class RoleByIdResource extends RoleResource {
     @Context
     private UriInfo uriInfo;
 
-    public RoleByIdResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public RoleByIdResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         super(realm);
 
         this.realm = realm;
@@ -80,9 +87,9 @@ public class RoleByIdResource extends RoleResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public RoleRepresentation getRole(final @PathParam("role-id") String id) {
-        auth.requireAny();
 
         RoleModel roleModel = getRoleModel(id);
+        auth.roles().requireView(roleModel);
         return getRole(roleModel);
     }
 
@@ -91,17 +98,7 @@ public class RoleByIdResource extends RoleResource {
         if (roleModel == null) {
             throw new NotFoundException("Could not find role with id");
         }
-
-        RealmAuth.Resource r = null;
-        if (roleModel.getContainer() instanceof RealmModel) {
-            r = RealmAuth.Resource.REALM;
-        } else if (roleModel.getContainer() instanceof ClientModel) {
-            r = RealmAuth.Resource.CLIENT;
-        } else if (roleModel.getContainer() instanceof UserModel) {
-            r = RealmAuth.Resource.USER;
-        }
-        auth.init(r);
-        return roleModel;
+       return roleModel;
     }
 
     /**
@@ -113,9 +110,8 @@ public class RoleByIdResource extends RoleResource {
     @DELETE
     @NoCache
     public void deleteRole(final @PathParam("role-id") String id) {
-        auth.requireManage();
-
         RoleModel role = getRoleModel(id);
+        auth.roles().requireManage(role);
         deleteRole(role);
 
         if (role.isClientRole()) {
@@ -137,9 +133,8 @@ public class RoleByIdResource extends RoleResource {
     @PUT
     @Consumes(MediaType.APPLICATION_JSON)
     public void updateRole(final @PathParam("role-id") String id, final RoleRepresentation rep) {
-        auth.requireManage();
-
         RoleModel role = getRoleModel(id);
+        auth.roles().requireManage(role);
         updateRole(rep, role);
 
         if (role.isClientRole()) {
@@ -161,10 +156,9 @@ public class RoleByIdResource extends RoleResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public void addComposites(final @PathParam("role-id") String id, List<RoleRepresentation> roles) {
-        auth.requireManage();
-
         RoleModel role = getRoleModel(id);
-        addComposites(adminEvent, uriInfo, roles, role);
+        auth.roles().requireManage(role);
+        addComposites(auth, adminEvent, uriInfo, roles, role);
     }
 
     /**
@@ -180,11 +174,10 @@ public class RoleByIdResource extends RoleResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Set<RoleRepresentation> getRoleComposites(final @PathParam("role-id") String id) {
-        auth.requireAny();
 
         if (logger.isDebugEnabled()) logger.debug("*** getRoleComposites: '" + id + "'");
         RoleModel role = getRoleModel(id);
-        auth.requireView();
+        auth.roles().requireView(role);
         return getRoleComposites(role);
     }
 
@@ -199,9 +192,9 @@ public class RoleByIdResource extends RoleResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Set<RoleRepresentation> getRealmRoleComposites(final @PathParam("role-id") String id) {
-        auth.requireAny();
-
         RoleModel role = getRoleModel(id);
+        auth.roles().requireView(role);
+        auth.roles().requireView(role);
         return getRealmRoleComposites(role);
     }
 
@@ -218,9 +211,9 @@ public class RoleByIdResource extends RoleResource {
     @Produces(MediaType.APPLICATION_JSON)
     public Set<RoleRepresentation> getClientRoleComposites(final @PathParam("role-id") String id,
                                                                 final @PathParam("client") String client) {
-        auth.requireAny();
 
         RoleModel role = getRoleModel(id);
+        auth.roles().requireView(role);
         ClientModel clientModel = realm.getClientById(client);
         if (clientModel == null) {
             throw new NotFoundException("Could not find client");
@@ -238,10 +231,64 @@ public class RoleByIdResource extends RoleResource {
     @DELETE
     @Consumes(MediaType.APPLICATION_JSON)
     public void deleteComposites(final @PathParam("role-id") String id, List<RoleRepresentation> roles) {
-        auth.requireManage();
-
         RoleModel role = getRoleModel(id);
+        auth.roles().requireManage(role);
         deleteComposites(adminEvent, uriInfo, roles, role);
     }
 
+    /**
+     * Return object stating whether role Authoirzation permissions have been initialized or not and a reference
+     *
+     *
+     * @param id
+     * @return
+     */
+    @Path("{role-id}/management/permissions")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    public ManagementPermissionReference getManagementPermissions(final @PathParam("role-id") String id) {
+        RoleModel role = getRoleModel(id);
+        auth.roles().requireView(role);
+
+        AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+        if (!permissions.roles().isPermissionsEnabled(role)) {
+            return new ManagementPermissionReference();
+        }
+        return toMgmtRef(role, permissions);
+    }
+
+    public static ManagementPermissionReference toMgmtRef(RoleModel role, AdminPermissionManagement permissions) {
+        ManagementPermissionReference ref = new ManagementPermissionReference();
+        ref.setEnabled(true);
+        ref.setResource(permissions.roles().resource(role).getId());
+        ref.setScopePermissions(permissions.roles().getPermissions(role));
+        return ref;
+    }
+
+    /**
+     * Return object stating whether role Authoirzation permissions have been initialized or not and a reference
+     *
+     *
+     * @param id
+     * @return initialized manage permissions reference
+     */
+    @Path("{role-id}/management/permissions")
+    @PUT
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @NoCache
+    public ManagementPermissionReference setManagementPermissionsEnabled(final @PathParam("role-id") String id, ManagementPermissionReference ref) {
+        RoleModel role = getRoleModel(id);
+        auth.roles().requireManage(role);
+
+        AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+        permissions.roles().setPermissionsEnabled(role, ref.isEnabled());
+        if (ref.isEnabled()) {
+            return toMgmtRef(role, permissions);
+        } else {
+            return new ManagementPermissionReference();
+        }
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
index 47f9c64..79bb6c8 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
@@ -19,26 +19,33 @@ package org.keycloak.services.resources.admin;
 
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
+import org.keycloak.services.resources.admin.permissions.AdminPermissions;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.idm.ManagementPermissionReference;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.services.ErrorResponse;
 
 import javax.ws.rs.BadRequestException;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
 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.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
@@ -54,18 +61,22 @@ import java.util.Set;
  */
 public class RoleContainerResource extends RoleResource {
     private final RealmModel realm;
-    private final RealmAuth auth;
+    protected AdminPermissionEvaluator auth;
+
     protected RoleContainerModel roleContainer;
     private AdminEventBuilder adminEvent;
     private UriInfo uriInfo;
+    private KeycloakSession session;
 
-    public RoleContainerResource(UriInfo uriInfo, RealmModel realm, RealmAuth auth, RoleContainerModel roleContainer, AdminEventBuilder adminEvent) {
+    public RoleContainerResource(KeycloakSession session, UriInfo uriInfo, RealmModel realm,
+                                 AdminPermissionEvaluator auth, RoleContainerModel roleContainer, AdminEventBuilder adminEvent) {
         super(realm);
         this.uriInfo = uriInfo;
         this.realm = realm;
         this.auth = auth;
         this.roleContainer = roleContainer;
         this.adminEvent = adminEvent;
+        this.session = session;
     }
 
     /**
@@ -77,11 +88,7 @@ public class RoleContainerResource extends RoleResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public List<RoleRepresentation> getRoles() {
-        auth.requireAny();
-
-        if (roleContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.roles().requireList(roleContainer);
 
         Set<RoleModel> roleModels = roleContainer.getRoles();
         List<RoleRepresentation> roles = new ArrayList<RoleRepresentation>();
@@ -100,11 +107,7 @@ public class RoleContainerResource extends RoleResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public Response createRole(final RoleRepresentation rep) {
-        auth.requireManage();
-
-        if (roleContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.roles().requireManage(roleContainer);
 
         if (rep.getName() == null) {
             throw new BadRequestException();
@@ -143,11 +146,7 @@ public class RoleContainerResource extends RoleResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public RoleRepresentation getRole(final @PathParam("role-name") String roleName) {
-        auth.requireView();
-
-        if (roleContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        auth.roles().requireView(roleContainer);
 
         RoleModel roleModel = roleContainer.getRole(roleName);
         if (roleModel == null) {
@@ -166,12 +165,7 @@ public class RoleContainerResource extends RoleResource {
     @DELETE
     @NoCache
     public void deleteRole(final @PathParam("role-name") String roleName) {
-        auth.requireManage();
-
-        if (roleContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
-
+        auth.roles().requireManage(roleContainer);
         RoleModel role = roleContainer.getRole(roleName);
         if (role == null) {
             throw new NotFoundException("Could not find role");
@@ -199,12 +193,7 @@ public class RoleContainerResource extends RoleResource {
     @PUT
     @Consumes(MediaType.APPLICATION_JSON)
     public Response updateRole(final @PathParam("role-name") String roleName, final RoleRepresentation rep) {
-        auth.requireManage();
-
-        if (roleContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
-
+        auth.roles().requireManage(roleContainer);
         RoleModel role = roleContainer.getRole(roleName);
         if (role == null) {
             throw new NotFoundException("Could not find role");
@@ -236,17 +225,12 @@ public class RoleContainerResource extends RoleResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public void addComposites(final @PathParam("role-name") String roleName, List<RoleRepresentation> roles) {
-        auth.requireManage();
-
-        if (roleContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
-
+        auth.roles().requireManage(roleContainer);
         RoleModel role = roleContainer.getRole(roleName);
         if (role == null) {
             throw new NotFoundException("Could not find role");
         }
-        addComposites(adminEvent, uriInfo, roles, role);
+        addComposites(auth, adminEvent, uriInfo, roles, role);
     }
 
     /**
@@ -260,12 +244,7 @@ public class RoleContainerResource extends RoleResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Set<RoleRepresentation> getRoleComposites(final @PathParam("role-name") String roleName) {
-        auth.requireView();
-
-        if (roleContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
-
+        auth.roles().requireView(roleContainer);
         RoleModel role = roleContainer.getRole(roleName);
         if (role == null) {
             throw new NotFoundException("Could not find role");
@@ -284,12 +263,7 @@ public class RoleContainerResource extends RoleResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Set<RoleRepresentation> getRealmRoleComposites(final @PathParam("role-name") String roleName) {
-        auth.requireView();
-
-        if (roleContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
-
+        auth.roles().requireView(roleContainer);
         RoleModel role = roleContainer.getRole(roleName);
         if (role == null) {
             throw new NotFoundException("Could not find role");
@@ -311,12 +285,7 @@ public class RoleContainerResource extends RoleResource {
     public Set<RoleRepresentation> getClientRoleComposites(@Context final UriInfo uriInfo,
                                                                 final @PathParam("role-name") String roleName,
                                                                 final @PathParam("client") String client) {
-        auth.requireView();
-
-        if (roleContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
-
+        auth.roles().requireView(roleContainer);
         RoleModel role = roleContainer.getRole(roleName);
         if (role == null) {
             throw new NotFoundException("Could not find role");
@@ -342,17 +311,66 @@ public class RoleContainerResource extends RoleResource {
     public void deleteComposites(
                                    final @PathParam("role-name") String roleName,
                                    List<RoleRepresentation> roles) {
-        auth.requireManage();
 
-        if (roleContainer == null) {
-            throw new NotFoundException("Could not find client");
+        auth.roles().requireManage(roleContainer);
+        RoleModel role = roleContainer.getRole(roleName);
+        if (role == null) {
+            throw new NotFoundException("Could not find role");
         }
+        deleteComposites(adminEvent, uriInfo, roles, role);
+    }
 
+    /**
+     * Return object stating whether role Authoirzation permissions have been initialized or not and a reference
+     *
+     *
+     * @param roleName
+     * @return
+     */
+    @Path("{role-name}/management/permissions")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    public ManagementPermissionReference getManagementPermissions(final @PathParam("role-name") String roleName) {
+        auth.roles().requireView(roleContainer);
         RoleModel role = roleContainer.getRole(roleName);
         if (role == null) {
             throw new NotFoundException("Could not find role");
         }
-        deleteComposites(adminEvent, uriInfo, roles, role);
+
+        AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+        if (!permissions.roles().isPermissionsEnabled(role)) {
+            return new ManagementPermissionReference();
+        }
+        return RoleByIdResource.toMgmtRef(role, permissions);
+    }
+
+    /**
+     * Return object stating whether role Authoirzation permissions have been initialized or not and a reference
+     *
+     *
+     * @param roleName
+     * @return initialized manage permissions reference
+     */
+    @Path("{role-name}/management/permissions")
+    @PUT
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @NoCache
+    public ManagementPermissionReference setManagementPermissionsEnabled(final @PathParam("role-name") String roleName, ManagementPermissionReference ref) {
+        auth.roles().requireManage(roleContainer);
+        RoleModel role = roleContainer.getRole(roleName);
+        if (role == null) {
+            throw new NotFoundException("Could not find role");
+        }
+
+        if (ref.isEnabled()) {
+            AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+            permissions.roles().setPermissionsEnabled(role, ref.isEnabled());
+            return RoleByIdResource.toMgmtRef(role, permissions);
+        } else {
+            return new ManagementPermissionReference();
+        }
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java
index 5b7af1f..b785b1a 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java
@@ -19,6 +19,7 @@ package org.keycloak.services.resources.admin;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
@@ -33,6 +34,7 @@ import org.keycloak.representations.idm.ClientMappingsRepresentation;
 import org.keycloak.representations.idm.MappingsRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.ForbiddenException;
 import org.keycloak.services.managers.RealmManager;
 
 import javax.ws.rs.Consumes;
@@ -55,6 +57,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Base resource for managing users
@@ -64,16 +67,19 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public class RoleMapperResource {
+
     protected static final Logger logger = Logger.getLogger(RoleMapperResource.class);
 
     protected RealmModel realm;
 
-    private RealmAuth auth;
-
     private RoleMapperModel roleMapper;
 
     private AdminEventBuilder adminEvent;
 
+    protected AdminPermissionEvaluator.RequirePermissionCheck managePermission;
+    protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission;
+    private AdminPermissionEvaluator auth;
+
     @Context
     protected ClientConnection clientConnection;
 
@@ -86,15 +92,21 @@ public class RoleMapperResource {
     @Context
     protected HttpHeaders headers;
 
-    public RoleMapperResource(RealmModel realm, RealmAuth auth,  RoleMapperModel roleMapper, AdminEventBuilder adminEvent) {
+    public RoleMapperResource(RealmModel realm,
+                              AdminPermissionEvaluator auth,
+                              RoleMapperModel roleMapper,
+                              AdminEventBuilder adminEvent,
+                              AdminPermissionEvaluator.RequirePermissionCheck manageCheck,
+                              AdminPermissionEvaluator.RequirePermissionCheck viewCheck) {
         this.auth = auth;
         this.realm = realm;
         this.adminEvent = adminEvent.resource(ResourceType.REALM_ROLE_MAPPING);
         this.roleMapper = roleMapper;
+        this.managePermission = manageCheck;
+        this.viewPermission = viewCheck;
 
     }
 
-
     /**
      * Get role mappings
      *
@@ -104,11 +116,7 @@ public class RoleMapperResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public MappingsRepresentation getRoleMappings() {
-        auth.requireView();
-
-        if (roleMapper == null) {
-            throw new NotFoundException("User not found");
-        }
+        viewPermission.require();
 
         MappingsRepresentation all = new MappingsRepresentation();
         Set<RoleModel> realmMappings = roleMapper.getRealmRoleMappings();
@@ -153,11 +161,7 @@ public class RoleMapperResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getRealmRoleMappings() {
-        auth.requireView();
-
-        if (roleMapper == null) {
-            throw new NotFoundException("User not found");
-        }
+        viewPermission.require();
 
         Set<RoleModel> realmMappings = roleMapper.getRealmRoleMappings();
         List<RoleRepresentation> realmMappingsRep = new ArrayList<RoleRepresentation>();
@@ -179,11 +183,7 @@ public class RoleMapperResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getCompositeRealmRoleMappings() {
-        auth.requireView();
-
-        if (roleMapper == null) {
-            throw new NotFoundException("User not found");
-        }
+        viewPermission.require();
 
         Set<RoleModel> roles = realm.getRoles();
         List<RoleRepresentation> realmMappingsRep = new ArrayList<RoleRepresentation>();
@@ -205,14 +205,13 @@ public class RoleMapperResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getAvailableRealmRoleMappings() {
-        auth.requireView();
-
-        if (roleMapper == null) {
-            throw new NotFoundException("User not found");
-        }
+        viewPermission.require();
 
         Set<RoleModel> available = realm.getRoles();
-        return ClientRoleMappingsResource.getAvailableRoles(roleMapper, available);
+        Set<RoleModel> set = available.stream().filter(r ->
+            canMapRole(r)
+        ).collect(Collectors.toSet());
+        return ClientRoleMappingsResource.getAvailableRoles(roleMapper, set);
     }
 
     /**
@@ -224,11 +223,7 @@ public class RoleMapperResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public void addRealmRoleMappings(List<RoleRepresentation> roles) {
-        auth.requireManage();
-
-        if (roleMapper == null) {
-            throw new NotFoundException("User not found");
-        }
+        managePermission.require();
 
         logger.debugv("** addRealmRoleMappings: {0}", roles);
 
@@ -237,6 +232,7 @@ public class RoleMapperResource {
             if (roleModel == null || !roleModel.getId().equals(role.getId())) {
                 throw new NotFoundException("Role not found");
             }
+            auth.roles().requireMapRole(roleModel);
             roleMapper.grantRole(roleModel);
         }
 
@@ -252,11 +248,7 @@ public class RoleMapperResource {
     @DELETE
     @Consumes(MediaType.APPLICATION_JSON)
     public void deleteRealmRoleMappings(List<RoleRepresentation> roles) {
-        auth.requireManage();
-
-        if (roleMapper == null) {
-            throw new NotFoundException("User not found");
-        }
+        managePermission.require();
 
         logger.debug("deleteRealmRoleMappings");
         if (roles == null) {
@@ -264,6 +256,7 @@ public class RoleMapperResource {
             roles = new LinkedList<>();
 
             for (RoleModel roleModel : roleModels) {
+                auth.roles().requireMapRole(roleModel);
                 roleMapper.deleteRoleMapping(roleModel);
                 roles.add(ModelToRepresentation.toRepresentation(roleModel));
             }
@@ -274,11 +267,11 @@ public class RoleMapperResource {
                 if (roleModel == null || !roleModel.getId().equals(role.getId())) {
                     throw new NotFoundException("Role not found");
                 }
-
+                auth.roles().requireMapRole(roleModel);
                 try {
                     roleMapper.deleteRoleMapping(roleModel);
                 } catch (ModelException me) {
-                    Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
+                    Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale());
                     throw new ErrorResponseException(me.getMessage(), MessageFormat.format(messages.getProperty(me.getMessage(), me.getMessage()), me.getParameters()),
                             Response.Status.BAD_REQUEST);
                 }
@@ -290,10 +283,20 @@ public class RoleMapperResource {
 
     }
 
+    private boolean canMapRole(RoleModel roleModel) {
+        return auth.roles().canMapRole(roleModel);
+    }
+
     @Path("clients/{client}")
     public ClientRoleMappingsResource getUserClientRoleMappingsResource(@PathParam("client") String client) {
         ClientModel clientModel = realm.getClientById(client);
-        return new ClientRoleMappingsResource(uriInfo, session, realm, auth, roleMapper, clientModel, adminEvent);
+        if (clientModel == null) {
+            throw new NotFoundException("Client not found");
+        }
+        ClientRoleMappingsResource resource = new ClientRoleMappingsResource(uriInfo, session, realm, auth, roleMapper,
+                clientModel, adminEvent,
+                managePermission, viewPermission);
+        return resource;
 
     }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java
index 5fb1d34..0161ee6 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java
@@ -25,6 +25,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.ws.rs.core.UriInfo;
 import java.util.Collections;
@@ -60,12 +61,13 @@ public abstract class RoleResource {
         if (rep.isScopeParamRequired() != null) role.setScopeParamRequired(rep.isScopeParamRequired());
     }
 
-    protected void addComposites(AdminEventBuilder adminEvent, UriInfo uriInfo, List<RoleRepresentation> roles, RoleModel role) {
+    protected void addComposites(AdminPermissionEvaluator auth, AdminEventBuilder adminEvent, UriInfo uriInfo, List<RoleRepresentation> roles, RoleModel role) {
         for (RoleRepresentation rep : roles) {
             RoleModel composite = realm.getRoleById(rep.getId());
             if (composite == null) {
                 throw new NotFoundException("Could not find composite role");
             }
+            auth.roles().requireMapComposite(composite);
             role.addCompositeRole(composite);
         }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java
index 431e97c..4f7b5dc 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java
@@ -29,6 +29,7 @@ import org.keycloak.models.ScopeContainerModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -49,19 +50,25 @@ import java.util.Set;
  */
 public class ScopeMappedClientResource {
     protected RealmModel realm;
-    private RealmAuth auth;
+    protected AdminPermissionEvaluator auth;
+    protected AdminPermissionEvaluator.RequirePermissionCheck managePermission;
+    protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission;
     protected ScopeContainerModel scopeContainer;
     protected KeycloakSession session;
     protected ClientModel scopedClient;
     protected AdminEventBuilder adminEvent;
     
-    public ScopeMappedClientResource(RealmModel realm, RealmAuth auth, ScopeContainerModel scopeContainer, KeycloakSession session, ClientModel scopedClient, AdminEventBuilder adminEvent) {
+    public ScopeMappedClientResource(RealmModel realm, AdminPermissionEvaluator auth, ScopeContainerModel scopeContainer, KeycloakSession session, ClientModel scopedClient, AdminEventBuilder adminEvent,
+                                     AdminPermissionEvaluator.RequirePermissionCheck managePermission,
+                                     AdminPermissionEvaluator.RequirePermissionCheck viewPermission) {
         this.realm = realm;
         this.auth = auth;
         this.scopeContainer = scopeContainer;
         this.session = session;
         this.scopedClient = scopedClient;
         this.adminEvent = adminEvent.resource(ResourceType.CLIENT_SCOPE_MAPPING);
+        this.managePermission = managePermission;
+        this.viewPermission = viewPermission;
     }
 
     /**
@@ -75,11 +82,7 @@ public class ScopeMappedClientResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getClientScopeMappings() {
-        auth.requireView();
-
-        if (scopeContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        viewPermission.require();
 
         Set<RoleModel> mappings = KeycloakModelUtils.getClientScopeMappings(scopedClient, scopeContainer); //scopedClient.getClientScopeMappings(client);
         List<RoleRepresentation> mapRep = new ArrayList<RoleRepresentation>();
@@ -101,14 +104,10 @@ public class ScopeMappedClientResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getAvailableClientScopeMappings() {
-        auth.requireView();
-
-        if (scopeContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        viewPermission.require();
 
         Set<RoleModel> roles = scopedClient.getRoles();
-        return ScopeMappedResource.getAvailable(scopeContainer, roles);
+        return ScopeMappedResource.getAvailable(auth, scopeContainer, roles);
     }
 
     /**
@@ -123,11 +122,7 @@ public class ScopeMappedClientResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getCompositeClientScopeMappings() {
-        auth.requireView();
-
-        if (scopeContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        viewPermission.require();
 
         Set<RoleModel> roles = scopedClient.getRoles();
         return ScopeMappedResource.getComposite(scopeContainer, roles);
@@ -141,11 +136,7 @@ public class ScopeMappedClientResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public void addClientScopeMapping(List<RoleRepresentation> roles) {
-        auth.requireManage();
-
-        if (scopeContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        managePermission.require();
 
         for (RoleRepresentation role : roles) {
             RoleModel roleModel = scopedClient.getRole(role.getName());
@@ -166,11 +157,7 @@ public class ScopeMappedClientResource {
     @DELETE
     @Consumes(MediaType.APPLICATION_JSON)
     public void deleteClientScopeMapping(List<RoleRepresentation> roles) {
-        auth.requireManage();
-
-        if (scopeContainer == null) {
-            throw new NotFoundException("Could not find client");
-        }
+        managePermission.require();
 
         if (roles == null) {
             Set<RoleModel> roleModels = KeycloakModelUtils.getClientScopeMappings(scopedClient, scopeContainer);//scopedClient.getClientScopeMappings(client);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java
index 19a32f9..286e22b 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java
@@ -31,6 +31,7 @@ import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.representations.idm.ClientMappingsRepresentation;
 import org.keycloak.representations.idm.MappingsRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -56,17 +57,25 @@ import java.util.Set;
  */
 public class ScopeMappedResource {
     protected RealmModel realm;
-    private RealmAuth auth;
+    protected AdminPermissionEvaluator auth;
+    protected AdminPermissionEvaluator.RequirePermissionCheck managePermission;
+    protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission;
+
     protected ScopeContainerModel scopeContainer;
     protected KeycloakSession session;
     protected AdminEventBuilder adminEvent;
 
-    public ScopeMappedResource(RealmModel realm, RealmAuth auth, ScopeContainerModel scopeContainer, KeycloakSession session, AdminEventBuilder adminEvent) {
+    public ScopeMappedResource(RealmModel realm, AdminPermissionEvaluator auth, ScopeContainerModel scopeContainer,
+                               KeycloakSession session, AdminEventBuilder adminEvent,
+                               AdminPermissionEvaluator.RequirePermissionCheck managePermission,
+                               AdminPermissionEvaluator.RequirePermissionCheck viewPermission) {
         this.realm = realm;
         this.auth = auth;
         this.scopeContainer = scopeContainer;
         this.session = session;
         this.adminEvent = adminEvent.resource(ResourceType.REALM_SCOPE_MAPPING);
+        this.managePermission = managePermission;
+        this.viewPermission = viewPermission;
     }
 
     /**
@@ -78,7 +87,7 @@ public class ScopeMappedResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public MappingsRepresentation getScopeMappings() {
-        auth.requireView();
+        viewPermission.require();
 
         if (scopeContainer == null) {
             throw new NotFoundException("Could not find client");
@@ -126,7 +135,7 @@ public class ScopeMappedResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getRealmScopeMappings() {
-        auth.requireView();
+        viewPermission.require();
 
         if (scopeContainer == null) {
             throw new NotFoundException("Could not find client");
@@ -150,20 +159,21 @@ public class ScopeMappedResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getAvailableRealmScopeMappings() {
-        auth.requireView();
+        viewPermission.require();
 
         if (scopeContainer == null) {
             throw new NotFoundException("Could not find client");
         }
 
         Set<RoleModel> roles = realm.getRoles();
-        return getAvailable(scopeContainer, roles);
+        return getAvailable(auth, scopeContainer, roles);
     }
 
-    public static List<RoleRepresentation> getAvailable(ScopeContainerModel client, Set<RoleModel> roles) {
+    public static List<RoleRepresentation> getAvailable(AdminPermissionEvaluator auth, ScopeContainerModel client, Set<RoleModel> roles) {
         List<RoleRepresentation> available = new ArrayList<RoleRepresentation>();
         for (RoleModel roleModel : roles) {
             if (client.hasScope(roleModel)) continue;
+            if (!auth.roles().canMapClientScope(roleModel)) continue;
             available.add(ModelToRepresentation.toRepresentation(roleModel));
         }
         return available;
@@ -183,7 +193,7 @@ public class ScopeMappedResource {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public List<RoleRepresentation> getCompositeRealmScopeMappings() {
-        auth.requireView();
+        viewPermission.require();
 
         if (scopeContainer == null) {
             throw new NotFoundException("Could not find client");
@@ -210,7 +220,7 @@ public class ScopeMappedResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public void addRealmScopeMappings(List<RoleRepresentation> roles) {
-        auth.requireManage();
+        managePermission.require();
 
         if (scopeContainer == null) {
             throw new NotFoundException("Could not find client");
@@ -236,7 +246,7 @@ public class ScopeMappedResource {
     @DELETE
     @Consumes(MediaType.APPLICATION_JSON)
     public void deleteRealmScopeMappings(List<RoleRepresentation> roles) {
-        auth.requireManage();
+        managePermission.require();
 
         if (scopeContainer == null) {
             throw new NotFoundException("Could not find client");
@@ -268,6 +278,9 @@ public class ScopeMappedResource {
     @Path("clients/{client}")
     public ScopeMappedClientResource getClientByIdScopeMappings(@PathParam("client") String client) {
         ClientModel clientModel = realm.getClientById(client);
-        return new ScopeMappedClientResource(realm, auth, this.scopeContainer, session, clientModel, adminEvent);
+        if (clientModel == null) {
+            throw new NotFoundException("Could not find client");
+        }
+        return new ScopeMappedClientResource(realm, auth, this.scopeContainer, session, clientModel, adminEvent, managePermission, viewPermission);
     }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
new file mode 100755
index 0000000..bf3b236
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
@@ -0,0 +1,795 @@
+/*
+ * 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.services.resources.admin;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.BadRequestException;
+import org.jboss.resteasy.spi.NotFoundException;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.authentication.RequiredActionProvider;
+import org.keycloak.authentication.actiontoken.execactions.ExecuteActionsActionToken;
+import org.keycloak.common.ClientConnection;
+import org.keycloak.common.Profile;
+import org.keycloak.common.util.Time;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.email.EmailException;
+import org.keycloak.email.EmailTemplateProvider;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
+import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserLoginFailureModel;
+import org.keycloak.models.UserManager;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.FederatedIdentityRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.UserConsentRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.UserSessionRepresentation;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.ServicesLogger;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.BruteForceProtector;
+import org.keycloak.services.managers.UserSessionManager;
+import org.keycloak.services.resources.AccountService;
+import org.keycloak.services.resources.LoginActionsService;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.validation.Validation;
+import org.keycloak.storage.ReadOnlyException;
+import org.keycloak.utils.ProfileHelper;
+
+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.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base resource for managing users
+ *
+ * @resource Users
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserResource {
+    private static final Logger logger = Logger.getLogger(UserResource.class);
+
+    protected RealmModel realm;
+
+    private AdminPermissionEvaluator auth;
+
+    private AdminEventBuilder adminEvent;
+    private UserModel user;
+
+    @Context
+    protected ClientConnection clientConnection;
+
+    @Context
+    protected UriInfo uriInfo;
+
+    @Context
+    protected KeycloakSession session;
+
+    @Context
+    protected HttpHeaders headers;
+
+    public UserResource(RealmModel realm, UserModel user, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
+        this.auth = auth;
+        this.realm = realm;
+        this.user = user;
+        this.adminEvent = adminEvent.resource(ResourceType.USER);
+    }
+
+    /**
+     * Update the user
+     *
+     * @param rep
+     * @return
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response updateUser(final UserRepresentation rep) {
+
+        auth.users().requireManage(user);
+        try {
+            Set<String> attrsToRemove;
+            if (rep.getAttributes() != null) {
+                attrsToRemove = new HashSet<>(user.getAttributes().keySet());
+                attrsToRemove.removeAll(rep.getAttributes().keySet());
+            } else {
+                attrsToRemove = Collections.emptySet();
+            }
+
+            if (rep.isEnabled() != null && rep.isEnabled()) {
+                UserLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, user.getId());
+                if (failureModel != null) {
+                    failureModel.clearFailures();
+                }
+            }
+
+            updateUserFromRep(user, rep, attrsToRemove, realm, session, true);
+            adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
+
+            if (session.getTransactionManager().isActive()) {
+                session.getTransactionManager().commit();
+            }
+            return Response.noContent().build();
+        } catch (ModelDuplicateException e) {
+            return ErrorResponse.exists("User exists with same username or email");
+        } catch (ReadOnlyException re) {
+            return ErrorResponse.exists("User is read only!");
+        } catch (ModelException me) {
+            logger.warn("Could not update user!", me);
+            return ErrorResponse.exists("Could not update user!");
+        } catch (ForbiddenException fe) {
+            throw fe;
+        } catch (Exception me) { // JPA
+            logger.warn("Could not update user!", me);// may be committed by JTA which can't
+            return ErrorResponse.exists("Could not update user!");
+        }
+    }
+
+    public static void updateUserFromRep(UserModel user, UserRepresentation rep, Set<String> attrsToRemove, RealmModel realm, KeycloakSession session, boolean removeMissingRequiredActions) {
+        if (rep.getUsername() != null && realm.isEditUsernameAllowed()) {
+            user.setUsername(rep.getUsername());
+        }
+        if (rep.getEmail() != null) user.setEmail(rep.getEmail());
+        if (rep.getFirstName() != null) user.setFirstName(rep.getFirstName());
+        if (rep.getLastName() != null) user.setLastName(rep.getLastName());
+
+        if (rep.isEnabled() != null) user.setEnabled(rep.isEnabled());
+        if (rep.isEmailVerified() != null) user.setEmailVerified(rep.isEmailVerified());
+
+        List<String> reqActions = rep.getRequiredActions();
+
+        if (reqActions != null) {
+            Set<String> allActions = new HashSet<>();
+            for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class)) {
+                allActions.add(factory.getId());
+            }
+            for (String action : allActions) {
+                if (reqActions.contains(action)) {
+                    user.addRequiredAction(action);
+                } else if (removeMissingRequiredActions) {
+                    user.removeRequiredAction(action);
+                }
+            }
+        }
+
+        if (rep.getAttributes() != null) {
+            for (Map.Entry<String, List<String>> attr : rep.getAttributes().entrySet()) {
+                user.setAttribute(attr.getKey(), attr.getValue());
+            }
+
+            for (String attr : attrsToRemove) {
+                user.removeAttribute(attr);
+            }
+        }
+    }
+
+    /**
+     * Get representation of the user
+     *
+     * @return
+     */
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public UserRepresentation getUser() {
+        auth.users().requireView(user);
+
+        UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, user);
+
+        if (realm.isIdentityFederationEnabled()) {
+            List<FederatedIdentityRepresentation> reps = getFederatedIdentities(user);
+            rep.setFederatedIdentities(reps);
+        }
+
+        if (session.getProvider(BruteForceProtector.class).isTemporarilyDisabled(session, realm, user)) {
+            rep.setEnabled(false);
+        }
+        rep.setAccess(auth.users().getAccess(user));
+
+        return rep;
+    }
+
+    /**
+     * Impersonate the user
+     *
+     * @return
+     */
+    @Path("impersonation")
+    @POST
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public Map<String, Object> impersonate() {
+        ProfileHelper.requireFeature(Profile.Feature.IMPERSONATION);
+
+        auth.users().requireImpersonate(user);
+        RealmModel authenticatedRealm = auth.adminAuth().getRealm();
+        // if same realm logout before impersonation
+        boolean sameRealm = false;
+        if (authenticatedRealm.getId().equals(realm.getId())) {
+            sameRealm = true;
+            UserSessionModel userSession = session.sessions().getUserSession(authenticatedRealm, auth.adminAuth().getToken().getSessionState());
+            AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
+            AuthenticationManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
+            AuthenticationManager.backchannelLogout(session, authenticatedRealm, userSession, uriInfo, clientConnection, headers, true);
+        }
+        EventBuilder event = new EventBuilder(realm, session, clientConnection);
+
+        String sessionId = KeycloakModelUtils.generateId();
+        UserSessionModel userSession = session.sessions().createUserSession(sessionId, realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
+        AuthenticationManager.createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
+        URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(realm.getName());
+        Map<String, Object> result = new HashMap<>();
+        result.put("sameRealm", sameRealm);
+        result.put("redirect", redirect.toString());
+        event.event(EventType.IMPERSONATE)
+             .session(userSession)
+             .user(user)
+             .detail(Details.IMPERSONATOR_REALM,authenticatedRealm.getName())
+             .detail(Details.IMPERSONATOR, auth.adminAuth().getUser().getUsername()).success();
+
+        return result;
+    }
+
+
+    /**
+     * Get sessions associated with the user
+     *
+     * @return
+     */
+    @Path("sessions")
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<UserSessionRepresentation> getSessions() {
+        auth.users().requireView(user);
+        List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
+        List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
+        for (UserSessionModel session : sessions) {
+            UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
+            reps.add(rep);
+        }
+        return reps;
+    }
+
+    /**
+     * Get offline sessions associated with the user and client
+     *
+     * @return
+     */
+    @Path("offline-sessions/{clientId}")
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<UserSessionRepresentation> getOfflineSessions(final @PathParam("clientId") String clientId) {
+        auth.users().requireView(user);
+        ClientModel client = realm.getClientById(clientId);
+        if (client == null) {
+            throw new NotFoundException("Client not found");
+        }
+        List<UserSessionModel> sessions = new UserSessionManager(session).findOfflineSessions(realm, user);
+        List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
+        for (UserSessionModel session : sessions) {
+            UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
+
+            // Update lastSessionRefresh with the timestamp from clientSession
+            AuthenticatedClientSessionModel clientSession = session.getAuthenticatedClientSessions().get(clientId);
+
+            // Skip if userSession is not for this client
+            if (clientSession == null) {
+                continue;
+            }
+
+            rep.setLastAccess(clientSession.getTimestamp());
+
+            reps.add(rep);
+        }
+        return reps;
+    }
+
+    /**
+     * Get social logins associated with the user
+     *
+     * @return
+     */
+    @Path("federated-identity")
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<FederatedIdentityRepresentation> getFederatedIdentity() {
+        auth.users().requireView(user);
+
+        return getFederatedIdentities(user);
+    }
+
+    private List<FederatedIdentityRepresentation> getFederatedIdentities(UserModel user) {
+        Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
+        List<FederatedIdentityRepresentation> result = new ArrayList<FederatedIdentityRepresentation>();
+
+        for (FederatedIdentityModel identity : identities) {
+            for (IdentityProviderModel identityProviderModel : realm.getIdentityProviders()) {
+                if (identityProviderModel.getAlias().equals(identity.getIdentityProvider())) {
+                    FederatedIdentityRepresentation rep = ModelToRepresentation.toRepresentation(identity);
+                    result.add(rep);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Add a social login provider to the user
+     *
+     * @param provider Social login provider id
+     * @param rep
+     * @return
+     */
+    @Path("federated-identity/{provider}")
+    @POST
+    @NoCache
+    public Response addFederatedIdentity(final @PathParam("provider") String provider, FederatedIdentityRepresentation rep) {
+        auth.users().requireManage(user);
+        if (session.users().getFederatedIdentity(user, provider, realm) != null) {
+            return ErrorResponse.exists("User is already linked with provider");
+        }
+
+        FederatedIdentityModel socialLink = new FederatedIdentityModel(provider, rep.getUserId(), rep.getUserName());
+        session.users().addFederatedIdentity(realm, user, socialLink);
+        adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(rep).success();
+        return Response.noContent().build();
+    }
+
+    /**
+     * Remove a social login provider from user
+     *
+     * @param provider Social login provider id
+     */
+    @Path("federated-identity/{provider}")
+    @DELETE
+    @NoCache
+    public void removeFederatedIdentity(final @PathParam("provider") String provider) {
+        auth.users().requireManage(user);
+        if (!session.users().removeFederatedIdentity(realm, user, provider)) {
+            throw new NotFoundException("Link not found");
+        }
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
+    }
+
+    /**
+     * Get consents granted by the user
+     *
+     * @return
+     */
+    @Path("consents")
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<Map<String, Object>> getConsents() {
+        auth.users().requireView(user);
+        List<Map<String, Object>> result = new LinkedList<>();
+
+        Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
+
+        for (ClientModel client : realm.getClients()) {
+            UserConsentModel consent = session.users().getConsentByClient(realm, user.getId(), client.getId());
+            boolean hasOfflineToken = offlineClients.contains(client);
+
+            if (consent == null && !hasOfflineToken) {
+                continue;
+            }
+
+            UserConsentRepresentation rep = (consent == null) ? null : ModelToRepresentation.toRepresentation(consent);
+
+            Map<String, Object> currentRep = new HashMap<>();
+            currentRep.put("clientId", client.getClientId());
+            currentRep.put("grantedProtocolMappers", (rep==null ? Collections.emptyMap() : rep.getGrantedProtocolMappers()));
+            currentRep.put("grantedRealmRoles", (rep==null ? Collections.emptyList() : rep.getGrantedRealmRoles()));
+            currentRep.put("grantedClientRoles", (rep==null ? Collections.emptyMap() : rep.getGrantedClientRoles()));
+            currentRep.put("createdDate", (rep==null ? null : rep.getCreatedDate()));
+            currentRep.put("lastUpdatedDate", (rep==null ? null : rep.getLastUpdatedDate()));
+
+            List<Map<String, String>> additionalGrants = new LinkedList<>();
+            if (hasOfflineToken) {
+                Map<String, String> offlineTokens = new HashMap<>();
+                offlineTokens.put("client", client.getId());
+                // TODO: translate
+                offlineTokens.put("key", "Offline Token");
+                additionalGrants.add(offlineTokens);
+            }
+            currentRep.put("additionalGrants", additionalGrants);
+
+            result.add(currentRep);
+        }
+
+        return result;
+    }
+
+    /**
+     * Revoke consent and offline tokens for particular client from user
+     *
+     * @param clientId Client id
+     */
+    @Path("consents/{client}")
+    @DELETE
+    @NoCache
+    public void revokeConsent(final @PathParam("client") String clientId) {
+        auth.users().requireManage(user);
+
+        ClientModel client = realm.getClientByClientId(clientId);
+        if (client == null) {
+            throw new NotFoundException("Client not found");
+        }
+        boolean revokedConsent = session.users().revokeConsentForClient(realm, user.getId(), client.getId());
+        boolean revokedOfflineToken = new UserSessionManager(session).revokeOfflineToken(user, client);
+
+        if (revokedConsent) {
+            // Logout clientSessions for this user and client
+            AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
+        }
+
+        if (!revokedConsent && !revokedOfflineToken) {
+            throw new NotFoundException("Consent nor offline token not found");
+        }
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+    }
+
+    /**
+     * Remove all user sessions associated with the user
+     *
+     * Also send notification to all clients that have an admin URL to invalidate the sessions for the particular user.
+     *
+     */
+    @Path("logout")
+    @POST
+    public void logout() {
+        auth.users().requireManage(user);
+
+        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
+        for (UserSessionModel userSession : userSessions) {
+            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
+        }
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+    }
+
+    /**
+     * Delete the user
+     */
+    @DELETE
+    @NoCache
+    public Response deleteUser() {
+        auth.users().requireManage(user);
+
+        boolean removed = new UserManager(session).removeUser(realm, user);
+        if (removed) {
+            adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
+            return Response.noContent().build();
+        } else {
+            return ErrorResponse.error("User couldn't be deleted", Status.BAD_REQUEST);
+        }
+    }
+
+    @Path("role-mappings")
+    public RoleMapperResource getRoleMappings() {
+        AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.users().requireMapRoles(user);
+        AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.users().requireView(user);
+        RoleMapperResource resource =  new RoleMapperResource(realm, auth, user, adminEvent, manageCheck, viewCheck);
+        ResteasyProviderFactory.getInstance().injectProperties(resource);
+        return resource;
+
+    }
+
+    /**
+     * Disable all credentials for a user of a specific type
+     *
+     * @param credentialTypes
+     */
+    @Path("disable-credential-types")
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void disableCredentialType(List<String> credentialTypes) {
+        auth.users().requireManage(user);
+        if (credentialTypes == null) return;
+        for (String type : credentialTypes) {
+            session.userCredentialManager().disableCredentialType(realm, user, type);
+
+        }
+
+
+    }
+
+    /**
+     * Set up a temporary password for the user
+     *
+     * User will have to reset the temporary password next time they log in.
+     *
+     * @param pass A Temporary password
+     */
+    @Path("reset-password")
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void resetPassword(CredentialRepresentation pass) {
+        auth.users().requireManage(user);
+        if (pass == null || pass.getValue() == null || !CredentialRepresentation.PASSWORD.equals(pass.getType())) {
+            throw new BadRequestException("No password provided");
+        }
+        if (Validation.isBlank(pass.getValue())) {
+            throw new BadRequestException("Empty password not allowed");
+        }
+
+        UserCredentialModel cred = UserCredentialModel.password(pass.getValue(), true);
+        try {
+            session.userCredentialManager().updateCredential(realm, user, cred);
+        } catch (IllegalStateException ise) {
+            throw new BadRequestException("Resetting to N old passwords is not allowed.");
+        } catch (ReadOnlyException mre) {
+            throw new BadRequestException("Can't reset password as account is read only");
+        } catch (ModelException e) {
+            Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale());
+            throw new ErrorResponseException(e.getMessage(), MessageFormat.format(messages.getProperty(e.getMessage(), e.getMessage()), e.getParameters()),
+                    Status.BAD_REQUEST);
+        }
+        if (pass.isTemporary() != null && pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+    }
+
+    /**
+     * Remove TOTP from the user
+     *
+     */
+    @Path("remove-totp")
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void removeTotp() {
+        auth.users().requireManage(user);
+
+        session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP);
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+    }
+
+    /**
+     * Send an email to the user with a link they can click to reset their password.
+     * The redirectUri and clientId parameters are optional. The default for the
+     * redirect is the account client.
+     *
+     * This endpoint has been deprecated.  Please use the execute-actions-email passing a list with
+     * UPDATE_PASSWORD within it.
+     *
+     * @param redirectUri redirect uri
+     * @param clientId client id
+     * @return
+     */
+    @Deprecated
+    @Path("reset-password-email")
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response resetPasswordEmail(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri,
+                                       @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) {
+        List<String> actions = new LinkedList<>();
+        actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
+        return executeActionsEmail(redirectUri, clientId, null, actions);
+    }
+
+
+    /**
+     * Send a update account email to the user
+     *
+     * An email contains a link the user can click to perform a set of required actions.
+     * The redirectUri and clientId parameters are optional. If no redirect is given, then there will
+     * be no link back to click after actions have completed.  Redirect uri must be a valid uri for the
+     * particular clientId.
+     *
+     * @param redirectUri Redirect uri
+     * @param clientId Client id
+     * @param lifespan Number of seconds after which the generated token expires
+     * @param actions required actions the user needs to complete
+     * @return
+     */
+    @Path("execute-actions-email")
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response executeActionsEmail(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri,
+                                        @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId,
+                                        @QueryParam("lifespan") Integer lifespan,
+                                        List<String> actions) {
+        auth.users().requireManage(user);
+
+        if (user.getEmail() == null) {
+            return ErrorResponse.error("User email missing", Status.BAD_REQUEST);
+        }
+
+        if (!user.isEnabled()) {
+            throw new WebApplicationException(
+                ErrorResponse.error("User is disabled", Status.BAD_REQUEST));
+        }
+
+        if (redirectUri != null && clientId == null) {
+            throw new WebApplicationException(
+                ErrorResponse.error("Client id missing", Status.BAD_REQUEST));
+        }
+
+        if (clientId == null) {
+            clientId = Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
+        }
+
+        ClientModel client = realm.getClientByClientId(clientId);
+        if (client == null || !client.isEnabled()) {
+            throw new WebApplicationException(
+                ErrorResponse.error(clientId + " not enabled", Status.BAD_REQUEST));
+        }
+
+        String redirect;
+        if (redirectUri != null) {
+            redirect = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
+            if (redirect == null) {
+                throw new WebApplicationException(
+                    ErrorResponse.error("Invalid redirect uri.", Status.BAD_REQUEST));
+            }
+        }
+
+        if (lifespan == null) {
+            lifespan = realm.getActionTokenGeneratedByAdminLifespan();
+        }
+        int expiration = Time.currentTime() + lifespan;
+        ExecuteActionsActionToken token = new ExecuteActionsActionToken(user.getId(), expiration, actions, redirectUri, clientId);
+
+        try {
+            UriBuilder builder = LoginActionsService.actionTokenProcessor(uriInfo);
+            builder.queryParam("key", token.serialize(session, realm, uriInfo));
+
+            String link = builder.build(realm.getName()).toString();
+
+            this.session.getProvider(EmailTemplateProvider.class)
+              .setRealm(realm)
+              .setUser(user)
+              .sendExecuteActions(link, TimeUnit.SECONDS.toMinutes(lifespan));
+
+            //audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
+
+            adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+
+            return Response.ok().build();
+        } catch (EmailException e) {
+            ServicesLogger.LOGGER.failedToSendActionsEmail(e);
+            return ErrorResponse.error("Failed to send execute actions email", Status.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    /**
+     * Send an email-verification email to the user
+     *
+     * An email contains a link the user can click to verify their email address.
+     * The redirectUri and clientId parameters are optional. The default for the
+     * redirect is the account client.
+     *
+     * @param redirectUri Redirect uri
+     * @param clientId Client id
+     * @return
+     */
+    @Path("send-verify-email")
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response sendVerifyEmail(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri, @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) {
+        List<String> actions = new LinkedList<>();
+        actions.add(UserModel.RequiredAction.VERIFY_EMAIL.name());
+        return executeActionsEmail(redirectUri, clientId, null, actions);
+    }
+
+    @GET
+    @Path("groups")
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<GroupRepresentation> groupMembership() {
+        auth.users().requireView(user);
+        List<GroupRepresentation> memberships = new LinkedList<>();
+        for (GroupModel group : user.getGroups()) {
+            memberships.add(ModelToRepresentation.toRepresentation(group, false));
+        }
+        return memberships;
+    }
+
+    @DELETE
+    @Path("groups/{groupId}")
+    @NoCache
+    public void removeMembership(@PathParam("groupId") String groupId) {
+        auth.users().requireManageGroupMembership(user);
+
+        GroupModel group = session.realms().getGroupById(groupId, realm);
+        if (group == null) {
+            throw new NotFoundException("Group not found");
+        }
+        auth.groups().requireManageMembership(group);
+
+        try {
+            if (user.isMemberOf(group)){
+                user.leaveGroup(group);
+                adminEvent.operation(OperationType.DELETE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(uriInfo).success();
+            }
+        } catch (ModelException me) {
+            Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale());
+            throw new ErrorResponseException(me.getMessage(), MessageFormat.format(messages.getProperty(me.getMessage(), me.getMessage()), me.getParameters()),
+                    Status.BAD_REQUEST);
+        }
+    }
+
+    @PUT
+    @Path("groups/{groupId}")
+    @NoCache
+    public void joinGroup(@PathParam("groupId") String groupId) {
+        auth.users().requireManageGroupMembership(user);
+        GroupModel group = session.realms().getGroupById(groupId, realm);
+        if (group == null) {
+            throw new NotFoundException("Group not found");
+        }
+        auth.groups().requireManageMembership(group);
+        if (!user.isMemberOf(group)){
+            user.joinGroup(group);
+            adminEvent.operation(OperationType.CREATE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(uriInfo).success();
+        }
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index d99940d..c7b9945 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -23,6 +23,7 @@ import org.jboss.resteasy.spi.NotFoundException;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.authentication.actiontoken.execactions.ExecuteActionsActionToken;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.common.Profile;
 import org.keycloak.common.util.Time;
@@ -34,6 +35,20 @@ import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserLoginFailureModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.*;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.ModelToRepresentation;
@@ -68,18 +83,26 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import java.net.URI;
 import java.text.MessageFormat;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
-import javax.ws.rs.*;
-import javax.ws.rs.core.*;
 
 /**
  * Base resource for managing users
@@ -93,7 +116,7 @@ public class UsersResource {
 
     protected RealmModel realm;
 
-    private RealmAuth auth;
+    private AdminPermissionEvaluator auth;
 
     private AdminEventBuilder adminEvent;
 
@@ -109,66 +132,10 @@ public class UsersResource {
     @Context
     protected HttpHeaders headers;
 
-    public UsersResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public UsersResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.auth = auth;
         this.realm = realm;
         this.adminEvent = adminEvent.resource(ResourceType.USER);
-
-        auth.init(RealmAuth.Resource.USER);
-    }
-
-    /**
-     * Update the user
-     *
-     * @param id User id
-     * @param rep
-     * @return
-     */
-    @Path("{id}")
-    @PUT
-    @Consumes(MediaType.APPLICATION_JSON)
-    public Response updateUser(final @PathParam("id") String id, final UserRepresentation rep) {
-        auth.requireManage();
-
-        try {
-            UserModel user = session.users().getUserById(id, realm);
-            if (user == null) {
-                return Response.status(Status.NOT_FOUND).build();
-            }
-
-            Set<String> attrsToRemove;
-            if (rep.getAttributes() != null) {
-                attrsToRemove = new HashSet<>(user.getAttributes().keySet());
-                attrsToRemove.removeAll(rep.getAttributes().keySet());
-            } else {
-                attrsToRemove = Collections.emptySet();
-            }
-
-            if (rep.isEnabled() != null && rep.isEnabled()) {
-                UserLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, id);
-                if (failureModel != null) {
-                    failureModel.clearFailures();
-                }
-            }
-
-            updateUserFromRep(user, rep, attrsToRemove, realm, session, true);
-            adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
-
-            if (session.getTransactionManager().isActive()) {
-                session.getTransactionManager().commit();
-            }
-            return Response.noContent().build();
-        } catch (ModelDuplicateException e) {
-            return ErrorResponse.exists("User exists with same username or email");
-        } catch (ReadOnlyException re) {
-            return ErrorResponse.exists("User is read only!");
-        } catch (ModelException me) {
-            logger.warn("Could not update user!", me);
-            return ErrorResponse.exists("Could not update user!");
-        } catch (Exception me) { // JPA
-            logger.warn("Could not update user!", me);// may be committed by JTA which can't
-            return ErrorResponse.exists("Could not update user!");
-        }
     }
 
     /**
@@ -183,7 +150,7 @@ public class UsersResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public Response createUser(final @Context UriInfo uriInfo, final UserRepresentation rep) {
-        auth.requireManage();
+        auth.users().requireManage();
 
         // Double-check duplicated username and email here due to federation
         if (session.users().getUserByUsername(rep.getUsername(), realm) != null) {
@@ -196,9 +163,9 @@ public class UsersResource {
         try {
             UserModel user = session.users().addUser(realm, rep.getUsername());
             Set<String> emptySet = Collections.emptySet();
-            updateUserFromRep(user, rep, emptySet, realm, session, false);
+
+            UserResource.updateUserFromRep(user, rep, emptySet, realm, session, false);
             RepresentationToModel.createCredentials(rep, session, realm, user);
-            
             adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, user.getId()).representation(rep).success();
 
             if (session.getTransactionManager().isActive()) {
@@ -219,45 +186,6 @@ public class UsersResource {
             return ErrorResponse.exists("Could not create user");
         }
     }
-
-    public static void updateUserFromRep(UserModel user, UserRepresentation rep, Set<String> attrsToRemove, RealmModel realm, KeycloakSession session, boolean removeMissingRequiredActions) {
-        if (rep.getUsername() != null && realm.isEditUsernameAllowed()) {
-            user.setUsername(rep.getUsername());
-        }
-        if (rep.getEmail() != null) user.setEmail(rep.getEmail());
-        if (rep.getFirstName() != null) user.setFirstName(rep.getFirstName());
-        if (rep.getLastName() != null) user.setLastName(rep.getLastName());
-
-        if (rep.isEnabled() != null) user.setEnabled(rep.isEnabled());
-        if (rep.isEmailVerified() != null) user.setEmailVerified(rep.isEmailVerified());
-
-        List<String> reqActions = rep.getRequiredActions();
-
-        if (reqActions != null) {
-            Set<String> allActions = new HashSet<>();
-            for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class)) {
-                allActions.add(factory.getId());
-            }
-            for (String action : allActions) {
-                if (reqActions.contains(action)) {
-                    user.addRequiredAction(action);
-                } else if (removeMissingRequiredActions) {
-                    user.removeRequiredAction(action);
-                }
-            }
-        }
-
-        if (rep.getAttributes() != null) {
-            for (Map.Entry<String, List<String>> attr : rep.getAttributes().entrySet()) {
-                user.setAttribute(attr.getKey(), attr.getValue());
-            }
-
-            for (String attr : attrsToRemove) {
-                user.removeAttribute(attr);
-            }
-        }
-    }
-
     /**
      * Get representation of the user
      *
@@ -265,368 +193,17 @@ public class UsersResource {
      * @return
      */
     @Path("{id}")
-    @GET
-    @NoCache
-    @Produces(MediaType.APPLICATION_JSON)
-    public UserRepresentation getUser(final @PathParam("id") String id) {
-        auth.requireView();
-
+    public UserResource user(final @PathParam("id") String id) {
         UserModel user = session.users().getUserById(id, realm);
         if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, user);
-
-        if (realm.isIdentityFederationEnabled()) {
-            List<FederatedIdentityRepresentation> reps = getFederatedIdentities(user);
-            rep.setFederatedIdentities(reps);
-        }
-
-        if (session.getProvider(BruteForceProtector.class).isTemporarilyDisabled(session, realm, user)) {
-            rep.setEnabled(false);
-        }
-
-        return rep;
-    }
-
-    /**
-     * Impersonate the user
-     *
-     * @param id User id
-     * @return
-     */
-    @Path("{id}/impersonation")
-    @POST
-    @NoCache
-    @Produces(MediaType.APPLICATION_JSON)
-    public Map<String, Object> impersonate(final @PathParam("id") String id) {
-        ProfileHelper.requireFeature(Profile.Feature.IMPERSONATION);
-
-        auth.init(RealmAuth.Resource.IMPERSONATION);
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-        RealmModel authenticatedRealm = auth.getAuth().getRealm();
-        // if same realm logout before impersonation
-        boolean sameRealm = false;
-        if (authenticatedRealm.getId().equals(realm.getId())) {
-            sameRealm = true;
-            UserSessionModel userSession = session.sessions().getUserSession(authenticatedRealm, auth.getAuth().getToken().getSessionState());
-            AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
-            AuthenticationManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
-            AuthenticationManager.backchannelLogout(session, authenticatedRealm, userSession, uriInfo, clientConnection, headers, true);
-        }
-        EventBuilder event = new EventBuilder(realm, session, clientConnection);
-
-        String sessionId = KeycloakModelUtils.generateId();
-        UserSessionModel userSession = session.sessions().createUserSession(sessionId, realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
-        AuthenticationManager.createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
-        URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(realm.getName());
-        Map<String, Object> result = new HashMap<>();
-        result.put("sameRealm", sameRealm);
-        result.put("redirect", redirect.toString());
-        event.event(EventType.IMPERSONATE)
-             .session(userSession)
-             .user(user)
-             .detail(Details.IMPERSONATOR_REALM,authenticatedRealm.getName())
-             .detail(Details.IMPERSONATOR, auth.getAuth().getUser().getUsername()).success();
-
-        return result;
-    }
-
-
-    /**
-     * Get sessions associated with the user
-     *
-     * @param id User id
-     * @return
-     */
-    @Path("{id}/sessions")
-    @GET
-    @NoCache
-    @Produces(MediaType.APPLICATION_JSON)
-    public List<UserSessionRepresentation> getSessions(final @PathParam("id") String id) {
-        auth.requireView();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-        List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
-        List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
-        for (UserSessionModel session : sessions) {
-            UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
-            reps.add(rep);
-        }
-        return reps;
-    }
-
-    /**
-     * Get offline sessions associated with the user and client
-     *
-     * @param id User id
-     * @return
-     */
-    @Path("{id}/offline-sessions/{clientId}")
-    @GET
-    @NoCache
-    @Produces(MediaType.APPLICATION_JSON)
-    public List<UserSessionRepresentation> getOfflineSessions(final @PathParam("id") String id, final @PathParam("clientId") String clientId) {
-        auth.requireView();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-        ClientModel client = realm.getClientById(clientId);
-        if (client == null) {
-            throw new NotFoundException("Client not found");
-        }
-        List<UserSessionModel> sessions = new UserSessionManager(session).findOfflineSessions(realm, user);
-        List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
-        for (UserSessionModel session : sessions) {
-            UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
-
-            // Update lastSessionRefresh with the timestamp from clientSession
-            AuthenticatedClientSessionModel clientSession = session.getAuthenticatedClientSessions().get(clientId);
-
-            // Skip if userSession is not for this client
-            if (clientSession == null) {
-                continue;
-            }
-
-            rep.setLastAccess(clientSession.getTimestamp());
-
-            reps.add(rep);
-        }
-        return reps;
-    }
-
-    /**
-     * Get social logins associated with the user
-     *
-     * @param id User id
-     * @return
-     */
-    @Path("{id}/federated-identity")
-    @GET
-    @NoCache
-    @Produces(MediaType.APPLICATION_JSON)
-    public List<FederatedIdentityRepresentation> getFederatedIdentity(final @PathParam("id") String id) {
-        auth.requireView();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        return getFederatedIdentities(user);
-    }
-
-    private List<FederatedIdentityRepresentation> getFederatedIdentities(UserModel user) {
-        Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
-        List<FederatedIdentityRepresentation> result = new ArrayList<FederatedIdentityRepresentation>();
-
-        for (FederatedIdentityModel identity : identities) {
-            for (IdentityProviderModel identityProviderModel : realm.getIdentityProviders()) {
-                if (identityProviderModel.getAlias().equals(identity.getIdentityProvider())) {
-                    FederatedIdentityRepresentation rep = ModelToRepresentation.toRepresentation(identity);
-                    result.add(rep);
-                }
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Add a social login provider to the user
-     *
-     * @param id User id
-     * @param provider Social login provider id
-     * @param rep
-     * @return
-     */
-    @Path("{id}/federated-identity/{provider}")
-    @POST
-    @NoCache
-    public Response addFederatedIdentity(final @PathParam("id") String id, final @PathParam("provider") String provider, FederatedIdentityRepresentation rep) {
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-        if (session.users().getFederatedIdentity(user, provider, realm) != null) {
-            return ErrorResponse.exists("User is already linked with provider");
-        }
-
-        FederatedIdentityModel socialLink = new FederatedIdentityModel(provider, rep.getUserId(), rep.getUserName());
-        session.users().addFederatedIdentity(realm, user, socialLink);
-        adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(rep).success();
-        return Response.noContent().build();
-    }
-
-    /**
-     * Remove a social login provider from user
-     *
-     * @param id User id
-     * @param provider Social login provider id
-     */
-    @Path("{id}/federated-identity/{provider}")
-    @DELETE
-    @NoCache
-    public void removeFederatedIdentity(final @PathParam("id") String id, final @PathParam("provider") String provider) {
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-        if (!session.users().removeFederatedIdentity(realm, user, provider)) {
-            throw new NotFoundException("Link not found");
-        }
-        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
-    }
-
-    /**
-     * Get consents granted by the user
-     *
-     * @param id User id
-     * @return
-     */
-    @Path("{id}/consents")
-    @GET
-    @NoCache
-    @Produces(MediaType.APPLICATION_JSON)
-    public List<Map<String, Object>> getConsents(final @PathParam("id") String id) {
-        auth.requireView();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        List<Map<String, Object>> result = new LinkedList<>();
-
-        Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
-
-        for (ClientModel client : realm.getClients()) {
-            UserConsentModel consent = session.users().getConsentByClient(realm, user.getId(), client.getId());
-            boolean hasOfflineToken = offlineClients.contains(client);
-
-            if (consent == null && !hasOfflineToken) {
-                continue;
-            }
-
-            UserConsentRepresentation rep = (consent == null) ? null : ModelToRepresentation.toRepresentation(consent);
-
-            Map<String, Object> currentRep = new HashMap<>();
-            currentRep.put("clientId", client.getClientId());
-            currentRep.put("grantedProtocolMappers", (rep==null ? Collections.emptyMap() : rep.getGrantedProtocolMappers()));
-            currentRep.put("grantedRealmRoles", (rep==null ? Collections.emptyList() : rep.getGrantedRealmRoles()));
-            currentRep.put("grantedClientRoles", (rep==null ? Collections.emptyMap() : rep.getGrantedClientRoles()));
-            currentRep.put("createdDate", (rep==null ? null : rep.getCreatedDate()));
-            currentRep.put("lastUpdatedDate", (rep==null ? null : rep.getLastUpdatedDate()));
-
-            List<Map<String, String>> additionalGrants = new LinkedList<>();
-            if (hasOfflineToken) {
-                Map<String, String> offlineTokens = new HashMap<>();
-                offlineTokens.put("client", client.getId());
-                // TODO: translate
-                offlineTokens.put("key", "Offline Token");
-                additionalGrants.add(offlineTokens);
-            }
-            currentRep.put("additionalGrants", additionalGrants);
-
-            result.add(currentRep);
-        }
-
-        return result;
-    }
-
-    /**
-     * Revoke consent and offline tokens for particular client from user
-     *
-     * @param id User id
-     * @param clientId Client id
-     */
-    @Path("{id}/consents/{client}")
-    @DELETE
-    @NoCache
-    public void revokeConsent(final @PathParam("id") String id, final @PathParam("client") String clientId) {
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        ClientModel client = realm.getClientByClientId(clientId);
-        boolean revokedConsent = session.users().revokeConsentForClient(realm, user.getId(), client.getId());
-        boolean revokedOfflineToken = new UserSessionManager(session).revokeOfflineToken(user, client);
-
-        if (revokedConsent) {
-            // Logout clientSessions for this user and client
-            AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
-        }
-
-        if (!revokedConsent && !revokedOfflineToken) {
-            throw new NotFoundException("Consent nor offline token not found");
-        }
-        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
-    }
-
-    /**
-     * Remove all user sessions associated with the user
-     *
-     * Also send notification to all clients that have an admin URL to invalidate the sessions for the particular user.
-     *
-     * @param id User id
-     */
-    @Path("{id}/logout")
-    @POST
-    public void logout(final @PathParam("id") String id) {
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
-        for (UserSessionModel userSession : userSessions) {
-            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
-        }
-        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
-    }
-
-    /**
-     * Delete the user
-     *
-     * @param id User id
-     */
-    @Path("{id}")
-    @DELETE
-    @NoCache
-    public Response deleteUser(final @PathParam("id") String id) {
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        boolean removed = new UserManager(session).removeUser(realm, user);
-        if (removed) {
-            adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
-            return Response.noContent().build();
-        } else {
-            return ErrorResponse.error("User couldn't be deleted", Response.Status.BAD_REQUEST);
+            // we do this to make sure somebody can't phish ids
+            if (auth.users().canQuery()) throw new NotFoundException("User not found");
+            else throw new ForbiddenException();
         }
+        UserResource resource = new UserResource(realm, user, auth, adminEvent);
+        ResteasyProviderFactory.getInstance().injectProperties(resource);
+        //resourceContext.initResource(users);
+        return resource;
     }
 
     /**
@@ -653,7 +230,7 @@ public class UsersResource {
                                              @QueryParam("username") String username,
                                              @QueryParam("first") Integer firstResult,
                                              @QueryParam("max") Integer maxResults) {
-        auth.requireView();
+        auth.users().requireQuery();
 
         firstResult = firstResult != null ? firstResult : -1;
         maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
@@ -681,8 +258,12 @@ public class UsersResource {
             userModels = session.users().getUsers(realm, firstResult, maxResults, false);
         }
 
+        boolean canViewGlobal = auth.users().canView();
         for (UserModel user : userModels) {
-            results.add(ModelToRepresentation.toRepresentation(session, realm, user));
+            if (!canViewGlobal  && !auth.users().canView(user)) continue;
+            UserRepresentation userRep = ModelToRepresentation.toRepresentation(session, realm, user);
+            userRep.setAccess(auth.users().getAccess(user));
+            results.add(userRep);
         }
         return results;
     }
@@ -692,311 +273,8 @@ public class UsersResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public Integer getUsersCount() {
-        auth.requireView();
+        auth.users().requireView();
 
         return session.users().getUsersCount(realm);
     }
-
-    @Path("{id}/role-mappings")
-    public RoleMapperResource getRoleMappings(@PathParam("id") String id) {
-        auth.init(RealmAuth.Resource.USER);
-
-        UserModel user = session.users().getUserById(id, realm);
-
-        RoleMapperResource resource =  new RoleMapperResource(realm, auth, user, adminEvent);
-        ResteasyProviderFactory.getInstance().injectProperties(resource);
-        return resource;
-
-    }
-
-    /**
-     * Disable all credentials for a user of a specific type
-     *
-     * @param id
-     * @param credentialTypes
-     */
-    @Path("{id}/disable-credential-types")
-    @PUT
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void disableCredentialType(@PathParam("id") String id, List<String> credentialTypes) {
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-        if (credentialTypes == null) return;
-        for (String type : credentialTypes) {
-            session.userCredentialManager().disableCredentialType(realm, user, type);
-
-        }
-
-
-    }
-
-    /**
-     * Set up a temporary password for the user
-     *
-     * User will have to reset the temporary password next time they log in.
-     *
-     * @param id User id
-     * @param pass A Temporary password
-     */
-    @Path("{id}/reset-password")
-    @PUT
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void resetPassword(@PathParam("id") String id, CredentialRepresentation pass) {
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-        if (pass == null || pass.getValue() == null || !CredentialRepresentation.PASSWORD.equals(pass.getType())) {
-            throw new BadRequestException("No password provided");
-        }
-        if (Validation.isBlank(pass.getValue())) {
-            throw new BadRequestException("Empty password not allowed");
-        }
-
-        UserCredentialModel cred = UserCredentialModel.password(pass.getValue(), true);
-        try {
-            session.userCredentialManager().updateCredential(realm, user, cred);
-        } catch (IllegalStateException ise) {
-            throw new BadRequestException("Resetting to N old passwords is not allowed.");
-        } catch (ReadOnlyException mre) {
-            throw new BadRequestException("Can't reset password as account is read only");
-        } catch (ModelException e) {
-            Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
-            throw new ErrorResponseException(e.getMessage(), MessageFormat.format(messages.getProperty(e.getMessage(), e.getMessage()), e.getParameters()),
-                    Status.BAD_REQUEST);
-        }
-        if (pass.isTemporary() != null && pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
-
-        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
-    }
-
-    /**
-     * Remove TOTP from the user
-     *
-     * @param id User id
-     */
-    @Path("{id}/remove-totp")
-    @PUT
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void removeTotp(@PathParam("id") String id) {
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP);
-        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
-    }
-
-    /**
-     * Send an email to the user with a link they can click to reset their password.
-     * The redirectUri and clientId parameters are optional. The default for the
-     * redirect is the account client.
-     *
-     * This endpoint has been deprecated.  Please use the execute-actions-email passing a list with
-     * UPDATE_PASSWORD within it.
-     *
-     * @param id
-     * @param redirectUri redirect uri
-     * @param clientId client id
-     * @return
-     */
-    @Deprecated
-    @Path("{id}/reset-password-email")
-    @PUT
-    @Consumes(MediaType.APPLICATION_JSON)
-    public Response resetPasswordEmail(@PathParam("id") String id,
-                                        @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri,
-                                        @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) {
-        List<String> actions = new LinkedList<>();
-        actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
-        return executeActionsEmail(id, redirectUri, clientId, null, actions);
-    }
-
-
-    /**
-     * Send a update account email to the user
-     *
-     * An email contains a link the user can click to perform a set of required actions.
-     * The redirectUri and clientId parameters are optional. If no redirect is given, then there will
-     * be no link back to click after actions have completed.  Redirect uri must be a valid uri for the
-     * particular clientId.
-     *
-     * @param id User is
-     * @param redirectUri Redirect uri
-     * @param clientId Client id
-     * @param lifespan Number of seconds after which the generated token expires
-     * @param actions required actions the user needs to complete
-     * @return
-     */
-    @Path("{id}/execute-actions-email")
-    @PUT
-    @Consumes(MediaType.APPLICATION_JSON)
-    public Response executeActionsEmail(@PathParam("id") String id,
-                                        @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri,
-                                        @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId,
-                                        @QueryParam("lifespan") Integer lifespan,
-                                        List<String> actions) {
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            return ErrorResponse.error("User not found", Response.Status.NOT_FOUND);
-        }
-
-        if (user.getEmail() == null) {
-            return ErrorResponse.error("User email missing", Response.Status.BAD_REQUEST);
-        }
-
-        if (!user.isEnabled()) {
-            throw new WebApplicationException(
-                ErrorResponse.error("User is disabled", Response.Status.BAD_REQUEST));
-        }
-
-        if (redirectUri != null && clientId == null) {
-            throw new WebApplicationException(
-                ErrorResponse.error("Client id missing", Response.Status.BAD_REQUEST));
-        }
-
-        if (clientId == null) {
-            clientId = Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
-        }
-
-        ClientModel client = realm.getClientByClientId(clientId);
-        if (client == null || !client.isEnabled()) {
-            throw new WebApplicationException(
-                ErrorResponse.error(clientId + " not enabled", Response.Status.BAD_REQUEST));
-        }
-
-        String redirect;
-        if (redirectUri != null) {
-            redirect = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
-            if (redirect == null) {
-                throw new WebApplicationException(
-                    ErrorResponse.error("Invalid redirect uri.", Response.Status.BAD_REQUEST));
-            }
-        }
-
-        if (lifespan == null) {
-            lifespan = realm.getActionTokenGeneratedByAdminLifespan();
-        }
-        int expiration = Time.currentTime() + lifespan;
-        ExecuteActionsActionToken token = new ExecuteActionsActionToken(id, expiration, actions, redirectUri, clientId);
-
-        try {
-            UriBuilder builder = LoginActionsService.actionTokenProcessor(uriInfo);
-            builder.queryParam("key", token.serialize(session, realm, uriInfo));
-
-            String link = builder.build(realm.getName()).toString();
-
-            this.session.getProvider(EmailTemplateProvider.class)
-              .setRealm(realm)
-              .setUser(user)
-              .sendExecuteActions(link, TimeUnit.SECONDS.toMinutes(lifespan));
-
-            //audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
-
-            adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
-
-            return Response.ok().build();
-        } catch (EmailException e) {
-            ServicesLogger.LOGGER.failedToSendActionsEmail(e);
-            return ErrorResponse.error("Failed to send execute actions email", Response.Status.INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    /**
-     * Send an email-verification email to the user
-     *
-     * An email contains a link the user can click to verify their email address.
-     * The redirectUri and clientId parameters are optional. The default for the
-     * redirect is the account client.
-     *
-     * @param id User id
-     * @param redirectUri Redirect uri
-     * @param clientId Client id
-     * @return
-     */
-    @Path("{id}/send-verify-email")
-    @PUT
-    @Consumes(MediaType.APPLICATION_JSON)
-    public Response sendVerifyEmail(@PathParam("id") String id, @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri, @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) {
-        List<String> actions = new LinkedList<>();
-        actions.add(UserModel.RequiredAction.VERIFY_EMAIL.name());
-        return executeActionsEmail(id, redirectUri, clientId, null, actions);
-    }
-
-    @GET
-    @Path("{id}/groups")
-    @NoCache
-    @Produces(MediaType.APPLICATION_JSON)
-    public List<GroupRepresentation> groupMembership(@PathParam("id") String id) {
-        auth.requireView();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-        List<GroupRepresentation> memberships = new LinkedList<>();
-        for (GroupModel group : user.getGroups()) {
-            memberships.add(ModelToRepresentation.toRepresentation(group, false));
-        }
-        return memberships;
-    }
-
-    @DELETE
-    @Path("{id}/groups/{groupId}")
-    @NoCache
-    public void removeMembership(@PathParam("id") String id, @PathParam("groupId") String groupId) {
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-        GroupModel group = session.realms().getGroupById(groupId, realm);
-        if (group == null) {
-            throw new NotFoundException("Group not found");
-        }
-
-        try {
-            if (user.isMemberOf(group)){
-                user.leaveGroup(group);
-                adminEvent.operation(OperationType.DELETE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(uriInfo).success();
-            }
-        } catch (ModelException me) {
-            Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
-            throw new ErrorResponseException(me.getMessage(), MessageFormat.format(messages.getProperty(me.getMessage(), me.getMessage()), me.getParameters()),
-                    Response.Status.BAD_REQUEST);
-        }
-    }
-
-    @PUT
-    @Path("{id}/groups/{groupId}")
-    @NoCache
-    public void joinGroup(@PathParam("id") String id, @PathParam("groupId") String groupId) {
-        auth.requireManage();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-        GroupModel group = session.realms().getGroupById(groupId, realm);
-        if (group == null) {
-            throw new NotFoundException("Group not found");
-        }
-        if (!user.isMemberOf(group)){
-            user.joinGroup(group);
-            adminEvent.operation(OperationType.CREATE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(uriInfo).success();
-        }
-    }
-
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java
index 4ffcf86..638f57b 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java
@@ -26,6 +26,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.services.ServicesLogger;
 import org.keycloak.services.managers.UserStorageSyncManager;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
 import org.keycloak.storage.UserStorageProvider;
 import org.keycloak.storage.UserStorageProviderModel;
 import org.keycloak.storage.ldap.LDAPStorageProvider;
@@ -55,7 +56,7 @@ public class UserStorageProviderResource {
 
     protected RealmModel realm;
 
-    protected RealmAuth auth;
+    protected AdminPermissionEvaluator auth;
 
     protected AdminEventBuilder adminEvent;
 
@@ -71,12 +72,10 @@ public class UserStorageProviderResource {
     @Context
     protected HttpHeaders headers;
 
-    public UserStorageProviderResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+    public UserStorageProviderResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
         this.auth = auth;
         this.realm = realm;
         this.adminEvent = adminEvent;
-
-        auth.init(RealmAuth.Resource.USER);
     }
 
     /**
@@ -94,7 +93,7 @@ public class UserStorageProviderResource {
     @Produces(MediaType.APPLICATION_JSON)
     public SynchronizationResult syncUsers(@PathParam("id") String id,
                                            @QueryParam("action") String action) {
-        auth.requireManage();
+        auth.users().requireManage();
 
         ComponentModel model = realm.getComponent(id);
         if (model == null) {
@@ -139,7 +138,7 @@ public class UserStorageProviderResource {
     @Path("{id}/remove-imported-users")
     @NoCache
     public void removeImportedUsers(@PathParam("id") String id) {
-        auth.requireManage();
+        auth.users().requireManage();
 
         ComponentModel model = realm.getComponent(id);
         if (model == null) {
@@ -162,7 +161,7 @@ public class UserStorageProviderResource {
     @Path("{id}/unlink-users")
     @NoCache
     public void unlinkUsers(@PathParam("id") String id) {
-        auth.requireManage();
+        auth.users().requireManage();
 
         ComponentModel model = realm.getComponent(id);
         if (model == null) {
@@ -187,7 +186,7 @@ public class UserStorageProviderResource {
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
     public SynchronizationResult syncMapperData(@PathParam("parentId") String parentId, @PathParam("id") String mapperId, @QueryParam("direction") String direction) {
-        auth.requireManage();
+        auth.users().requireManage();
 
         ComponentModel parentModel = realm.getComponent(parentId);
         if (parentModel == null) throw new NotFoundException("Parent model not found");
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 15315d6..42a2fd8 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -47,6 +47,7 @@ import org.keycloak.services.managers.ApplianceBootstrap;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.UserStorageSyncManager;
 import org.keycloak.services.resources.admin.AdminRoot;
+import org.keycloak.services.scheduled.ClearExpiredClientInitialAccessTokens;
 import org.keycloak.services.scheduled.ClearExpiredEvents;
 import org.keycloak.services.scheduled.ClearExpiredUserSessions;
 import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
@@ -70,8 +71,10 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URL;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.StringTokenizer;
@@ -88,6 +91,8 @@ public class KeycloakApplication extends Application {
 
     public static final String KEYCLOAK_EMBEDDED = "keycloak.embedded";
 
+    public static final String SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES = "keycloak.server.context.config.property-overrides";
+
     private static final Logger logger = Logger.getLogger(KeycloakApplication.class);
 
     protected boolean embedded = false;
@@ -262,7 +267,7 @@ public class KeycloakApplication extends Application {
     public static void loadConfig(ServletContext context) {
         try {
             JsonNode node = null;
-            
+
             String dmrConfig = loadDmrConfig(context);
             if (dmrConfig != null) {
                 node = new ObjectMapper().readTree(dmrConfig);
@@ -287,7 +292,13 @@ public class KeycloakApplication extends Application {
             }
 
             if (node != null) {
-                Properties properties = new SystemEnvProperties();
+                Map<String, String> propertyOverridesMap = new HashMap<>();
+                String propertyOverrides = context.getInitParameter(SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES);
+                if (context.getInitParameter(SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES) != null) {
+                    JsonNode jsonObj = new ObjectMapper().readTree(propertyOverrides);
+                    jsonObj.fields().forEachRemaining(e -> propertyOverridesMap.put(e.getKey(), e.getValue().asText()));
+                }
+                Properties properties = new SystemEnvProperties(propertyOverridesMap);
                 Config.init(new JsonConfigProvider(node, properties));
             } else {
                 throw new RuntimeException("Keycloak config not found.");
@@ -321,6 +332,7 @@ public class KeycloakApplication extends Application {
         try {
             TimerProvider timer = session.getProvider(TimerProvider.class);
             timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredEvents(), interval), interval, "ClearExpiredEvents");
+            timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredClientInitialAccessTokens(), interval), interval, "ClearExpiredClientInitialAccessTokens");
             timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, "ClearExpiredUserSessions");
             new UserStorageSyncManager().bootstrapPeriodic(sessionFactory, timer);
         } finally {
diff --git a/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredClientInitialAccessTokens.java b/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredClientInitialAccessTokens.java
new file mode 100644
index 0000000..94a4f6b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredClientInitialAccessTokens.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017 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.services.scheduled;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.timer.ScheduledTask;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ClearExpiredClientInitialAccessTokens implements ScheduledTask {
+
+    @Override
+    public void run(KeycloakSession session) {
+        session.realms().removeExpiredClientInitialAccess();
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/ServicesLogger.java b/services/src/main/java/org/keycloak/services/ServicesLogger.java
index bd239a6..2d6c473 100644
--- a/services/src/main/java/org/keycloak/services/ServicesLogger.java
+++ b/services/src/main/java/org/keycloak/services/ServicesLogger.java
@@ -406,7 +406,7 @@ public interface ServicesLogger extends BasicLogger {
     void failedToCloseProviderSession(@Cause Throwable t);
 
     @LogMessage(level = WARN)
-    @Message(id=91, value="Request is missing scope 'openid' so it's not treated as OIDC, but just pure OAuth2 request. This can have impact in future versions (eg. removed IDToken from the Token Response)")
+    @Message(id=91, value="Request is missing scope 'openid' so it's not treated as OIDC, but just pure OAuth2 request.")
     @Once
     void oidcScopeMissing();
 
diff --git a/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java b/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
index 3726b99..489f73f 100644
--- a/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
@@ -58,6 +58,7 @@ public class AuthenticationFlowURLHelper {
 
         return session.getProvider(LoginFormsProvider.class)
                 .setActionUri(lastStepUrl)
+                .setExecution(getExecutionId(authSession))
                 .createLoginExpiredPage();
     }
 
@@ -76,7 +77,7 @@ public class AuthenticationFlowURLHelper {
 
 
     public URI getLastExecutionUrl(AuthenticationSessionModel authSession) {
-        String executionId = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+        String executionId = getExecutionId(authSession);
         String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
 
         if (latestFlowPath == null) {
@@ -90,4 +91,8 @@ public class AuthenticationFlowURLHelper {
         return getLastExecutionUrl(latestFlowPath, executionId, authSession.getClient().getClientId());
     }
 
+    private String getExecutionId(AuthenticationSessionModel authSession) {
+        return authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+    }
+
 }
diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties
index 6439950..5f0d60b 100755
--- a/testsuite/integration/src/test/resources/log4j.properties
+++ b/testsuite/integration/src/test/resources/log4j.properties
@@ -91,4 +91,5 @@ log4j.logger.org.apache.directory.server.ldap.LdapProtocolHandler=error
 #log4j.logger.org.keycloak.protocol=debug
 #log4j.logger.org.keycloak.services.resources.LoginActionsService=debug
 #log4j.logger.org.keycloak.services.managers=debug
-#log4j.logger.org.keycloak.services.resources.SessionCodeChecks=debug
\ No newline at end of file
+#log4j.logger.org.keycloak.services.resources.SessionCodeChecks=debug
+#log4j.logger.org.keycloak.authentication=debug
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/HOW-TO-RUN.md b/testsuite/integration-arquillian/HOW-TO-RUN.md
index 10becee..cd818c2 100644
--- a/testsuite/integration-arquillian/HOW-TO-RUN.md
+++ b/testsuite/integration-arquillian/HOW-TO-RUN.md
@@ -45,7 +45,23 @@ and adapter are all in the same JVM and you can debug them easily. If it is not 
    
 and you will be able to attach remote debugger to the test. Unfortunately server and adapter are running in different JVMs, so this won't help to debug those. 
 
-TODO: Improve and add more info about Wildfly debugging...
+### JBoss auth server debugging
+
+When tests are run on JBoss based container (WildFly/EAP) there is possibility to attach a debugger, by default on localhost:5005.
+
+The server won't wait to attach the debugger. There are some properties what can change the default behaviour.
+
+    -Dauth.server.debug.port=$PORT
+    -Dauth.server.debug.suspend=y
+
+More info: http://javahowto.blogspot.cz/2010/09/java-agentlibjdwp-for-attaching.html
+
+### JBoss app server debugging
+
+Analogically, there is the same behaviour for JBoss based app server as for auth server. The default port is set to 5006. There are app server properties.
+
+    -Dapp.server.debug.port=$PORT
+    -Dapp.server.debug.suspend=y    
 
 ## Testsuite logging
 
@@ -417,4 +433,34 @@ and argument: `-p 8181`
 
 3) Run loadbalancer (class `SimpleUndertowLoadBalancer`) without arguments and system properties. Loadbalancer runs on port 8180, so you can access Keycloak on `http://localhost:8180/auth`     
 
+## Cross-DC tests
+
+Cross-DC tests use 2 data centers, each with one automatically started and one manually controlled backend servers
+(currently only Keycloak on Undertow), and 1 frontend loadbalancer server node that sits in front of all servers.
+The browser usually communicates directly with the frontent node and the test controls where the HTTP requests
+land by adjusting load balancer configuration (e.g. to direct the traffic to only a single DC).
+
+For an example of a test, see [org.keycloak.testsuite.crossdc.ActionTokenCrossDCTest](tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java).
+
+The cross DC requires setting a profile specifying used cache server (currently only Infinispan) by specifying
+`cache-server-infinispan` profile in maven.
+
+#### Run Cross-DC Tests from Maven
+
+First compile the Infinispan/JDG test server via the following command:
+
+  `mvn -Pcache-server-infinispan -f testsuite/integration-arquillian -DskipTests clean install`
+
+or
+  
+  `mvn -Pcache-server-jdg -f testsuite/integration-arquillian -DskipTests clean install`
+
+Then you can run the tests using the following command (adjust the test specification according to your needs):
+
+  `mvn -Pcache-server-infinispan -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base test`
+
+or
+
+  `mvn -Pcache-server-jdg -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base test`
 
+_Someone using IntelliJ IDEA, please describe steps for that IDE_
diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml
index 0219f40..7e36d1a 100644
--- a/testsuite/integration-arquillian/pom.xml
+++ b/testsuite/integration-arquillian/pom.xml
@@ -46,6 +46,7 @@
         <arquillian-drone.version>2.0.1.Final</arquillian-drone.version>
         <arquillian-graphene.version>2.1.0.Alpha3</arquillian-graphene.version>
         <arquillian-wildfly-container.version>2.1.0.Alpha2</arquillian-wildfly-container.version>
+        <arquillian-infinispan-container.version>1.2.0.Beta2</arquillian-infinispan-container.version>
         <version.shrinkwrap.resolvers>2.2.2</version.shrinkwrap.resolvers>
         <undertow-embedded.version>1.0.0.Alpha2</undertow-embedded.version>
 
@@ -88,6 +89,11 @@
                 <scope>import</scope>
             </dependency>
             <dependency>
+                <groupId>org.infinispan.arquillian.container</groupId>
+                <artifactId>infinispan-arquillian-impl</artifactId>
+                <version>${arquillian-infinispan-container.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>org.wildfly.arquillian</groupId>
                 <artifactId>wildfly-arquillian-container-managed</artifactId>
                 <version>${arquillian-wildfly-container.version}</version>
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/arquillian/LoadBalancerController.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/arquillian/LoadBalancerController.java
new file mode 100644
index 0000000..1e435b2
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/arquillian/LoadBalancerController.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 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.testsuite.arquillian;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public interface LoadBalancerController {
+
+    void enableAllBackendNodes();
+
+    void disableAllBackendNodes();
+
+    void enableBackendNodeByName(String nodeName);
+
+    void disableBackendNodeByName(String nodeName);
+
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/ExpectedParamAuthenticator.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/ExpectedParamAuthenticator.java
new file mode 100644
index 0000000..08b475f
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/ExpectedParamAuthenticator.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 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.testsuite.authentication;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ExpectedParamAuthenticator implements Authenticator {
+
+    public static final String EXPECTED_VALUE = "expected_value";
+
+    public static final String LOGGED_USER = "logged_user";
+
+
+    private static final Logger logger = Logger.getLogger(ExpectedParamAuthenticator.class);
+
+    @Override
+    public void authenticate(AuthenticationFlowContext context) {
+        String paramValue = context.getAuthenticationSession().getClientNote(AuthorizationEndpoint.LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + "foo");
+        String expectedValue = context.getAuthenticatorConfig().getConfig().get(EXPECTED_VALUE);
+        logger.info("Value: " + paramValue + ", expectedValue: " + expectedValue);
+
+        if (paramValue != null && paramValue.equals(expectedValue)) {
+
+            String loggedUser = context.getAuthenticatorConfig().getConfig().get(LOGGED_USER);
+            if (loggedUser == null) {
+                logger.info("Successfully authenticated, but don't set any authenticated user");
+            } else {
+                UserModel user = context.getSession().users().getUserByUsername(loggedUser, context.getRealm());
+                logger.info("Successfully authenticated as user " + user.getUsername());
+                context.setUser(user);
+            }
+
+            context.success();
+        } else {
+            context.attempted();
+        }
+    }
+
+    @Override
+    public void action(AuthenticationFlowContext context) {
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return true;
+    }
+
+    @Override
+    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+
+    }
+
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/ExpectedParamAuthenticatorFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/ExpectedParamAuthenticatorFactory.java
new file mode 100644
index 0000000..2a10ab1
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/ExpectedParamAuthenticatorFactory.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017 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.testsuite.authentication;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.ConfigurableAuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ExpectedParamAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory {
+
+    public static final String PROVIDER_ID = "expected-param-authenticator";
+
+    private static final ExpectedParamAuthenticator SINGLETON = new ExpectedParamAuthenticator();
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return SINGLETON;
+    }
+
+    private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.ALTERNATIVE,
+            AuthenticationExecutionModel.Requirement.DISABLED
+    };
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
+    public boolean isUserSetupAllowed() {
+        return false;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return true;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "You will be approved if you send query string parameter 'foo' with expected value.";
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "TEST: Expected Parameter";
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return "Expected Parameter";
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+    static {
+        ProviderConfigProperty property;
+        property = new ProviderConfigProperty();
+        property.setName(ExpectedParamAuthenticator.EXPECTED_VALUE);
+        property.setLabel("Expected query parameter value");
+        property.setType(ProviderConfigProperty.STRING_TYPE);
+        property.setHelpText("Expected value of query parameter foo. Authenticator will success if request to OIDC authz endpoint has this parameter");
+        configProperties.add(property);
+
+        property = new ProviderConfigProperty();
+        property.setName(ExpectedParamAuthenticator.LOGGED_USER);
+        property.setLabel("Automatically logged user");
+        property.setType(ProviderConfigProperty.STRING_TYPE);
+        property.setHelpText("This user will be successfully authenticated automatically when present");
+        configProperties.add(property);
+    }
+
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+
+
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/PushButtonAuthenticator.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/PushButtonAuthenticator.java
new file mode 100644
index 0000000..bb7dcd3
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/PushButtonAuthenticator.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017 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.testsuite.authentication;
+
+import javax.ws.rs.core.Response;
+
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PushButtonAuthenticator implements Authenticator {
+
+    @Override
+    public void authenticate(AuthenticationFlowContext context) {
+        String accessCode = context.generateAccessCode();
+        String actionUrl = context.getActionUrl(accessCode).toString();
+
+        StringBuilder response = new StringBuilder("<html><head><title>PushTheButton</title></head><body>");
+
+        UserModel user = context.getUser();
+        if (user == null) {
+            response.append("No authenticated user<br>");
+        } else {
+            response.append("Authenticated user: " + user.getUsername() + "<br>");
+        }
+
+        response.append("<form method='POST' action='" + actionUrl + "'>");
+        response.append(" This is the Test Approver. Press login to continue.<br>");
+        response.append(" <input type='submit' name='submit1' value='Submit' />");
+        response.append("</form></body></html>");
+        String html = response.toString();
+
+        Response jaxrsResponse = Response
+                .status(Response.Status.OK)
+                .type("text/html")
+                .entity(html)
+                .build();
+
+        context.challenge(jaxrsResponse);
+
+//        Response challenge = context.form().createForm("login-approve.ftl");
+//        context.challenge(challenge);
+    }
+
+    @Override
+    public void action(AuthenticationFlowContext context) {
+        context.success();
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return false;
+    }
+
+    @Override
+    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+
+    }
+
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/PushButtonAuthenticatorFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/PushButtonAuthenticatorFactory.java
new file mode 100644
index 0000000..84b177b
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authentication/PushButtonAuthenticatorFactory.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2017 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.testsuite.authentication;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.ConfigurableAuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class PushButtonAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory {
+
+    public static final String PROVIDER_ID = "push-button-authenticator";
+    private static final PushButtonAuthenticator SINGLETON = new PushButtonAuthenticator();
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return SINGLETON;
+    }
+
+    private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.ALTERNATIVE,
+            AuthenticationExecutionModel.Requirement.DISABLED
+    };
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
+    public boolean isUserSetupAllowed() {
+        return false;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Just press the button to login.";
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "TEST: Button Login";
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return "Button Login";
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+
+
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index b868827..f18bbbd 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -164,6 +164,8 @@ public class TestingResourceProvider implements RealmResourceProvider {
 
         session.sessions().removeExpired(realm);
         session.authenticationSessions().removeExpired(realm);
+        session.realms().removeExpiredClientInitialAccess();
+
         return Response.ok().build();
     }
 
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
index 660f91f..18317d0 100755
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -17,4 +17,6 @@
 
 org.keycloak.testsuite.forms.PassThroughAuthenticator
 org.keycloak.testsuite.forms.PassThroughRegistration
-org.keycloak.testsuite.forms.ClickThroughAuthenticator
\ No newline at end of file
+org.keycloak.testsuite.forms.ClickThroughAuthenticator
+org.keycloak.testsuite.authentication.ExpectedParamAuthenticatorFactory
+org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
index 4298d35..fdcb092 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
@@ -71,6 +71,11 @@
             <artifactId>keycloak-undertow-adapter</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.keycloak.testsuite</groupId>
+            <artifactId>integration-arquillian-testsuite-providers</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-servlet-filter-adapter</artifactId>
         </dependency>
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 87a37d2..b77d9e0 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
@@ -48,6 +48,8 @@ import org.keycloak.services.filters.KeycloakSessionServletFilter;
 import org.keycloak.services.managers.ApplianceBootstrap;
 import org.keycloak.services.resources.KeycloakApplication;
 
+import org.keycloak.util.JsonSerialization;
+import java.io.IOException;
 import javax.servlet.DispatcherType;
 import javax.servlet.ServletException;
 
@@ -55,6 +57,7 @@ import java.lang.reflect.Field;
 import java.util.Collection;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
 
 public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUndertowConfiguration> {
 
@@ -75,6 +78,14 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
         di.setContextPath("/auth");
         di.setDeploymentName("Keycloak");
         di.addInitParameter(KeycloakApplication.KEYCLOAK_EMBEDDED, "true");
+        if (configuration.getKeycloakConfigPropertyOverridesMap() != null) {
+            try {
+                di.addInitParameter(KeycloakApplication.SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES,
+                  JsonSerialization.writeValueAsString(configuration.getKeycloakConfigPropertyOverridesMap()));
+            } catch (IOException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
 
         di.setDefaultServletConfig(new DefaultServletConfig(true));
         di.addWelcomePage("theme/keycloak/welcome/resources/index.html");
@@ -176,19 +187,14 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
             log.info("Using route: " + configuration.getRoute());
         }
 
-        SetSystemProperty setRouteProperty = new SetSystemProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME, configuration.getRoute());
-        try {
-            DeploymentInfo di = createAuthServerDeploymentInfo();
-            undertow.deploy(di);
-            ResteasyDeployment deployment = (ResteasyDeployment) di.getServletContextAttributes().get(ResteasyDeployment.class.getName());
-            sessionFactory = ((KeycloakApplication) deployment.getApplication()).getSessionFactory();
+        DeploymentInfo di = createAuthServerDeploymentInfo();
+        undertow.deploy(di);
+        ResteasyDeployment deployment = (ResteasyDeployment) di.getServletContextAttributes().get(ResteasyDeployment.class.getName());
+        sessionFactory = ((KeycloakApplication) deployment.getApplication()).getSessionFactory();
 
-            setupDevConfig();
+        setupDevConfig();
 
-            log.info("Auth server started in " + (System.currentTimeMillis() - start) + " ms\n");
-        } finally {
-            setRouteProperty.revert();
-        }
+        log.info("Auth server started in " + (System.currentTimeMillis() - start) + " ms\n");
     }
 
 
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertowConfiguration.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertowConfiguration.java
index bdf0ff7..6caca66 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertowConfiguration.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertowConfiguration.java
@@ -17,6 +17,11 @@
 
 package org.keycloak.testsuite.arquillian.undertow;
 
+import org.keycloak.util.JsonSerialization;
+import com.fasterxml.jackson.core.type.TypeReference;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
 import org.arquillian.undertow.UndertowContainerConfiguration;
 import org.jboss.arquillian.container.spi.ConfigurationException;
 import org.jboss.logging.Logger;
@@ -29,6 +34,8 @@ public class KeycloakOnUndertowConfiguration extends UndertowContainerConfigurat
     private String resourcesHome;
     private boolean remoteMode;
     private String route;
+    private String keycloakConfigPropertyOverrides;
+    private Map<String, String> keycloakConfigPropertyOverridesMap;
 
     private int bindHttpPortOffset = 0;
 
@@ -72,6 +79,18 @@ public class KeycloakOnUndertowConfiguration extends UndertowContainerConfigurat
         this.remoteMode = remoteMode;
     }
 
+    public String getKeycloakConfigPropertyOverrides() {
+        return keycloakConfigPropertyOverrides;
+    }
+
+    public void setKeycloakConfigPropertyOverrides(String keycloakConfigPropertyOverrides) {
+        this.keycloakConfigPropertyOverrides = keycloakConfigPropertyOverrides;
+    }
+
+    public Map<String, String> getKeycloakConfigPropertyOverridesMap() {
+        return keycloakConfigPropertyOverridesMap;
+    }
+
     @Override
     public void validate() throws ConfigurationException {
         super.validate();
@@ -80,6 +99,15 @@ public class KeycloakOnUndertowConfiguration extends UndertowContainerConfigurat
         int newPort = basePort + bindHttpPortOffset;
         setBindHttpPort(newPort);
         log.info("KeycloakOnUndertow will listen on port: " + newPort);
+        
+        if (this.keycloakConfigPropertyOverrides != null) {
+            try {
+                TypeReference<HashMap<String,Object>> typeRef = new TypeReference<HashMap<String,Object>>() {};
+                this.keycloakConfigPropertyOverridesMap = JsonSerialization.sysPropertiesAwareMapper.readValue(this.keycloakConfigPropertyOverrides, typeRef);
+            } catch (IOException ex) {
+                throw new ConfigurationException(ex);
+            }
+        }
 
         // TODO validate workerThreads
         
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java
index 3eda20c..3b533f1 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java
@@ -18,7 +18,6 @@
 package org.keycloak.testsuite.arquillian.undertow.lb;
 
 import java.net.URI;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
@@ -36,6 +35,8 @@ import io.undertow.util.AttachmentKey;
 import io.undertow.util.Headers;
 import org.jboss.logging.Logger;
 import org.keycloak.services.managers.AuthenticationSessionManager;
+import java.util.LinkedHashMap;
+import java.util.StringTokenizer;
 
 /**
  * Loadbalancer on embedded undertow. Supports sticky session over "AUTH_SESSION_ID" cookie and failover to different node when sticky node not available.
@@ -53,8 +54,9 @@ public class SimpleUndertowLoadBalancer {
 
     private final String host;
     private final int port;
-    private final String nodesString;
+    private final Map<String, URI> backendNodes;
     private Undertow undertow;
+    private LoadBalancingProxyClient lb;
 
 
     public static void main(String[] args) throws Exception {
@@ -77,15 +79,14 @@ public class SimpleUndertowLoadBalancer {
     public SimpleUndertowLoadBalancer(String host, int port, String nodesString) {
         this.host = host;
         this.port = port;
-        this.nodesString = nodesString;
-        log.infof("Keycloak nodes: %s", nodesString);
+        this.backendNodes = parseNodes(nodesString);
+        log.infof("Keycloak nodes: %s", backendNodes);
     }
 
 
     public void start() {
-        Map<String, String> nodes = parseNodes(nodesString);
         try {
-            HttpHandler proxyHandler = createHandler(nodes);
+            HttpHandler proxyHandler = createHandler();
 
             undertow = Undertow.builder()
                     .addHttpListener(port, host)
@@ -104,24 +105,51 @@ public class SimpleUndertowLoadBalancer {
         undertow.stop();
     }
 
+    public void enableAllBackendNodes() {
+        backendNodes.forEach((route, uri) -> {
+            lb.removeHost(uri);
+            lb.addHost(uri, route);
+        });
+    }
 
-    static Map<String, String> parseNodes(String nodes) {
-        String[] nodesArray = nodes.split(",");
-        Map<String, String> result = new HashMap<>();
+    public void disableAllBackendNodes() {
+        log.debugf("Load balancer: disabling all nodes");
+        backendNodes.values().forEach(lb::removeHost);
+    }
 
-        for (String nodeStr : nodesArray) {
-            String[] node = nodeStr.trim().split("=");
-            if (node.length != 2) {
-                throw new IllegalArgumentException("Illegal node format in the configuration: " + nodeStr);
-            }
-            result.put(node[0].trim(), node[1].trim());
+    public void enableBackendNodeByName(String nodeName) {
+        URI uri = backendNodes.get(nodeName);
+        if (uri == null) {
+            throw new IllegalArgumentException("Invalid node: " + nodeName);
+        }
+        log.debugf("Load balancer: enabling node %s", nodeName);
+        lb.addHost(uri, nodeName);
+    }
+
+    public void disableBackendNodeByName(String nodeName) {
+        URI uri = backendNodes.get(nodeName);
+        if (uri == null) {
+            throw new IllegalArgumentException("Invalid node: " + nodeName);
+        }
+        log.debugf("Load balancer: disabling node %s", nodeName);
+        lb.removeHost(uri);
+    }
+
+    static Map<String, URI> parseNodes(String nodes) {
+        StringTokenizer st = new StringTokenizer(nodes, ",");
+        Map<String, URI> result = new LinkedHashMap<>();
+
+        while (st.hasMoreElements()) {
+            String nodeStr = st.nextToken();
+            String[] node = nodeStr.trim().split("=", 2);
+            result.put(node[0].trim(), URI.create(node[1].trim()));
         }
 
         return result;
     }
 
 
-    private HttpHandler createHandler(Map<String, String> backendNodes) throws Exception {
+    private HttpHandler createHandler() throws Exception {
 
         // TODO: configurable options if needed
         String sessionCookieNames = AuthenticationSessionManager.AUTH_SESSION_ID;
@@ -133,15 +161,7 @@ public class SimpleUndertowLoadBalancer {
         int connectionIdleTimeout = 60;
         int maxRetryAttempts = backendNodes.size() - 1;
 
-        final LoadBalancingProxyClient lb = new CustomLoadBalancingClient(new ExclusivityChecker() {
-
-            @Override
-            public boolean isExclusivityRequired(HttpServerExchange exchange) {
-                //we always create a new connection for upgrade requests
-                return exchange.getRequestHeaders().contains(Headers.UPGRADE);
-            }
-
-        }, maxRetryAttempts)
+        lb = new CustomLoadBalancingClient(exchange -> exchange.getRequestHeaders().contains(Headers.UPGRADE), maxRetryAttempts)
                 .setConnectionsPerThread(connectionsPerThread)
                 .setMaxQueueSize(requestQueueSize)
                 .setSoftMaxConnectionsPerThread(cachedConnectionsPerThread)
@@ -152,13 +172,10 @@ public class SimpleUndertowLoadBalancer {
             lb.addSessionCookieName(id);
         }
 
-        for (Map.Entry<String, String> node : backendNodes.entrySet()) {
-            String route = node.getKey();
-            URI uri = new URI(node.getValue());
-
+        backendNodes.forEach((route, uri) -> {
             lb.addHost(uri, route);
-            log.infof("Added host: %s, route: %s", uri.toString(), route);
-        }
+            log.debugf("Added host: %s, route: %s", uri.toString(), route);
+        });
 
         ProxyHandler handler = new ProxyHandler(lb, maxTime, ResponseCodeHandler.HANDLE_404);
         return handler;
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerConfiguration.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerConfiguration.java
index 3a0312c..12d3564 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerConfiguration.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerConfiguration.java
@@ -19,13 +19,17 @@ package org.keycloak.testsuite.arquillian.undertow.lb;
 
 import org.arquillian.undertow.UndertowContainerConfiguration;
 import org.jboss.arquillian.container.spi.ConfigurationException;
+import org.jboss.logging.Logger;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class SimpleUndertowLoadBalancerConfiguration extends UndertowContainerConfiguration {
 
+    protected static final Logger log = Logger.getLogger(SimpleUndertowLoadBalancerConfiguration.class);
+
     private String nodes = SimpleUndertowLoadBalancer.DEFAULT_NODES;
+    private int bindHttpPortOffset = 0;
 
     public String getNodes() {
         return nodes;
@@ -35,6 +39,14 @@ public class SimpleUndertowLoadBalancerConfiguration extends UndertowContainerCo
         this.nodes = nodes;
     }
 
+    public int getBindHttpPortOffset() {
+        return bindHttpPortOffset;
+    }
+
+    public void setBindHttpPortOffset(int bindHttpPortOffset) {
+        this.bindHttpPortOffset = bindHttpPortOffset;
+    }
+
     @Override
     public void validate() throws ConfigurationException {
         super.validate();
@@ -44,5 +56,11 @@ public class SimpleUndertowLoadBalancerConfiguration extends UndertowContainerCo
         } catch (Exception e) {
             throw new ConfigurationException(e);
         }
+
+        int basePort = getBindHttpPort();
+        int newPort = basePort + bindHttpPortOffset;
+        setBindHttpPort(newPort);
+        log.info("SimpleUndertowLoadBalancer will listen on port: " + newPort);
+
     }
 }
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerContainer.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerContainer.java
index 4b24c15..a7f1ffe 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerContainer.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerContainer.java
@@ -25,13 +25,14 @@ import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaD
 import org.jboss.logging.Logger;
 import org.jboss.shrinkwrap.api.Archive;
 import org.jboss.shrinkwrap.descriptor.api.Descriptor;
+import org.keycloak.testsuite.arquillian.LoadBalancerController;
 
 /**
  * Arquillian container over {@link SimpleUndertowLoadBalancer}
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-public class SimpleUndertowLoadBalancerContainer implements DeployableContainer<SimpleUndertowLoadBalancerConfiguration> {
+public class SimpleUndertowLoadBalancerContainer implements DeployableContainer<SimpleUndertowLoadBalancerConfiguration>, LoadBalancerController {
 
     private static final Logger log = Logger.getLogger(SimpleUndertowLoadBalancerContainer.class);
 
@@ -84,4 +85,24 @@ public class SimpleUndertowLoadBalancerContainer implements DeployableContainer<
     public void undeploy(Descriptor descriptor) throws DeploymentException {
         throw new UnsupportedOperationException("Not implemented");
     }
+
+    @Override
+    public void enableAllBackendNodes() {
+        this.container.enableAllBackendNodes();
+    }
+
+    @Override
+    public void disableAllBackendNodes() {
+        this.container.disableAllBackendNodes();
+    }
+
+    @Override
+    public void enableBackendNodeByName(String nodeName) {
+        this.container.enableBackendNodeByName(nodeName);
+    }
+
+    @Override
+    public void disableBackendNodeByName(String nodeName) {
+        this.container.disableBackendNodeByName(nodeName);
+    }
 }
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SetSystemProperty.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SetSystemProperty.java
index 86a7e2e..e64b1b2 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SetSystemProperty.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SetSystemProperty.java
@@ -17,13 +17,15 @@
 
 package org.keycloak.testsuite.arquillian.undertow;
 
+import java.io.Closeable;
+
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-class SetSystemProperty {
+class SetSystemProperty implements Closeable {
 
-    private String name;
-    private String oldValue;
+    private final String name;
+    private final String oldValue;
 
     public SetSystemProperty(String name, String value) {
         this.name = name;
@@ -50,4 +52,9 @@ class SetSystemProperty {
         }
     }
 
+    @Override
+    public void close() {
+        revert();
+    }
+
 }
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/assembly.xml b/testsuite/integration-arquillian/servers/cache-server/jboss/assembly.xml
new file mode 100644
index 0000000..d623853
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/assembly.xml
@@ -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.
+  -->
+
+<assembly>
+    
+    <id>${cache.server.jboss}</id>
+    
+    <formats>
+        <format>zip</format>
+    </formats>
+
+    <includeBaseDirectory>false</includeBaseDirectory>
+
+    <fileSets>
+        <fileSet>
+            <directory>${cache.server.jboss.home}</directory>
+            <outputDirectory>cache-server-${cache.server}</outputDirectory>
+            <excludes>
+                <exclude>**/*.sh</exclude>
+            </excludes>
+        </fileSet>
+        <fileSet>
+            <directory>${cache.server.jboss.home}</directory>
+            <outputDirectory>cache-server-${cache.server}</outputDirectory>
+            <includes>
+                <include>**/*.sh</include>
+            </includes>
+            <fileMode>0755</fileMode>
+        </fileSet>
+    </fileSets>
+
+</assembly>
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/common/add-keycloak-caches.xsl b/testsuite/integration-arquillian/servers/cache-server/jboss/common/add-keycloak-caches.xsl
new file mode 100644
index 0000000..540b4b5
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/common/add-keycloak-caches.xsl
@@ -0,0 +1,52 @@
+<!--
+  ~ 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.
+  -->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                version="2.0"
+                exclude-result-prefixes="xalan">
+
+    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+    <xsl:strip-space elements="*"/>
+
+    <xsl:variable name="nsCacheServer" select="'urn:infinispan:server:core:'"/>
+
+    <xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsCacheServer)]
+                        /*[local-name()='cache-container' and starts-with(namespace-uri(), $nsCacheServer) and @name='local']">
+        <xsl:copy>
+            <xsl:apply-templates select="@* | node()" />
+            <local-cache name="work" start="EAGER" batching="false" />
+            <local-cache name="actionTokens" start="EAGER" batching="false" />
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsCacheServer)]
+                        /*[local-name()='cache-container' and starts-with(namespace-uri(), $nsCacheServer) and @name='clustered']">
+        <xsl:copy>
+            <xsl:apply-templates select="@* | node()" />
+            <replicated-cache name="work" start="EAGER" batching="false" />
+            <replicated-cache name="actionTokens" start="EAGER" batching="false" />
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="@*|node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@*|node()" />
+        </xsl:copy>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/common/io.xsl b/testsuite/integration-arquillian/servers/cache-server/jboss/common/io.xsl
new file mode 100644
index 0000000..03d518a
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/common/io.xsl
@@ -0,0 +1,40 @@
+<!--
+~ 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.
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                version="2.0"
+                exclude-result-prefixes="xalan">
+
+    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+    <xsl:strip-space elements="*"/>
+
+    <xsl:param name="worker.io-threads" select="'16'"/>
+    <xsl:param name="worker.task-max-threads" select="'128'"/>
+    
+    <!--set worker threads-->
+    <xsl:template match="//*[local-name()='worker' and @name='default']">
+        <worker name="default" io-threads="{$worker.io-threads}" task-max-threads="{$worker.task-max-threads}" />
+    </xsl:template>
+
+    <xsl:template match="@*|node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@*|node()" />
+        </xsl:copy>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/pom.xml b/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/pom.xml
new file mode 100644
index 0000000..3ac23ac
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.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.
+-->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <parent>
+        <groupId>org.keycloak.testsuite</groupId>
+        <artifactId>integration-arquillian-servers-cache-server-jboss</artifactId>
+        <version>3.2.0.CR1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>integration-arquillian-servers-cache-server-infinispan</artifactId>
+    <packaging>pom</packaging>
+    <name>Cache Server - JBoss - Infinispan</name>
+    
+    <properties>
+        <cache.server>infinispan</cache.server>
+        <cache.server.container>cache-server-${cache.server}</cache.server.container>
+        <cache.server.home>${containers.home}/${cache.server.container}</cache.server.home>
+        
+        <cache.server.jboss.groupId>org.infinispan.server</cache.server.jboss.groupId>
+        <cache.server.jboss.artifactId>infinispan-server</cache.server.jboss.artifactId>
+        <cache.server.jboss.version>${infinispan.version}</cache.server.jboss.version>
+        <cache.server.jboss.unpacked.folder.name>${cache.server.jboss.artifactId}-${infinispan.version}</cache.server.jboss.unpacked.folder.name>
+        
+        <cache.server.worker.io-threads>${cache.default.worker.io-threads}</cache.server.worker.io-threads>
+        <cache.server.worker.task-max-threads>${cache.default.worker.task-max-threads}</cache.server.worker.task-max-threads>
+    </properties>
+
+</project>
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/src/.dont-delete b/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/src/.dont-delete
new file mode 100644
index 0000000..c969aca
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/src/.dont-delete
@@ -0,0 +1 @@
+This file is to mark this Maven project as a valid option for building cache server artifact
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/pom.xml b/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/pom.xml
new file mode 100644
index 0000000..8fa5902
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.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.
+-->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <parent>
+        <groupId>org.keycloak.testsuite</groupId>
+        <artifactId>integration-arquillian-servers-cache-server-jboss</artifactId>
+        <version>3.2.0.CR1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>integration-arquillian-servers-cache-server-jdg</artifactId>
+    <packaging>pom</packaging>
+    <name>Cache Server - JDG</name>
+    
+    <properties>
+        <cache.server>jdg</cache.server>
+        <cache.server.container>cache-server-${cache.server}</cache.server.container>
+        <cache.server.home>${containers.home}/${cache.server.container}</cache.server.home>
+        
+        <cache.server.jboss.groupId>org.infinispan.server</cache.server.jboss.groupId>
+        <cache.server.jboss.artifactId>infinispan-server</cache.server.jboss.artifactId>
+        <cache.server.jboss.version>${jdg.version}</cache.server.jboss.version>
+        <cache.server.jboss.unpacked.folder.name>${cache.server.jboss.artifactId}-${jdg.version}</cache.server.jboss.unpacked.folder.name>
+        
+        <cache.server.worker.io-threads>${cache.default.worker.io-threads}</cache.server.worker.io-threads>
+        <cache.server.worker.task-max-threads>${cache.default.worker.task-max-threads}</cache.server.worker.task-max-threads>
+    </properties>
+
+</project>
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/src/.dont-delete b/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/src/.dont-delete
new file mode 100644
index 0000000..c969aca
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/src/.dont-delete
@@ -0,0 +1 @@
+This file is to mark this Maven project as a valid option for building cache server artifact
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/cache-server/jboss/pom.xml
new file mode 100644
index 0000000..9c2d1f9
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/pom.xml
@@ -0,0 +1,234 @@
+<?xml version="1.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.
+-->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <parent>
+        <groupId>org.keycloak.testsuite</groupId>
+        <artifactId>integration-arquillian-servers-cache-server</artifactId>
+        <version>3.2.0.CR1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>integration-arquillian-servers-cache-server-jboss</artifactId>
+    <packaging>pom</packaging>
+    <name>Cache Server - JBoss Family</name>
+
+    <properties>
+        <common.resources>${project.parent.basedir}/common</common.resources>
+        <assembly.xml>${project.parent.basedir}/assembly.xml</assembly.xml>
+        <cache.server.jboss.home>${containers.home}/${cache.server.jboss.unpacked.folder.name}</cache.server.jboss.home>
+        <security.xslt>security.xsl</security.xslt>
+    </properties>
+
+    <profiles>
+        
+        <profile>
+            <id>cache-server-jboss-submodules</id>
+            <activation>
+                <file>
+                    <exists>src</exists>
+                </file>
+            </activation>
+            <build>
+                <plugins>
+                    
+                    <plugin>
+                        <artifactId>maven-enforcer-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>enforce</goal>
+                                </goals>
+                                <configuration>
+                                    <rules>
+                                        <requireProperty>
+                                            <property>cache.server</property>
+                                            <property>cache.server.jboss.groupId</property>
+                                            <property>cache.server.jboss.artifactId</property>
+                                            <property>cache.server.jboss.version</property>
+                                            <property>cache.server.jboss.unpacked.folder.name</property>
+                                        </requireProperty>
+                                    </rules>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    
+                    <plugin>
+                        <artifactId>maven-dependency-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>unpack-cache-server</id>
+                                <phase>generate-resources</phase>
+                                <goals>
+                                    <goal>unpack</goal>
+                                </goals>
+                                <configuration>
+                                    <artifactItems>
+                                        <artifactItem>
+                                            <groupId>${cache.server.jboss.groupId}</groupId>
+                                            <artifactId>${cache.server.jboss.artifactId}</artifactId>
+                                            <version>${cache.server.jboss.version}</version>
+                                            <type>zip</type>
+                                            <classifier>bin</classifier>
+                                            <outputDirectory>${containers.home}</outputDirectory>
+                                        </artifactItem>
+                                    </artifactItems>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+            
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>xml-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>configure-keycloak-caches</id>
+                                <phase>process-test-resources</phase>
+                                <goals>
+                                    <goal>transform</goal>
+                                </goals>
+                                <configuration>
+                                    <transformationSets>
+                                        <transformationSet>
+                                            <dir>${cache.server.jboss.home}/standalone/configuration</dir>
+                                            <includes>
+                                                <include>standalone.xml</include>
+                                                <include>clustered.xml</include>
+                                            </includes>
+                                            <stylesheet>${common.resources}/add-keycloak-caches.xsl</stylesheet>
+                                            <outputDir>${cache.server.jboss.home}/standalone/configuration</outputDir>
+                                        </transformationSet>
+                                    </transformationSets>
+                                </configuration>
+                            </execution>
+                            <execution>
+                                <id>io-worker-threads</id>
+                                <phase>process-resources</phase>
+                                <goals>
+                                    <goal>transform</goal>
+                                </goals>
+                                <configuration>
+                                    <transformationSets>
+                                        <transformationSet>
+                                            <dir>${cache.server.jboss.home}/standalone/configuration</dir>
+                                            <includes>
+                                                <include>standalone.xml</include>
+                                                <include>standalone-ha.xml</include>
+                                            </includes>
+                                            <stylesheet>${common.resources}/io.xsl</stylesheet>
+                                            <outputDir>${cache.server.jboss.home}/standalone/configuration</outputDir>
+                                            <parameters>
+                                                <parameter>
+                                                    <name>worker.io-threads</name>
+                                                    <value>${cache.server.worker.io-threads}</value>
+                                                </parameter>
+                                                <parameter>
+                                                    <name>worker.task-max-threads</name>
+                                                    <value>${cache.server.worker.task-max-threads}</value>
+                                                </parameter>
+                                            </parameters>
+                                        </transformationSet>
+                                    </transformationSets>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    
+                    <plugin>
+                        <artifactId>maven-resources-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>enable-jboss-mgmt-admin</id>
+                                <phase>process-resources</phase>
+                                <goals>
+                                    <goal>copy-resources</goal>
+                                </goals>
+                                <configuration>
+                                    <outputDirectory>${cache.server.jboss.home}/standalone/configuration</outputDirectory>
+                                    <resources>
+                                        <resource>
+                                            <directory>${common.resources}</directory>
+                                            <includes>
+                                                <include>mgmt-users.properties</include>
+                                            </includes>
+                                        </resource>
+                                    </resources>
+                                    <overwrite>true</overwrite>
+                                </configuration>
+                            </execution>
+                            <execution>
+                                <id>copy-cache-server-configuration-for-dc-2</id>
+                                <phase>process-resources</phase>
+                                <goals>
+                                    <goal>copy-resources</goal>
+                                </goals>
+                                <configuration>
+                                    <outputDirectory>${cache.server.jboss.home}/standalone-dc-2/deployments</outputDirectory>
+                                    <includeEmptyDirs>true</includeEmptyDirs>
+                                    <resources>
+                                        <resource>
+                                            <directory>${cache.server.jboss.home}/standalone/deployments</directory>
+                                        </resource>
+                                    </resources>
+                                    <overwrite>true</overwrite>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                    <plugin>
+                        <artifactId>maven-assembly-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>create-zip</id>
+                                <phase>package</phase>
+                                <goals>
+                                    <goal>single</goal>
+                                </goals>
+                                <configuration>
+                                    <descriptors>
+                                        <descriptor>${assembly.xml}</descriptor>
+                                    </descriptors>
+                                    <appendAssemblyId>false</appendAssemblyId>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    
+                </plugins>
+            </build>
+        </profile>
+        
+        <profile>
+            <id>cache-server-infinispan</id>
+            <modules>
+                <module>infinispan</module>
+            </modules>
+        </profile>
+        <profile>
+            <id>cache-server-jdg</id>
+            <modules>
+                <module>jdg</module>
+            </modules>
+        </profile>
+    </profiles>
+
+</project>
diff --git a/testsuite/integration-arquillian/servers/cache-server/pom.xml b/testsuite/integration-arquillian/servers/cache-server/pom.xml
new file mode 100644
index 0000000..3f5a5a0
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.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.
+-->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <parent>
+        <groupId>org.keycloak.testsuite</groupId>
+        <artifactId>integration-arquillian-servers</artifactId>
+        <version>3.2.0.CR1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>integration-arquillian-servers-cache-server</artifactId>
+    <packaging>pom</packaging>
+    <name>Cache Server</name>
+
+    <properties>
+        <auth.server.worker.io-threads>${jboss.default.worker.io-threads}</auth.server.worker.io-threads>
+        <auth.server.worker.task-max-threads>${jboss.default.worker.task-max-threads}</auth.server.worker.task-max-threads>
+    </properties>
+
+    <modules>
+        <module>jboss</module>
+    </modules>
+
+</project>
diff --git a/testsuite/integration-arquillian/servers/pom.xml b/testsuite/integration-arquillian/servers/pom.xml
index 96d6c18..0b7e899 100644
--- a/testsuite/integration-arquillian/servers/pom.xml
+++ b/testsuite/integration-arquillian/servers/pom.xml
@@ -46,13 +46,21 @@
         <!--<fuse62.version>6.2.0.redhat-133</fuse62.version>-->
         <fuse62.version>6.2.1.redhat-084</fuse62.version>
         
+        <!-- cache server versions -->
+        <infinispan.version>9.0.1.Final</infinispan.version>
+        <jdg.version>8.4.0.Final-redhat-2</jdg.version><!-- JDG 7.1.0 -->
+        
         <jboss.default.worker.io-threads>16</jboss.default.worker.io-threads>
         <jboss.default.worker.task-max-threads>128</jboss.default.worker.task-max-threads>
+
+        <cache.default.worker.io-threads>2</cache.default.worker.io-threads>
+        <cache.default.worker.task-max-threads>4</cache.default.worker.task-max-threads>
     </properties>
 
     <modules>
         <module>auth-server</module>
         <module>app-server</module>
+        <module>cache-server</module>
     </modules>
 
     <profiles>
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index 7c52025..caa76aa 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -40,6 +40,7 @@
         <exclude.client>-</exclude.client>
         <!--exclude cluster tests by default, enabled by 'auth-server-*-cluster' profiles in tests/pom.xml-->
         <exclude.cluster>**/cluster/**/*Test.java</exclude.cluster>
+        <exclude.crossdc>**/crossdc/**/*Test.java</exclude.crossdc>
         <!-- exclude x509 tests by default, enabled by 'ssl' profile -->
         <exclude.x509>**/x509/*Test.java</exclude.x509>
         <!-- exclude undertow adapter tests. They can be added by -Dtest=org.keycloak.testsuite.adapter.undertow.**.*Test -->
@@ -125,6 +126,7 @@
                         <exclude>${exclude.account}</exclude>
                         <exclude>${exclude.client}</exclude>
                         <exclude>${exclude.cluster}</exclude>
+                        <exclude>${exclude.crossdc}</exclude>
                         <exclude>${exclude.undertow.adapter}</exclude>
                         <exclude>${exclude.x509}</exclude>
                     </excludes>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostAutodetectServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostAutodetectServlet.java
new file mode 100644
index 0000000..9e7e3b7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostAutodetectServlet.java
@@ -0,0 +1,39 @@
+/*
+ * 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.testsuite.adapter.page;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+
+import java.net.URL;
+
+/**
+ * @author mhajas
+ */
+public class SalesPostAutodetectServlet extends SAMLServlet {
+    public static final String DEPLOYMENT_NAME = "sales-post-autodetect";
+
+    @ArquillianResource
+    @OperateOnDeployment(DEPLOYMENT_NAME)
+    private URL url;
+
+    @Override
+    public URL getInjectedUrl() {
+        return url;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/JmxInfinispanCacheStatistics.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/JmxInfinispanCacheStatistics.java
new file mode 100644
index 0000000..2dd7bbc
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/JmxInfinispanCacheStatistics.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 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.testsuite.arquillian.annotation;
+
+import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
+import org.keycloak.testsuite.arquillian.InfinispanStatistics;
+import org.keycloak.testsuite.arquillian.InfinispanStatistics.Constants;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for a field / method parameter annotating {@link InfinispanStatistics} object that would be used
+ * to access statistics via JMX. By default, the access to "work" cache at remote infinispan / JDG server is requested
+ * yet the same annotation is used for other caches as well.
+ *
+ * @author hmlnarik
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+public @interface JmxInfinispanCacheStatistics {
+
+    /** JMX domain. Should be set to default (@{code ""}) if the node to get the statistics from should be obtained from {@link #dcIndex()} and {@link #dcNodeIndex()}. */
+    String domain() default "";
+
+    // JMX address properties
+    String type() default Constants.TYPE_CACHE;
+    String cacheName() default "work";
+    String cacheMode() default "*";
+    String cacheManagerName() default "*";
+    String component() default Constants.COMPONENT_STATISTICS;
+
+    // Host address - either given by arrangement of DC ...
+
+    /** Index of the data center, starting from 0 */
+    int dcIndex() default -1;
+    /** Index of the node within data center, starting from 0. Nodes are ordered by arquillian qualifier as per {@link AuthServerTestEnricher} */
+    int dcNodeIndex() default -1;
+
+    // ... or by specific host/port
+
+    /** Port for management */
+    int managementPort() default -1;
+    /** Name of system property to obtain management port from */
+    String managementPortProperty() default "";
+    /** Host name to connect to */
+    String host() default "";
+    /** Name of system property to obtain host name from */
+    String hostProperty() default "";
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/JmxInfinispanChannelStatistics.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/JmxInfinispanChannelStatistics.java
new file mode 100644
index 0000000..41e9f20
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/JmxInfinispanChannelStatistics.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 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.testsuite.arquillian.annotation;
+
+import org.keycloak.testsuite.arquillian.InfinispanStatistics.Constants;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author hmlnarik
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+public @interface JmxInfinispanChannelStatistics {
+
+    /** JMX domain. Should be set to default (@{code ""}) if the node to get the statistics from should be obtained from {@link #dcIndex()} and {@link #dcNodeIndex()}. */
+    String domain() default "";
+
+    // JMX address properties
+    String type() default Constants.TYPE_CHANNEL;
+    String cluster() default "*";
+
+    // Host address - either given by arrangement of DC ...
+
+    /** Index of the data center, starting from 0 */
+    int dcIndex() default -1;
+    /** Index of the node within data center, starting from 0. Nodes are ordered by arquillian qualifier as per {@link AuthServerTestEnricher} */
+    int dcNodeIndex() default -1;
+
+    /** Port for management */
+    int managementPort() default -1;
+    /** Name of system property to obtain management port from */
+    String managementPortProperty() default "";
+    /** Host name to connect to */
+    String host() default "";
+    /** Name of system property to obtain host name from */
+    String hostProperty() default "";
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/LoadBalancer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/LoadBalancer.java
new file mode 100644
index 0000000..a3652a1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/LoadBalancer.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017 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.testsuite.arquillian.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author hmlnarik
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.FIELD })
+public @interface LoadBalancer {
+    String value() default "";
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
index 7e7ee6f..97347d9 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
@@ -16,7 +16,6 @@
  */
 package org.keycloak.testsuite.arquillian;
 
-import org.jboss.arquillian.container.spi.Container;
 import org.jboss.arquillian.container.spi.ContainerRegistry;
 import org.jboss.arquillian.container.spi.event.StartContainer;
 import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
@@ -41,10 +40,11 @@ import org.keycloak.testsuite.util.OAuthClient;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
+import java.util.stream.Collectors;
 import javax.ws.rs.NotFoundException;
 
 /**
@@ -68,8 +68,18 @@ public class AuthServerTestEnricher {
     private static final String AUTH_SERVER_CONTAINER_PROPERTY = "auth.server.container";
     public static final String AUTH_SERVER_CONTAINER = System.getProperty(AUTH_SERVER_CONTAINER_PROPERTY, AUTH_SERVER_CONTAINER_DEFAULT);
 
+    private static final String AUTH_SERVER_BACKEND_DEFAULT = AUTH_SERVER_CONTAINER + "-backend";
+    private static final String AUTH_SERVER_BACKEND_PROPERTY = "auth.server.backend";
+    public static final String AUTH_SERVER_BACKEND = System.getProperty(AUTH_SERVER_BACKEND_PROPERTY, AUTH_SERVER_BACKEND_DEFAULT);
+
+    private static final String AUTH_SERVER_BALANCER_DEFAULT = "auth-server-balancer";
+    private static final String AUTH_SERVER_BALANCER_PROPERTY = "auth.server.balancer";
+    public static final String AUTH_SERVER_BALANCER = System.getProperty(AUTH_SERVER_BALANCER_PROPERTY, AUTH_SERVER_BALANCER_DEFAULT);
+
     private static final String AUTH_SERVER_CLUSTER_PROPERTY = "auth.server.cluster";
     public static final boolean AUTH_SERVER_CLUSTER = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CLUSTER_PROPERTY, "false"));
+    private static final String AUTH_SERVER_CROSS_DC_PROPERTY = "auth.server.crossdc";
+    public static final boolean AUTH_SERVER_CROSS_DC = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CROSS_DC_PROPERTY, "false"));
 
     private static final boolean AUTH_SERVER_UNDERTOW_CLUSTER = Boolean.parseBoolean(System.getProperty("auth.server.undertow.cluster", "false"));
 
@@ -106,46 +116,83 @@ public class AuthServerTestEnricher {
     }
 
     public void initializeSuiteContext(@Observes(precedence = 2) BeforeSuite event) {
-
-        Set<ContainerInfo> containers = new LinkedHashSet<>();
-        for (Container c : containerRegistry.get().getContainers()) {
-            containers.add(new ContainerInfo(c));
-        }
+        Set<ContainerInfo> containers = containerRegistry.get().getContainers().stream()
+          .map(ContainerInfo::new)
+          .collect(Collectors.toSet());
 
         suiteContext = new SuiteContext(containers);
 
-        String authServerFrontend = null;
-
-        if (AUTH_SERVER_CLUSTER) {
-            // if cluster mode enabled, load-balancer is the frontend
-            for (ContainerInfo c : containers) {
-                if (c.getQualifier().startsWith("auth-server-balancer")) {
-                    authServerFrontend = c.getQualifier();
-                }
+        if (AUTH_SERVER_CROSS_DC) {
+            // if cross-dc mode enabled, load-balancer is the frontend of datacenter cluster
+            containers.stream()
+              .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_BALANCER + "-cross-dc"))
+              .forEach(c -> {
+                String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0");
+                String dcString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("dataCenter", "0");
+                updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString));
+                suiteContext.addAuthServerInfo(Integer.valueOf(dcString), c);
+              });
+
+            if (suiteContext.getDcAuthServerInfo().isEmpty()) {
+                throw new IllegalStateException("Not found frontend container (load balancer): " + AUTH_SERVER_BALANCER);
             }
-
-            if (authServerFrontend != null) {
-                log.info("Using frontend container: " + authServerFrontend);
-            } else {
-                throw new IllegalStateException("Not found frontend container");
+            if (suiteContext.getDcAuthServerInfo().stream().anyMatch(Objects::isNull)) {
+                throw new IllegalStateException("Frontend container (load balancer) misconfiguration");
             }
-        } else {
-            authServerFrontend = AUTH_SERVER_CONTAINER; // single-node mode
-        }
 
-        String authServerBackend = AUTH_SERVER_CONTAINER + "-backend";
-        int backends = 0;
-        for (ContainerInfo container : suiteContext.getContainers()) {
-            // frontend
-            if (container.getQualifier().equals(authServerFrontend)) {
-                updateWithAuthServerInfo(container);
-                suiteContext.setAuthServerInfo(container);
+            containers.stream()
+              .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_CONTAINER + "-cross-dc-"))
+              .sorted((a, b) -> a.getQualifier().compareTo(b.getQualifier()))
+              .forEach(c -> {
+                String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0");
+                String dcString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("dataCenter", "0");
+                updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString));
+                suiteContext.addAuthServerBackendsInfo(Integer.valueOf(dcString), c);
+              });
+
+            if (suiteContext.getDcAuthServerInfo().isEmpty()) {
+                throw new RuntimeException(String.format("No auth server container matching '%s' found in arquillian.xml.", AUTH_SERVER_BACKEND));
             }
-            // backends
-            if (AUTH_SERVER_CLUSTER && container.getQualifier().startsWith(authServerBackend)) {
-                updateWithAuthServerInfo(container, ++backends);
-                suiteContext.getAuthServerBackendsInfo().add(container);
+            if (suiteContext.getDcAuthServerBackendsInfo().stream().anyMatch(Objects::isNull)) {
+                throw new IllegalStateException("Frontend container (load balancer) misconfiguration");
             }
+            if (suiteContext.getDcAuthServerBackendsInfo().stream().anyMatch(List::isEmpty)) {
+                throw new RuntimeException(String.format("Some data center has no auth server container matching '%s' defined in arquillian.xml.", AUTH_SERVER_BACKEND));
+            }
+
+            log.info("Using frontend containers: " + this.suiteContext.getDcAuthServerInfo().stream()
+              .map(ContainerInfo::getQualifier)
+              .collect(Collectors.joining(", ")));
+        } else if (AUTH_SERVER_CLUSTER) {
+            // if cluster mode enabled, load-balancer is the frontend
+            ContainerInfo container = containers.stream()
+              .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_BALANCER))
+              .findAny()
+              .orElseThrow(() -> new IllegalStateException("Not found frontend container: " + AUTH_SERVER_BALANCER));
+            updateWithAuthServerInfo(container);
+            suiteContext.setAuthServerInfo(container);
+
+            containers.stream()
+              .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_BACKEND))
+              .forEach(c -> {
+                String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0");
+                updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString));
+                suiteContext.addAuthServerBackendsInfo(0, c);
+              });
+
+            if (suiteContext.getAuthServerBackendsInfo().isEmpty()) {
+                throw new RuntimeException(String.format("No auth server container matching '%s' found in arquillian.xml.", AUTH_SERVER_BACKEND));
+            }
+
+            log.info("Using frontend container: " + container.getQualifier());
+        } else {
+            // frontend-only
+            ContainerInfo container = containers.stream()
+              .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_CONTAINER))
+              .findAny()
+              .orElseThrow(() -> new IllegalStateException("Not found frontend container: " + AUTH_SERVER_CONTAINER));
+            updateWithAuthServerInfo(container);
+            suiteContext.setAuthServerInfo(container);
         }
 
         // Setup with 2 undertow backend nodes and no loadbalancer.
@@ -153,14 +200,6 @@ public class AuthServerTestEnricher {
 //            suiteContext.setAuthServerInfo(suiteContext.getAuthServerBackendsInfo().get(0));
 //        }
 
-        // validate auth server setup
-        if (suiteContext.getAuthServerInfo() == null) {
-            throw new RuntimeException(String.format("No auth server container matching '%s' found in arquillian.xml.", authServerFrontend));
-        }
-        if (AUTH_SERVER_CLUSTER && suiteContext.getAuthServerBackendsInfo().isEmpty()) {
-            throw new RuntimeException(String.format("No auth server container matching '%sN' found in arquillian.xml.", authServerBackend));
-        }
-
         if (START_MIGRATION_CONTAINER) {
             // init migratedAuthServerInfo
             for (ContainerInfo container : suiteContext.getContainers()) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/CacheStatisticsControllerEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/CacheStatisticsControllerEnricher.java
new file mode 100644
index 0000000..4091ca4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/CacheStatisticsControllerEnricher.java
@@ -0,0 +1,356 @@
+package org.keycloak.testsuite.arquillian;
+
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.testsuite.Retry;
+import java.util.Map;
+import org.jboss.arquillian.core.api.Instance;
+import org.jboss.arquillian.core.api.annotation.Inject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXServiceURL;
+import org.jboss.arquillian.container.spi.Container;
+import org.jboss.arquillian.container.spi.ContainerRegistry;
+import org.jboss.arquillian.test.spi.TestEnricher;
+import java.io.IOException;
+import java.lang.reflect.Parameter;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MalformedObjectNameException;
+import javax.management.ReflectionException;
+import org.keycloak.testsuite.arquillian.annotation.JmxInfinispanCacheStatistics;
+import java.util.Set;
+import org.keycloak.testsuite.arquillian.annotation.JmxInfinispanChannelStatistics;
+import org.keycloak.testsuite.arquillian.jmx.JmxConnectorRegistry;
+import org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow;
+import java.io.NotSerializableException;
+import java.lang.management.ManagementFactory;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.jboss.arquillian.core.spi.Validate;
+import org.jboss.logging.Logger;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class CacheStatisticsControllerEnricher implements TestEnricher {
+
+    private static final Logger LOG = Logger.getLogger(CacheStatisticsControllerEnricher.class);
+
+    @Inject
+    private Instance<ContainerRegistry> registry;
+
+    @Inject
+    private Instance<JmxConnectorRegistry> jmxConnectorRegistry;
+
+    @Inject
+    private Instance<SuiteContext> suiteContext;
+
+    @Override
+    public void enrich(Object testCase) {
+        Validate.notNull(registry.get(), "registry should not be null");
+        Validate.notNull(jmxConnectorRegistry.get(), "jmxConnectorRegistry should not be null");
+        Validate.notNull(suiteContext.get(), "suiteContext should not be null");
+
+        for (Field field : FieldUtils.getAllFields(testCase.getClass())) {
+            JmxInfinispanCacheStatistics annotation = field.getAnnotation(JmxInfinispanCacheStatistics.class);
+
+            if (annotation == null) {
+                continue;
+            }
+
+            try {
+                FieldUtils.writeField(field, testCase, getInfinispanCacheStatistics(annotation), true);
+            } catch (IOException | IllegalAccessException | MalformedObjectNameException e) {
+                throw new RuntimeException("Could not set value on field " + field);
+            }
+        }
+    }
+
+    private InfinispanStatistics getInfinispanCacheStatistics(JmxInfinispanCacheStatistics annotation) throws MalformedObjectNameException, IOException, MalformedURLException {
+        MBeanServerConnection mbsc = getJmxServerConnection(annotation);
+
+        ObjectName mbeanName = new ObjectName(String.format(
+          "%s:type=%s,name=\"%s(%s)\",manager=\"%s\",component=%s",
+          annotation.domain().isEmpty() ? getDefaultDomain(annotation.dcIndex(), annotation.dcNodeIndex()) : InfinispanConnectionProvider.JMX_DOMAIN,
+          annotation.type(),
+          annotation.cacheName(),
+          annotation.cacheMode(),
+          annotation.cacheManagerName(),
+          annotation.component()
+        ));
+
+        InfinispanStatistics value = new InfinispanCacheStatisticsImpl(mbsc, mbeanName);
+
+        if (annotation.domain().isEmpty()) {
+            try {
+                Retry.execute(() -> value.reset(), 2, 150);
+            } catch (RuntimeException ex) {
+                if (annotation.dcIndex() != -1 && annotation.dcNodeIndex() != -1
+                   && suiteContext.get().getAuthServerBackendsInfo(annotation.dcIndex()).get(annotation.dcNodeIndex()).isStarted()) {
+                    LOG.warn("Could not reset statistics for " + mbeanName);
+                }
+            }
+        }
+
+        return value;
+    }
+
+    private InfinispanStatistics getJGroupsChannelStatistics(JmxInfinispanChannelStatistics annotation) throws MalformedObjectNameException, IOException, MalformedURLException {
+        MBeanServerConnection mbsc = getJmxServerConnection(annotation);
+
+        ObjectName mbeanName = new ObjectName(String.format(
+          "%s:type=%s,cluster=\"%s\"",
+          annotation.domain().isEmpty() ? getDefaultDomain(annotation.dcIndex(), annotation.dcNodeIndex()) : InfinispanConnectionProvider.JMX_DOMAIN,
+          annotation.type(),
+          annotation.cluster()
+        ));
+
+        InfinispanStatistics value = new InfinispanChannelStatisticsImpl(mbsc, mbeanName);
+
+        if (annotation.domain().isEmpty()) {
+            try {
+                Retry.execute(() -> value.reset(), 2, 150);
+            } catch (RuntimeException ex) {
+                if (annotation.dcIndex() != -1 && annotation.dcNodeIndex() != -1
+                   && suiteContext.get().getAuthServerBackendsInfo(annotation.dcIndex()).get(annotation.dcNodeIndex()).isStarted()) {
+                    LOG.warn("Could not reset statistics for " + mbeanName);
+                }
+            }
+        }
+
+        return value;
+    }
+
+    @Override
+    public Object[] resolve(Method method) {
+        Object[] values = new Object[method.getParameterCount()];
+
+        for (int i = 0; i < method.getParameterCount(); i ++) {
+            Parameter param = method.getParameters()[i];
+
+            JmxInfinispanCacheStatistics annotation = param.getAnnotation(JmxInfinispanCacheStatistics.class);
+            if (annotation != null) try {
+                values[i] = getInfinispanCacheStatistics(annotation);
+            } catch (IOException | MalformedObjectNameException e) {
+                throw new RuntimeException("Could not set value on field " + param);
+            }
+
+            JmxInfinispanChannelStatistics channelAnnotation = param.getAnnotation(JmxInfinispanChannelStatistics.class);
+            if (channelAnnotation != null) try {
+                values[i] = getJGroupsChannelStatistics(channelAnnotation);
+            } catch (IOException | MalformedObjectNameException e) {
+                throw new RuntimeException("Could not set value on field " + param);
+            }
+        }
+
+        return values;
+    }
+
+    private String getDefaultDomain(int dcIndex, int dcNodeIndex) {
+        if (dcIndex != -1 && dcNodeIndex != -1) {
+            return InfinispanConnectionProvider.JMX_DOMAIN + "-" + suiteContext.get().getAuthServerBackendsInfo(dcIndex).get(dcNodeIndex).getQualifier();
+        }
+        return InfinispanConnectionProvider.JMX_DOMAIN;
+    }
+
+    private MBeanServerConnection getJmxServerConnection(JmxInfinispanCacheStatistics annotation) throws MalformedURLException, IOException {
+        final String host;
+        final int port;
+
+        if (annotation.dcIndex() != -1 && annotation.dcNodeIndex() != -1) {
+            ContainerInfo node = suiteContext.get().getAuthServerBackendsInfo(annotation.dcIndex()).get(annotation.dcNodeIndex());
+            Container container = node.getArquillianContainer();
+            if (container.getDeployableContainer() instanceof KeycloakOnUndertow) {
+                return ManagementFactory.getPlatformMBeanServer();
+            }
+            host = "localhost";
+            port = container.getContainerConfiguration().getContainerProperties().containsKey("managementPort")
+              ? Integer.valueOf(container.getContainerConfiguration().getContainerProperties().get("managementPort"))
+              : 9990;
+        } else {
+            host = annotation.host().isEmpty()
+              ? System.getProperty((annotation.hostProperty().isEmpty()
+                ? "keycloak.connectionsInfinispan.remoteStoreServer"
+                : annotation.hostProperty()))
+              : annotation.host();
+
+            port = annotation.managementPort() == -1
+              ? Integer.valueOf(System.getProperty((annotation.managementPortProperty().isEmpty()
+                ? "cache.server.management.port"
+                : annotation.managementPortProperty())))
+              : annotation.managementPort();
+        }
+
+        JMXServiceURL url = new JMXServiceURL("service:jmx:remote+http://" + host + ":" + port);
+        JMXConnector jmxc = jmxConnectorRegistry.get().getConnection(url);
+
+        return jmxc.getMBeanServerConnection();
+    }
+
+    private MBeanServerConnection getJmxServerConnection(JmxInfinispanChannelStatistics annotation) throws MalformedURLException, IOException {
+        final String host;
+        final int port;
+
+        if (annotation.dcIndex() != -1 && annotation.dcNodeIndex() != -1) {
+            ContainerInfo node = suiteContext.get().getAuthServerBackendsInfo(annotation.dcIndex()).get(annotation.dcNodeIndex());
+            Container container = node.getArquillianContainer();
+            if (container.getDeployableContainer() instanceof KeycloakOnUndertow) {
+                return ManagementFactory.getPlatformMBeanServer();
+            }
+            host = "localhost";
+            port = container.getContainerConfiguration().getContainerProperties().containsKey("managementPort")
+              ? Integer.valueOf(container.getContainerConfiguration().getContainerProperties().get("managementPort"))
+              : 9990;
+        } else {
+            host = annotation.host().isEmpty()
+              ? System.getProperty((annotation.hostProperty().isEmpty()
+                ? "keycloak.connectionsInfinispan.remoteStoreServer"
+                : annotation.hostProperty()))
+              : annotation.host();
+
+            port = annotation.managementPort() == -1
+              ? Integer.valueOf(System.getProperty((annotation.managementPortProperty().isEmpty()
+                ? "cache.server.management.port"
+                : annotation.managementPortProperty())))
+              : annotation.managementPort();
+        }
+
+        JMXServiceURL url = new JMXServiceURL("service:jmx:remote+http://" + host + ":" + port);
+        JMXConnector jmxc = jmxConnectorRegistry.get().getConnection(url);
+
+        return jmxc.getMBeanServerConnection();
+    }
+
+    private static abstract class CacheStatisticsImpl implements InfinispanStatistics {
+
+        protected final MBeanServerConnection mbsc;
+        private final ObjectName mbeanNameTemplate;
+        private ObjectName mbeanName;
+
+        public CacheStatisticsImpl(MBeanServerConnection mbsc, ObjectName mbeanNameTemplate) {
+            this.mbsc = mbsc;
+            this.mbeanNameTemplate = mbeanNameTemplate;
+        }
+
+        @Override
+        public boolean exists() {
+            try {
+                getMbeanName();
+                return true;
+            } catch (Exception ex) {
+                return false;
+            }
+        }
+
+        @Override
+        public Map<String, Object> getStatistics() {
+            try {
+                MBeanInfo mBeanInfo = mbsc.getMBeanInfo(getMbeanName());
+                String[] statAttrs = Arrays.asList(mBeanInfo.getAttributes()).stream()
+                  .filter(MBeanAttributeInfo::isReadable)
+                  .map(MBeanAttributeInfo::getName)
+                  .collect(Collectors.toList())
+                  .toArray(new String[] {});
+                return mbsc.getAttributes(getMbeanName(), statAttrs)
+                  .asList()
+                  .stream()
+                  .collect(Collectors.toMap(Attribute::getName, Attribute::getValue));
+            } catch (IOException | InstanceNotFoundException | ReflectionException | IntrospectionException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        protected ObjectName getMbeanName() throws IOException, RuntimeException {
+            if (this.mbeanName == null) {
+                Set<ObjectName> queryNames = mbsc.queryNames(mbeanNameTemplate, null);
+                if (queryNames.isEmpty()) {
+                    throw new RuntimeException("No MBean of template " + mbeanNameTemplate + " found at JMX server");
+                }
+                this.mbeanName = queryNames.iterator().next();
+            }
+
+            return this.mbeanName;
+        }
+
+        @Override
+        public Comparable getSingleStatistics(String statisticsName) {
+            try {
+                return (Comparable) mbsc.getAttribute(getMbeanName(), statisticsName);
+            } catch (IOException | InstanceNotFoundException | MBeanException | ReflectionException | AttributeNotFoundException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public void waitToBecomeAvailable(int time, TimeUnit unit) {
+            long timeInMillis = TimeUnit.MILLISECONDS.convert(time, unit);
+            Retry.execute(() -> {
+                try {
+                    getMbeanName();
+                    if (! isAvailable()) throw new RuntimeException("Not available");
+                } catch (Exception ex) {
+                    throw new RuntimeException("Timed out while waiting for " + mbeanNameTemplate + " to become available", ex);
+                }
+            }, 1 + (int) timeInMillis / 100, 100);
+        }
+
+        protected abstract boolean isAvailable();
+    }
+
+    private static class InfinispanCacheStatisticsImpl extends CacheStatisticsImpl {
+
+        public InfinispanCacheStatisticsImpl(MBeanServerConnection mbsc, ObjectName mbeanName) {
+            super(mbsc, mbeanName);
+        }
+
+        @Override
+        public void reset() {
+            try {
+                mbsc.invoke(getMbeanName(), "resetStatistics", new Object[] {}, new String[] {});
+            } catch (IOException | InstanceNotFoundException | MBeanException | ReflectionException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        protected boolean isAvailable() {
+            return getSingleStatistics(Constants.STAT_CACHE_ELAPSED_TIME) != null;
+        }
+    }
+
+    private static class InfinispanChannelStatisticsImpl extends CacheStatisticsImpl {
+
+        public InfinispanChannelStatisticsImpl(MBeanServerConnection mbsc, ObjectName mbeanName) {
+            super(mbsc, mbeanName);
+        }
+
+        @Override
+        public void reset() {
+            try {
+                mbsc.invoke(getMbeanName(), "resetStats", new Object[] {}, new String[] {});
+            } catch (NotSerializableException ex) {
+                // Ignore return value not serializable, the invocation has already done its job
+            } catch (IOException | InstanceNotFoundException | MBeanException | ReflectionException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        protected boolean isAvailable() {
+            return Objects.equals(getSingleStatistics(Constants.STAT_CHANNEL_CONNECTED), Boolean.TRUE);
+       }
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java
index 68c9f17..37abe7b 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java
@@ -5,6 +5,8 @@ import org.jboss.arquillian.container.spi.Container;
 import java.net.URL;
 import java.util.Map;
 import java.util.Objects;
+import java.util.stream.Stream;
+import org.jboss.arquillian.container.spi.Container.State;
 
 /**
  *
@@ -97,4 +99,12 @@ public class ContainerInfo {
                 other.arquillianContainer.getContainerConfiguration().getContainerName());
     }
 
+    public boolean isStarted() {
+        return arquillianContainer.getState() == State.STARTED;
+    }
+
+    public boolean isManual() {
+        return Objects.equals(arquillianContainer.getContainerConfiguration().getMode(), "manual");
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/RegistryCreator.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/RegistryCreator.java
index 99b6772..41278fc 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/RegistryCreator.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/RegistryCreator.java
@@ -35,6 +35,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
+import org.mvel2.MVEL;
 import static org.keycloak.testsuite.arquillian.containers.SecurityActions.isClassPresent;
 import static org.keycloak.testsuite.arquillian.containers.SecurityActions.loadClass;
 
@@ -97,10 +98,14 @@ public class RegistryCreator {
 
     private static final String ENABLED = "enabled";
 
-    private boolean isEnabled(ContainerDef containerDef) {
+    private static boolean isEnabled(ContainerDef containerDef) {
         Map<String, String> props = containerDef.getContainerProperties();
-        return !props.containsKey(ENABLED)
-                || (props.containsKey(ENABLED) && props.get(ENABLED).equals("true"));
+        try {
+            return !props.containsKey(ENABLED)
+                    || (props.containsKey(ENABLED) && ! props.get(ENABLED).isEmpty() && MVEL.evalToBoolean(props.get(ENABLED), (Object) null));
+        } catch (Exception ex) {
+            return false;
+        }
     }
 
     @SuppressWarnings("rawtypes")
@@ -110,6 +115,8 @@ public class RegistryCreator {
             if (isClassPresent(getAdapterImplClassValue(containerDef))) {
                 return DeployableContainer.class.isAssignableFrom(
                         loadClass(getAdapterImplClassValue(containerDef)));
+            } else {
+                log.warn("Cannot load adapterImpl class for " + containerDef.getContainerName());
             }
         }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/InfinispanStatistics.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/InfinispanStatistics.java
new file mode 100644
index 0000000..b315937
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/InfinispanStatistics.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 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.testsuite.arquillian;
+
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public interface InfinispanStatistics {
+
+    public static class Constants {
+        public static final String DOMAIN_INFINISPAN_DATAGRID = InfinispanConnectionProvider.JMX_DOMAIN;
+
+        public static final String TYPE_CHANNEL = "channel";
+        public static final String TYPE_CACHE = "Cache";
+        public static final String TYPE_CACHE_MANAGER = "CacheManager";
+
+        public static final String COMPONENT_STATISTICS = "Statistics";
+
+        /** Cache statistics */
+        public static final String STAT_CACHE_AVERAGE_READ_TIME = "averageReadTime";
+        public static final String STAT_CACHE_AVERAGE_WRITE_TIME = "averageWriteTime";
+        public static final String STAT_CACHE_ELAPSED_TIME = "elapsedTime";
+        public static final String STAT_CACHE_EVICTIONS = "evictions";
+        public static final String STAT_CACHE_HITS = "hits";
+        public static final String STAT_CACHE_HIT_RATIO = "hitRatio";
+        public static final String STAT_CACHE_MISSES = "misses";
+        public static final String STAT_CACHE_NUMBER_OF_ENTRIES = "numberOfEntries";
+        public static final String STAT_CACHE_NUMBER_OF_ENTRIES_IN_MEMORY = "numberOfEntriesInMemory";
+        public static final String STAT_CACHE_READ_WRITE_RATIO = "readWriteRatio";
+        public static final String STAT_CACHE_REMOVE_HITS = "removeHits";
+        public static final String STAT_CACHE_REMOVE_MISSES = "removeMisses";
+        public static final String STAT_CACHE_STORES = "stores";
+        public static final String STAT_CACHE_TIME_SINCE_RESET = "timeSinceReset";
+
+        /** JGroups channel statistics */
+        public static final String STAT_CHANNEL_ADDRESS = "address";
+        public static final String STAT_CHANNEL_ADDRESS_UUID = "address_uuid";
+        public static final String STAT_CHANNEL_CLOSED = "closed";
+        public static final String STAT_CHANNEL_CLUSTER_NAME = "cluster_name";
+        public static final String STAT_CHANNEL_CONNECTED = "connected";
+        public static final String STAT_CHANNEL_CONNECTING = "connecting";
+        public static final String STAT_CHANNEL_DISCARD_OWN_MESSAGES = "discard_own_messages";
+        public static final String STAT_CHANNEL_OPEN = "open";
+        public static final String STAT_CHANNEL_RECEIVED_BYTES = "received_bytes";
+        public static final String STAT_CHANNEL_RECEIVED_MESSAGES = "received_messages";
+        public static final String STAT_CHANNEL_SENT_BYTES = "sent_bytes";
+        public static final String STAT_CHANNEL_SENT_MESSAGES = "sent_messages";
+        public static final String STAT_CHANNEL_STATE = "state";
+        public static final String STAT_CHANNEL_STATS = "stats";
+        public static final String STAT_CHANNEL_VIEW = "view";
+
+    }
+
+    Map<String, Object> getStatistics();
+
+    Comparable getSingleStatistics(String statisticsName);
+
+    void waitToBecomeAvailable(int time, TimeUnit unit);
+
+    /**
+     * Resets the statistics counters.
+     */
+    void reset();
+
+    /**
+     * Returns {@code true} iff the statistics represented by this object can be retrieved from the server.
+     */
+    boolean exists();
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistry.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistry.java
new file mode 100644
index 0000000..3a87c5b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistry.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 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.testsuite.arquillian.jmx;
+
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXServiceURL;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public interface JmxConnectorRegistry {
+    JMXConnector getConnection(JMXServiceURL url);
+
+    void closeAll();
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistryCreator.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistryCreator.java
new file mode 100644
index 0000000..50c9b96
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistryCreator.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017 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.testsuite.arquillian.jmx;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+import org.jboss.arquillian.core.api.InstanceProducer;
+import org.jboss.arquillian.core.api.annotation.ApplicationScoped;
+import org.jboss.arquillian.core.api.annotation.Inject;
+import org.jboss.arquillian.core.api.annotation.Observes;
+import org.jboss.arquillian.test.spi.event.suite.BeforeSuite;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class JmxConnectorRegistryCreator {
+
+    @Inject
+    @ApplicationScoped
+    private InstanceProducer<JmxConnectorRegistry> connectorRegistry;
+
+    public void configureJmxConnectorRegistry(@Observes BeforeSuite event) {
+        if (connectorRegistry.get() == null) {
+            connectorRegistry.set(new JmxConnectorRegistry() {
+
+                private volatile ConcurrentMap<JMXServiceURL, JMXConnector> connectors = new ConcurrentHashMap<>();
+
+                @Override
+                public JMXConnector getConnection(JMXServiceURL url) {
+                    JMXConnector res = connectors.get(url);
+                    if (res == null) {
+                        try {
+                            final JMXConnector conn = JMXConnectorFactory.newJMXConnector(url, null);
+                            res = connectors.putIfAbsent(url, conn);
+                            if (res == null) {
+                                res = conn;
+                            }
+                            res.connect();
+                        } catch (IOException ex) {
+                            throw new RuntimeException("Could not instantiate JMX connector for " + url, ex);
+                        }
+                    }
+                    return res;
+                }
+
+                @Override
+                public void closeAll() {
+                    connectors.values().forEach(c -> { try { c.close(); } catch (IOException e) {} });
+                    connectors.clear();
+                }
+            });
+        }
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
index 7b9c139..33dc8c2 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
@@ -32,9 +32,11 @@ import org.jboss.arquillian.graphene.location.CustomizableURLResourceProvider;
 import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
 import org.jboss.arquillian.test.spi.execution.TestExecutionDecider;
 import org.keycloak.testsuite.arquillian.h2.H2TestEnricher;
+import org.keycloak.testsuite.arquillian.jmx.JmxConnectorRegistryCreator;
 import org.keycloak.testsuite.arquillian.karaf.CustomKarafContainer;
 import org.keycloak.testsuite.arquillian.migration.MigrationTestExecutionDecider;
 import org.keycloak.testsuite.arquillian.provider.AdminClientProvider;
+import org.keycloak.testsuite.arquillian.provider.LoadBalancerControllerProvider;
 import org.keycloak.testsuite.arquillian.provider.OAuthClientProvider;
 import org.keycloak.testsuite.arquillian.provider.SuiteContextProvider;
 import org.keycloak.testsuite.arquillian.provider.TestContextProvider;
@@ -43,6 +45,7 @@ import org.keycloak.testsuite.drone.HtmlUnitScreenshots;
 import org.keycloak.testsuite.drone.KeycloakDronePostSetup;
 import org.keycloak.testsuite.drone.KeycloakHtmlUnitInstantiator;
 import org.keycloak.testsuite.drone.KeycloakWebDriverConfigurator;
+import org.jboss.arquillian.test.spi.TestEnricher;
 
 /**
  *
@@ -57,12 +60,15 @@ public class KeycloakArquillianExtension implements LoadableExtension {
                 .service(ResourceProvider.class, SuiteContextProvider.class)
                 .service(ResourceProvider.class, TestContextProvider.class)
                 .service(ResourceProvider.class, AdminClientProvider.class)
-                .service(ResourceProvider.class, OAuthClientProvider.class);
+                .service(ResourceProvider.class, OAuthClientProvider.class)
+                .service(ResourceProvider.class, LoadBalancerControllerProvider.class);
 
         builder
                 .service(DeploymentScenarioGenerator.class, DeploymentTargetModifier.class)
                 .service(ApplicationArchiveProcessor.class, DeploymentArchiveProcessor.class)
                 .service(DeployableContainer.class, CustomKarafContainer.class)
+                .service(TestEnricher.class, CacheStatisticsControllerEnricher.class)
+                .observer(JmxConnectorRegistryCreator.class)
                 .observer(AuthServerTestEnricher.class)
                 .observer(AppServerTestEnricher.class)
                 .observer(H2TestEnricher.class);
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/LoadBalancerControllerProvider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/LoadBalancerControllerProvider.java
new file mode 100644
index 0000000..af1703d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/LoadBalancerControllerProvider.java
@@ -0,0 +1,61 @@
+package org.keycloak.testsuite.arquillian.provider;
+
+import org.keycloak.testsuite.arquillian.annotation.LoadBalancer;
+import java.lang.annotation.Annotation;
+import org.jboss.arquillian.core.api.Instance;
+import org.jboss.arquillian.core.api.annotation.Inject;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
+import org.keycloak.testsuite.arquillian.LoadBalancerController;
+import org.jboss.arquillian.container.spi.Container;
+import org.jboss.arquillian.container.spi.ContainerRegistry;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class LoadBalancerControllerProvider implements ResourceProvider {
+
+    @Inject
+    private Instance<ContainerRegistry> registry;
+
+    @Override
+    public boolean canProvide(Class<?> type) {
+        return type.equals(LoadBalancerController.class);
+    }
+
+    @Override
+    public Object lookup(ArquillianResource resource, Annotation... qualifiers) {
+        String balancerName = null;
+
+        // Check for the presence of possible qualifiers
+        for (Annotation a : qualifiers) {
+            Class<? extends Annotation> annotationType = a.annotationType();
+
+            if (annotationType.equals(LoadBalancer.class)) {
+                balancerName = ((LoadBalancer) a).value();
+            }
+        }
+
+        ContainerRegistry reg = registry.get();
+        Container container = null;
+        if (balancerName == null || "".equals(balancerName.trim())) {
+            if (reg.getContainers().size() == 1) {
+                container = reg.getContainers().get(0);
+            } else {
+                throw new IllegalArgumentException("Invalid load balancer configuration request - need to specify load balancer name in @LoadBalancerController");
+            }
+        } else {
+            container = reg.getContainer(balancerName);
+        }
+
+        if (container == null) {
+            throw new IllegalArgumentException("Invalid load balancer configuration - load balancer not found: '" + balancerName + "'");
+        }
+        if (! (container.getDeployableContainer() instanceof LoadBalancerController)) {
+            throw new IllegalArgumentException("Invalid load balancer configuration - container " + container.getName() + " is not a load balancer");
+        }
+
+        return container.getDeployableContainer();
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
index fab303a..8a6d300 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
@@ -24,6 +24,7 @@ import java.util.Set;
 
 import org.keycloak.testsuite.arquillian.migration.MigrationContext;
 
+import java.util.LinkedList;
 import static org.keycloak.testsuite.util.MailServerConfiguration.FROM;
 import static org.keycloak.testsuite.util.MailServerConfiguration.HOST;
 import static org.keycloak.testsuite.util.MailServerConfiguration.PORT;
@@ -36,8 +37,8 @@ public final class SuiteContext {
 
     private final Set<ContainerInfo> container;
 
-    private ContainerInfo authServerInfo;
-    private final List<ContainerInfo> authServerBackendsInfo = new ArrayList<>();
+    private List<ContainerInfo> authServerInfo = new LinkedList<>();
+    private final List<List<ContainerInfo>> authServerBackendsInfo = new ArrayList<>();
 
     private ContainerInfo migratedAuthServerInfo;
     private final MigrationContext migrationContext = new MigrationContext();
@@ -72,17 +73,48 @@ public final class SuiteContext {
     }
 
     public ContainerInfo getAuthServerInfo() {
+        return getAuthServerInfo(0);
+    }
+
+    public ContainerInfo getAuthServerInfo(int dcIndex) {
+        return authServerInfo.get(dcIndex);
+    }
+
+    public List<ContainerInfo> getDcAuthServerInfo() {
         return authServerInfo;
     }
 
     public void setAuthServerInfo(ContainerInfo authServerInfo) {
-        this.authServerInfo = authServerInfo;
+        this.authServerInfo = new LinkedList<>();
+        this.authServerInfo.add(authServerInfo);
+    }
+
+    public void addAuthServerInfo(int dcIndex, ContainerInfo serverInfo) {
+        while (dcIndex >= authServerInfo.size()) {
+            authServerInfo.add(null);
+        }
+        this.authServerInfo.set(dcIndex, serverInfo);
     }
 
     public List<ContainerInfo> getAuthServerBackendsInfo() {
+        return getAuthServerBackendsInfo(0);
+    }
+
+    public List<ContainerInfo> getAuthServerBackendsInfo(int dcIndex) {
+        return authServerBackendsInfo.get(dcIndex);
+    }
+
+    public List<List<ContainerInfo>> getDcAuthServerBackendsInfo() {
         return authServerBackendsInfo;
     }
 
+    public void addAuthServerBackendsInfo(int dcIndex, ContainerInfo container) {
+        while (dcIndex >= authServerBackendsInfo.size()) {
+            authServerBackendsInfo.add(new LinkedList<>());
+        }
+        authServerBackendsInfo.get(dcIndex).add(container);
+    }
+
     public ContainerInfo getMigratedAuthServerInfo() {
         return migratedAuthServerInfo;
     }
@@ -96,7 +128,11 @@ public final class SuiteContext {
     }
 
     public boolean isAuthServerCluster() {
-        return !authServerBackendsInfo.isEmpty();
+        return ! authServerBackendsInfo.isEmpty();
+    }
+
+    public boolean isAuthServerCrossDc() {
+        return authServerBackendsInfo.size() > 1;
     }
 
     public boolean isAuthServerMigrationEnabled() {
@@ -113,19 +149,38 @@ public final class SuiteContext {
 
     @Override
     public String toString() {
-        String containers = "Auth server: " + (isAuthServerCluster() ? "\nFrontend: " : "")
-                + authServerInfo.getQualifier() + "\n";
-        for (ContainerInfo bInfo : getAuthServerBackendsInfo()) {
-            containers += "Backend: " + bInfo + "\n";
+        StringBuilder sb = new StringBuilder("SUITE CONTEXT:\nAuth server: ");
+
+        if (isAuthServerCrossDc()) {
+            for (int i = 0; i < authServerInfo.size(); i ++) {
+                ContainerInfo frontend = this.authServerInfo.get(i);
+                sb.append("\nFrontend (dc=").append(i).append("): ").append(frontend.getQualifier()).append("\n");
+            }
+
+            for (int i = 0; i < authServerBackendsInfo.size(); i ++) {
+                int dcIndex = i;
+                getDcAuthServerBackendsInfo().get(i).forEach(bInfo -> sb.append("Backend (dc=").append(dcIndex).append("): ").append(bInfo).append("\n"));
+            }
+        } else if (isAuthServerCluster()) {
+            sb.append(isAuthServerCluster() ? "\nFrontend: " : "")
+              .append(getAuthServerInfo().getQualifier())
+              .append("\n");
+
+            getAuthServerBackendsInfo().forEach(bInfo -> sb.append("  Backend: ").append(bInfo).append("\n"));
+        } else {
+          sb.append(getAuthServerInfo().getQualifier())
+            .append("\n");
         }
+
+
         if (isAuthServerMigrationEnabled()) {
-            containers += "Migrated from: " + System.getProperty("migrated.auth.server.version") + "\n";
+            sb.append("Migrated from: ").append(System.getProperty("migrated.auth.server.version")).append("\n");
         }
+
         if (isAdapterCompatTesting()) {
-            containers += "Adapter backward compatibility testing mode!\n";
+            sb.append("Adapter backward compatibility testing mode!\n");
         }
-        return "SUITE CONTEXT:\n"
-                + containers;
+        return sb.toString();
     }
 
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java
index 30b0405..cfe6d84 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import javax.ws.rs.NotFoundException;
 
@@ -54,7 +55,7 @@ public final class TestContext {
     private boolean initialized;
 
     // Key is realmName, value are objects to clean after the test method
-    private Map<String, TestCleanup> cleanups = new HashMap<>();
+    private Map<String, TestCleanup> cleanups = new ConcurrentHashMap<>();
 
     public TestContext(SuiteContext suiteContext, Class testClass) {
         this.suiteContext = suiteContext;
@@ -146,7 +147,11 @@ public final class TestContext {
         TestCleanup cleanup = cleanups.get(realmName);
         if (cleanup == null) {
             cleanup = new TestCleanup(adminClient, realmName);
-            cleanups.put(realmName, cleanup);
+            TestCleanup existing = cleanups.putIfAbsent(realmName, cleanup);
+
+            if (existing != null) {
+                cleanup = existing;
+            }
         }
         return cleanup;
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LanguageComboboxAwarePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LanguageComboboxAwarePage.java
new file mode 100644
index 0000000..29d512e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LanguageComboboxAwarePage.java
@@ -0,0 +1,44 @@
+/*
+ * 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.testsuite.pages;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class LanguageComboboxAwarePage extends AbstractPage {
+
+    @FindBy(id = "kc-current-locale-link")
+    private WebElement languageText;
+
+    @FindBy(id = "kc-locale-dropdown")
+    private WebElement localeDropdown;
+
+    public String getLanguageDropdownText() {
+        return languageText.getText();
+    }
+
+    public void openLanguage(String language){
+        WebElement langLink = localeDropdown.findElement(By.xpath("//a[text()='" + language + "']"));
+        String url = langLink.getAttribute("href");
+        driver.navigate().to(url);
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java
index 11d8fb2..b025ec7 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -26,7 +26,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginPage extends AbstractPage {
+public class LoginPage extends LanguageComboboxAwarePage {
 
     @ArquillianResource
     protected OAuthClient oauth;
@@ -75,12 +75,6 @@ public class LoginPage extends AbstractPage {
     private WebElement instruction;
 
 
-    @FindBy(id = "kc-current-locale-link")
-    private WebElement languageText;
-
-    @FindBy(id = "kc-locale-dropdown")
-    private WebElement localeDropdown;
-
     public void login(String username, String password) {
         usernameInput.clear();
         usernameInput.sendKeys(username);
@@ -191,14 +185,4 @@ public class LoginPage extends AbstractPage {
         assertCurrent();
     }
 
-    public String getLanguageDropdownText() {
-        return languageText.getText();
-    }
-
-    public void openLanguage(String language){
-        WebElement langLink = localeDropdown.findElement(By.xpath("//a[text()='" +language +"']"));
-        String url = langLink.getAttribute("href");
-        driver.navigate().to(url);
-    }
-
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
index 93d203d..7a963e1 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
@@ -22,7 +22,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginPasswordUpdatePage extends AbstractPage {
+public class LoginPasswordUpdatePage extends LanguageComboboxAwarePage {
 
     @FindBy(id = "password-new")
     private WebElement newPasswordInput;
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
index 1a550ec..cfb1f06 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
@@ -22,7 +22,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class OAuthGrantPage extends AbstractPage {
+public class OAuthGrantPage extends LanguageComboboxAwarePage {
 
     @FindBy(css = "input[name=\"accept\"]")
     private WebElement acceptButton;
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/AdminClientUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/AdminClientUtil.java
index a6f42c8..7dc4d6e 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/AdminClientUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/AdminClientUtil.java
@@ -43,6 +43,14 @@ import static org.keycloak.testsuite.util.IOUtil.PROJECT_BUILD_DIRECTORY;
 public class AdminClientUtil {
 
     public static Keycloak createAdminClient(boolean ignoreUnknownProperties, String authServerContextRoot) throws Exception {
+        return createAdminClient(ignoreUnknownProperties, authServerContextRoot, MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID, null);
+
+    }
+    public static Keycloak createAdminClient(boolean ignoreUnknownProperties, String realmName, String username, String password, String clientId, String clientSecret) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException {
+        return createAdminClient(ignoreUnknownProperties, AuthServerTestEnricher.getAuthServerContextRoot(), realmName, username, password, clientId, clientSecret);
+    }
+
+    public static Keycloak createAdminClient(boolean ignoreUnknownProperties, String authServerContextRoot, String realmName, String username, String password, String clientId, String clientSecret) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException {
         SSLContext ssl = null;
         if ("true".equals(System.getProperty("auth.server.ssl.required"))) {
             File trustore = new File(PROJECT_BUILD_DIRECTORY, "dependency/keystore/keycloak.truststore");
@@ -63,13 +71,17 @@ public class AdminClientUtil {
         }
 
         return Keycloak.getInstance(authServerContextRoot + "/auth",
-                MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID, null, ssl, jacksonProvider);
+                realmName, username, password, clientId, clientSecret, ssl, jacksonProvider);
     }
 
     public static Keycloak createAdminClient() throws Exception {
         return createAdminClient(false, AuthServerTestEnricher.getAuthServerContextRoot());
     }
 
+    public static Keycloak createAdminClient(boolean ignoreUnknownProperties) throws Exception {
+        return createAdminClient(ignoreUnknownProperties, AuthServerTestEnricher.getAuthServerContextRoot());
+    }
+
     private static SSLContext getSSLContextWithTrustore(File file, String password) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException {
         if (!file.isFile()) {
             throw new RuntimeException("Truststore file not found: " + file.getAbsolutePath());
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 4c89eaa..207a317 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -102,7 +102,7 @@ public class OAuthClient {
 
     private String maxAge;
 
-    private String responseType = OAuth2Constants.CODE;
+    private String responseType;
 
     private String responseMode;
 
@@ -171,6 +171,8 @@ public class OAuthClient {
         clientSessionState = null;
         clientSessionHost = null;
         maxAge = null;
+        responseType = OAuth2Constants.CODE;
+        responseMode = null;
         nonce = null;
         request = null;
         requestUri = null;
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java
index 17ff44a..192712e 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java
@@ -17,13 +17,13 @@
 
 package org.keycloak.testsuite.util;
 
-import java.util.LinkedList;
 import java.util.List;
 
 import javax.ws.rs.NotFoundException;
 
 import org.keycloak.admin.client.Keycloak;
 import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.common.util.ConcurrentMultivaluedHashMap;
 
 /**
  * Enlist resources to be cleaned after test method
@@ -32,18 +32,21 @@ import org.keycloak.admin.client.resource.RealmResource;
  */
 public class TestCleanup {
 
+    private static final String IDENTITY_PROVIDER_ALIASES = "IDENTITY_PROVIDER_ALIASES";
+    private static final String USER_IDS = "USER_IDS";
+    private static final String COMPONENT_IDS = "COMPONENT_IDS";
+    private static final String CLIENT_UUIDS = "CLIENT_UUIDS";
+    private static final String ROLE_IDS = "ROLE_IDS";
+    private static final String GROUP_IDS = "GROUP_IDS";
+    private static final String AUTH_FLOW_IDS = "AUTH_FLOW_IDS";
+    private static final String AUTH_CONFIG_IDS = "AUTH_CONFIG_IDS";
+
     private final Keycloak adminClient;
     private final String realmName;
 
+    // Key is kind of entity (eg. "client", "role", "user" etc), Values are all kind of entities of given type to cleanup
+    private ConcurrentMultivaluedHashMap<String, String> entities = new ConcurrentMultivaluedHashMap<>();
 
-    private List<String> identityProviderAliases;
-    private List<String> userIds;
-    private List<String> componentIds;
-    private List<String> clientUuids;
-    private List<String> roleIds;
-    private List<String> groupIds;
-    private List<String> authFlowIds;
-    private List<String> authConfigIds;
 
     public TestCleanup(Keycloak adminClient, String realmName) {
         this.adminClient = adminClient;
@@ -52,72 +55,49 @@ public class TestCleanup {
 
 
     public void addUserId(String userId) {
-        if (userIds == null) {
-            userIds = new LinkedList<>();
-        }
-        userIds.add(userId);
+        entities.add(USER_IDS, userId);
     }
 
 
     public void addIdentityProviderAlias(String identityProviderAlias) {
-        if (identityProviderAliases == null) {
-            identityProviderAliases = new LinkedList<>();
-        }
-        identityProviderAliases.add(identityProviderAlias);
+        entities.add(IDENTITY_PROVIDER_ALIASES, identityProviderAlias);
     }
 
 
     public void addComponentId(String componentId) {
-        if (componentIds == null) {
-            componentIds = new LinkedList<>();
-        }
-        componentIds.add(componentId);
+        entities.add(COMPONENT_IDS, componentId);
     }
 
 
     public void addClientUuid(String clientUuid) {
-        if (clientUuids == null) {
-            clientUuids = new LinkedList<>();
-        }
-        clientUuids.add(clientUuid);
+        entities.add(CLIENT_UUIDS, clientUuid);
     }
 
 
     public void addRoleId(String roleId) {
-        if (roleIds == null) {
-            roleIds = new LinkedList<>();
-        }
-        roleIds.add(roleId);
+        entities.add(ROLE_IDS, roleId);
     }
 
 
     public void addGroupId(String groupId) {
-        if (groupIds == null) {
-            groupIds = new LinkedList<>();
-        }
-        groupIds.add(groupId);
+        entities.add(GROUP_IDS, groupId);
     }
 
 
     public void addAuthenticationFlowId(String flowId) {
-        if (authFlowIds == null) {
-            authFlowIds = new LinkedList<>();
-        }
-        authFlowIds.add(flowId);
+        entities.add(AUTH_FLOW_IDS, flowId);
     }
 
 
     public void addAuthenticationConfigId(String executionConfigId) {
-        if (authConfigIds == null) {
-            authConfigIds = new LinkedList<>();
-        }
-        authConfigIds.add(executionConfigId);
+        entities.add(AUTH_CONFIG_IDS, executionConfigId);
     }
 
 
     public void executeCleanup() {
         RealmResource realm = adminClient.realm(realmName);
 
+        List<String> userIds = entities.get(USER_IDS);
         if (userIds != null) {
             for (String userId : userIds) {
                 try {
@@ -128,6 +108,7 @@ public class TestCleanup {
             }
         }
 
+        List<String> identityProviderAliases = entities.get(IDENTITY_PROVIDER_ALIASES);
         if (identityProviderAliases != null) {
             for (String idpAlias : identityProviderAliases) {
                 try {
@@ -138,6 +119,7 @@ public class TestCleanup {
             }
         }
 
+        List<String> componentIds = entities.get(COMPONENT_IDS);
         if (componentIds != null) {
             for (String componentId : componentIds) {
                 try {
@@ -148,6 +130,7 @@ public class TestCleanup {
             }
         }
 
+        List<String> clientUuids = entities.get(CLIENT_UUIDS);
         if (clientUuids != null) {
             for (String clientUuId : clientUuids) {
                 try {
@@ -158,6 +141,7 @@ public class TestCleanup {
             }
         }
 
+        List<String> roleIds = entities.get(ROLE_IDS);
         if (roleIds != null) {
             for (String roleId : roleIds) {
                 try {
@@ -168,6 +152,7 @@ public class TestCleanup {
             }
         }
 
+        List<String> groupIds = entities.get(GROUP_IDS);
         if (groupIds != null) {
             for (String groupId : groupIds) {
                 try {
@@ -178,6 +163,7 @@ public class TestCleanup {
             }
         }
 
+        List<String> authFlowIds = entities.get(AUTH_FLOW_IDS);
         if (authFlowIds != null) {
             for (String flowId : authFlowIds) {
                 try {
@@ -188,6 +174,7 @@ public class TestCleanup {
             }
         }
 
+        List<String> authConfigIds = entities.get(AUTH_CONFIG_IDS);
         if (authConfigIds != null) {
             for (String configId : authConfigIds) {
                 try {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
index 1739370..262d0b2 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
@@ -167,6 +167,7 @@ public abstract class AbstractKeycloakTest {
                 removeRealm(testRealm.getRealm());
             }
         } else {
+            log.info("calling all TestCleanup");
             // Logout all users after the test
             List<RealmRepresentation> realms = testContext.getTestRealmReps();
             for (RealmRepresentation realm : realms) {
@@ -175,7 +176,12 @@ public abstract class AbstractKeycloakTest {
 
             // Cleanup objects
             for (TestCleanup cleanup : testContext.getCleanups().values()) {
-                cleanup.executeCleanup();
+                try {
+                    if (cleanup != null) cleanup.executeCleanup();
+                } catch (Exception e) {
+                    log.error("failed cleanup!", e);
+                    throw new RuntimeException(e);
+                }
             }
             testContext.getCleanups().clear();
         }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
index ecf48bf..420fad8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
@@ -198,6 +198,9 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
     @Page
     private SAMLIDPInitiatedLogin samlidpInitiatedLoginPage;
 
+    @Page
+    protected SalesPostAutodetectServlet salesPostAutodetectServletPage;
+
     public static final String FORBIDDEN_TEXT = "HTTP status code: 403";
 
     @Deployment(name = BadClientSalesPostSigServlet.DEPLOYMENT_NAME)
@@ -325,6 +328,11 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
         return samlServletDeployment(EmployeeServlet.DEPLOYMENT_NAME, "employee/WEB-INF/web.xml", SamlSPFacade.class, ServletTestUtils.class);
     }
 
+    @Deployment(name = SalesPostAutodetectServlet.DEPLOYMENT_NAME)
+    protected static WebArchive salesPostAutodetect() {
+        return samlServletDeployment(SalesPostAutodetectServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
+    }
+
     @Override
     public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
         testRealms.add(loadRealm("/adapter-test/keycloak-saml/testsaml.json"));
@@ -1115,6 +1123,54 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
         checkLoggedOut(differentCookieNameServletPage, testRealmSAMLPostLoginPage);
     }
 
+    @Test
+    /* KEYCLOAK-4980 */
+    public void testAutodetectBearerOnly() throws Exception {
+        Client client = ClientBuilder.newClient();
+
+        // Do not redirect client to login page if it's an XHR
+        WebTarget target = client.target(salesPostAutodetectServletPage.toString());
+        Response response = target.request().header("X-Requested-With", "XMLHttpRequest").get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+
+        // Do not redirect client to login page if it's a partial Faces request
+        response = target.request().header("Faces-Request", "partial/ajax").get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+
+        // Do not redirect client to login page if it's a SOAP request
+        response = target.request().header("SOAPAction", "").get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+
+        // Do not redirect client to login page if Accept header is missing
+        response = target.request().get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+
+        // Do not redirect client to login page if client does not understand HTML reponses
+        response = target.request().header(HttpHeaders.ACCEPT, "application/json,text/xml").get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+
+        // Redirect client to login page if it's not an XHR
+        response = target.request().header("X-Requested-With", "Dont-Know").header(HttpHeaders.ACCEPT, "*/*").get();
+        Assert.assertEquals(200, response.getStatus());
+        response.close();
+
+        // Redirect client to login page if client explicitely understands HTML responses
+        response = target.request().header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9").get();
+        Assert.assertEquals(200, response.getStatus());
+        response.close();
+
+        // Redirect client to login page if client understands all response types
+        response = target.request().header(HttpHeaders.ACCEPT, "*/*").get();
+        Assert.assertEquals(200, response.getStatus());
+        response.close();
+        client.close();
+    }
+
     private URI getAuthServerSamlEndpoint(String realm) throws IllegalArgumentException, UriBuilderException {
         return RealmsResource
                 .protocolUrl(UriBuilder.fromUri(getAuthServerRoot()))
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
index 5a50b08..e13794d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
@@ -151,6 +151,8 @@ public class ProvidersTest extends AbstractAuthenticationTest {
                 "Validates the password supplied as a 'password' form parameter in direct grant request");
         addProviderInfo(result, "direct-grant-validate-username", "Username Validation",
                 "Validates the username supplied as a 'username' form parameter in direct grant request");
+        addProviderInfo(result, "expected-param-authenticator", "TEST: Expected Parameter",
+                "You will be approved if you send query string parameter 'foo' with expected value.");
         addProviderInfo(result, "http-basic-authenticator", "HTTP Basic Authentication", "Validates username and password from Authorization HTTP header");
         addProviderInfo(result, "identity-provider-redirector", "Identity Provider Redirector", "Redirects to default Identity Provider or Identity Provider specified with kc_idp_hint query parameter");
         addProviderInfo(result, "idp-confirm-link", "Confirm link existing account", "Show the form where user confirms if he wants " +
@@ -163,6 +165,8 @@ public class ProvidersTest extends AbstractAuthenticationTest {
                 "User reviews and updates profile data retrieved from Identity Provider in the displayed form");
         addProviderInfo(result, "idp-username-password-form", "Username Password Form for identity provider reauthentication",
                 "Validates a password from login form. Username is already known from identity provider authentication");
+        addProviderInfo(result, "push-button-authenticator", "TEST: Button Login",
+                "Just press the button to login.");
         addProviderInfo(result, "reset-credential-email", "Send Reset Email", "Send email to user and wait for response.");
         addProviderInfo(result, "reset-credentials-choose-user", "Choose User", "Choose a user to reset credentials for");
         addProviderInfo(result, "reset-otp", "Reset OTP", "Sets the Configure OTP required action if execution is REQUIRED.  " +
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
index 244323b..ae122f6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
@@ -107,7 +107,9 @@ public abstract class AbstractClientTest extends AbstractAuthTest {
         clientRep.setPublicClient(Boolean.FALSE);
         clientRep.setAuthorizationServicesEnabled(Boolean.TRUE);
         clientRep.setServiceAccountsEnabled(Boolean.TRUE);
-        return createClient(clientRep);
+        String id = createClient(clientRep);
+        assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientResourcePath(id), ResourceType.AUTHORIZATION_RESOURCE_SERVER);
+        return id;
     }
 
     protected ClientRepresentation createOidcClientRep(String name) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ExportAuthorizationSettingsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ExportAuthorizationSettingsTest.java
index 6f5e65e..c71d12f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ExportAuthorizationSettingsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ExportAuthorizationSettingsTest.java
@@ -22,7 +22,6 @@ import java.util.List;
 import java.util.Map;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.AuthorizationResource;
 import org.keycloak.admin.client.resource.ClientResource;
@@ -120,7 +119,6 @@ public class ExportAuthorizationSettingsTest extends AbstractAuthorizationTest {
     
     //KEYCLOAK-4983
     @Test
-    @Ignore
     public void testRoleBasedPolicyWithMultipleRoles() {
         ClientResource clientResource = getClientResource();
 
@@ -152,31 +150,17 @@ public class ExportAuthorizationSettingsTest extends AbstractAuthorizationTest {
         //export authorization settings
         ResourceServerRepresentation exportSettings = authorizationResource.exportSettings();
         
-        //delete test-resource-server client
-        testRealmResource().clients().get(clientResource.toRepresentation().getId()).remove();
-        
-        //clear cache
-        testRealmResource().clearRealmCache();
-        //workaround for the fact that clearing realm cache doesn't clear authz cache
-        testingClient.testing("test").cache("authorization").clear();
-        
-        //create new client
-        ClientRepresentation client = ClientBuilder.create()
-                .clientId(RESOURCE_SERVER_CLIENT_ID)
-                .authorizationServicesEnabled(true)
-                .serviceAccountsEnabled(true)
-                .build();
-        testRealmResource().clients().create(client).close();
-        
-        //import exported settings
-        AuthorizationResource authorization = testRealmResource().clients().get(getClientByClientId(RESOURCE_SERVER_CLIENT_ID).getId()).authorization();
-        authorization.importSettings(exportSettings);
-        
-        //check imported settings - TODO
-        PolicyRepresentation result = authorization.policies().findByName("role-based-policy");
-        Map<String, String> config1 = result.getConfig();
-        ResourceServerRepresentation settings = authorization.getSettings();
-        System.out.println("");
+        boolean found = false;
+        for (PolicyRepresentation p : exportSettings.getPolicies()) {
+            if (p.getName().equals("role-based-policy")) {
+                found = true;
+                Assert.assertTrue(p.getConfig().get("roles").contains("test-client-1/client-role") && 
+                        p.getConfig().get("roles").contains("test-client-2/client-role"));
+            }
+        }
+        if (!found) {
+            Assert.fail("Policy \"role-based-policy\" was not found in exported settings.");
+        }
     }
     
     private ClientRepresentation getClientByClientId(String clientId) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/GroupPolicyManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/GroupPolicyManagementTest.java
new file mode 100644
index 0000000..57d86a7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/GroupPolicyManagementTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2017 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.testsuite.admin.client.authorization;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+
+import org.junit.Test;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.GroupPoliciesResource;
+import org.keycloak.admin.client.resource.GroupPolicyResource;
+import org.keycloak.admin.client.resource.PolicyResource;
+import org.keycloak.admin.client.resource.RolePoliciesResource;
+import org.keycloak.admin.client.resource.RolePolicyResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.authorization.DecisionStrategy;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.Logic;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+import org.keycloak.testsuite.util.GroupBuilder;
+import org.keycloak.testsuite.util.RealmBuilder;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class GroupPolicyManagementTest extends AbstractPolicyManagementTest {
+
+    @Override
+    protected RealmBuilder createTestRealm() {
+        return super.createTestRealm().group(GroupBuilder.create().name("Group A")
+                .subGroups(Arrays.asList("Group B", "Group D").stream().map(name -> {
+                    if ("Group B".equals(name)) {
+                        return GroupBuilder.create().name(name).subGroups(Arrays.asList("Group C", "Group E").stream().map(new Function<String, GroupRepresentation>() {
+                            @Override
+                            public GroupRepresentation apply(String name) {
+                                return GroupBuilder.create().name(name).build();
+                            }
+                        }).collect(Collectors.toList())).build();
+                    }
+                    return GroupBuilder.create().name(name).build();
+                }).collect(Collectors.toList()))
+                .build()).group(GroupBuilder.create().name("Group E").build());
+    }
+
+    @Test
+    public void testCreate() {
+        AuthorizationResource authorization = getClient().authorization();
+        GroupPolicyRepresentation representation = new GroupPolicyRepresentation();
+
+        representation.setName("Group Policy");
+        representation.setDescription("description");
+        representation.setDecisionStrategy(DecisionStrategy.CONSENSUS);
+        representation.setLogic(Logic.NEGATIVE);
+        representation.setGroupsClaim("groups");
+        representation.addGroupPath("/Group A/Group B/Group C", true);
+        representation.addGroupPath("Group E");
+
+        assertCreated(authorization, representation);
+    }
+
+    @Test
+    public void testUpdate() {
+        AuthorizationResource authorization = getClient().authorization();
+        GroupPolicyRepresentation representation = new GroupPolicyRepresentation();
+
+        representation.setName("Update Group Policy");
+        representation.setDescription("description");
+        representation.setDecisionStrategy(DecisionStrategy.CONSENSUS);
+        representation.setLogic(Logic.NEGATIVE);
+        representation.setGroupsClaim("groups");
+        representation.addGroupPath("/Group A/Group B/Group C", true);
+        representation.addGroupPath("Group E");
+
+        assertCreated(authorization, representation);
+
+        representation.setName("changed");
+        representation.setDescription("changed");
+        representation.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
+        representation.setLogic(Logic.POSITIVE);
+        representation.removeGroup("/Group A/Group B");
+
+        GroupPoliciesResource policies = authorization.policies().group();
+        GroupPolicyResource permission = policies.findById(representation.getId());
+
+        permission.update(representation);
+        assertRepresentation(representation, permission);
+
+        for (GroupPolicyRepresentation.GroupDefinition roleDefinition : representation.getGroups()) {
+            if (roleDefinition.getPath().equals("Group E")) {
+                roleDefinition.setExtendChildren(true);
+            }
+        }
+
+        permission.update(representation);
+        assertRepresentation(representation, permission);
+
+        representation.getGroups().clear();
+        representation.addGroupPath("/Group A/Group B");
+
+        permission.update(representation);
+        assertRepresentation(representation, permission);
+    }
+
+    @Test
+    public void testDelete() {
+        AuthorizationResource authorization = getClient().authorization();
+        GroupPolicyRepresentation representation = new GroupPolicyRepresentation();
+
+        representation.setName("Delete Group Policy");
+        representation.setGroupsClaim("groups");
+        representation.addGroupPath("/Group A/Group B/Group C", true);
+        representation.addGroupPath("Group E");
+
+        GroupPoliciesResource policies = authorization.policies().group();
+        Response response = policies.create(representation);
+        GroupPolicyRepresentation created = response.readEntity(GroupPolicyRepresentation.class);
+
+        policies.findById(created.getId()).remove();
+
+        GroupPolicyResource removed = policies.findById(created.getId());
+
+        try {
+            removed.toRepresentation();
+            fail("Permission not removed");
+        } catch (NotFoundException ignore) {
+
+        }
+    }
+
+    @Test
+    public void testGenericConfig() {
+        AuthorizationResource authorization = getClient().authorization();
+        GroupPolicyRepresentation representation = new GroupPolicyRepresentation();
+
+        representation.setName("Test Generic Config Permission");
+        representation.setGroupsClaim("groups");
+        representation.addGroupPath("/Group A");
+
+        GroupPoliciesResource policies = authorization.policies().group();
+        Response response = policies.create(representation);
+        GroupPolicyRepresentation created = response.readEntity(GroupPolicyRepresentation.class);
+
+        PolicyResource policy = authorization.policies().policy(created.getId());
+        PolicyRepresentation genericConfig = policy.toRepresentation();
+
+        assertNotNull(genericConfig.getConfig());
+        assertNotNull(genericConfig.getConfig().get("groups"));
+
+        GroupRepresentation group = getRealm().groups().groups().stream().filter(groupRepresentation -> groupRepresentation.getName().equals("Group A")).findFirst().get();
+
+        assertTrue(genericConfig.getConfig().get("groups").contains(group.getId()));
+    }
+
+    private void assertCreated(AuthorizationResource authorization, GroupPolicyRepresentation representation) {
+        GroupPoliciesResource policies = authorization.policies().group();
+        Response response = policies.create(representation);
+        GroupPolicyRepresentation created = response.readEntity(GroupPolicyRepresentation.class);
+        GroupPolicyResource policy = policies.findById(created.getId());
+        assertRepresentation(representation, policy);
+    }
+
+    private void assertRepresentation(GroupPolicyRepresentation representation, GroupPolicyResource permission) {
+        GroupPolicyRepresentation actual = permission.toRepresentation();
+        assertRepresentation(representation, actual, () -> permission.resources(), () -> Collections.emptyList(), () -> permission.associatedPolicies());
+        assertEquals(representation.getGroups().size(), actual.getGroups().size());
+        assertEquals(0, actual.getGroups().stream().filter(actualDefinition -> !representation.getGroups().stream()
+                .filter(groupDefinition -> getGroupPath(actualDefinition.getId()).equals(getCanonicalGroupPath(groupDefinition.getPath())) && actualDefinition.isExtendChildren() == groupDefinition.isExtendChildren())
+                .findFirst().isPresent())
+                .count());
+    }
+
+    private String getGroupPath(String id) {
+        return getRealm().groups().group(id).toRepresentation().getPath();
+    }
+
+    private String getCanonicalGroupPath(String path) {
+        if (path.charAt(0) == '/') {
+            return path;
+        }
+        return "/" + path;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
index 257eb7d..9b001ac 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
@@ -479,8 +479,8 @@ public class ClientTest extends AbstractAdminTest {
         // Create foo mapper
         ProtocolMapperRepresentation fooMapper = new ProtocolMapperRepresentation();
         fooMapper.setName("foo");
-        fooMapper.setProtocol("fooProtocol");
-        fooMapper.setProtocolMapper("fooMapper");
+        fooMapper.setProtocol("openid-connect");
+        fooMapper.setProtocolMapper("oidc-hardcoded-claim-mapper");
         fooMapper.setConsentRequired(true);
         Response response = mappersResource.createMapper(fooMapper);
         String location = response.getLocation().toString();
@@ -493,13 +493,13 @@ public class ClientTest extends AbstractAdminTest {
         assertEquals(fooMapper.getName(), "foo");
 
         // Update foo mapper
-        fooMapper.setProtocolMapper("foo-mapper-updated");
+        fooMapper.setConsentRequired(false);
         mappersResource.update(fooMapperId, fooMapper);
 
         assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.clientProtocolMapperPath(clientDbId, fooMapperId), fooMapper, ResourceType.PROTOCOL_MAPPER);
 
         fooMapper = mappersResource.getMapperById(fooMapperId);
-        assertEquals(fooMapper.getProtocolMapper(), "foo-mapper-updated");
+        assertFalse(fooMapper.isConsentRequired());
 
         // Remove foo mapper
         mappersResource.delete(fooMapperId);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java
new file mode 100644
index 0000000..8386aa6
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java
@@ -0,0 +1,705 @@
+/*
+ * 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.testsuite.admin;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.AuthorizationProviderFactory;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.representations.idm.ClientTemplateRepresentation;
+import org.keycloak.representations.idm.authorization.Logic;
+import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
+import org.keycloak.services.resources.admin.permissions.AdminPermissions;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.DecisionStrategy;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.util.AdminClientUtil;
+
+import javax.ws.rs.ClientErrorException;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+//@Ignore
+public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
+
+    public static final String CLIENT_NAME = "application";
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        RealmRepresentation testRealmRep = new RealmRepresentation();
+        testRealmRep.setId(TEST);
+        testRealmRep.setRealm(TEST);
+        testRealmRep.setEnabled(true);
+        testRealms.add(testRealmRep);
+    }
+    public static void setupDemo(KeycloakSession session) {
+        RealmModel realm = session.realms().getRealmByName(TEST);
+        ClientModel client = realm.addClient("sales-pipeline-application");
+        RoleModel clientAdmin = client.addRole("admin");
+        client.addRole("leader-creator");
+        client.addRole("viewLeads");
+        ClientModel client2 = realm.addClient("market-analysis-application");
+        RoleModel client2Admin = client2.addRole("admin");
+        client2.addRole("market-manager");
+        client2.addRole("viewMarkets");
+        GroupModel sales = realm.createGroup("sales");
+        RoleModel salesAppsAdminRole = realm.addRole("sales-apps-admin");
+        salesAppsAdminRole.addCompositeRole(clientAdmin);
+        salesAppsAdminRole.addCompositeRole(client2Admin);
+        ClientModel realmManagementClient = realm.getClientByClientId("realm-management");
+        RoleModel queryClient = realmManagementClient.getRole(AdminRoles.QUERY_CLIENTS);
+
+
+        UserModel admin = session.users().addUser(realm, "salesManager");
+        admin.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, admin, UserCredentialModel.password("password"));
+        admin = session.users().addUser(realm, "sales-group-admin");
+        admin.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, admin, UserCredentialModel.password("password"));
+        admin = session.users().addUser(realm, "sales-it");
+        admin.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, admin, UserCredentialModel.password("password"));
+        admin = session.users().addUser(realm, "sales-pipeline-admin");
+        admin.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, admin, UserCredentialModel.password("password"));
+        admin = session.users().addUser(realm, "client-admin");
+        admin.setEnabled(true);
+        admin.grantRole(queryClient);
+        session.userCredentialManager().updateCredential(realm, admin, UserCredentialModel.password("password"));
+
+        UserModel user = session.users().addUser(realm, "salesman");
+        user.setEnabled(true);
+        user.joinGroup(sales);
+
+        user = session.users().addUser(realm, "saleswoman");
+        user.setEnabled(true);
+
+    }
+
+    public static void setupPolices(KeycloakSession session) {
+        RealmModel realm = session.realms().getRealmByName(TEST);
+        AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+        RoleModel realmRole = realm.addRole("realm-role");
+        RoleModel realmRole2 = realm.addRole("realm-role2");
+        ClientModel client1 = realm.addClient(CLIENT_NAME);
+        ClientTemplateModel template = realm.addClientTemplate("template");
+        client1.setFullScopeAllowed(false);
+        RoleModel client1Role = client1.addRole("client-role");
+        GroupModel group = realm.createGroup("top");
+
+        RoleModel mapperRole = realm.addRole("mapper");
+        RoleModel managerRole = realm.addRole("manager");
+        RoleModel compositeRole = realm.addRole("composite-role");
+        compositeRole.addCompositeRole(mapperRole);
+        compositeRole.addCompositeRole(managerRole);
+
+        // realm-role and application.client-role will have a role policy associated with their map-role permission
+        {
+            permissions.roles().setPermissionsEnabled(client1Role, true);
+            Policy mapRolePermission = permissions.roles().mapRolePermission(client1Role);
+            ResourceServer server = permissions.roles().resourceServer(client1Role);
+            Policy mapperPolicy = permissions.roles().rolePolicy(server, mapperRole);
+            mapRolePermission.addAssociatedPolicy(mapperPolicy);
+        }
+
+        {
+            permissions.roles().setPermissionsEnabled(realmRole, true);
+            Policy mapRolePermission = permissions.roles().mapRolePermission(realmRole);
+            ResourceServer server = permissions.roles().resourceServer(realmRole);
+            Policy mapperPolicy = permissions.roles().rolePolicy(server, mapperRole);
+            mapRolePermission.addAssociatedPolicy(mapperPolicy);
+        }
+
+        // realmRole2 will have an empty map-role policy
+        {
+            permissions.roles().setPermissionsEnabled(realmRole2, true);
+        }
+
+        // setup Users manage policies
+        {
+            permissions.users().setPermissionsEnabled(true);
+            ResourceServer server = permissions.realmResourceServer();
+            Policy managerPolicy = permissions.roles().rolePolicy(server, managerRole);
+            Policy permission = permissions.users().managePermission();
+            permission.addAssociatedPolicy(managerPolicy);
+            permission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
+        }
+        {
+            permissions.groups().setPermissionsEnabled(group, true);
+        }
+        {
+            permissions.clients().setPermissionsEnabled(client1, true);
+        }
+        // setup Users impersonate policy
+        {
+            ClientModel realmManagementClient = realm.getClientByClientId("realm-management");
+            RoleModel adminRole = realmManagementClient.getRole(AdminRoles.REALM_ADMIN);
+            permissions.users().setPermissionsEnabled(true);
+            ResourceServer server = permissions.realmResourceServer();
+            Policy adminPolicy = permissions.roles().rolePolicy(server, adminRole);
+            adminPolicy.setLogic(Logic.NEGATIVE);
+            Policy permission = permissions.users().userImpersonatedPermission();
+            permission.addAssociatedPolicy(adminPolicy);
+            permission.setDecisionStrategy(DecisionStrategy.UNANIMOUS);
+        }
+
+
+    }
+
+    public static void setupUsers(KeycloakSession session) {
+        RealmModel realm = session.realms().getRealmByName(TEST);
+        ClientModel client = realm.getClientByClientId(CLIENT_NAME);
+        RoleModel realmRole = realm.getRole("realm-role");
+        RoleModel realmRole2 = realm.getRole("realm-role2");
+        RoleModel clientRole = client.getRole("client-role");
+        RoleModel mapperRole = realm.getRole("mapper");
+        RoleModel managerRole = realm.getRole("manager");
+        RoleModel compositeRole = realm.getRole("composite-role");
+        ClientModel realmManagementClient = realm.getClientByClientId("realm-management");
+        RoleModel adminRole = realmManagementClient.getRole(AdminRoles.REALM_ADMIN);
+        RoleModel queryGroupsRole = realmManagementClient.getRole(AdminRoles.QUERY_GROUPS);
+        RoleModel queryUsersRole = realmManagementClient.getRole(AdminRoles.QUERY_USERS);
+        RoleModel queryClientsRole = realmManagementClient.getRole(AdminRoles.QUERY_CLIENTS);
+
+        UserModel nomapAdmin = session.users().addUser(realm, "nomap-admin");
+        nomapAdmin.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, nomapAdmin, UserCredentialModel.password("password"));
+        nomapAdmin.grantRole(adminRole);
+
+        UserModel anotherAdmin = session.users().addUser(realm, "anotherAdmin");
+        anotherAdmin.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, anotherAdmin, UserCredentialModel.password("password"));
+        anotherAdmin.grantRole(adminRole);
+
+        UserModel authorizedUser = session.users().addUser(realm, "authorized");
+        authorizedUser.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, authorizedUser, UserCredentialModel.password("password"));
+        authorizedUser.grantRole(mapperRole);
+        authorizedUser.grantRole(managerRole);
+
+        UserModel authorizedComposite = session.users().addUser(realm, "authorizedComposite");
+        authorizedComposite.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, authorizedComposite, UserCredentialModel.password("password"));
+        authorizedComposite.grantRole(compositeRole);
+
+        UserModel unauthorizedUser = session.users().addUser(realm, "unauthorized");
+        unauthorizedUser.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, unauthorizedUser, UserCredentialModel.password("password"));
+
+        UserModel unauthorizedMapper = session.users().addUser(realm, "unauthorizedMapper");
+        unauthorizedMapper.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, unauthorizedMapper, UserCredentialModel.password("password"));
+        unauthorizedMapper.grantRole(managerRole);
+
+        UserModel user1 = session.users().addUser(realm, "user1");
+        user1.setEnabled(true);
+
+        // group management
+        AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+
+        GroupModel group =  KeycloakModelUtils.findGroupByPath(realm, "top");
+        UserModel groupMember = session.users().addUser(realm, "groupMember");
+        groupMember.joinGroup(group);
+        groupMember.setEnabled(true);
+        UserModel groupManager = session.users().addUser(realm, "groupManager");
+        groupManager.grantRole(queryGroupsRole);
+        groupManager.grantRole(queryUsersRole);
+        groupManager.setEnabled(true);
+        groupManager.grantRole(mapperRole);
+        session.userCredentialManager().updateCredential(realm, groupManager, UserCredentialModel.password("password"));
+
+        UserModel groupManagerNoMapper = session.users().addUser(realm, "noMapperGroupManager");
+        groupManagerNoMapper.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, groupManagerNoMapper, UserCredentialModel.password("password"));
+        groupManagerNoMapper.grantRole(queryGroupsRole);
+        groupManagerNoMapper.grantRole(queryUsersRole);
+
+        UserPolicyRepresentation groupManagerRep = new UserPolicyRepresentation();
+        groupManagerRep.setName("groupManagers");
+        groupManagerRep.addUser("groupManager");
+        groupManagerRep.addUser("noMapperGroupManager");
+        ResourceServer server = permissions.realmResourceServer();
+        Policy groupManagerPolicy = permissions.authz().getStoreFactory().getPolicyStore().create(groupManagerRep, server);
+        Policy groupManagerPermission = permissions.groups().manageMembersPermission(group);
+        groupManagerPermission.addAssociatedPolicy(groupManagerPolicy);
+        permissions.groups().viewPermission(group).addAssociatedPolicy(groupManagerPolicy);
+
+        UserModel clientMapper = session.users().addUser(realm, "clientMapper");
+        clientMapper.setEnabled(true);
+        clientMapper.grantRole(managerRole);
+        clientMapper.grantRole(queryUsersRole);
+        session.userCredentialManager().updateCredential(realm, clientMapper, UserCredentialModel.password("password"));
+        Policy clientMapperPolicy = permissions.clients().mapRolesPermission(client);
+        UserPolicyRepresentation userRep = new UserPolicyRepresentation();
+        userRep.setName("userClientMapper");
+        userRep.addUser("clientMapper");
+        Policy userPolicy = permissions.authz().getStoreFactory().getPolicyStore().create(userRep, permissions.clients().resourceServer(client));
+        clientMapperPolicy.addAssociatedPolicy(userPolicy);
+
+        UserModel clientManager = session.users().addUser(realm, "clientManager");
+        clientManager.setEnabled(true);
+        clientManager.grantRole(queryClientsRole);
+        session.userCredentialManager().updateCredential(realm, clientManager, UserCredentialModel.password("password"));
+
+        Policy clientManagerPolicy = permissions.clients().managePermission(client);
+        userRep = new UserPolicyRepresentation();
+        userRep.setName("clientManager");
+        userRep.addUser("clientManager");
+        userPolicy = permissions.authz().getStoreFactory().getPolicyStore().create(userRep, permissions.clients().resourceServer(client));
+        clientManagerPolicy.addAssociatedPolicy(userPolicy);
+
+
+        UserModel clientConfigurer = session.users().addUser(realm, "clientConfigurer");
+        clientConfigurer.setEnabled(true);
+        clientConfigurer.grantRole(queryClientsRole);
+        session.userCredentialManager().updateCredential(realm, clientConfigurer, UserCredentialModel.password("password"));
+
+        Policy clientConfigurePolicy = permissions.clients().configurePermission(client);
+        userRep = new UserPolicyRepresentation();
+        userRep.setName("clientConfigure");
+        userRep.addUser("clientConfigurer");
+        userPolicy = permissions.authz().getStoreFactory().getPolicyStore().create(userRep, permissions.clients().resourceServer(client));
+        clientConfigurePolicy.addAssociatedPolicy(userPolicy);
+
+
+
+
+
+
+    }
+
+    public static void evaluateLocally(KeycloakSession session) {
+        RealmModel realm = session.realms().getRealmByName(TEST);
+        RoleModel realmRole = realm.getRole("realm-role");
+        RoleModel realmRole2 = realm.getRole("realm-role2");
+        ClientModel client = realm.getClientByClientId(CLIENT_NAME);
+        RoleModel clientRole = client.getRole("client-role");
+
+        // test authorized
+        {
+            UserModel user = session.users().getUserByUsername("authorized", realm);
+            AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, user);
+            Assert.assertTrue(permissionsForAdmin.users().canManage());
+            Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole));
+            Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2));
+            Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole));
+        }
+        // test composite role
+        {
+            UserModel user = session.users().getUserByUsername("authorizedComposite", realm);
+            AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, user);
+            Assert.assertTrue(permissionsForAdmin.users().canManage());
+            Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole));
+            Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2));
+            Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole));
+        }
+
+        // test unauthorized
+        {
+            UserModel user = session.users().getUserByUsername("unauthorized", realm);
+            AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, user);
+            Assert.assertFalse(permissionsForAdmin.users().canManage());
+            Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole));
+            Assert.assertFalse(permissionsForAdmin.roles().canMapRole(clientRole));
+            Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2));
+        }
+        // test unauthorized mapper
+        {
+            UserModel user = session.users().getUserByUsername("unauthorizedMapper", realm);
+            AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, user);
+            Assert.assertTrue(permissionsForAdmin.users().canManage());
+            Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole));
+            Assert.assertFalse(permissionsForAdmin.roles().canMapRole(clientRole));
+            // will result to true because realmRole2 does not have any policies attached to this permission
+            Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2));
+        }
+        // test group management
+        {
+            UserModel admin = session.users().getUserByUsername("groupManager", realm);
+            AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, admin);
+            UserModel user = session.users().getUserByUsername("authorized", realm);
+            Assert.assertFalse(permissionsForAdmin.users().canManage(user));
+            Assert.assertFalse(permissionsForAdmin.users().canView(user));
+            UserModel member = session.users().getUserByUsername("groupMember", realm);
+            Assert.assertTrue(permissionsForAdmin.users().canManage(member));
+            Assert.assertTrue(permissionsForAdmin.users().canView(member));
+            Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole));
+            Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole));
+            Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2));
+
+        }
+        // test client.mapRoles
+        {
+            UserModel admin = session.users().getUserByUsername("clientMapper", realm);
+            AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, admin);
+            UserModel user = session.users().getUserByUsername("authorized", realm);
+            Assert.assertTrue(permissionsForAdmin.users().canManage(user));
+            Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole));
+            Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole));
+            Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2));
+
+        }
+
+    }
+
+
+    protected boolean isImportAfterEachMethod() {
+        return true;
+    }
+    //@Test
+    public void testDemo() throws Exception {
+        testingClient.server().run(FineGrainAdminUnitTest::setupDemo);
+        Thread.sleep(1000000000);
+    }
+
+
+    @Test
+    public void testEvaluationLocal() throws Exception {
+        testingClient.server().run(FineGrainAdminUnitTest::setupPolices);
+        testingClient.server().run(FineGrainAdminUnitTest::setupUsers);
+        testingClient.server().run(FineGrainAdminUnitTest::evaluateLocally);
+    }
+
+    @Test
+    public void testRestEvaluation() throws Exception {
+        testingClient.server().run(FineGrainAdminUnitTest::setupPolices);
+        testingClient.server().run(FineGrainAdminUnitTest::setupUsers);
+
+        UserRepresentation user1 = adminClient.realm(TEST).users().search("user1").get(0);
+        UserRepresentation anotherAdmin = adminClient.realm(TEST).users().search("anotherAdmin").get(0);
+        UserRepresentation groupMember = adminClient.realm(TEST).users().search("groupMember").get(0);
+        RoleRepresentation realmRole = adminClient.realm(TEST).roles().get("realm-role").toRepresentation();
+        List<RoleRepresentation> realmRoleSet = new LinkedList<>();
+        realmRoleSet.add(realmRole);
+        RoleRepresentation realmRole2 = adminClient.realm(TEST).roles().get("realm-role2").toRepresentation();
+        List<RoleRepresentation> realmRole2Set = new LinkedList<>();
+        realmRole2Set.add(realmRole2);
+        ClientRepresentation client = adminClient.realm(TEST).clients().findByClientId(CLIENT_NAME).get(0);
+        ClientTemplateRepresentation template = adminClient.realm(TEST).clientTemplates().findAll().get(0);
+        RoleRepresentation clientRole = adminClient.realm(TEST).clients().get(client.getId()).roles().get("client-role").toRepresentation();
+        List<RoleRepresentation> clientRoleSet = new LinkedList<>();
+        clientRoleSet.add(clientRole);
+
+        // test configure client
+        {
+            Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
+                    TEST, "clientConfigurer", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
+            client.setAdminUrl("http://nowhere");
+            realmClient.realm(TEST).clients().get(client.getId()).update(client);
+            client.setFullScopeAllowed(true);
+            try {
+                realmClient.realm(TEST).clients().get(client.getId()).update(client);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+            client.setFullScopeAllowed(false);
+            realmClient.realm(TEST).clients().get(client.getId()).update(client);
+
+            client.setClientTemplate(template.getName());
+            try {
+                realmClient.realm(TEST).clients().get(client.getId()).update(client);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+            client.setClientTemplate(null);
+            realmClient.realm(TEST).clients().get(client.getId()).update(client);
+
+            try {
+                realmClient.realm(TEST).clients().get(client.getId()).getScopeMappings().realmLevel().add(realmRoleSet);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+        }
+
+        // test illegal impersonation
+        {
+            Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
+                    TEST, "nomap-admin", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
+            realmClient.realm(TEST).users().get(user1.getId()).impersonate();
+            realmClient.close(); // just in case of cookie settings
+            realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
+                    TEST, "nomap-admin", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
+            try {
+                realmClient.realm(TEST).users().get(anotherAdmin.getId()).impersonate();
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+        }
+
+
+        {
+            Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
+                    TEST, "authorized", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
+            realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet);
+            List<RoleRepresentation> roles = adminClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAll();
+            Assert.assertTrue(roles.stream().anyMatch((r) -> {
+                return r.getName().equals("realm-role");
+            }));
+            realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().remove(realmRoleSet);
+            roles = adminClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAll();
+            Assert.assertTrue(roles.stream().noneMatch((r) -> {
+                return r.getName().equals("realm-role");
+            }));
+
+            realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).add(clientRoleSet);
+            roles = adminClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll();
+            Assert.assertTrue(roles.stream().anyMatch((r) -> {
+                return r.getName().equals("client-role");
+            }));
+            realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).remove(clientRoleSet);
+            roles = adminClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll();
+            Assert.assertTrue(roles.stream().noneMatch((r) -> {
+                return r.getName().equals("client-role");
+            }));
+            realmClient.close();
+        }
+
+        {
+            Keycloak realmClient= AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
+                    TEST, "authorizedComposite", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
+            realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet);
+            List<RoleRepresentation> roles = adminClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAll();
+            Assert.assertTrue(roles.stream().anyMatch((r) -> {
+                return r.getName().equals("realm-role");
+            }));
+            realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().remove(realmRoleSet);
+            roles = adminClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAll();
+            Assert.assertTrue(roles.stream().noneMatch((r) -> {
+                return r.getName().equals("realm-role");
+            }));
+
+            realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).add(clientRoleSet);
+            roles = adminClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll();
+            Assert.assertTrue(roles.stream().anyMatch((r) -> {
+                return r.getName().equals("client-role");
+            }));
+            realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).remove(clientRoleSet);
+            roles = adminClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll();
+            Assert.assertTrue(roles.stream().noneMatch((r) -> {
+                return r.getName().equals("client-role");
+            }));
+        }
+        {
+            Keycloak realmClient= AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
+                    TEST, "unauthorized", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
+            try {
+                realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+        }
+        {
+            Keycloak realmClient= AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
+                    TEST, "unauthorizedMapper", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
+            try {
+                realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+        }
+
+        {
+            Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
+                    TEST, "groupManager", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
+            List<RoleRepresentation> roles = null;
+            realmClient.realm(TEST).users().get(groupMember.getId()).roles().clientLevel(client.getId()).add(clientRoleSet);
+            roles = realmClient.realm(TEST).users().get(groupMember.getId()).roles().clientLevel(client.getId()).listAll();
+            Assert.assertTrue(roles.stream().anyMatch((r) -> {
+                return r.getName().equals("client-role");
+            }));
+            realmClient.realm(TEST).users().get(groupMember.getId()).roles().clientLevel(client.getId()).remove(clientRoleSet);
+
+            roles = realmClient.realm(TEST).users().get(groupMember.getId()).roles().realmLevel().listAvailable();
+            Assert.assertEquals(roles.size(), 1);
+            realmClient.realm(TEST).users().get(groupMember.getId()).roles().realmLevel().add(realmRoleSet);
+            realmClient.realm(TEST).users().get(groupMember.getId()).roles().realmLevel().remove(realmRoleSet);
+            try {
+                realmClient.realm(TEST).users().get(groupMember.getId()).roles().realmLevel().add(realmRole2Set);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+            try {
+                realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+        }
+
+
+        // test client.mapRoles
+        {
+            Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
+                    TEST, "clientMapper", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
+            List<RoleRepresentation> roles = null;
+            roles = realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll();
+            Assert.assertTrue(roles.isEmpty());
+            realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).add(clientRoleSet);
+            roles = realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll();
+            Assert.assertTrue(roles.stream().anyMatch((r) -> {
+                return r.getName().equals("client-role");
+            }));
+            roles = realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAvailable();
+            Assert.assertTrue(roles.isEmpty());
+            try {
+                realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+        }
+
+
+    }
+
+    @Test
+    public void testMasterRealm() throws Exception {
+        // test that master realm can still perform operations when policies are in place
+        //
+        testingClient.server().run(FineGrainAdminUnitTest::setupPolices);
+        testingClient.server().run(FineGrainAdminUnitTest::setupUsers);
+
+        UserRepresentation user1 = adminClient.realm(TEST).users().search("user1").get(0);
+        RoleRepresentation realmRole = adminClient.realm(TEST).roles().get("realm-role").toRepresentation();
+        List<RoleRepresentation> realmRoleSet = new LinkedList<>();
+        realmRoleSet.add(realmRole);
+        RoleRepresentation realmRole2 = adminClient.realm(TEST).roles().get("realm-role2").toRepresentation();
+        List<RoleRepresentation> realmRole2Set = new LinkedList<>();
+        realmRole2Set.add(realmRole);
+        ClientRepresentation client = adminClient.realm(TEST).clients().findByClientId(CLIENT_NAME).get(0);
+        RoleRepresentation clientRole = adminClient.realm(TEST).clients().get(client.getId()).roles().get("client-role").toRepresentation();
+        List<RoleRepresentation> clientRoleSet = new LinkedList<>();
+        clientRoleSet.add(clientRole);
+
+
+        {
+            Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting());
+            realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet);
+            List<RoleRepresentation> roles = adminClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAll();
+            Assert.assertTrue(roles.stream().anyMatch((r) -> {
+                return r.getName().equals("realm-role");
+            }));
+            realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().remove(realmRoleSet);
+            roles = adminClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAll();
+            Assert.assertTrue(roles.stream().noneMatch((r) -> {
+                return r.getName().equals("realm-role");
+            }));
+
+            realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).add(clientRoleSet);
+            roles = adminClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll();
+            Assert.assertTrue(roles.stream().anyMatch((r) -> {
+                return r.getName().equals("client-role");
+            }));
+            realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).remove(clientRoleSet);
+            roles = adminClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll();
+            Assert.assertTrue(roles.stream().noneMatch((r) -> {
+                return r.getName().equals("client-role");
+            }));
+            realmClient.close();
+        }
+
+    }
+    // testRestEvaluationMasterRealm
+    // testRestEvaluationMasterAdminTestRealm
+
+    // test role deletion that it cleans up authz objects
+    public static void setupDeleteTest(KeycloakSession session )  {
+        RealmModel realm = session.realms().getRealmByName(TEST);
+        RoleModel removedRole = realm.addRole("removedRole");
+        ClientModel client = realm.addClient("removedClient");
+        RoleModel removedClientRole = client.addRole("removedClientRole");
+        GroupModel removedGroup = realm.createGroup("removedGroup");
+        AdminPermissionManagement management = AdminPermissions.management(session, realm);
+        management.roles().setPermissionsEnabled(removedRole, true);
+        management.roles().setPermissionsEnabled(removedClientRole, true);
+        management.groups().setPermissionsEnabled(removedGroup, true);
+        management.clients().setPermissionsEnabled(client, true);
+    }
+
+    public static void invokeDelete(KeycloakSession session)  {
+        RealmModel realm = session.realms().getRealmByName(TEST);
+        AdminPermissionManagement management = AdminPermissions.management(session, realm);
+        List<Resource> byResourceServer = management.authz().getStoreFactory().getResourceStore().findByResourceServer(management.realmResourceServer().getId());
+        Assert.assertEquals(4, byResourceServer.size());
+        RoleModel removedRole = realm.getRole("removedRole");
+        realm.removeRole(removedRole);
+        ClientModel client = realm.getClientByClientId("removedClient");
+        RoleModel removedClientRole = client.getRole("removedClientRole");
+        client.removeRole(removedClientRole);
+        GroupModel group = KeycloakModelUtils.findGroupByPath(realm, "removedGroup");
+        realm.removeGroup(group);
+        byResourceServer = management.authz().getStoreFactory().getResourceStore().findByResourceServer(management.realmResourceServer().getId());
+        Assert.assertEquals(1, byResourceServer.size());
+        realm.removeClient(client.getId());
+        byResourceServer = management.authz().getStoreFactory().getResourceStore().findByResourceServer(management.realmResourceServer().getId());
+        Assert.assertEquals(0, byResourceServer.size());
+    }
+
+    @Test
+    public void testRemoveCleanup() throws Exception {
+        testingClient.server().run(FineGrainAdminUnitTest::setupDeleteTest);
+        testingClient.server().run(FineGrainAdminUnitTest::invokeDelete);
+    }
+
+
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IllegalAdminUpgradeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IllegalAdminUpgradeTest.java
new file mode 100644
index 0000000..7c6ced5
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IllegalAdminUpgradeTest.java
@@ -0,0 +1,648 @@
+/*
+ * 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.testsuite.admin;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.ImpersonationConstants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
+import org.keycloak.services.resources.admin.permissions.AdminPermissions;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.util.AdminClientUtil;
+
+import javax.ws.rs.ClientErrorException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class IllegalAdminUpgradeTest extends AbstractKeycloakTest {
+
+    public static final String CLIENT_NAME = "application";
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        RealmRepresentation testRealmRep = new RealmRepresentation();
+        testRealmRep.setId(TEST);
+        testRealmRep.setRealm(TEST);
+        testRealmRep.setEnabled(true);
+        testRealms.add(testRealmRep);
+    }
+
+    protected boolean isImportAfterEachMethod() {
+        return true;
+    }
+
+
+    public static void setupUsers(KeycloakSession session) {
+        RealmModel realm = session.realms().getRealmByName(TEST);
+        RealmModel master = session.realms().getRealmByName("master");
+        ClientModel realmAdminClient = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID);
+        ClientModel realmMasterAdminClient = realm.getMasterAdminClient();
+        RoleModel realmManageUsers = realmAdminClient.getRole(AdminRoles.MANAGE_USERS);
+        RoleModel masterManageUsers = realmMasterAdminClient.getRole(AdminRoles.MANAGE_USERS);
+        RoleModel masterMasterManageUSers = master.getMasterAdminClient().getRole(AdminRoles.MANAGE_USERS);
+
+        UserModel realmUser = session.users().addUser(realm, "userAdmin");
+        realmUser.grantRole(realmManageUsers);
+        realmUser.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, realmUser, UserCredentialModel.password("password"));
+
+        UserModel masterUser = session.users().addUser(master, "userAdmin");
+        masterUser.grantRole(masterManageUsers);
+        masterUser.setEnabled(true);
+        session.userCredentialManager().updateCredential(master, masterUser, UserCredentialModel.password("password"));
+
+        UserModel masterAdmin = session.users().addUser(master, "masterAdmin");
+        masterAdmin.grantRole(masterMasterManageUSers);
+        masterAdmin.setEnabled(true);
+        session.userCredentialManager().updateCredential(master, masterAdmin, UserCredentialModel.password("password"));
+
+        UserModel user = session.users().addUser(master, "user");
+        user.grantRole(masterManageUsers);
+        user.setEnabled(true);
+        session.userCredentialManager().updateCredential(master, user, UserCredentialModel.password("password"));
+
+        user = session.users().addUser(realm, "user");
+        user.grantRole(realmManageUsers);
+        user.setEnabled(true);
+        session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password("password"));
+    }
+
+    @Test
+    public void testRestEvaluation() throws Exception {
+        testingClient.server().run(IllegalAdminUpgradeTest::setupUsers);
+
+        UserRepresentation realmUserAdmin = adminClient.realm(TEST).users().search("userAdmin").get(0);
+        UserRepresentation masterUserAdmin = adminClient.realm("master").users().search("userAdmin").get(0);
+        UserRepresentation realmUser = adminClient.realm(TEST).users().search("user").get(0);
+        UserRepresentation masterUser = adminClient.realm("master").users().search("user").get(0);
+
+        ClientRepresentation realmAdminClient = adminClient.realm(TEST).clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0);
+        RoleRepresentation realmManageAuthorization = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_AUTHORIZATION).toRepresentation();
+        RoleRepresentation realmViewAuthorization = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_AUTHORIZATION).toRepresentation();
+        RoleRepresentation realmManageClients = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_CLIENTS).toRepresentation();
+        RoleRepresentation realmViewClients = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_CLIENTS).toRepresentation();
+        RoleRepresentation realmManageEvents = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_EVENTS).toRepresentation();
+        RoleRepresentation realmViewEvents = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_EVENTS).toRepresentation();
+        RoleRepresentation realmManageIdentityProviders = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_IDENTITY_PROVIDERS).toRepresentation();
+        RoleRepresentation realmViewIdentityProviders = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_IDENTITY_PROVIDERS).toRepresentation();
+        RoleRepresentation realmManageRealm = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_REALM).toRepresentation();
+        RoleRepresentation realmViewRealm = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_REALM).toRepresentation();
+        RoleRepresentation realmImpersonate = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(ImpersonationConstants.IMPERSONATION_ROLE).toRepresentation();
+        RoleRepresentation realmManageUsers = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_USERS).toRepresentation();
+        RoleRepresentation realmViewUsers = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_USERS).toRepresentation();
+        RoleRepresentation realmQueryUsers = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.QUERY_USERS).toRepresentation();
+        RoleRepresentation realmQueryClients = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.QUERY_CLIENTS).toRepresentation();
+        RoleRepresentation realmQueryGroups = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.QUERY_GROUPS).toRepresentation();
+
+        ClientRepresentation masterClient = adminClient.realm("master").clients().findByClientId(TEST + "-realm").get(0);
+        RoleRepresentation masterManageAuthorization = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_AUTHORIZATION).toRepresentation();
+        RoleRepresentation masterViewAuthorization = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_AUTHORIZATION).toRepresentation();
+        RoleRepresentation masterManageClients = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_CLIENTS).toRepresentation();
+        RoleRepresentation masterViewClients = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_CLIENTS).toRepresentation();
+        RoleRepresentation masterManageEvents = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_EVENTS).toRepresentation();
+        RoleRepresentation masterViewEvents = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_EVENTS).toRepresentation();
+        RoleRepresentation masterManageIdentityProviders = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_IDENTITY_PROVIDERS).toRepresentation();
+        RoleRepresentation masterViewIdentityProviders = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_IDENTITY_PROVIDERS).toRepresentation();
+        RoleRepresentation masterManageRealm = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_REALM).toRepresentation();
+        RoleRepresentation masterViewRealm = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_REALM).toRepresentation();
+        RoleRepresentation masterImpersonate = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(ImpersonationConstants.IMPERSONATION_ROLE).toRepresentation();
+        RoleRepresentation masterManageUsers = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_USERS).toRepresentation();
+        RoleRepresentation masterViewUsers = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_USERS).toRepresentation();
+        RoleRepresentation masterQueryUsers = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.QUERY_USERS).toRepresentation();
+        RoleRepresentation masterQueryClients = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.QUERY_CLIENTS).toRepresentation();
+        RoleRepresentation masterQueryGroups = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.QUERY_GROUPS).toRepresentation();
+
+        List<RoleRepresentation> roles = new LinkedList<>();
+
+        {
+            ClientRepresentation client = realmAdminClient;
+            Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
+                    TEST, "userAdmin", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
+            roles.clear();
+            roles.add(realmManageAuthorization);
+            try {
+                realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(realmViewAuthorization);
+            try {
+                realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(realmManageClients);
+            try {
+                realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(realmViewClients);
+            try {
+                realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(realmManageEvents);
+            try {
+                realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(realmViewEvents);
+            try {
+                realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(realmManageIdentityProviders);
+            try {
+                realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(realmViewIdentityProviders);
+            try {
+                realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(realmManageRealm);
+            try {
+                realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(realmViewRealm);
+            try {
+                realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(realmImpersonate);
+            try {
+                realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(realmManageUsers);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmViewUsers);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmQueryUsers);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmQueryGroups);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmQueryClients);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            realmClient.close();
+        }
+        // test master manageUsers only admin can do with master realm admin roles
+        {
+            ClientRepresentation client = masterClient;
+            Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
+                    "master", "masterAdmin", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
+            roles.clear();
+            roles.add(masterManageAuthorization);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterViewAuthorization);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterManageClients);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterViewClients);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterManageEvents);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterViewEvents);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterManageIdentityProviders);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterViewIdentityProviders);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterManageRealm);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterViewRealm);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterImpersonate);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterManageUsers);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterViewUsers);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterQueryUsers);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterQueryGroups);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            roles.clear();
+            roles.add(masterQueryClients);
+            try {
+                realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+                Assert.fail("should fail with forbidden exception");
+            } catch (ClientErrorException e) {
+                Assert.assertEquals(e.getResponse().getStatus(), 403);
+
+            }
+
+            realmClient.close();
+        }
+        // test master admin can add all admin roles in realm
+        {
+            ClientRepresentation client = realmAdminClient;
+            Keycloak realmClient = AdminClientUtil.createAdminClient();
+            roles.clear();
+            roles.add(realmManageAuthorization);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmViewAuthorization);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmManageClients);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmViewClients);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmManageEvents);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmViewEvents);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmManageIdentityProviders);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmViewIdentityProviders);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmManageRealm);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmViewRealm);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmImpersonate);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmManageUsers);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+
+            roles.clear();
+            roles.add(realmViewUsers);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+
+            roles.clear();
+            roles.add(realmQueryUsers);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+
+            roles.clear();
+            roles.add(realmQueryGroups);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(realmQueryClients);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            realmClient.close();
+        }
+        // test that "admin" in master realm can assign all roles of master realm realm client admin roles
+        {
+            ClientRepresentation client = masterClient;
+            Keycloak realmClient = AdminClientUtil.createAdminClient();
+            roles.clear();
+            roles.add(masterManageAuthorization);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterViewAuthorization);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterManageClients);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterViewClients);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterManageEvents);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterViewEvents);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterManageIdentityProviders);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterViewIdentityProviders);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterManageRealm);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterViewRealm);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterImpersonate);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterManageUsers);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+
+            roles.clear();
+            roles.add(masterViewUsers);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+
+            roles.clear();
+            roles.add(masterQueryUsers);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+
+            roles.clear();
+            roles.add(masterQueryGroups);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            roles.clear();
+            roles.add(masterQueryClients);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles);
+            realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles);
+
+            realmClient.close();
+        }
+    }
+
+
+
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/InitialAccessTokenResourceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/InitialAccessTokenResourceTest.java
index 055bfa2..064e7b8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/InitialAccessTokenResourceTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/InitialAccessTokenResourceTest.java
@@ -20,14 +20,17 @@ package org.keycloak.testsuite.admin;
 import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.ClientInitialAccessResource;
+import org.keycloak.client.registration.ClientRegistrationException;
 import org.keycloak.common.util.Time;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
 import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.testsuite.Assert;
 import org.keycloak.testsuite.util.AdminEventPaths;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -88,4 +91,40 @@ public class InitialAccessTokenResourceTest extends AbstractAdminTest {
         assertEquals(5, list.get(0).getCount() + list.get(1).getCount());
     }
 
+
+    @Test
+    public void testPeriodicExpiration() throws ClientRegistrationException, InterruptedException {
+        ClientInitialAccessPresentation response1 = resource.create(new ClientInitialAccessCreatePresentation(1, 1));
+        ClientInitialAccessPresentation response2 = resource.create(new ClientInitialAccessCreatePresentation(1000, 1));
+        ClientInitialAccessPresentation response3 = resource.create(new ClientInitialAccessCreatePresentation(1000, 0));
+        ClientInitialAccessPresentation response4 = resource.create(new ClientInitialAccessCreatePresentation(0, 1));
+
+        List<ClientInitialAccessPresentation> list = resource.list();
+        assertEquals(4, list.size());
+
+        setTimeOffset(10);
+
+        testingClient.testing().removeExpired(REALM_NAME);
+
+        list = resource.list();
+        assertEquals(2, list.size());
+
+        List<String> remainingIds = list.stream()
+                .map(initialAccessPresentation -> initialAccessPresentation.getId())
+                .collect(Collectors.toList());
+
+        Assert.assertNames(remainingIds, response2.getId(), response4.getId());
+
+        setTimeOffset(2000);
+
+        testingClient.testing().removeExpired(REALM_NAME);
+
+        list = resource.list();
+        assertEquals(1, list.size());
+        Assert.assertEquals(list.get(0).getId(), response4.getId());
+
+        // Cleanup
+        realm.clientInitialAccess().delete(response4.getId());
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
index 531157d..16b0804 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
@@ -51,7 +51,7 @@ import org.keycloak.representations.idm.authorization.PolicyRepresentation;
 import org.keycloak.representations.idm.authorization.ResourceRepresentation;
 import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
 import org.keycloak.representations.idm.authorization.ScopeRepresentation;
-import org.keycloak.services.resources.admin.RealmAuth.Resource;
+import org.keycloak.services.resources.admin.AdminAuth.Resource;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.Assert;
 import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
@@ -77,8 +77,8 @@ import java.util.concurrent.atomic.AtomicReference;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
-import static org.keycloak.services.resources.admin.RealmAuth.Resource.AUTHORIZATION;
-import static org.keycloak.services.resources.admin.RealmAuth.Resource.CLIENT;
+import static org.keycloak.services.resources.admin.AdminAuth.Resource.AUTHORIZATION;
+import static org.keycloak.services.resources.admin.AdminAuth.Resource.CLIENT;
 import org.keycloak.testsuite.ProfileAssume;
 
 /**
@@ -313,11 +313,18 @@ public class PermissionsTest extends AbstractKeycloakTest {
                 realm.removeDefaultGroup("nosuch");
             }
         }, Resource.REALM, true);
+        GroupRepresentation newGroup = new GroupRepresentation();
+        newGroup.setName("sample");
+        adminClient.realm(REALM_NAME).groups().add(newGroup);
+        GroupRepresentation group = adminClient.realms().realm(REALM_NAME).getGroupByPath("sample");
+
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.getGroupByPath("nosuch");
+                realm.getGroupByPath("sample");
             }
-        }, Resource.REALM, false);
+        }, Resource.USER, false);
+
+        adminClient.realms().realm(REALM_NAME).groups().group(group.getId()).remove();
 
         invoke(new InvocationWithResponse() {
             public void invoke(RealmResource realm, AtomicReference<Response> response) {
@@ -425,14 +432,19 @@ public class PermissionsTest extends AbstractKeycloakTest {
 
     @Test
     public void attackDetection() {
+        UserRepresentation newUser = new UserRepresentation();
+        newUser.setUsername("attacked");
+        newUser.setEnabled(true);
+        adminClient.realms().realm(REALM_NAME).users().create(newUser);
+        UserRepresentation user = adminClient.realms().realm(REALM_NAME).users().search("attacked").get(0);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.attackDetection().bruteForceUserStatus("nosuch");
+                realm.attackDetection().bruteForceUserStatus(user.getId());
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.attackDetection().clearBruteForceForUser("nosuch");
+                realm.attackDetection().clearBruteForceForUser(user.getId());
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
@@ -440,6 +452,7 @@ public class PermissionsTest extends AbstractKeycloakTest {
                 realm.attackDetection().clearAllBruteForce();
             }
         }, Resource.USER, true);
+        adminClient.realms().realm(REALM_NAME).users().get(user.getId()).remove();
     }
 
     @Test
@@ -462,183 +475,187 @@ public class PermissionsTest extends AbstractKeycloakTest {
                 response.set(realm.clients().create(ClientBuilder.create().clientId("foo").build()));
             }
         }, Resource.CLIENT, true);
+        ClientRepresentation foo = adminClient.realms().realm(REALM_NAME).clients().findByClientId("foo").get(0);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").toRepresentation();
+                realm.clients().get(foo.getId()).toRepresentation();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getInstallationProvider("nosuch");
+                realm.clients().get(foo.getId()).getInstallationProvider("nosuch");
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").update(new ClientRepresentation());
+                realm.clients().get(foo.getId()).update(foo);
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").remove();
+                realm.clients().get(foo.getId()).remove();
+                realm.clients().create(foo);
+                ClientRepresentation temp = realm.clients().findByClientId("foo").get(0);
+                foo.setId(temp.getId());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").generateNewSecret();
+                realm.clients().get(foo.getId()).generateNewSecret();
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").regenerateRegistrationAccessToken();
+                realm.clients().get(foo.getId()).regenerateRegistrationAccessToken();
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getSecret();
+                realm.clients().get(foo.getId()).getSecret();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getServiceAccountUser();
+                realm.clients().get(foo.getId()).getServiceAccountUser();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").pushRevocation();
+                realm.clients().get(foo.getId()).pushRevocation();
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getApplicationSessionCount();
+                realm.clients().get(foo.getId()).getApplicationSessionCount();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getUserSessions(0, 100);
+                realm.clients().get(foo.getId()).getUserSessions(0, 100);
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getOfflineSessionCount();
+                realm.clients().get(foo.getId()).getOfflineSessionCount();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getOfflineUserSessions(0, 100);
+                realm.clients().get(foo.getId()).getOfflineUserSessions(0, 100);
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").registerNode(Collections.<String, String>emptyMap());
+                realm.clients().get(foo.getId()).registerNode(Collections.<String, String>emptyMap());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").unregisterNode("nosuch");
+                realm.clients().get(foo.getId()).unregisterNode("nosuch");
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").testNodesAvailable();
+                realm.clients().get(foo.getId()).testNodesAvailable();
             }
         }, Resource.CLIENT, true);
 
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getCertficateResource("nosuch").generate();
+                realm.clients().get(foo.getId()).getCertficateResource("nosuch").generate();
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getCertficateResource("nosuch").generateAndGetKeystore(new KeyStoreConfig());
+                realm.clients().get(foo.getId()).getCertficateResource("nosuch").generateAndGetKeystore(new KeyStoreConfig());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getCertficateResource("nosuch").getKeyInfo();
+                realm.clients().get(foo.getId()).getCertficateResource("nosuch").getKeyInfo();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getCertficateResource("nosuch").getKeystore(new KeyStoreConfig());
+                realm.clients().get(foo.getId()).getCertficateResource("nosuch").getKeystore(new KeyStoreConfig());
             }
         }, Resource.CLIENT, false);
 
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getCertficateResource("nosuch").uploadJks(new MultipartFormDataOutput());
+                realm.clients().get(foo.getId()).getCertficateResource("nosuch").uploadJks(new MultipartFormDataOutput());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getCertficateResource("nosuch").uploadJksCertificate(new MultipartFormDataOutput());
+                realm.clients().get(foo.getId()).getCertficateResource("nosuch").uploadJksCertificate(new MultipartFormDataOutput());
             }
         }, Resource.CLIENT, true);
 
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getProtocolMappers().createMapper(Collections.EMPTY_LIST);
+                realm.clients().get(foo.getId()).getProtocolMappers().createMapper(Collections.EMPTY_LIST);
             }
         }, Resource.CLIENT, true);
         invoke(new InvocationWithResponse() {
             public void invoke(RealmResource realm, AtomicReference<Response> response) {
-                response.set(realm.clients().get("nosuch").getProtocolMappers().createMapper(new ProtocolMapperRepresentation()));
+                response.set(realm.clients().get(foo.getId()).getProtocolMappers().createMapper(new ProtocolMapperRepresentation()));
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getProtocolMappers().getMapperById("nosuch");
+                realm.clients().get(foo.getId()).getProtocolMappers().getMapperById("nosuch");
             }
         }, Resource.CLIENT, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getProtocolMappers().getMappers();
+                realm.clients().get(foo.getId()).getProtocolMappers().getMappers();
             }
         }, Resource.CLIENT, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getProtocolMappers().getMappersPerProtocol("nosuch");
+                realm.clients().get(foo.getId()).getProtocolMappers().getMappersPerProtocol("nosuch");
             }
         }, Resource.CLIENT, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getProtocolMappers().update("nosuch", new ProtocolMapperRepresentation());
+                realm.clients().get(foo.getId()).getProtocolMappers().update("nosuch", new ProtocolMapperRepresentation());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getProtocolMappers().delete("nosuch");
+                realm.clients().get(foo.getId()).getProtocolMappers().delete("nosuch");
             }
         }, Resource.CLIENT, true);
 
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getScopeMappings().getAll();
+                realm.clients().get(foo.getId()).getScopeMappings().getAll();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getScopeMappings().realmLevel().listAll();
+                realm.clients().get(foo.getId()).getScopeMappings().realmLevel().listAll();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getScopeMappings().realmLevel().listEffective();
+                realm.clients().get(foo.getId()).getScopeMappings().realmLevel().listEffective();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getScopeMappings().realmLevel().listAvailable();
+                realm.clients().get(foo.getId()).getScopeMappings().realmLevel().listAvailable();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getScopeMappings().realmLevel().add(Collections.<RoleRepresentation>emptyList());
+                realm.clients().get(foo.getId()).getScopeMappings().realmLevel().add(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").getScopeMappings().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
+                realm.clients().get(foo.getId()).getScopeMappings().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.CLIENT, true);
 
@@ -649,47 +666,47 @@ public class PermissionsTest extends AbstractKeycloakTest {
         }, Resource.CLIENT, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").roles().create(new RoleRepresentation());
+                realm.clients().get(foo.getId()).roles().create(new RoleRepresentation());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").roles().get("nosuch").toRepresentation();
+                realm.clients().get(foo.getId()).roles().get("nosuch").toRepresentation();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").roles().deleteRole("nosuch");
+                realm.clients().get(foo.getId()).roles().deleteRole("nosuch");
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").roles().get("nosuch").update(new RoleRepresentation());
+                realm.clients().get(foo.getId()).roles().get("nosuch").update(new RoleRepresentation());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").roles().get("nosuch").addComposites(Collections.<RoleRepresentation>emptyList());
+                realm.clients().get(foo.getId()).roles().get("nosuch").addComposites(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").roles().get("nosuch").deleteComposites(Collections.<RoleRepresentation>emptyList());
+                realm.clients().get(foo.getId()).roles().get("nosuch").deleteComposites(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").roles().get("nosuch").getRoleComposites();
+                realm.clients().get(foo.getId()).roles().get("nosuch").getRoleComposites();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").roles().get("nosuch").getRealmRoleComposites();
+                realm.clients().get(foo.getId()).roles().get("nosuch").getRealmRoleComposites();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clients().get("nosuch").roles().get("nosuch").getClientRoleComposites("nosuch");
+                realm.clients().get(foo.getId()).roles().get("nosuch").getClientRoleComposites("nosuch");
             }
         }, Resource.CLIENT, false);
     }
@@ -700,117 +717,123 @@ public class PermissionsTest extends AbstractKeycloakTest {
             public void invoke(RealmResource realm) {
                 realm.clientTemplates().findAll();
             }
-        }, Resource.CLIENT, false);
+        }, Resource.CLIENT, false, true);
         invoke(new InvocationWithResponse() {
             public void invoke(RealmResource realm, AtomicReference<Response> response) {
-                response.set(realm.clientTemplates().create(new ClientTemplateRepresentation()));
+                ClientTemplateRepresentation template = new ClientTemplateRepresentation();
+                template.setName("template");
+                response.set(realm.clientTemplates().create(template));
             }
         }, Resource.CLIENT, true);
+
+        ClientTemplateRepresentation template = adminClient.realms().realm(REALM_NAME).clientTemplates().findAll().get(0);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").toRepresentation();
+                realm.clientTemplates().get(template.getId()).toRepresentation();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").update(new ClientTemplateRepresentation());
+                realm.clientTemplates().get(template.getId()).update(template);
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").remove();
+                realm.clientTemplates().get(template.getId()).remove();
+                realm.clientTemplates().create(template);
             }
         }, Resource.CLIENT, true);
 
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getProtocolMappers().getMappers();
+                realm.clientTemplates().get(template.getId()).getProtocolMappers().getMappers();
             }
         }, Resource.CLIENT, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getProtocolMappers().getMappersPerProtocol("nosuch");
+                realm.clientTemplates().get(template.getId()).getProtocolMappers().getMappersPerProtocol("nosuch");
             }
         }, Resource.CLIENT, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getProtocolMappers().getMapperById("nosuch");
+                realm.clientTemplates().get(template.getId()).getProtocolMappers().getMapperById("nosuch");
             }
         }, Resource.CLIENT, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getProtocolMappers().update("nosuch", new ProtocolMapperRepresentation());
+                realm.clientTemplates().get(template.getId()).getProtocolMappers().update("nosuch", new ProtocolMapperRepresentation());
             }
         }, Resource.CLIENT, true);
         invoke(new InvocationWithResponse() {
             public void invoke(RealmResource realm, AtomicReference<Response> response) {
-                response.set(realm.clientTemplates().get("nosuch").getProtocolMappers().createMapper(new ProtocolMapperRepresentation()));
+                response.set(realm.clientTemplates().get(template.getId()).getProtocolMappers().createMapper(new ProtocolMapperRepresentation()));
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getProtocolMappers().createMapper(Collections.<ProtocolMapperRepresentation>emptyList());
+                realm.clientTemplates().get(template.getId()).getProtocolMappers().createMapper(Collections.<ProtocolMapperRepresentation>emptyList());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getProtocolMappers().delete("nosuch");
+                realm.clientTemplates().get(template.getId()).getProtocolMappers().delete("nosuch");
             }
         }, Resource.CLIENT, true);
 
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getScopeMappings().getAll();
+                realm.clientTemplates().get(template.getId()).getScopeMappings().getAll();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getScopeMappings().realmLevel().listAll();
+                realm.clientTemplates().get(template.getId()).getScopeMappings().realmLevel().listAll();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getScopeMappings().realmLevel().listAvailable();
+                realm.clientTemplates().get(template.getId()).getScopeMappings().realmLevel().listAvailable();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getScopeMappings().realmLevel().listEffective();
+                realm.clientTemplates().get(template.getId()).getScopeMappings().realmLevel().listEffective();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getScopeMappings().realmLevel().add(Collections.<RoleRepresentation>emptyList());
+                realm.clientTemplates().get(template.getId()).getScopeMappings().realmLevel().add(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getScopeMappings().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
+                realm.clientTemplates().get(template.getId()).getScopeMappings().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.CLIENT, true);
+        ClientRepresentation realmAccessClient = adminClient.realms().realm(REALM_NAME).clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getScopeMappings().clientLevel("nosuch").listAll();
+                realm.clientTemplates().get(template.getId()).getScopeMappings().clientLevel(realmAccessClient.getId()).listAll();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getScopeMappings().clientLevel("nosuch").listAvailable();
+                realm.clientTemplates().get(template.getId()).getScopeMappings().clientLevel(realmAccessClient.getId()).listAvailable();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getScopeMappings().clientLevel("nosuch").listEffective();
+                realm.clientTemplates().get(template.getId()).getScopeMappings().clientLevel(realmAccessClient.getId()).listEffective();
             }
         }, Resource.CLIENT, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getScopeMappings().clientLevel("nosuch").add(Collections.<RoleRepresentation>emptyList());
+                realm.clientTemplates().get(template.getId()).getScopeMappings().clientLevel(realmAccessClient.getId()).add(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.clientTemplates().get("nosuch").getScopeMappings().clientLevel("nosuch").remove(Collections.<RoleRepresentation>emptyList());
+                realm.clientTemplates().get(template.getId()).getScopeMappings().clientLevel(realmAccessClient.getId()).remove(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.CLIENT, true);
     }
@@ -837,10 +860,13 @@ public class PermissionsTest extends AbstractKeycloakTest {
     @Test
     public void clientAuthorization() {
         ProfileAssume.assumePreview();
+
+        ClientRepresentation newClient = new ClientRepresentation();
+        newClient.setClientId("foo-authz");
+        adminClient.realms().realm(REALM_NAME).clients().create(newClient);
+        ClientRepresentation foo = adminClient.realms().realm(REALM_NAME).clients().findByClientId("foo-authz").get(0);
         invoke(new InvocationWithResponse() {
             public void invoke(RealmResource realm, AtomicReference<Response> response) {
-                realm.clients().create(ClientBuilder.create().clientId("foo-authz").build());
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 foo.setServiceAccountsEnabled(true);
                 foo.setAuthorizationServicesEnabled(true);
                 realm.clients().get(foo.getId()).update(foo);
@@ -848,13 +874,11 @@ public class PermissionsTest extends AbstractKeycloakTest {
         }, CLIENT, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 realm.clients().get(foo.getId()).authorization().getSettings();
             }
         }, AUTHORIZATION, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 ResourceServerRepresentation settings = authorization.getSettings();
                 authorization.update(settings);
@@ -862,42 +886,36 @@ public class PermissionsTest extends AbstractKeycloakTest {
         }, AUTHORIZATION, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 authorization.resources().resources();
             }
         }, AUTHORIZATION, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 authorization.scopes().scopes();
             }
         }, AUTHORIZATION, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 authorization.policies().policies();
             }
         }, AUTHORIZATION, false);
         invoke(new InvocationWithResponse() {
             public void invoke(RealmResource realm, AtomicReference<Response> response) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 response.set(authorization.resources().create(new ResourceRepresentation("Test", Collections.emptySet())));
             }
         }, AUTHORIZATION, true);
         invoke(new InvocationWithResponse() {
             public void invoke(RealmResource realm, AtomicReference<Response> response) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 response.set(authorization.scopes().create(new ScopeRepresentation("Test")));
             }
         }, AUTHORIZATION, true);
         invoke(new InvocationWithResponse() {
             public void invoke(RealmResource realm, AtomicReference<Response> response) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 PolicyRepresentation representation = new PolicyRepresentation();
                 representation.setName("Test PermissionsTest");
@@ -910,42 +928,36 @@ public class PermissionsTest extends AbstractKeycloakTest {
         }, AUTHORIZATION, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 authorization.resources().resource("nosuch").update(new ResourceRepresentation());
             }
         }, AUTHORIZATION, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 authorization.scopes().scope("nosuch").update(new ScopeRepresentation());
             }
         }, AUTHORIZATION, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 authorization.policies().policy("nosuch").update(new PolicyRepresentation());
             }
         }, AUTHORIZATION, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 authorization.resources().resource("nosuch").remove();
             }
         }, AUTHORIZATION, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 authorization.scopes().scope("nosuch").remove();
             }
         }, AUTHORIZATION, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
                 AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
                 authorization.policies().policy("nosuch").remove();
             }
@@ -954,6 +966,10 @@ public class PermissionsTest extends AbstractKeycloakTest {
 
     @Test
     public void roles() {
+        RoleRepresentation newRole = new RoleRepresentation();
+        newRole.setName("sample-role");
+        adminClient.realm(REALM_NAME).roles().create(newRole);
+
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
                 realm.roles().list();
@@ -961,12 +977,12 @@ public class PermissionsTest extends AbstractKeycloakTest {
         }, Resource.REALM, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.roles().get("nosuch").toRepresentation();
+                realm.roles().get("sample-role").toRepresentation();
             }
         }, Resource.REALM, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.roles().get("nosuch").update(new RoleRepresentation());
+                realm.roles().get("sample-role").update(newRole);
             }
         }, Resource.REALM, true);
         invoke(new Invocation() {
@@ -976,39 +992,42 @@ public class PermissionsTest extends AbstractKeycloakTest {
         }, Resource.REALM, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.roles().deleteRole("nosuch");
+                realm.roles().deleteRole("sample-role");
+                // need to recreate for other tests
+                realm.roles().create(newRole);
             }
         }, Resource.REALM, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.roles().get("nosuch").getRoleComposites();
+                realm.roles().get("sample-role").getRoleComposites();
             }
         }, Resource.REALM, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.roles().get("nosuch").addComposites(Collections.<RoleRepresentation>emptyList());
+                realm.roles().get("sample-role").addComposites(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.REALM, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.roles().get("nosuch").deleteComposites(Collections.<RoleRepresentation>emptyList());
+                realm.roles().get("sample-role").deleteComposites(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.REALM, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.roles().get("nosuch").getRoleComposites();
+                realm.roles().get("sample-role").getRoleComposites();
             }
         }, Resource.REALM, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.roles().get("nosuch").getRealmRoleComposites();
+                realm.roles().get("sample-role").getRealmRoleComposites();
             }
         }, Resource.REALM, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.roles().get("nosuch").getClientRoleComposites("nosuch");
+                realm.roles().get("sample-role").getClientRoleComposites("nosuch");
             }
         }, Resource.REALM, false);
+        adminClient.realms().realm(REALM_NAME).roles().deleteRole("sample-role");
     }
 
     @Test
@@ -1175,51 +1194,61 @@ public class PermissionsTest extends AbstractKeycloakTest {
 
     @Test
     public void rolesById() {
+        RoleRepresentation newRole = new RoleRepresentation();
+        newRole.setName("role-by-id");
+        adminClient.realm(REALM_NAME).roles().create(newRole);
+        RoleRepresentation role = adminClient.realm(REALM_NAME).roles().get("role-by-id").toRepresentation();
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.rolesById().getRole("nosuch");
+                realm.rolesById().getRole(role.getId());
             }
         }, Resource.REALM, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.rolesById().updateRole("nosuch", new RoleRepresentation());
+                realm.rolesById().updateRole(role.getId(), role);
             }
         }, Resource.REALM, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.rolesById().deleteRole("nosuch");
+                realm.rolesById().deleteRole(role.getId());
+                // need to recreate for other tests
+                realm.roles().create(newRole);
+                RoleRepresentation temp = realm.roles().get("role-by-id").toRepresentation();
+                role.setId(temp.getId());
             }
         }, Resource.REALM, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.rolesById().getRoleComposites("nosuch");
+                realm.rolesById().getRoleComposites(role.getId());
             }
         }, Resource.REALM, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.rolesById().addComposites("nosuch", Collections.<RoleRepresentation>emptyList());
+                realm.rolesById().addComposites(role.getId(), Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.REALM, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.rolesById().deleteComposites("nosuch", Collections.<RoleRepresentation>emptyList());
+                realm.rolesById().deleteComposites(role.getId(), Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.REALM, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.rolesById().getRoleComposites("nosuch");
+                realm.rolesById().getRoleComposites(role.getId());
             }
         }, Resource.REALM, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.rolesById().getRealmRoleComposites("nosuch");
+                realm.rolesById().getRealmRoleComposites(role.getId());
             }
         }, Resource.REALM, false, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.rolesById().getClientRoleComposites("nosuch", "nosuch");
+                realm.rolesById().getClientRoleComposites(role.getId(), "nosuch");
             }
         }, Resource.REALM, false, true);
+
+        adminClient.realm(REALM_NAME).roles().deleteRole("role-by-id");
     }
 
     @Test
@@ -1237,85 +1266,95 @@ public class PermissionsTest extends AbstractKeycloakTest {
             }
         }, Resource.USER, true);
 
+        GroupRepresentation group = adminClient.realms().realm(REALM_NAME).getGroupByPath("mygroup");
+        ClientRepresentation realmAccessClient = adminClient.realms().realm(REALM_NAME).clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0);
+
+
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").toRepresentation();
+                realm.groups().group(group.getId()).toRepresentation();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").update(new GroupRepresentation());
-            }
-        }, Resource.USER, true);
-        invoke(new Invocation() {
-            public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").remove();
+                realm.groups().group(group.getId()).update(group);
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").members(0, 100);
+                realm.groups().group(group.getId()).members(0, 100);
             }
         }, Resource.USER, false);
         invoke(new InvocationWithResponse() {
             public void invoke(RealmResource realm, AtomicReference<Response> response) {
-                response.set(realm.groups().group("nosuch").subGroup(new GroupRepresentation()));
+                GroupRepresentation subgroup = new GroupRepresentation();
+                subgroup.setName("sub");
+                response.set(realm.groups().group(group.getId()).subGroup(subgroup));
             }
         }, Resource.USER, true);
 
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").roles().getAll();
+                realm.groups().group(group.getId()).roles().getAll();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").roles().realmLevel().listAll();
+                realm.groups().group(group.getId()).roles().realmLevel().listAll();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").roles().realmLevel().listEffective();
+                realm.groups().group(group.getId()).roles().realmLevel().listEffective();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").roles().realmLevel().listAvailable();
+                realm.groups().group(group.getId()).roles().realmLevel().listAvailable();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").roles().realmLevel().add(Collections.<RoleRepresentation>emptyList());
+                realm.groups().group(group.getId()).roles().realmLevel().add(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").roles().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
+                realm.groups().group(group.getId()).roles().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").roles().clientLevel("nosuch").listAll();
+                realm.groups().group(group.getId()).roles().clientLevel(realmAccessClient.getId()).listAll();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").roles().clientLevel("nosuch").listEffective();
+                realm.groups().group(group.getId()).roles().clientLevel(realmAccessClient.getId()).listEffective();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").roles().clientLevel("nosuch").listAvailable();
+                realm.groups().group(group.getId()).roles().clientLevel(realmAccessClient.getId()).listAvailable();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").roles().clientLevel("nosuch").add(Collections.<RoleRepresentation>emptyList());
+                realm.groups().group(group.getId()).roles().clientLevel(realmAccessClient.getId()).add(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.groups().group("nosuch").roles().clientLevel("nosuch").remove(Collections.<RoleRepresentation>emptyList());
+                realm.groups().group(group.getId()).roles().clientLevel(realmAccessClient.getId()).remove(Collections.<RoleRepresentation>emptyList());
+            }
+        }, Resource.USER, true);
+        invoke(new Invocation() {
+            public void invoke(RealmResource realm) {
+                realm.groups().group(group.getId()).remove();
+                group.setId(null);
+                realm.groups().add(group);
+                GroupRepresentation temp = realm.getGroupByPath("mygroup");
+                group.setId(temp.getId());
             }
         }, Resource.USER, true);
     }
@@ -1323,176 +1362,181 @@ public class PermissionsTest extends AbstractKeycloakTest {
     // Permissions for impersonation tested in ImpersonationTest
     @Test
     public void users() {
-        invoke(new Invocation() {
-            public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").toRepresentation();
-            }
-        }, Resource.USER, false);
         invoke(new InvocationWithResponse() {
             public void invoke(RealmResource realm, AtomicReference<Response> response) {
                 response.set(realm.users().create(UserBuilder.create().username("testuser").build()));
             }
         }, Resource.USER, true);
+        UserRepresentation user = adminClient.realms().realm(REALM_NAME).users().search("testuser").get(0);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").update(UserBuilder.create().enabled(true).build());
+                realm.users().get(user.getId()).remove();
+                realm.users().create(user);
+                UserRepresentation temp = realm.users().search("testuser").get(0);
+                user.setId(temp.getId());
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().search("foo", 0, 1);
+                realm.users().get(user.getId()).toRepresentation();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
+                realm.users().get(user.getId()).update(user);
+            }
+        }, Resource.USER, true);
+        invoke(new Invocation() {
+            public void invoke(RealmResource realm) {
                 realm.users().count();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").getUserSessions();
+                realm.users().get(user.getId()).getUserSessions();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").getOfflineSessions("nosuch");
+                realm.users().get(user.getId()).getOfflineSessions("nosuch");
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").getFederatedIdentity();
+                realm.users().get(user.getId()).getFederatedIdentity();
             }
         }, Resource.USER, false);
         invoke(new InvocationWithResponse() {
             public void invoke(RealmResource realm, AtomicReference<Response> response) {
                 response.set(realm.users()
-                        .get("nosuch")
+                        .get(user.getId())
                         .addFederatedIdentity("nosuch",
                                 FederatedIdentityBuilder.create().identityProvider("nosuch").userId("nosuch").userName("nosuch").build()));
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").removeFederatedIdentity("nosuch");
+                realm.users().get(user.getId()).removeFederatedIdentity("nosuch");
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").getConsents();
+                realm.users().get(user.getId()).getConsents();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").revokeConsent("testclient");
+                realm.users().get(user.getId()).revokeConsent("testclient");
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").logout();
+                realm.users().get(user.getId()).logout();
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").remove();
+                realm.users().get(user.getId()).resetPassword(CredentialBuilder.create().password("password").build());
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").resetPassword(CredentialBuilder.create().password("password").build());
+                realm.users().get(user.getId()).removeTotp();
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").removeTotp();
+                realm.users().get(user.getId()).resetPasswordEmail();
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").resetPasswordEmail();
+                realm.users().get(user.getId()).executeActionsEmail(Collections.<String>emptyList());
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").executeActionsEmail(Collections.<String>emptyList());
+                realm.users().get(user.getId()).sendVerifyEmail();
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").sendVerifyEmail();
-            }
-        }, Resource.USER, true);
-        invoke(new Invocation() {
-            public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").groups();
+                realm.users().get(user.getId()).groups();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").leaveGroup("nosuch");
+                realm.users().get(user.getId()).leaveGroup("nosuch");
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").joinGroup("nosuch");
+                realm.users().get(user.getId()).joinGroup("nosuch");
             }
         }, Resource.USER, true);
 
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").roles().getAll();
+                realm.users().get(user.getId()).roles().getAll();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").roles().realmLevel().listAll();
+                realm.users().get(user.getId()).roles().realmLevel().listAll();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").roles().realmLevel().listAvailable();
+                realm.users().get(user.getId()).roles().realmLevel().listAvailable();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").roles().realmLevel().listEffective();
+                realm.users().get(user.getId()).roles().realmLevel().listEffective();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").roles().realmLevel().add(Collections.<RoleRepresentation>emptyList());
+                realm.users().get(user.getId()).roles().realmLevel().add(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").roles().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
+                realm.users().get(user.getId()).roles().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.USER, true);
 
+        ClientRepresentation realmAccessClient = adminClient.realms().realm(REALM_NAME).clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").roles().clientLevel("nosuch").listAll();
+                realm.users().get(user.getId()).roles().clientLevel(realmAccessClient.getId()).listAll();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").roles().clientLevel("nosuch").listAvailable();
+                realm.users().get(user.getId()).roles().clientLevel(realmAccessClient.getId()).listAvailable();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").roles().clientLevel("nosuch").listEffective();
+                realm.users().get(user.getId()).roles().clientLevel(realmAccessClient.getId()).listEffective();
             }
         }, Resource.USER, false);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").roles().clientLevel("nosuch").add(Collections.<RoleRepresentation>emptyList());
+                realm.users().get(user.getId()).roles().clientLevel(realmAccessClient.getId()).add(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.USER, true);
         invoke(new Invocation() {
             public void invoke(RealmResource realm) {
-                realm.users().get("nosuch").roles().clientLevel("nosuch").remove(Collections.<RoleRepresentation>emptyList());
+                realm.users().get(user.getId()).roles().clientLevel(realmAccessClient.getId()).remove(Collections.<RoleRepresentation>emptyList());
             }
         }, Resource.USER, true);
+        invoke(new Invocation() {
+            public void invoke(RealmResource realm) {
+                realm.users().search("foo", 0, 1);
+            }
+        }, Resource.USER, false);
     }
 
     @Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageRestTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageRestTest.java
index 15f0564..d7f494f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageRestTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageRestTest.java
@@ -156,6 +156,65 @@ public class UserStorageRestTest extends AbstractAdminTest {
 
     }
 
+
+    // KEYCLOAK-4438
+    @Test
+    public void testKerberosAuthenticatorDisabledWhenProviderRemoved() {
+        // Assert kerberos authenticator DISABLED
+        AuthenticationExecutionInfoRepresentation kerberosExecution = findKerberosExecution();
+        Assert.assertEquals(kerberosExecution.getRequirement(), AuthenticationExecutionModel.Requirement.DISABLED.toString());
+
+        // create LDAP provider with kerberos
+        ComponentRepresentation ldapRep = new ComponentRepresentation();
+        ldapRep.setName("ldap2");
+        ldapRep.setProviderId("ldap");
+        ldapRep.setProviderType(UserStorageProvider.class.getName());
+        ldapRep.setConfig(new MultivaluedHashMap<>());
+        ldapRep.getConfig().putSingle("priority", Integer.toString(2));
+        ldapRep.getConfig().putSingle(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "true");
+
+
+        String id = createComponent(ldapRep);
+
+        // Assert kerberos authenticator ALTERNATIVE
+        kerberosExecution = findKerberosExecution();
+        Assert.assertEquals(kerberosExecution.getRequirement(), AuthenticationExecutionModel.Requirement.ALTERNATIVE.toString());
+
+        // Remove LDAP provider
+        realm.components().component(id).remove();
+
+        // Assert kerberos authenticator DISABLED
+        kerberosExecution = findKerberosExecution();
+        Assert.assertEquals(kerberosExecution.getRequirement(), AuthenticationExecutionModel.Requirement.DISABLED.toString());
+
+        // Add kerberos provider
+        ComponentRepresentation kerberosRep = new ComponentRepresentation();
+        kerberosRep.setName("kerberos");
+        kerberosRep.setProviderId("kerberos");
+        kerberosRep.setProviderType(UserStorageProvider.class.getName());
+        kerberosRep.setConfig(new MultivaluedHashMap<>());
+        kerberosRep.getConfig().putSingle("priority", Integer.toString(2));
+
+        id = createComponent(kerberosRep);
+
+
+        // Assert kerberos authenticator ALTERNATIVE
+        kerberosExecution = findKerberosExecution();
+        Assert.assertEquals(kerberosExecution.getRequirement(), AuthenticationExecutionModel.Requirement.ALTERNATIVE.toString());
+
+        // Switch kerberos authenticator to REQUIRED
+        kerberosExecution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED.toString());
+        realm.flows().updateExecutions("browser", kerberosExecution);
+
+        // Remove Kerberos provider
+        realm.components().component(id).remove();
+
+        // Assert kerberos authenticator DISABLED
+        kerberosExecution = findKerberosExecution();
+        Assert.assertEquals(kerberosExecution.getRequirement(), AuthenticationExecutionModel.Requirement.DISABLED.toString());
+    }
+
+
     @Test
     public void testValidateAndCreateLdapProvider() {
         // Invalid filter
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupNamePolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupNamePolicyTest.java
new file mode 100644
index 0000000..cc4b911
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupNamePolicyTest.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2017 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.testsuite.authz;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.authorization.client.AuthorizationDeniedException;
+import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.Configuration;
+import org.keycloak.authorization.client.representation.AuthorizationRequest;
+import org.keycloak.authorization.client.representation.AuthorizationResponse;
+import org.keycloak.authorization.client.representation.PermissionRequest;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
+import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.util.AdminClientUtil;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.GroupBuilder;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.RolesBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class GroupNamePolicyTest extends AbstractKeycloakTest {
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        ProtocolMapperRepresentation groupProtocolMapper = new ProtocolMapperRepresentation();
+
+        groupProtocolMapper.setName("groups");
+        groupProtocolMapper.setProtocolMapper(GroupMembershipMapper.PROVIDER_ID);
+        groupProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        groupProtocolMapper.setConsentRequired(false);
+        Map<String, String> config = new HashMap<>();
+        config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "groups");
+        config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
+        config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
+        groupProtocolMapper.setConfig(config);
+
+        testRealms.add(RealmBuilder.create().name("authz-test")
+                .roles(RolesBuilder.create()
+                        .realmRole(RoleBuilder.create().name("uma_authorization").build())
+                )
+                .group(GroupBuilder.create().name("Group A")
+                    .subGroups(Arrays.asList("Group B", "Group D").stream().map(name -> {
+                        if ("Group B".equals(name)) {
+                            return GroupBuilder.create().name(name).subGroups(Arrays.asList("Group C", "Group E").stream().map(new Function<String, GroupRepresentation>() {
+                                @Override
+                                public GroupRepresentation apply(String name) {
+                                    return GroupBuilder.create().name(name).build();
+                                }
+                            }).collect(Collectors.toList())).build();
+                        }
+                        return GroupBuilder.create().name(name).build();
+                    }).collect(Collectors.toList())).build())
+                .group(GroupBuilder.create().name("Group E").build())
+                .user(UserBuilder.create().username("marta").password("password").addRoles("uma_authorization").addGroups("Group A"))
+                .user(UserBuilder.create().username("alice").password("password").addRoles("uma_authorization"))
+                .user(UserBuilder.create().username("kolo").password("password").addRoles("uma_authorization"))
+                .client(ClientBuilder.create().clientId("resource-server-test")
+                    .secret("secret")
+                    .authorizationServicesEnabled(true)
+                    .redirectUris("http://localhost/resource-server-test")
+                    .defaultRoles("uma_protection")
+                    .directAccessGrants()
+                    .protocolMapper(groupProtocolMapper))
+                .build());
+    }
+
+    @Before
+    public void configureAuthorization() throws Exception {
+        createResource("Resource A");
+        createResource("Resource B");
+        createResource("Resource C");
+
+        createGroupPolicy("Only Group A Policy", "/Group A", true);
+        createGroupPolicy("Only Group B Policy", "/Group A/Group B", false);
+        createGroupPolicy("Only Group C Policy", "/Group A/Group B/Group C", false);
+
+        createResourcePermission("Resource A Permission", "Resource A", "Only Group A Policy");
+        createResourcePermission("Resource B Permission", "Resource B", "Only Group B Policy");
+        createResourcePermission("Resource C Permission", "Resource C", "Only Group C Policy");
+
+        RealmResource realm = getRealm();
+        GroupRepresentation group = getGroup("/Group A/Group B/Group C");
+        UserRepresentation user = realm.users().search("kolo").get(0);
+
+        realm.users().get(user.getId()).joinGroup(group.getId());
+
+        group = getGroup("/Group A/Group B");
+        user = realm.users().search("alice").get(0);
+
+        realm.users().get(user.getId()).joinGroup(group.getId());
+    }
+
+    @Test
+    public void testExactNameMatch() {
+        AuthzClient authzClient = getAuthzClient();
+        PermissionRequest request = new PermissionRequest();
+
+        request.setResourceSetName("Resource A");
+
+        String ticket = authzClient.protection().permission().forResource(request).getTicket();
+        AuthorizationResponse response = authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket));
+
+        assertNotNull(response.getRpt());
+
+        try {
+            authzClient.authorization("kolo", "password").authorize(new AuthorizationRequest(ticket));
+            fail("Should fail because user is not granted with expected group");
+        } catch (AuthorizationDeniedException ignore) {
+
+        }
+
+        try {
+            authzClient.authorization("alice", "password").authorize(new AuthorizationRequest(ticket));
+            fail("Should fail because user is not granted with expected group");
+        } catch (AuthorizationDeniedException ignore) {
+
+        }
+    }
+
+    @Test
+    public void testOnlyChildrenPolicy() throws Exception {
+        RealmResource realm = getRealm();
+        AuthzClient authzClient = getAuthzClient();
+        PermissionRequest request = new PermissionRequest();
+
+        request.setResourceSetName("Resource B");
+
+        String ticket = authzClient.protection().permission().forResource(request).getTicket();
+
+        try {
+            authzClient.authorization("kolo", "password").authorize(new AuthorizationRequest(ticket));
+            fail("Should fail because user is not granted with expected group");
+        } catch (AuthorizationDeniedException ignore) {
+
+        }
+
+        AuthorizationResponse response = authzClient.authorization("alice", "password").authorize(new AuthorizationRequest(ticket));
+
+        assertNotNull(response.getRpt());
+
+        try {
+            authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket));
+            fail("Should fail because user is not granted with expected role");
+        } catch (AuthorizationDeniedException ignore) {
+
+        }
+
+        request = new PermissionRequest();
+
+        request.setResourceSetName("Resource C");
+
+        ticket = authzClient.protection().permission().forResource(request).getTicket();
+
+        response = authzClient.authorization("kolo", "password").authorize(new AuthorizationRequest(ticket));
+
+        assertNotNull(response.getRpt());
+    }
+
+    private void createGroupPolicy(String name, String groupPath, boolean extendChildren) {
+        GroupPolicyRepresentation policy = new GroupPolicyRepresentation();
+
+        policy.setName(name);
+        policy.setGroupsClaim("groups");
+        policy.addGroupPath(groupPath, extendChildren);
+
+        getClient().authorization().policies().group().create(policy);
+    }
+
+    private void createResourcePermission(String name, String resource, String... policies) {
+        ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
+
+        permission.setName(name);
+        permission.addResource(resource);
+        permission.addPolicy(policies);
+
+        getClient().authorization().permissions().resource().create(permission);
+    }
+
+    private void createResource(String name) {
+        AuthorizationResource authorization = getClient().authorization();
+        ResourceRepresentation resource = new ResourceRepresentation(name);
+
+        authorization.resources().create(resource);
+    }
+
+    private RealmResource getRealm() {
+        try {
+            return AdminClientUtil.createAdminClient().realm("authz-test");
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to create admin client");
+        }
+    }
+
+    private ClientResource getClient(RealmResource realm) {
+        ClientsResource clients = realm.clients();
+        return clients.findByClientId("resource-server-test").stream().map(representation -> clients.get(representation.getId())).findFirst().orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]"));
+    }
+
+    private AuthzClient getAuthzClient() {
+        try {
+            return AuthzClient.create(JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/default-keycloak.json"), Configuration.class));
+        } catch (IOException cause) {
+            throw new RuntimeException("Failed to create authz client", cause);
+        }
+    }
+
+    private ClientResource getClient() {
+        return getClient(getRealm());
+    }
+
+    private GroupRepresentation getGroup(String path) {
+        String[] parts = path.split("/");
+        RealmResource realm = getRealm();
+        GroupRepresentation parent = null;
+
+        for (String part : parts) {
+            if ("".equals(part)) {
+                continue;
+            }
+            if (parent == null) {
+                parent = realm.groups().groups().stream().filter(new Predicate<GroupRepresentation>() {
+                    @Override
+                    public boolean test(GroupRepresentation groupRepresentation) {
+                        return part.equals(groupRepresentation.getName());
+                    }
+                }).findFirst().get();
+                continue;
+            }
+
+            GroupRepresentation group = getGroup(part, parent.getSubGroups());
+
+            if (path.endsWith(group.getName())) {
+                return group;
+            }
+
+            parent = group;
+        }
+
+        return null;
+    }
+
+    private GroupRepresentation getGroup(String name, List<GroupRepresentation> groups) {
+        for (GroupRepresentation group : groups) {
+            if (name.equals(group.getName())) {
+                return group;
+            }
+
+            GroupRepresentation child = getGroup(name, group.getSubGroups());
+
+            if (child != null && name.equals(child.getName())) {
+                return child;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupPathPolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupPathPolicyTest.java
new file mode 100644
index 0000000..19f74b4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupPathPolicyTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2017 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.testsuite.authz;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.authorization.client.AuthorizationDeniedException;
+import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.Configuration;
+import org.keycloak.authorization.client.representation.AuthorizationRequest;
+import org.keycloak.authorization.client.representation.AuthorizationResponse;
+import org.keycloak.authorization.client.representation.PermissionRequest;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
+import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.util.AdminClientUtil;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.GroupBuilder;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.RolesBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class GroupPathPolicyTest extends AbstractKeycloakTest {
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        ProtocolMapperRepresentation groupProtocolMapper = new ProtocolMapperRepresentation();
+
+        groupProtocolMapper.setName("groups");
+        groupProtocolMapper.setProtocolMapper(GroupMembershipMapper.PROVIDER_ID);
+        groupProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        groupProtocolMapper.setConsentRequired(false);
+        Map<String, String> config = new HashMap<>();
+        config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "groups");
+        config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
+        config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
+        config.put("full.path", "true");
+        groupProtocolMapper.setConfig(config);
+
+        testRealms.add(RealmBuilder.create().name("authz-test")
+                .roles(RolesBuilder.create()
+                        .realmRole(RoleBuilder.create().name("uma_authorization").build())
+                )
+                .group(GroupBuilder.create().name("Group A")
+                    .subGroups(Arrays.asList("Group B", "Group D").stream().map(name -> {
+                        if ("Group B".equals(name)) {
+                            return GroupBuilder.create().name(name).subGroups(Arrays.asList("Group C", "Group E").stream().map(new Function<String, GroupRepresentation>() {
+                                @Override
+                                public GroupRepresentation apply(String name) {
+                                    return GroupBuilder.create().name(name).build();
+                                }
+                            }).collect(Collectors.toList())).build();
+                        }
+                        return GroupBuilder.create().name(name).build();
+                    }).collect(Collectors.toList())).build())
+                .group(GroupBuilder.create().name("Group E").build())
+                .user(UserBuilder.create().username("marta").password("password").addRoles("uma_authorization").addGroups("Group A"))
+                .user(UserBuilder.create().username("alice").password("password").addRoles("uma_authorization"))
+                .user(UserBuilder.create().username("kolo").password("password").addRoles("uma_authorization"))
+                .client(ClientBuilder.create().clientId("resource-server-test")
+                    .secret("secret")
+                    .authorizationServicesEnabled(true)
+                    .redirectUris("http://localhost/resource-server-test")
+                    .defaultRoles("uma_protection")
+                    .directAccessGrants()
+                    .protocolMapper(groupProtocolMapper))
+                .build());
+    }
+
+    @Before
+    public void configureAuthorization() throws Exception {
+        createResource("Resource A");
+        createResource("Resource B");
+
+        createGroupPolicy("Parent And Children Policy", "/Group A", true);
+        createGroupPolicy("Only Children Policy", "/Group A/Group B/Group C", false);
+
+        createResourcePermission("Resource A Permission", "Resource A", "Parent And Children Policy");
+        createResourcePermission("Resource B Permission", "Resource B", "Only Children Policy");
+    }
+
+    @Test
+    public void testAllowParentAndChildren() {
+        AuthzClient authzClient = getAuthzClient();
+        PermissionRequest request = new PermissionRequest();
+
+        request.setResourceSetName("Resource A");
+
+        String ticket = authzClient.protection().permission().forResource(request).getTicket();
+        AuthorizationResponse response = authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket));
+
+        assertNotNull(response.getRpt());
+
+        RealmResource realm = getRealm();
+        GroupRepresentation group = getGroup("/Group A/Group B/Group C");
+        UserRepresentation user = realm.users().search("kolo").get(0);
+
+        realm.users().get(user.getId()).joinGroup(group.getId());
+
+        ticket = authzClient.protection().permission().forResource(request).getTicket();
+        response = authzClient.authorization("kolo", "password").authorize(new AuthorizationRequest(ticket));
+
+        assertNotNull(response.getRpt());
+    }
+
+    @Test
+    public void testOnlyChildrenPolicy() throws Exception {
+        RealmResource realm = getRealm();
+        AuthzClient authzClient = getAuthzClient();
+        PermissionRequest request = new PermissionRequest();
+
+        request.setResourceSetName("Resource B");
+
+        String ticket = authzClient.protection().permission().forResource(request).getTicket();
+
+        try {
+            authzClient.authorization("kolo", "password").authorize(new AuthorizationRequest(ticket));
+            fail("Should fail because user is not granted with expected role");
+        } catch (AuthorizationDeniedException ignore) {
+
+        }
+
+        GroupRepresentation group = getGroup("/Group A/Group B/Group C");
+        UserRepresentation user = realm.users().search("kolo").get(0);
+
+        realm.users().get(user.getId()).joinGroup(group.getId());
+
+        AuthorizationResponse response = authzClient.authorization("kolo", "password").authorize(new AuthorizationRequest(ticket));
+
+        assertNotNull(response.getRpt());
+
+        try {
+            authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket));
+            fail("Should fail because user is not granted with expected role");
+        } catch (AuthorizationDeniedException ignore) {
+
+        }
+    }
+
+    private void createGroupPolicy(String name, String groupPath, boolean extendChildren) {
+        GroupPolicyRepresentation policy = new GroupPolicyRepresentation();
+
+        policy.setName(name);
+        policy.setGroupsClaim("groups");
+        policy.addGroupPath(groupPath, extendChildren);
+
+        getClient().authorization().policies().group().create(policy);
+    }
+
+    private void createResourcePermission(String name, String resource, String... policies) {
+        ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
+
+        permission.setName(name);
+        permission.addResource(resource);
+        permission.addPolicy(policies);
+
+        getClient().authorization().permissions().resource().create(permission);
+    }
+
+    private void createResource(String name) {
+        AuthorizationResource authorization = getClient().authorization();
+        ResourceRepresentation resource = new ResourceRepresentation(name);
+
+        authorization.resources().create(resource);
+    }
+
+    private RealmResource getRealm() {
+        try {
+            return AdminClientUtil.createAdminClient().realm("authz-test");
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to create admin client");
+        }
+    }
+
+    private ClientResource getClient(RealmResource realm) {
+        ClientsResource clients = realm.clients();
+        return clients.findByClientId("resource-server-test").stream().map(representation -> clients.get(representation.getId())).findFirst().orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]"));
+    }
+
+    private AuthzClient getAuthzClient() {
+        try {
+            return AuthzClient.create(JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/default-keycloak.json"), Configuration.class));
+        } catch (IOException cause) {
+            throw new RuntimeException("Failed to create authz client", cause);
+        }
+    }
+
+    private ClientResource getClient() {
+        return getClient(getRealm());
+    }
+
+    private GroupRepresentation getGroup(String path) {
+        String[] parts = path.split("/");
+        RealmResource realm = getRealm();
+        GroupRepresentation parent = null;
+
+        for (String part : parts) {
+            if ("".equals(part)) {
+                continue;
+            }
+            if (parent == null) {
+                parent = realm.groups().groups().stream().filter(new Predicate<GroupRepresentation>() {
+                    @Override
+                    public boolean test(GroupRepresentation groupRepresentation) {
+                        return part.equals(groupRepresentation.getName());
+                    }
+                }).findFirst().get();
+                continue;
+            }
+
+            GroupRepresentation group = getGroup(part, parent.getSubGroups());
+
+            if (path.endsWith(group.getName())) {
+                return group;
+            }
+
+            parent = group;
+        }
+
+        return null;
+    }
+
+    private GroupRepresentation getGroup(String name, List<GroupRepresentation> groups) {
+        for (GroupRepresentation group : groups) {
+            if (name.equals(group.getName())) {
+                return group;
+            }
+
+            GroupRepresentation child = getGroup(name, group.getSubGroups());
+
+            if (child != null && name.equals(child.getName())) {
+                return child;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationCompositeRoleTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationCompositeRoleTest.java
index 80de8b0..e5f84e2 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationCompositeRoleTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationCompositeRoleTest.java
@@ -22,6 +22,7 @@ import org.junit.Assert;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.AuthorizationProviderFactory;
 import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.model.Resource;
 import org.keycloak.authorization.model.ResourceServer;
@@ -77,7 +78,8 @@ public class PolicyEvaluationCompositeRoleTest extends AbstractKeycloakTest {
         RoleModel role1 = client.addRole("client-role1");
 
 
-        AuthorizationProvider authz = session.getProvider(AuthorizationProvider.class);
+        AuthorizationProviderFactory factory = (AuthorizationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class);
+        AuthorizationProvider authz = factory.create(session, realm);
         ResourceServer resourceServer = authz.getStoreFactory().getResourceServerStore().create(client.getId());
         Policy policy = createRolePolicy(authz, resourceServer, role1);
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedDocumentOnlyBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedDocumentOnlyBrokerTest.java
new file mode 100644
index 0000000..5f9fd0e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedDocumentOnlyBrokerTest.java
@@ -0,0 +1,83 @@
+package org.keycloak.testsuite.broker;
+
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.arquillian.SuiteContext;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
+
+public class KcSamlSignedDocumentOnlyBrokerTest extends KcSamlBrokerTest {
+
+    public static class KcSamlSignedBrokerConfiguration extends KcSamlBrokerConfiguration {
+
+        @Override
+        public RealmRepresentation createProviderRealm() {
+            RealmRepresentation realm = super.createProviderRealm();
+
+            realm.setPublicKey(REALM_PUBLIC_KEY);
+            realm.setPrivateKey(REALM_PRIVATE_KEY);
+
+            return realm;
+        }
+
+        @Override
+        public RealmRepresentation createConsumerRealm() {
+            RealmRepresentation realm = super.createConsumerRealm();
+
+            realm.setPublicKey(REALM_PUBLIC_KEY);
+            realm.setPrivateKey(REALM_PRIVATE_KEY);
+
+            return realm;
+        }
+
+        @Override
+        public List<ClientRepresentation> createProviderClients(SuiteContext suiteContext) {
+            List<ClientRepresentation> clientRepresentationList = super.createProviderClients(suiteContext);
+
+            for (ClientRepresentation client : clientRepresentationList) {
+                client.setClientAuthenticatorType("client-secret");
+                client.setSurrogateAuthRequired(false);
+
+                Map<String, String> attributes = client.getAttributes();
+                if (attributes == null) {
+                    attributes = new HashMap<>();
+                    client.setAttributes(attributes);
+                }
+
+                attributes.put("saml.assertion.signature", "false");
+                attributes.put("saml.server.signature", "true");
+                attributes.put("saml.client.signature", "true");
+                attributes.put("saml.signature.algorithm", "RSA_SHA256");
+                attributes.put("saml.signing.private.key", IDP_SAML_SIGN_KEY);
+                attributes.put("saml.signing.certificate", IDP_SAML_SIGN_CERT);
+            }
+
+            return clientRepresentationList;
+        }
+
+        @Override
+        public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
+            IdentityProviderRepresentation result = super.setUpIdentityProvider(suiteContext);
+
+            Map<String, String> config = result.getConfig();
+
+            config.put("validateSignature", "true");
+            config.put("wantAssertionsSigned", "false");
+            config.put("wantAuthnRequestsSigned", "true");
+            config.put("signingCertificate", IDP_SAML_SIGN_CERT);
+
+            return result;
+        }
+    }
+
+    @Override
+    protected BrokerConfiguration getBrokerConfiguration() {
+        return KcSamlSignedBrokerConfiguration.INSTANCE;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java
index c5bb785..8666e04 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java
@@ -18,6 +18,7 @@
 package org.keycloak.testsuite.client;
 
 
+import org.apache.commons.lang.StringUtils;
 import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.ClientResource;
@@ -44,6 +45,7 @@ import org.keycloak.testsuite.util.UserInfoClientUtil;
 import javax.ws.rs.client.Client;
 import javax.ws.rs.core.Response;
 import java.util.ArrayList;
+import java.util.Base64;
 import java.util.Collections;
 import java.util.List;
 
@@ -319,11 +321,20 @@ public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrati
         oauth.openLoginForm();
         loginResponse = new OAuthClient.AuthorizationEndpointResponse(oauth);
         accessTokenResponse = oauth.doAccessTokenRequest(loginResponse.getCode(), pairwiseClient.getClientSecret());
+
+        // Assert token payloads don't contain more than one "sub"
+        String accessTokenPayload = getPayload(accessTokenResponse.getAccessToken());
+        Assert.assertEquals(1, StringUtils.countMatches(accessTokenPayload, "\"sub\""));
+        String idTokenPayload = getPayload(accessTokenResponse.getIdToken());
+        Assert.assertEquals(1, StringUtils.countMatches(idTokenPayload, "\"sub\""));
+        String refreshTokenPayload = getPayload(accessTokenResponse.getRefreshToken());
+        Assert.assertEquals(1, StringUtils.countMatches(refreshTokenPayload, "\"sub\""));
+
         accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
         Assert.assertEquals("test-user", accessToken.getPreferredUsername());
         Assert.assertEquals("test-user@localhost", accessToken.getEmail());
 
-        // Assert pairwise client has different subject like userId
+        // Assert pairwise client has different subject than userId
         String pairwiseUserId = accessToken.getSubject();
         Assert.assertNotEquals(pairwiseUserId, user.getId());
 
@@ -339,4 +350,9 @@ public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrati
             jaxrsClient.close();
         }
     }
-}
+
+    private String getPayload(String token) {
+        String payloadBase64 = token.split("\\.")[1];
+        return new String(Base64.getDecoder().decode(payloadBase64));
+    }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractAdminCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractAdminCrossDCTest.java
new file mode 100644
index 0000000..2ad3cc3
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractAdminCrossDCTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 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.testsuite.crossdc;
+
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.Retry;
+import org.keycloak.testsuite.arquillian.InfinispanStatistics;
+import org.keycloak.testsuite.events.EventsListenerProviderFactory;
+import org.keycloak.testsuite.util.TestCleanup;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import static org.junit.Assert.assertThat;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public abstract class AbstractAdminCrossDCTest extends AbstractCrossDCTest {
+
+    protected static final String REALM_NAME = "admin-client-test";
+
+    protected RealmResource realm;
+    protected String realmId;
+
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+        findTestApp(testRealm).setDirectAccessGrantsEnabled(true);
+    }
+
+
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        super.addTestRealms(testRealms);
+
+        RealmRepresentation adminRealmRep = new RealmRepresentation();
+        adminRealmRep.setId(REALM_NAME);
+        adminRealmRep.setRealm(REALM_NAME);
+        adminRealmRep.setEnabled(true);
+        Map<String, String> config = new HashMap<>();
+        config.put("from", "auto@keycloak.org");
+        config.put("host", "localhost");
+        config.put("port", "3025");
+        adminRealmRep.setSmtpServer(config);
+
+        List<String> eventListeners = new ArrayList<>();
+        eventListeners.add(JBossLoggingEventListenerProviderFactory.ID);
+        eventListeners.add(EventsListenerProviderFactory.PROVIDER_ID);
+        adminRealmRep.setEventsListeners(eventListeners);
+
+        testRealms.add(adminRealmRep);
+    }
+
+    @Before
+    public void setRealm() {
+        realm = adminClient.realm(REALM_NAME);
+        realmId = realm.toRepresentation().getId();
+    }
+
+    @Override
+    protected TestCleanup getCleanup() {
+        return getCleanup(REALM_NAME);
+    }
+
+    protected <T extends Comparable> void assertSingleStatistics(InfinispanStatistics stats, String key, Runnable testedCode, Function<T, Matcher<? super T>> matcherOnOldStat) {
+        stats.reset();
+
+        T oldStat = (T) stats.getSingleStatistics(key);
+        testedCode.run();
+
+        Retry.execute(() -> {
+            T newStat = (T) stats.getSingleStatistics(key);
+
+            Matcher<? super T> matcherInstance = matcherOnOldStat.apply(oldStat);
+            assertThat(newStat, matcherInstance);
+        }, 5, 200);
+    }
+
+    protected void assertStatistics(InfinispanStatistics stats, Runnable testedCode, BiConsumer<Map<String, Object>, Map<String, Object>> assertionOnStats) {
+        stats.reset();
+
+        Map<String, Object> oldStat = stats.getStatistics();
+        testedCode.run();
+
+        Retry.execute(() -> {
+            Map<String, Object> newStat = stats.getStatistics();
+            assertionOnStats.accept(oldStat, newStat);
+        }, 5, 200);
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractCrossDCTest.java
new file mode 100644
index 0000000..b4d4236
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractCrossDCTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2017 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.testsuite.crossdc;
+
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.models.Constants;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+import org.keycloak.testsuite.arquillian.LoadBalancerController;
+import org.keycloak.testsuite.arquillian.annotation.LoadBalancer;
+import org.keycloak.testsuite.auth.page.AuthRealm;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.jboss.arquillian.container.test.api.ContainerController;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.junit.After;
+import org.junit.Before;
+
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest {
+
+    // Keep the following constants in sync with arquillian
+    public static final String QUALIFIER_NODE_BALANCER = "auth-server-balancer-cross-dc";
+
+    @ArquillianResource
+    @LoadBalancer(value = QUALIFIER_NODE_BALANCER)
+    protected LoadBalancerController loadBalancerCtrl;
+
+    @ArquillianResource
+    protected ContainerController containerController;
+
+    protected Map<ContainerInfo, Keycloak> backendAdminClients = new HashMap<>();
+
+    @After
+    @Before
+    public void enableOnlyFirstNodeInFirstDc() {
+        this.loadBalancerCtrl.disableAllBackendNodes();
+        loadBalancerCtrl.enableBackendNodeByName(getAutomaticallyStartedBackendNodes(0)
+          .findFirst()
+          .orElseThrow(() -> new IllegalStateException("No node is started automatically"))
+          .getQualifier()
+        );
+    }
+
+    @Before
+    public void terminateManuallyStartedServers() {
+        log.debug("Halting all nodes that are started manually");
+        this.suiteContext.getDcAuthServerBackendsInfo().stream()
+          .flatMap(List::stream)
+          .filter(ContainerInfo::isStarted)
+          .filter(ContainerInfo::isManual)
+          .map(ContainerInfo::getQualifier)
+          .forEach(containerController::stop);
+    }
+
+    @Override
+    public void importTestRealms() {
+        enableOnlyFirstNodeInFirstDc();
+        super.importTestRealms();
+    }
+
+    @Override
+    public void afterAbstractKeycloakTest() {
+        enableOnlyFirstNodeInFirstDc();
+        super.afterAbstractKeycloakTest();
+    }
+
+    @Override
+    public void deleteCookies() {
+        enableOnlyFirstNodeInFirstDc();
+        super.deleteCookies();
+    }
+
+    @Before
+    public void initLoadBalancer() {
+        log.debug("Initializing load balancer - only enabling started nodes in the first DC");
+        this.loadBalancerCtrl.disableAllBackendNodes();
+        // Enable only the started nodes in each datacenter
+        this.suiteContext.getDcAuthServerBackendsInfo().get(0).stream()
+          .filter(ContainerInfo::isStarted)
+          .map(ContainerInfo::getQualifier)
+          .forEach(loadBalancerCtrl::enableBackendNodeByName);
+    }
+
+    protected Keycloak createAdminClientFor(ContainerInfo node) {
+        log.info("Initializing admin client for " + node.getContextRoot() + "/auth");
+        return Keycloak.getInstance(node.getContextRoot() + "/auth", AuthRealm.MASTER, AuthRealm.ADMIN, AuthRealm.ADMIN, Constants.ADMIN_CLI_CLIENT_ID);
+    }
+
+    /**
+     * Creates admin client directed to the given node.
+     * @param node
+     * @return
+     */
+    protected Keycloak getAdminClientFor(ContainerInfo node) {
+        Keycloak client = backendAdminClients.get(node);
+        if (client == null && node.equals(suiteContext.getAuthServerInfo())) {
+            client = this.adminClient;
+        }
+        return client;
+    }
+
+    /**
+     * Disables routing requests to the given data center in the load balancer.
+     * @param dcIndex
+     */
+    public void disableDcOnLoadBalancer(int dcIndex) {
+        log.infof("Disabling load balancer for dc=%d", dcIndex);
+        this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex).forEach(c -> loadBalancerCtrl.disableBackendNodeByName(c.getQualifier()));
+    }
+
+    /**
+     * Enables routing requests to all started nodes to the given data center in the load balancer.
+     * @param dcIndex
+     */
+    public void enableDcOnLoadBalancer(int dcIndex) {
+        log.infof("Enabling load balancer for dc=%d", dcIndex);
+        final List<ContainerInfo> dcNodes = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex);
+        if (! dcNodes.stream().anyMatch(ContainerInfo::isStarted)) {
+            log.warnf("No node is started in DC %d", dcIndex);
+        } else {
+            dcNodes.stream()
+              .filter(ContainerInfo::isStarted)
+              .forEach(c -> loadBalancerCtrl.enableBackendNodeByName(c.getQualifier()));
+        }
+    }
+
+    /**
+     * Disables routing requests to the given node within the given data center in the load balancer.
+     * @param dcIndex
+     * @param nodeIndex
+     */
+    public void disableLoadBalancerNode(int dcIndex, int nodeIndex) {
+        log.infof("Disabling load balancer for dc=%d, node=%d", dcIndex, nodeIndex);
+        loadBalancerCtrl.disableBackendNodeByName(this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex).get(nodeIndex).getQualifier());
+    }
+
+    /**
+     * Enables routing requests to the given node within the given data center in the load balancer.
+     * @param dcIndex
+     * @param nodeIndex
+     */
+    public void enableLoadBalancerNode(int dcIndex, int nodeIndex) {
+        log.infof("Enabling load balancer for dc=%d, node=%d", dcIndex, nodeIndex);
+        final ContainerInfo backendNode = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex).get(nodeIndex);
+        if (backendNode == null) {
+            throw new IllegalArgumentException("Invalid node with index " + nodeIndex + " for DC " + dcIndex);
+        }
+        if (! backendNode.isStarted()) {
+            log.warnf("Node %s is not started in DC %d", backendNode.getQualifier(), dcIndex);
+        }
+        loadBalancerCtrl.enableBackendNodeByName(backendNode.getQualifier());
+    }
+
+    /**
+     * Starts a manually-controlled backend auth-server node in cross-DC scenario.
+     * @param dcIndex
+     * @param nodeIndex
+     * @return Started instance descriptor.
+     */
+    public ContainerInfo startBackendNode(int dcIndex, int nodeIndex) {
+        assertThat((Integer) dcIndex, lessThan(this.suiteContext.getDcAuthServerBackendsInfo().size()));
+        final List<ContainerInfo> dcNodes = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex);
+        assertThat((Integer) nodeIndex, lessThan(dcNodes.size()));
+        ContainerInfo dcNode = dcNodes.get(nodeIndex);
+        assertTrue("Node " + dcNode.getQualifier() + " has to be controlled manually", dcNode.isManual());
+        containerController.start(dcNode.getQualifier());
+        return dcNode;
+    }
+
+    /**
+     * Stops a manually-controlled backend auth-server node in cross-DC scenario.
+     * @param dcIndex
+     * @param nodeIndex
+     * @return Stopped instance descriptor.
+     */
+    public ContainerInfo stopBackendNode(int dcIndex, int nodeIndex) {
+        assertThat((Integer) dcIndex, lessThan(this.suiteContext.getDcAuthServerBackendsInfo().size()));
+        final List<ContainerInfo> dcNodes = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex);
+        assertThat((Integer) nodeIndex, lessThan(dcNodes.size()));
+        ContainerInfo dcNode = dcNodes.get(nodeIndex);
+        assertTrue("Node " + dcNode.getQualifier() + " has to be controlled manually", dcNode.isManual());
+        containerController.stop(dcNode.getQualifier());
+        return dcNode;
+    }
+
+    /**
+     * Returns stream of all nodes in the given dc that are started manually.
+     * @param dcIndex
+     * @return
+     */
+    public Stream<ContainerInfo> getManuallyStartedBackendNodes(int dcIndex) {
+        final List<ContainerInfo> dcNodes = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex);
+        return dcNodes.stream().filter(ContainerInfo::isManual);
+    }
+
+    /**
+     * Returns stream of all nodes in the given dc that are started automatically.
+     * @param dcIndex
+     * @return
+     */
+    public Stream<ContainerInfo> getAutomaticallyStartedBackendNodes(int dcIndex) {
+        final List<ContainerInfo> dcNodes = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex);
+        return dcNodes.stream().filter(c -> ! c.isManual());
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java
new file mode 100644
index 0000000..972be31
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2017 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.testsuite.crossdc;
+
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.Retry;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.page.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.util.GreenMailRule;
+import org.keycloak.testsuite.util.MailUtils;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.ws.rs.core.Response;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import org.keycloak.testsuite.arquillian.annotation.JmxInfinispanCacheStatistics;
+import org.keycloak.testsuite.arquillian.annotation.JmxInfinispanChannelStatistics;
+import org.keycloak.testsuite.arquillian.InfinispanStatistics;
+import org.keycloak.testsuite.arquillian.InfinispanStatistics.Constants;
+import java.util.concurrent.TimeUnit;
+import org.hamcrest.Matchers;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class ActionTokenCrossDCTest extends AbstractAdminCrossDCTest {
+
+    @Rule
+    public GreenMailRule greenMail = new GreenMailRule();
+
+    @Page
+    protected LoginPasswordUpdatePage passwordUpdatePage;
+
+    @Page
+    protected ErrorPage errorPage;
+
+    private String createUser(UserRepresentation userRep) {
+        Response response = realm.users().create(userRep);
+        String createdId = ApiUtil.getCreatedId(response);
+        response.close();
+
+        getCleanup().addUserId(createdId);
+
+        return createdId;
+    }
+
+    @Test
+    public void sendResetPasswordEmailSuccessWorksInCrossDc(
+      @JmxInfinispanCacheStatistics(dcIndex=0, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.ACTION_TOKEN_CACHE) InfinispanStatistics cacheDc0Node0Statistics,
+      @JmxInfinispanCacheStatistics(dcIndex=0, dcNodeIndex=1, cacheName=InfinispanConnectionProvider.ACTION_TOKEN_CACHE) InfinispanStatistics cacheDc0Node1Statistics,
+      @JmxInfinispanCacheStatistics(dcIndex=1, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.ACTION_TOKEN_CACHE) InfinispanStatistics cacheDc1Node0Statistics,
+      @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
+        startBackendNode(0, 1);
+        cacheDc0Node1Statistics.waitToBecomeAvailable(10, TimeUnit.SECONDS);
+
+        Comparable originalNumberOfEntries = cacheDc0Node0Statistics.getSingleStatistics(Constants.STAT_CACHE_NUMBER_OF_ENTRIES);
+
+        UserRepresentation userRep = new UserRepresentation();
+        userRep.setEnabled(true);
+        userRep.setUsername("user1");
+        userRep.setEmail("user1@test.com");
+
+        String id = createUser(userRep);
+
+        UserResource user = realm.users().get(id);
+        List<String> actions = new LinkedList<>();
+        actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
+        user.executeActionsEmail(actions);
+
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+
+        String link = MailUtils.getPasswordResetEmailLink(message);
+
+        Retry.execute(() -> channelStatisticsCrossDc.reset(), 3, 100);
+
+        assertSingleStatistics(cacheDc0Node0Statistics, Constants.STAT_CACHE_NUMBER_OF_ENTRIES,
+          () -> driver.navigate().to(link),
+          Matchers::is
+        );
+
+        passwordUpdatePage.assertCurrent();
+
+        // Verify that there was at least one message sent via the channel
+        assertSingleStatistics(channelStatisticsCrossDc, Constants.STAT_CHANNEL_SENT_MESSAGES,
+          () -> passwordUpdatePage.changePassword("new-pass", "new-pass"),
+          old -> greaterThan((Comparable) 0l)
+        );
+
+        assertEquals("Your account has been updated.", driver.getTitle());
+
+        // Verify that there was an action token added in the node which was targetted by the link
+        assertThat(cacheDc0Node0Statistics.getSingleStatistics(Constants.STAT_CACHE_NUMBER_OF_ENTRIES), greaterThan(originalNumberOfEntries));
+
+        disableDcOnLoadBalancer(0);
+        enableDcOnLoadBalancer(1);
+
+        // Make sure that after going to the link, the invalidated action token has been retrieved from Infinispan server cluster in the other DC
+        assertSingleStatistics(cacheDc1Node0Statistics, Constants.STAT_CACHE_NUMBER_OF_ENTRIES,
+          () -> driver.navigate().to(link),
+          Matchers::greaterThan
+        );
+
+        errorPage.assertCurrent();
+    }
+
+    @Test
+    public void sendResetPasswordEmailAfterNewNodeAdded() throws IOException, MessagingException {
+        disableDcOnLoadBalancer(1);
+
+        UserRepresentation userRep = new UserRepresentation();
+        userRep.setEnabled(true);
+        userRep.setUsername("user1");
+        userRep.setEmail("user1@test.com");
+
+        String id = createUser(userRep);
+
+        UserResource user = realm.users().get(id);
+        List<String> actions = new LinkedList<>();
+        actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
+        user.executeActionsEmail(actions);
+
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+
+        String link = MailUtils.getPasswordResetEmailLink(message);
+
+        driver.navigate().to(link);
+
+        passwordUpdatePage.assertCurrent();
+
+        passwordUpdatePage.changePassword("new-pass", "new-pass");
+
+        assertEquals("Your account has been updated.", driver.getTitle());
+
+        disableDcOnLoadBalancer(0);
+        getManuallyStartedBackendNodes(1)
+          .findFirst()
+          .ifPresent(c -> {
+              containerController.start(c.getQualifier());
+              loadBalancerCtrl.enableBackendNodeByName(c.getQualifier());
+          });
+
+        Retry.execute(() -> {
+            driver.navigate().to(link);
+            errorPage.assertCurrent();
+        }, 3, 400);
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
index b29abc1..677430d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
@@ -627,12 +627,13 @@ public class ExportImportUtil {
         assertPredicate(scopes, scopePredicates);
 
         List<PolicyRepresentation> policies = authzResource.policies().policies();
-        Assert.assertEquals(13, policies.size());
+        Assert.assertEquals(14, policies.size());
         List<Predicate<PolicyRepresentation>> policyPredicates = new ArrayList<>();
         policyPredicates.add(policyRepresentation -> "Any Admin Policy".equals(policyRepresentation.getName()));
         policyPredicates.add(policyRepresentation -> "Any User Policy".equals(policyRepresentation.getName()));
         policyPredicates.add(representation -> "Client and Realm Role Policy".equals(representation.getName()));
         policyPredicates.add(representation -> "Client Test Policy".equals(representation.getName()));
+        policyPredicates.add(representation -> "Group Policy Test".equals(representation.getName()));
         policyPredicates.add(policyRepresentation -> "Only Premium User Policy".equals(policyRepresentation.getName()));
         policyPredicates.add(policyRepresentation -> "wburke policy".equals(policyRepresentation.getName()));
         policyPredicates.add(policyRepresentation -> "All Users Policy".equals(policyRepresentation.getName()));
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/AuthenticatorSubflowsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/AuthenticatorSubflowsTest.java
new file mode 100644
index 0000000..d057ace
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/AuthenticatorSubflowsTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2017 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.testsuite.forms;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
+import org.keycloak.events.Details;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorConfigModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.authentication.ExpectedParamAuthenticator;
+import org.keycloak.testsuite.authentication.ExpectedParamAuthenticatorFactory;
+import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.openqa.selenium.By;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticatorSubflowsTest extends AbstractTestRealmKeycloakTest {
+
+    @Rule
+    public AssertEvents events = new AssertEvents(this);
+
+    @Page
+    protected AppPage appPage;
+
+    @Page
+    protected LoginPage loginPage;
+
+    @Page
+    protected ErrorPage errorPage;
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+
+    @Deployment
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class)
+                .addPackages(true, "org.keycloak.testsuite");
+    }
+
+
+    @Before
+    public void setupFlows() {
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+
+            if (realm.getBrowserFlow().getAlias().equals("parent-flow")) {
+                return;
+            }
+
+            // Parent flow
+            AuthenticationFlowModel browser = new AuthenticationFlowModel();
+            browser.setAlias("parent-flow");
+            browser.setDescription("browser based authentication");
+            browser.setProviderId("basic-flow");
+            browser.setTopLevel(true);
+            browser.setBuiltIn(true);
+            browser = realm.addAuthenticationFlow(browser);
+            realm.setBrowserFlow(browser);
+
+            // Subflow1
+            AuthenticationFlowModel subflow1 = new AuthenticationFlowModel();
+            subflow1.setTopLevel(false);
+            subflow1.setBuiltIn(true);
+            subflow1.setAlias("subflow-1");
+            subflow1.setDescription("Parameter 'foo=bar1' AND username+password");
+            subflow1.setProviderId("basic-flow");
+            subflow1 = realm.addAuthenticationFlow(subflow1);
+
+            AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
+            execution.setParentFlow(browser.getId());
+            execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+            execution.setFlowId(subflow1.getId());
+            execution.setPriority(10);
+            execution.setAuthenticatorFlow(true);
+            realm.addAuthenticatorExecution(execution);
+
+            // Subflow1 - foo=bar1
+            execution = new AuthenticationExecutionModel();
+            execution.setParentFlow(subflow1.getId());
+            execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+            execution.setAuthenticator(ExpectedParamAuthenticatorFactory.PROVIDER_ID);
+            execution.setPriority(10);
+            execution.setAuthenticatorFlow(false);
+
+            AuthenticatorConfigModel configModel = new AuthenticatorConfigModel();
+            configModel.setAlias("bar1");
+            Map<String, String> config = new HashMap<>();
+            config.put(ExpectedParamAuthenticator.EXPECTED_VALUE, "bar1");
+            configModel.setConfig(config);
+            configModel = realm.addAuthenticatorConfig(configModel);
+            execution.setAuthenticatorConfig(configModel.getId());
+
+            realm.addAuthenticatorExecution(execution);
+
+            // Subflow1 - username password
+            execution = new AuthenticationExecutionModel();
+            execution.setParentFlow(subflow1.getId());
+            execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+            execution.setAuthenticator(UsernamePasswordFormFactory.PROVIDER_ID);
+            execution.setPriority(20);
+            execution.setAuthenticatorFlow(false);
+
+            realm.addAuthenticatorExecution(execution);
+
+
+
+            // Subflow2
+            AuthenticationFlowModel subflow2 = new AuthenticationFlowModel();
+            subflow2.setTopLevel(false);
+            subflow2.setBuiltIn(true);
+            subflow2.setAlias("subflow-2");
+            subflow2.setDescription("username+password AND pushButton");
+            subflow2.setProviderId("basic-flow");
+            subflow2 = realm.addAuthenticationFlow(subflow2);
+
+            execution = new AuthenticationExecutionModel();
+            execution.setParentFlow(browser.getId());
+            execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+            execution.setFlowId(subflow2.getId());
+            execution.setPriority(20);
+            execution.setAuthenticatorFlow(true);
+            realm.addAuthenticatorExecution(execution);
+
+            // Subflow2 - push the button
+            execution = new AuthenticationExecutionModel();
+            execution.setParentFlow(subflow2.getId());
+            execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+            execution.setAuthenticator(PushButtonAuthenticatorFactory.PROVIDER_ID);
+            execution.setPriority(10);
+            execution.setAuthenticatorFlow(false);
+
+            realm.addAuthenticatorExecution(execution);
+
+            // Subflow2 - username-password
+            execution = new AuthenticationExecutionModel();
+            execution.setParentFlow(subflow2.getId());
+            execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+            execution.setAuthenticator(UsernamePasswordFormFactory.PROVIDER_ID);
+            execution.setPriority(20);
+            execution.setAuthenticatorFlow(false);
+
+            realm.addAuthenticatorExecution(execution);
+
+            // Subflow3
+            AuthenticationFlowModel subflow3 = new AuthenticationFlowModel();
+            subflow3.setTopLevel(false);
+            subflow3.setBuiltIn(true);
+            subflow3.setAlias("subflow-3");
+            subflow3.setDescription("alternative subflow with child subflows");
+            subflow3.setProviderId("basic-flow");
+            subflow3 = realm.addAuthenticationFlow(subflow3);
+
+            execution = new AuthenticationExecutionModel();
+            execution.setParentFlow(browser.getId());
+            execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+            execution.setFlowId(subflow3.getId());
+            execution.setPriority(30);
+            execution.setAuthenticatorFlow(true);
+            realm.addAuthenticatorExecution(execution);
+
+            // Subflow3-1
+            AuthenticationFlowModel subflow31 = new AuthenticationFlowModel();
+            subflow31.setTopLevel(false);
+            subflow31.setBuiltIn(true);
+            subflow31.setAlias("subflow-31");
+            subflow31.setDescription("subflow-31");
+            subflow31.setProviderId("basic-flow");
+            subflow31 = realm.addAuthenticationFlow(subflow31);
+
+            execution = new AuthenticationExecutionModel();
+            execution.setParentFlow(subflow3.getId());
+            execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+            execution.setFlowId(subflow31.getId());
+            execution.setPriority(10);
+            execution.setAuthenticatorFlow(true);
+            realm.addAuthenticatorExecution(execution);
+
+            // Subflow3-1 - foo=bar2
+            execution = new AuthenticationExecutionModel();
+            execution.setParentFlow(subflow31.getId());
+            execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+            execution.setAuthenticator(ExpectedParamAuthenticatorFactory.PROVIDER_ID);
+            execution.setPriority(10);
+            execution.setAuthenticatorFlow(false);
+
+            configModel = new AuthenticatorConfigModel();
+            configModel.setAlias("bar2");
+            config = new HashMap<>();
+            config.put(ExpectedParamAuthenticator.EXPECTED_VALUE, "bar2");
+            config.put(ExpectedParamAuthenticator.LOGGED_USER, "john-doh@localhost");
+            configModel.setConfig(config);
+            configModel = realm.addAuthenticatorConfig(configModel);
+            execution.setAuthenticatorConfig(configModel.getId());
+
+            realm.addAuthenticatorExecution(execution);
+
+            // Subflow3-1 - push the button
+            execution = new AuthenticationExecutionModel();
+            execution.setParentFlow(subflow31.getId());
+            execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+            execution.setAuthenticator(PushButtonAuthenticatorFactory.PROVIDER_ID);
+            execution.setPriority(20);
+            execution.setAuthenticatorFlow(false);
+
+            realm.addAuthenticatorExecution(execution);
+
+            // Subflow3  - foo=bar3
+            execution = new AuthenticationExecutionModel();
+            execution.setParentFlow(subflow3.getId());
+            execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+            execution.setAuthenticator(ExpectedParamAuthenticatorFactory.PROVIDER_ID);
+            execution.setPriority(20);
+            execution.setAuthenticatorFlow(false);
+
+            configModel = new AuthenticatorConfigModel();
+            configModel.setAlias("bar3");
+            config = new HashMap<>();
+            config.put(ExpectedParamAuthenticator.EXPECTED_VALUE, "bar3");
+            config.put(ExpectedParamAuthenticator.LOGGED_USER, "keycloak-user@localhost");
+            configModel.setConfig(config);
+            configModel = realm.addAuthenticatorConfig(configModel);
+            execution.setAuthenticatorConfig(configModel.getId());
+
+            realm.addAuthenticatorExecution(execution);
+
+
+        });
+    }
+
+
+//    @Test
+//    public void testSleep() throws Exception {
+//        log.info("Start sleeping");
+//        Thread.sleep(1000000);
+//    }
+
+
+    @Test
+    public void testSubflow1() throws Exception {
+        // Add foo=bar1 . I am redirected to subflow1 - username+password form
+        String loginFormUrl = oauth.getLoginFormUrl();
+        loginFormUrl = loginFormUrl + "&foo=bar1";
+        log.info("loginFormUrl: " + loginFormUrl);
+
+        //Thread.sleep(10000000);
+
+        driver.navigate().to(loginFormUrl);
+
+        loginPage.assertCurrent();
+
+        // Fill username+password. I am successfully authenticated
+        oauth.fillLoginForm("test-user@localhost", "password");
+        appPage.assertCurrent();
+
+        events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
+    }
+
+
+    @Test
+    public void testSubflow2() throws Exception {
+        // Don't add 'foo' parameter. I am redirected to subflow2 - push the button
+        String loginFormUrl = oauth.getLoginFormUrl();
+        log.info("loginFormUrl: " + loginFormUrl);
+
+        //Thread.sleep(10000000);
+
+        driver.navigate().to(loginFormUrl);
+
+        Assert.assertEquals("PushTheButton", driver.getTitle());
+
+        // Push the button. I am redirected to username+password form
+        driver.findElement(By.name("submit1")).click();
+
+
+        loginPage.assertCurrent();
+
+        // Fill username+password. I am successfully authenticated
+        oauth.fillLoginForm("test-user@localhost", "password");
+        appPage.assertCurrent();
+
+        events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
+    }
+
+
+//    @Test
+//    public void testSubflow31() {
+//        // Fill foo=bar2. I am see the pushButton
+//        String loginFormUrl = oauth.getLoginFormUrl();
+//        loginFormUrl = loginFormUrl + "&foo=bar2";
+//        log.info("loginFormUrl: " + loginFormUrl);
+//
+//        //Thread.sleep(10000000);
+//
+//        driver.navigate().to(loginFormUrl);
+//        Assert.assertEquals("PushTheButton", driver.getTitle());
+//
+//        // Confirm push button. I am authenticated as john-doh@localhost
+//        driver.findElement(By.name("submit1")).click();
+//
+//        appPage.assertCurrent();
+//
+//        events.expectLogin().detail(Details.USERNAME, "john-doh@localhost").assertEvent();
+//    }
+//
+//
+//    @Test
+//    public void testSubflow32() {
+//        // Fill foo=bar3. I am login automatically as "keycloak-user@localhost"
+//
+//
+//    }
+
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java
index 5c9ff74..2d92719 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java
@@ -16,19 +16,30 @@
  */
 package org.keycloak.testsuite.i18n;
 
+import java.util.Arrays;
+
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.jboss.resteasy.client.jaxrs.ResteasyClient;
 import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
 import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
 import org.junit.Assert;
 import org.junit.Test;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.adapters.HttpClientBuilder;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.LanguageComboboxAwarePage;
 import org.keycloak.testsuite.pages.LoginPage;
 
 import javax.ws.rs.core.Response;
 import org.jboss.arquillian.graphene.page.Page;
 import org.keycloak.testsuite.ProfileAssume;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
 import org.keycloak.testsuite.util.IdentityProviderBuilder;
 
 /**
@@ -38,8 +49,18 @@ import org.keycloak.testsuite.util.IdentityProviderBuilder;
 public class LoginPageTest extends AbstractI18NTest {
 
     @Page
+    protected AppPage appPage;
+
+    @Page
     protected LoginPage loginPage;
 
+    @Page
+    protected LoginPasswordUpdatePage changePasswordPage;
+
+    @Page
+    protected OAuthGrantPage grantPage;
+
+
     @Override
     public void configureTestRealm(RealmRepresentation testRealm) {
         testRealm.addIdentityProvider(IdentityProviderBuilder.create()
@@ -63,11 +84,7 @@ public class LoginPageTest extends AbstractI18NTest {
         loginPage.open();
         Assert.assertEquals("English", loginPage.getLanguageDropdownText());
 
-        loginPage.openLanguage("Deutsch");
-        Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText());
-
-        loginPage.openLanguage("English");
-        Assert.assertEquals("English", loginPage.getLanguageDropdownText());
+        switchLanguageToGermanAndBack("Username or email", "Benutzername oder E-Mail", loginPage);
     }
 
     @Test
@@ -109,6 +126,8 @@ public class LoginPageTest extends AbstractI18NTest {
 
         response = client.target(driver.getCurrentUrl()).request().acceptLanguage("en").get();
         Assert.assertTrue(response.readEntity(String.class).contains("Log in to test"));
+
+        client.close();
     }
 
     @Test
@@ -119,4 +138,73 @@ public class LoginPageTest extends AbstractI18NTest {
         Assert.assertEquals("MyOIDC", loginPage.findSocialButton("myoidc").getText());
 
     }
+
+
+    // KEYCLOAK-3887
+    @Test
+    public void languageChangeRequiredActions() {
+        UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost");
+        UserRepresentation userRep = user.toRepresentation();
+        userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
+        user.update(userRep);
+
+        loginPage.open();
+
+        loginPage.login("test-user@localhost", "password");
+        changePasswordPage.assertCurrent();
+        Assert.assertEquals("English", changePasswordPage.getLanguageDropdownText());
+
+        // Switch language
+        switchLanguageToGermanAndBack("Update password", "Passwort aktualisieren", changePasswordPage);
+
+        // Update password
+        changePasswordPage.changePassword("password", "password");
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+    }
+
+
+    // KEYCLOAK-3887
+    @Test
+    public void languageChangeConsentScreen() {
+        // Set client, which requires consent
+        oauth.clientId("third-party");
+
+        loginPage.open();
+
+        loginPage.login("test-user@localhost", "password");
+
+        grantPage.assertCurrent();
+        Assert.assertEquals("English", grantPage.getLanguageDropdownText());
+
+        // Switch language
+        switchLanguageToGermanAndBack("Do you grant these access privileges?", "Wollen Sie diese Zugriffsrechte", changePasswordPage);
+
+        // Confirm grant
+        grantPage.accept();
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        // Revert client
+        oauth.clientId("test-app");
+    }
+
+
+    private void switchLanguageToGermanAndBack(String expectedEnglishMessage, String expectedGermanMessage, LanguageComboboxAwarePage page) {
+        // Switch language to Deutsch
+        page.openLanguage("Deutsch");
+        Assert.assertEquals("Deutsch", page.getLanguageDropdownText());
+        String pageSource = driver.getPageSource();
+        Assert.assertFalse(pageSource.contains(expectedEnglishMessage));
+        Assert.assertTrue(pageSource.contains(expectedGermanMessage));
+
+        // Revert language
+        page.openLanguage("English");
+        Assert.assertEquals("English", page.getLanguageDropdownText());
+        pageSource = driver.getPageSource();
+        Assert.assertTrue(pageSource.contains(expectedEnglishMessage));
+        Assert.assertFalse(pageSource.contains(expectedGermanMessage));
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthProofKeyForCodeExchangeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthProofKeyForCodeExchangeTest.java
index a72aa3a..20757c1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthProofKeyForCodeExchangeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthProofKeyForCodeExchangeTest.java
@@ -444,14 +444,10 @@ public class OAuthProofKeyForCodeExchangeTest extends AbstractKeycloakTest {
     
     private String generateS256CodeChallenge(String codeVerifier) throws Exception {
         MessageDigest md = MessageDigest.getInstance("SHA-256");
-        md.update(codeVerifier.getBytes());
-        StringBuilder sb = new StringBuilder();
-        for (byte b : md.digest()) {
-            String hex = String.format("%02x", b);
-            sb.append(hex);
-        }
-        String codeChallenge = Base64Url.encode(sb.toString().getBytes());
-    	return codeChallenge;
+        md.update(codeVerifier.getBytes("ISO_8859_1"));
+        byte[] digestBytes = md.digest();
+        String codeChallenge = Base64Url.encode(digestBytes);
+        return codeChallenge;
     }
  
     private void expectSuccessfulResponseFromTokenEndpoint(String codeId, String sessionId, String code)  throws Exception {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java
index b4f3130..842e406 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java
@@ -18,7 +18,9 @@
 package org.keycloak.testsuite.util;
 
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -175,7 +177,15 @@ public class ClientBuilder {
     }
 
     public ClientBuilder authorizationServicesEnabled(boolean enable) {
-        rep.setAuthorizationServicesEnabled(true);
+        rep.setAuthorizationServicesEnabled(enable);
+        return this;
+    }
+
+    public ClientBuilder protocolMapper(ProtocolMapperRepresentation... mappers) {
+        if (rep.getProtocolMappers() == null) {
+            rep.setProtocolMappers(new ArrayList<>());
+        }
+        rep.getProtocolMappers().addAll(Arrays.asList(mappers));
         return this;
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-autodetect/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-autodetect/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..b00a1c0
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-autodetect/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ 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.
+  -->
+
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_9.xsd">
+    <SP entityID="http://localhost:8081/sales-post/"
+        sslPolicy="EXTERNAL"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false"
+	autodetectBearerOnly="true">
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleIdentifiers>
+            <Attribute name="Role"/>
+        </RoleIdentifiers>
+        <IDP entityID="idp">
+            <SingleSignOnService requestBinding="POST"
+                                 bindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
index 6bc040f..58ef272 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -49,7 +49,7 @@
     
     <container qualifier="auth-server-undertow" mode="suite" >
         <configuration>
-            <property name="enabled">${auth.server.undertow}</property>
+            <property name="enabled">${auth.server.undertow} &amp;&amp; ! ${auth.server.undertow.crossdc}</property>
             <property name="bindAddress">localhost</property>
             <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
             <property name="bindHttpPort">${auth.server.http.port}</property>
@@ -74,6 +74,7 @@
                 ${auth.server.feature}
             </property>
             <property name="javaVmArguments">
+                ${auth.server.jboss.jvm.debug.args}
                 ${auth.server.memory.settings}
                 -Djava.net.preferIPv4Stack=true
             </property>
@@ -103,6 +104,7 @@
                 <property name="outputToConsole">${backends.console.output}</property>
                 <property name="managementPort">${auth.server.backend1.management.port}</property>
                 <property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
+                <property name="bindHttpPortOffset">${auth.server.backend1.port.offset}</property>
             </configuration>
         </container>
         <container qualifier="auth-server-${auth.server}-backend2" mode="manual" >
@@ -124,6 +126,7 @@
                 <property name="outputToConsole">${backends.console.output}</property>
                 <property name="managementPort">${auth.server.backend2.management.port}</property>
                 <property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
+                <property name="bindHttpPortOffset">${auth.server.backend2.port.offset}</property>
             </configuration>
         </container>
     </group>
@@ -165,6 +168,162 @@
     </group>
 
 
+    <!-- Cross DC with embedded undertow. Node numbering is [centre #].[node #] -->
+    <group qualifier="auth-server-undertow-cross-dc">
+        <container qualifier="cache-server-cross-dc-1" mode="suite" >
+            <configuration>
+                <property name="enabled">${auth.server.undertow.crossdc}</property>
+                <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
+                <property name="jbossHome">${cache.server.home}</property>
+                <property name="serverConfig">clustered.xml</property>
+                <property name="jbossArguments">
+                    -Djboss.socket.binding.port-offset=${cache.server.port.offset}
+                    -Djboss.default.multicast.address=234.56.78.99
+                    -Djboss.node.name=cache-server
+                    ${adapter.test.props}
+                    ${auth.server.profile}
+                </property>
+                <property name="javaVmArguments">
+                    ${auth.server.memory.settings}
+                    -Djava.net.preferIPv4Stack=true
+                </property>
+                <property name="outputToConsole">${cache.server.console.output}</property>
+                <property name="managementPort">${cache.server.management.port}</property>
+                <property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
+            </configuration>
+        </container>
+
+        <container qualifier="cache-server-cross-dc-2" mode="suite" >
+            <configuration>
+                <property name="enabled">${auth.server.undertow.crossdc}</property>
+                <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
+                <property name="jbossHome">${cache.server.home}</property>
+                <property name="setupCleanServerBaseDir">true</property>
+                <property name="cleanServerBaseDir">${cache.server.home}/standalone-dc-2</property>
+                <property name="serverConfig">clustered.xml</property>
+                <property name="jbossArguments">
+                    -Djboss.socket.binding.port-offset=${cache.server.2.port.offset}
+                    -Djboss.default.multicast.address=234.56.78.99
+                    -Djboss.node.name=cache-server-dc-2
+                    ${adapter.test.props}
+                    ${auth.server.profile}
+                </property>
+                <property name="javaVmArguments">
+                    ${auth.server.memory.settings}
+                    -Djava.net.preferIPv4Stack=true
+                </property>
+                <property name="outputToConsole">${cache.server.console.output}</property>
+                <property name="managementPort">${cache.server.2.management.port}</property>
+                <property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
+            </configuration>
+        </container>
+
+        <container qualifier="auth-server-balancer-cross-dc" mode="suite" >
+            <configuration>
+                <property name="enabled">${auth.server.undertow.crossdc}</property>
+                <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.lb.SimpleUndertowLoadBalancerContainer</property>
+                <property name="bindAddress">localhost</property>
+                <property name="bindHttpPort">${auth.server.http.port}</property>
+                <property name="nodes">auth-server-undertow-cross-dc-0_1=http://localhost:8101,auth-server-undertow-cross-dc-0_2-manual=http://localhost:8102,auth-server-undertow-cross-dc-1_1=http://localhost:8111,auth-server-undertow-cross-dc-1_2-manual=http://localhost:8112</property>
+            </configuration>
+        </container>
+
+        <container qualifier="auth-server-undertow-cross-dc-0_1" mode="suite" >
+            <configuration>
+                <property name="enabled">${auth.server.undertow.crossdc}</property>
+                <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
+                <property name="bindAddress">localhost</property>
+                <property name="bindHttpPort">${auth.server.http.port}</property>
+                <property name="bindHttpPortOffset">-79</property>
+                <property name="route">auth-server-undertow-cross-dc-0_1</property>
+                <property name="remoteMode">${undertow.remote}</property>
+                <property name="dataCenter">0</property>
+                <property name="keycloakConfigPropertyOverrides">{
+                    "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.1",
+                    "keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-0_1",
+                    "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
+                    "keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
+                    "keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
+                    "keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
+                    "keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
+                    "keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
+                    "keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
+                }</property>
+            </configuration>
+        </container>
+        <container qualifier="auth-server-undertow-cross-dc-0_2-manual" mode="manual" >
+            <configuration>
+                <property name="enabled">${auth.server.undertow.crossdc}</property>
+                <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
+                <property name="bindAddress">localhost</property>
+                <property name="bindHttpPort">${auth.server.http.port}</property>
+                <property name="bindHttpPortOffset">-78</property>
+                <property name="route">auth-server-undertow-cross-dc-0_2-manual</property>
+                <property name="remoteMode">${undertow.remote}</property>
+                <property name="dataCenter">0</property>
+                <property name="keycloakConfigPropertyOverrides">{
+                    "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.1",
+                    "keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-0_2-manual",
+                    "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
+                    "keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
+                    "keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
+                    "keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
+                    "keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
+                    "keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
+                    "keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
+                }</property>
+            </configuration>
+        </container>
+
+        <container qualifier="auth-server-undertow-cross-dc-1_1" mode="suite" >
+            <configuration>
+                <property name="enabled">${auth.server.undertow.crossdc}</property>
+                <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
+                <property name="bindAddress">localhost</property>
+                <property name="bindHttpPort">${auth.server.http.port}</property>
+                <property name="bindHttpPortOffset">-69</property>
+                <property name="route">auth-server-undertow-cross-dc-1_1</property>
+                <property name="remoteMode">${undertow.remote}</property>
+                <property name="dataCenter">1</property>
+                <property name="keycloakConfigPropertyOverrides">{
+                    "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.2",
+                    "keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-1_1",
+                    "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
+                    "keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
+                    "keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort.2:11222}",
+                    "keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
+                    "keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
+                    "keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
+                    "keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
+                }</property>
+            </configuration>
+        </container>
+        <container qualifier="auth-server-undertow-cross-dc-1_2-manual" mode="manual" >
+            <configuration>
+                <property name="enabled">${auth.server.undertow.crossdc}</property>
+                <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
+                <property name="bindAddress">localhost</property>
+                <property name="bindHttpPort">${auth.server.http.port}</property>
+                <property name="bindHttpPortOffset">-68</property>
+                <property name="route">auth-server-undertow-cross-dc-1_2-manual</property>
+                <property name="remoteMode">${undertow.remote}</property>
+                <property name="dataCenter">1</property>
+                <property name="keycloakConfigPropertyOverrides">{
+                    "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.2",
+                    "keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-1_2-manual",
+                    "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
+                    "keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
+                    "keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort.2:11222}",
+                    "keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
+                    "keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
+                    "keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
+                    "keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
+                }</property>
+            </configuration>
+        </container>
+    </group>
+
+
     <container qualifier="auth-server-balancer-wildfly" mode="suite" >
         <configuration>
             <property name="enabled">${auth.server.cluster}</property>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties
index 9c9bfc2..167c611 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties
@@ -69,3 +69,5 @@ log4j.logger.org.apache.directory.server.core=warn
 # log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
 # log4j.logger.org.keycloak.keys.infinispan=trace
 log4j.logger.org.keycloak.services.clientregistration.policy=debug
+
+#log4j.logger.org.keycloak.authentication=debug
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
index 9d801e5..7a0e6b6 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
@@ -85,7 +85,7 @@
 
     "connectionsJpa": {
         "default": {
-            "url": "${keycloak.connectionsJpa.url:jdbc:h2:mem:test}",
+            "url": "${keycloak.connectionsJpa.url:jdbc:h2:mem:test;MVCC=TRUE}",
             "driver": "${keycloak.connectionsJpa.driver:org.h2.Driver}",
             "driverDialect": "${keycloak.connectionsJpa.driverDialect:}",
             "user": "${keycloak.connectionsJpa.user:sa}",
@@ -107,15 +107,23 @@
 
     "connectionsInfinispan": {
         "default": {
+            "jgroupsUdpMcastAddr": "${keycloak.connectionsInfinispan.jgroupsUdpMcastAddr:234.56.78.90}",
+            "nodeName": "${keycloak.connectionsInfinispan.nodeName,jboss.node.name:defaultNodeName}",
             "clustered": "${keycloak.connectionsInfinispan.clustered:false}",
             "async": "${keycloak.connectionsInfinispan.async:false}",
             "sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:1}",
             "l1Lifespan": "${keycloak.connectionsInfinispan.l1Lifespan:600000}",
             "remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:false}",
-            "remoteStoreHost": "${keycloak.connectionsInfinispan.remoteStoreHost:localhost}",
+            "remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
             "remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}"
         }
     },
+    
+    "stickySessionEncoder": {
+        "infinispan": {
+            "nodeName": "${keycloak.stickySessionEncoder.nodeName,jboss.node.name:defaultNodeName}"
+        }
+    },
 
 
     "truststore": {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json
index 5f84e38..fb1a7e0 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json
@@ -71,6 +71,50 @@
             }
         }
     ],
+    "groups": [
+        {
+            "name": "Group A",
+            "path": "/Group A",
+            "attributes": {
+                "topAttribute": [
+                    "true"
+                ]
+            },
+            "subGroups": [
+                {
+                    "name": "Group B",
+                    "path": "/Group A/Group B",
+                    "attributes": {
+                        "level2Attribute": [
+                            "true"
+                        ]
+                    },
+                    "subGroups": []
+                }
+            ]
+        },
+        {
+            "name": "Group C",
+            "path": "/Group C",
+            "attributes": {
+                "topAttribute": [
+                    "true"
+                ]
+            },
+            "subGroups": [
+                {
+                    "name": "Group D",
+                    "path": "/Group C/Group D",
+                    "attributes": {
+                        "level2Attribute": [
+                            "true"
+                        ]
+                    },
+                    "subGroups": []
+                }
+            ]
+        }
+    ],
     "users": [
         {
             "username": "wburke",
@@ -299,6 +343,14 @@
                         }
                     },
                     {
+                        "name": "Group Policy Test",
+                        "type": "group",
+                        "config": {
+                            "groupsClaim": "groups",
+                            "groups": "[{\"path\":\"/Group A\",\"extendChildren\":true},{\"path\":\"/Group A/Group B\",\"extendChildren\":false},{\"path\":\"/Group C/Group D\",\"extendChildren\":true}]"
+                        }
+                    },
+                    {
                         "name": "Only Premium User Policy",
                         "description": "Defines that only premium users can do something",
                         "type": "role",
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/arquillian.xsl b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/arquillian.xsl
index 30e7dba..67a494a 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/arquillian.xsl
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/arquillian.xsl
@@ -39,6 +39,7 @@
                         ${adapter.test.props}
                     </property>
                     <property name="javaVmArguments">
+                        ${app.server.jboss.jvm.debug.args}
                         ${app.server.memory.settings}
                         -Djava.net.preferIPv4Stack=true
                     </property>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
index e90f353..70533a9 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
@@ -52,6 +52,11 @@
         <app.server.startup.timeout>60</app.server.startup.timeout>
         <app.server.memory.settings>-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m</app.server.memory.settings>
 
+        <!--debug properties-->
+        <app.server.debug.port>5006</app.server.debug.port>
+        <app.server.debug.suspend>n</app.server.debug.suspend>
+        <app.server.jboss.jvm.debug.args>-agentlib:jdwp=transport=dt_socket,server=y,suspend=${app.server.debug.suspend},address=${app.server.host}:${app.server.debug.port}</app.server.jboss.jvm.debug.args>
+        
         <app.server.ssl.required>false</app.server.ssl.required>
 
         <app.server.reverse-proxy.port.offset>500</app.server.reverse-proxy.port.offset>
@@ -205,7 +210,8 @@
 
                                 <app.server.startup.timeout>${app.server.startup.timeout}</app.server.startup.timeout>
                                 <app.server.memory.settings>${app.server.memory.settings}</app.server.memory.settings>
-
+                                <app.server.jboss.jvm.debug.args>${app.server.jboss.jvm.debug.args}</app.server.jboss.jvm.debug.args>
+                                
                                 <app.server.reverse-proxy.port.offset>${app.server.reverse-proxy.port.offset}</app.server.reverse-proxy.port.offset>
 
                                 <app.server.1.port.offset>${app.server.1.port.offset}</app.server.1.port.offset>
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java
index 7e4c29c..09f5f1c 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java
@@ -85,4 +85,60 @@ public class RequiredActions extends Authentication {
     public void setUpdateProfileDefaultAction(boolean value) {
         setRequiredActionDefaultValue(UPDATE_PROFILE, value);
     }
+
+    private boolean getRequiredActionValue(String id) {
+        WaitUtils.waitUntilElement(requiredActionTable).is().present();
+
+        WebElement checkbox = requiredActionTable.findElement(By.id(id));
+
+        return checkbox.isSelected();
+    }
+
+    private boolean getRequiredActionEnabledValue(String id) {
+        return getRequiredActionValue(id + ENABLED);
+    }
+
+    private boolean getRequiredActionDefaultValue(String id) {
+        return getRequiredActionValue(id + DEFAULT);
+    }
+
+    public boolean getTermsAndConditionEnabled() {
+        return getRequiredActionEnabledValue(TERMS_AND_CONDITIONS);
+    }
+
+    public boolean getTermsAndConditionDefaultAction() {
+        return getRequiredActionDefaultValue(TERMS_AND_CONDITIONS);
+    }
+
+    public boolean getVerifyEmailEnabled() {
+        return getRequiredActionEnabledValue(VERIFY_EMAIL);
+    }
+
+    public boolean getVerifyEmailDefaultAction() {
+        return getRequiredActionDefaultValue(VERIFY_EMAIL);
+    }
+
+    public boolean getUpdatePasswordEnabled() {
+        return getRequiredActionEnabledValue(UPDATE_PASSWORD);
+    }
+
+    public boolean getUpdatePasswordDefaultAction() {
+        return getRequiredActionDefaultValue(UPDATE_PASSWORD);
+    }
+
+    public boolean getConfigureTotpEnabled() {
+        return getRequiredActionEnabledValue(CONFIGURE_TOTP);
+    }
+
+    public boolean getConfigureTotpDefaultAction() {
+        return getRequiredActionDefaultValue(CONFIGURE_TOTP);
+    }
+
+    public boolean getUpdateProfileEnabled() {
+        return getRequiredActionEnabledValue(UPDATE_PROFILE);
+    }
+
+    public boolean getUpdateProfileDefaultAction() {
+        return getRequiredActionDefaultValue(UPDATE_PROFILE);
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/GroupPolicy.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/GroupPolicy.java
new file mode 100644
index 0000000..2fd68f4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/GroupPolicy.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 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.testsuite.console.page.clients.authorization.policy;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class GroupPolicy implements PolicyTypeUI {
+
+    @Page
+    private GroupPolicyForm form;
+
+    public GroupPolicyForm form() {
+        return form;
+    }
+
+    public GroupPolicyRepresentation toRepresentation() {
+        return form.toRepresentation();
+    }
+
+    public void update(GroupPolicyRepresentation expected) {
+        form().populate(expected);
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/GroupPolicyForm.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/GroupPolicyForm.java
new file mode 100644
index 0000000..389a214
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/GroupPolicyForm.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2017 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.testsuite.console.page.clients.authorization.policy;
+
+import static org.openqa.selenium.By.tagName;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.jboss.arquillian.drone.api.annotation.Drone;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.Logic;
+import org.keycloak.testsuite.page.Form;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class GroupPolicyForm extends Form {
+
+    @FindBy(id = "name")
+    private WebElement name;
+
+    @FindBy(id = "description")
+    private WebElement description;
+
+    @FindBy(id = "groupsClaim")
+    private WebElement groupsClaim;
+
+    @FindBy(id = "logic")
+    private Select logic;
+
+    @FindBy(xpath = "//i[contains(@class,'pficon-delete')]")
+    private WebElement deleteButton;
+
+    @FindBy(xpath = ACTIVE_DIV_XPATH + "/button[text()='Delete']")
+    private WebElement confirmDelete;
+
+    @FindBy(id = "selectGroup")
+    private WebElement selectGroupButton;
+
+    @Drone
+    private WebDriver driver;
+
+    public void populate(GroupPolicyRepresentation expected) {
+        setInputValue(name, expected.getName());
+        setInputValue(description, expected.getDescription());
+        setInputValue(groupsClaim, expected.getGroupsClaim());
+        logic.selectByValue(expected.getLogic().name());
+
+
+        for (GroupPolicyRepresentation.GroupDefinition definition : toRepresentation().getGroups()) {
+            boolean isExpected = false;
+
+            for (GroupPolicyRepresentation.GroupDefinition expectedDef : expected.getGroups()) {
+                if (definition.getPath().equals(expectedDef.getPath())) {
+                    isExpected = true;
+                    break;
+                }
+            }
+
+            if (!isExpected) {
+                unselect(definition.getPath());
+            }
+        }
+
+        for (GroupPolicyRepresentation.GroupDefinition definition : expected.getGroups()) {
+            String path = definition.getPath();
+            String groupName = path.substring(path.lastIndexOf('/') + 1);
+            WebElement element = driver.findElement(By.xpath("//span[text()='" + groupName + "']"));
+            element.click();
+            selectGroupButton.click();
+            driver.findElements(By.xpath("(//table[@id='selected-groups'])/tbody/tr")).stream()
+                    .filter(webElement -> webElement.findElements(tagName("td")).size() > 1)
+                    .map(webElement -> webElement.findElements(tagName("td")))
+                    .filter(tds -> tds.get(0).getText().equals(definition.getPath()))
+                    .forEach(tds -> {
+                        if (!tds.get(1).findElement(By.tagName("input")).isSelected()) {
+                            if (definition.isExtendChildren()) {
+                                tds.get(1).findElement(By.tagName("input")).click();
+                            }
+                        } else {
+                            if (!definition.isExtendChildren() && tds.get(1).findElement(By.tagName("input")).isSelected()) {
+                                tds.get(1).findElement(By.tagName("input")).click();
+                            }
+                        }
+                    });
+        }
+
+        save();
+    }
+
+    private void unselect(String path) {
+        for (WebElement webElement : driver.findElements(By.xpath("(//table[@id='selected-groups'])/tbody/tr"))) {
+            List<WebElement> tds = webElement.findElements(tagName("td"));
+
+            if (tds.size() > 1) {
+                if (tds.get(0).getText().equals(path)) {
+                    tds.get(2).findElement(By.tagName("button")).click();
+                    return;
+                }
+            }
+        }
+    }
+
+    public void delete() {
+        deleteButton.click();
+        confirmDelete.click();
+    }
+
+    public GroupPolicyRepresentation toRepresentation() {
+        GroupPolicyRepresentation representation = new GroupPolicyRepresentation();
+
+        representation.setName(getInputValue(name));
+        representation.setDescription(getInputValue(description));
+        representation.setGroupsClaim(getInputValue(groupsClaim));
+        representation.setLogic(Logic.valueOf(logic.getFirstSelectedOption().getText().toUpperCase()));
+        representation.setGroups(new HashSet<>());
+
+        driver.findElements(By.xpath("(//table[@id='selected-groups'])/tbody/tr")).stream()
+                .filter(webElement -> webElement.findElements(tagName("td")).size() > 1)
+                .forEach(webElement -> {
+                    List<WebElement> tds = webElement.findElements(tagName("td"));
+                    representation.addGroupPath(tds.get(0).getText(), tds.get(1).findElement(By.tagName("input")).isSelected());
+                });
+
+        return representation;
+    }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/Policies.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/Policies.java
index af2a540..7be563e 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/Policies.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/Policies.java
@@ -22,6 +22,7 @@ import org.jboss.arquillian.graphene.page.Page;
 import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
 import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation;
 import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
 import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
 import org.keycloak.representations.idm.authorization.PolicyRepresentation;
 import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
@@ -66,6 +67,9 @@ public class Policies extends Form {
     @Page
     private ClientPolicy clientPolicy;
 
+    @Page
+    private GroupPolicy groupPolicy;
+
     public PoliciesTable policies() {
         return table;
     }
@@ -103,6 +107,10 @@ public class Policies extends Form {
             clientPolicy.form().populate((ClientPolicyRepresentation) expected);
             clientPolicy.form().save();
             return (P) clientPolicy;
+        } else if ("group".equals(type)) {
+            groupPolicy.form().populate((GroupPolicyRepresentation) expected);
+            groupPolicy.form().save();
+            return (P) groupPolicy;
         }
 
         return null;
@@ -130,6 +138,8 @@ public class Policies extends Form {
                     rulePolicy.form().populate((RulePolicyRepresentation) representation);
                 } else if ("client".equals(type)) {
                     clientPolicy.form().populate((ClientPolicyRepresentation) representation);
+                } else if ("group".equals(type)) {
+                    groupPolicy.form().populate((GroupPolicyRepresentation) representation);
                 }
 
                 return;
@@ -158,6 +168,8 @@ public class Policies extends Form {
                     return (P) rulePolicy;
                 } else if ("client".equals(type)) {
                     return (P) clientPolicy;
+                } else if ("group".equals(type)) {
+                    return (P) groupPolicy;
                 }
             }
         }
@@ -187,6 +199,8 @@ public class Policies extends Form {
                     rulePolicy.form().delete();
                 } else if ("client".equals(type)) {
                     clientPolicy.form().delete();
+                } else if ("group".equals(type)) {
+                    groupPolicy.form().delete();
                 }
 
                 return;
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/RequiredActionsTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/RequiredActionsTest.java
index b8217f8..b879e18 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/RequiredActionsTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/RequiredActionsTest.java
@@ -21,8 +21,11 @@ import org.jboss.arquillian.graphene.page.Page;
 import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.auth.page.AuthRealm;
 import org.keycloak.testsuite.auth.page.login.Registration;
 import org.keycloak.testsuite.console.AbstractConsoleTest;
+import org.keycloak.testsuite.console.page.AdminConsoleRealm;
 import org.keycloak.testsuite.console.page.authentication.RequiredActions;
 import org.keycloak.testsuite.console.page.realm.LoginSettings;
 import org.openqa.selenium.By;
@@ -72,6 +75,52 @@ public class RequiredActionsTest extends AbstractConsoleTest {
     }
 
     @Test
+    public void defaultCheckboxUncheckableWhenEnabledIsFalse() {
+        requiredActionsPage.setTermsAndConditionEnabled(false);
+        Assert.assertFalse(requiredActionsPage.getTermsAndConditionEnabled());
+        requiredActionsPage.setTermsAndConditionDefaultAction(true);
+        Assert.assertFalse(requiredActionsPage.getTermsAndConditionDefaultAction());
+    }
+
+    @Test
+    public void defaultCheckboxUncheckedWhenEnabledBecomesFalse() {
+        requiredActionsPage.setTermsAndConditionEnabled(true);
+        Assert.assertTrue(requiredActionsPage.getTermsAndConditionEnabled());
+        requiredActionsPage.setTermsAndConditionDefaultAction(true);
+        Assert.assertTrue(requiredActionsPage.getTermsAndConditionDefaultAction());
+        requiredActionsPage.setTermsAndConditionEnabled(false);
+        Assert.assertFalse(requiredActionsPage.getTermsAndConditionEnabled());
+        Assert.assertFalse(requiredActionsPage.getTermsAndConditionDefaultAction());
+        assertAlertSuccess();
+    }
+
+    @Test
+    public void defaultCheckboxKeepsValueWhenEnabledIsToggled() {
+        requiredActionsPage.setTermsAndConditionEnabled(true);
+        requiredActionsPage.setTermsAndConditionDefaultAction(false);
+        Assert.assertTrue(requiredActionsPage.getTermsAndConditionEnabled());
+        Assert.assertFalse(requiredActionsPage.getTermsAndConditionDefaultAction());
+        requiredActionsPage.setTermsAndConditionEnabled(false);
+        Assert.assertFalse(requiredActionsPage.getTermsAndConditionEnabled());
+        Assert.assertFalse(requiredActionsPage.getTermsAndConditionDefaultAction());
+        requiredActionsPage.setTermsAndConditionEnabled(true);
+        Assert.assertTrue(requiredActionsPage.getTermsAndConditionEnabled());
+        Assert.assertFalse(requiredActionsPage.getTermsAndConditionDefaultAction());
+
+        requiredActionsPage.setTermsAndConditionDefaultAction(true);
+        Assert.assertTrue(requiredActionsPage.getTermsAndConditionEnabled());
+        Assert.assertTrue(requiredActionsPage.getTermsAndConditionDefaultAction());
+        requiredActionsPage.setTermsAndConditionEnabled(false);
+        Assert.assertFalse(requiredActionsPage.getTermsAndConditionEnabled());
+        Assert.assertFalse(requiredActionsPage.getTermsAndConditionDefaultAction());
+        requiredActionsPage.setTermsAndConditionEnabled(true);
+        Assert.assertTrue(requiredActionsPage.getTermsAndConditionEnabled());
+        Assert.assertTrue(requiredActionsPage.getTermsAndConditionDefaultAction());
+
+        assertAlertSuccess();
+    }
+
+    @Test
     public void configureTotpDefaultActionTest() {
         requiredActionsPage.setConfigureTotpDefaultAction(true);
         assertAlertSuccess();
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/GroupPolicyManagementTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/GroupPolicyManagementTest.java
new file mode 100644
index 0000000..e8b05bf
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/GroupPolicyManagementTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2017 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.testsuite.console.authorization;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.util.Arrays;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.RolesResource;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.Logic;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.console.page.clients.authorization.policy.GroupPolicy;
+import org.keycloak.testsuite.console.page.clients.authorization.policy.RolePolicy;
+import org.keycloak.testsuite.console.page.clients.authorization.policy.UserPolicy;
+import org.keycloak.testsuite.util.GroupBuilder;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class GroupPolicyManagementTest extends AbstractAuthorizationSettingsTest {
+
+    @Before
+    public void configureTest() {
+        super.configureTest();
+        RealmResource realmResource = testRealmResource();
+        String groupAId = ApiUtil.getCreatedId(realmResource.groups().add(GroupBuilder.create().name("Group A").build()));
+        String groupBId = ApiUtil.getCreatedId(realmResource.groups().group(groupAId).subGroup(GroupBuilder.create().name("Group B").build()));
+        realmResource.groups().group(groupBId).subGroup(GroupBuilder.create().name("Group D").build());
+        realmResource.groups().group(groupBId).subGroup(GroupBuilder.create().name("Group E").build());
+        realmResource.groups().group(groupAId).subGroup(GroupBuilder.create().name("Group C").build());
+        realmResource.groups().add(GroupBuilder.create().name("Group F").build());
+    }
+
+    @Test
+    public void testUpdate() throws InterruptedException {
+        authorizationPage.navigateTo();
+        GroupPolicyRepresentation expected = new GroupPolicyRepresentation();
+
+        expected.setName("Test Group Policy");
+        expected.setDescription("description");
+        expected.setGroupsClaim("groups");
+        expected.addGroupPath("/Group A", true);
+        expected.addGroupPath("/Group A/Group B/Group D");
+        expected.addGroupPath("Group F");
+
+        expected = createPolicy(expected);
+
+        String previousName = expected.getName();
+
+        expected.setName("Changed Test Group Policy");
+        expected.setDescription("Changed description");
+        expected.setLogic(Logic.NEGATIVE);
+
+        authorizationPage.navigateTo();
+        authorizationPage.authorizationTabs().policies().update(previousName, expected);
+        assertAlertSuccess();
+
+        authorizationPage.navigateTo();
+        GroupPolicy actual = authorizationPage.authorizationTabs().policies().name(expected.getName());
+
+        assertPolicy(expected, actual);
+
+        expected.getGroups().clear();
+        expected.addGroupPath("/Group A", false);
+        expected.addGroupPath("/Group A/Group B/Group D");
+
+        authorizationPage.navigateTo();
+        authorizationPage.authorizationTabs().policies().update(expected.getName(), expected);
+        assertAlertSuccess();
+
+        authorizationPage.navigateTo();
+        actual = authorizationPage.authorizationTabs().policies().name(expected.getName());
+
+        assertPolicy(expected, actual);
+
+        expected.getGroups().clear();
+        expected.addGroupPath("/Group E");
+        expected.addGroupPath("/Group A/Group B", true);
+        expected.addGroupPath("/Group A/Group C");
+
+
+        authorizationPage.navigateTo();
+        authorizationPage.authorizationTabs().policies().update(expected.getName(), expected);
+        assertAlertSuccess();
+
+        authorizationPage.navigateTo();
+        actual = authorizationPage.authorizationTabs().policies().name(expected.getName());
+
+        assertPolicy(expected, actual);
+    }
+
+    @Test
+    public void testDelete() throws InterruptedException {
+        authorizationPage.navigateTo();
+        GroupPolicyRepresentation expected = new GroupPolicyRepresentation();
+
+        expected.setName("Test Delete Group Policy");
+        expected.setDescription("description");
+        expected.setGroupsClaim("groups");
+        expected.addGroupPath("/Group A", true);
+        expected.addGroupPath("/Group A/Group B/Group D");
+        expected.addGroupPath("Group F");
+
+        expected = createPolicy(expected);
+        authorizationPage.navigateTo();
+        authorizationPage.authorizationTabs().policies().delete(expected.getName());
+        assertAlertSuccess();
+        authorizationPage.navigateTo();
+        assertNull(authorizationPage.authorizationTabs().policies().policies().findByName(expected.getName()));
+    }
+
+    private GroupPolicyRepresentation createPolicy(GroupPolicyRepresentation expected) {
+        GroupPolicy policy = authorizationPage.authorizationTabs().policies().create(expected);
+        assertAlertSuccess();
+        return assertPolicy(expected, policy);
+    }
+
+    private GroupPolicyRepresentation assertPolicy(GroupPolicyRepresentation expected, GroupPolicy policy) {
+        GroupPolicyRepresentation actual = policy.toRepresentation();
+
+        assertEquals(expected.getName(), actual.getName());
+        assertEquals(expected.getDescription(), actual.getDescription());
+        assertEquals(expected.getLogic(), actual.getLogic());
+
+        assertNotNull(actual.getGroups());
+        assertEquals(expected.getGroups().size(), actual.getGroups().size());
+        assertEquals(0, actual.getGroups().stream().filter(actualDefinition -> !expected.getGroups().stream()
+                .filter(groupDefinition -> actualDefinition.getPath().contains(groupDefinition.getPath()) && actualDefinition.isExtendChildren() == groupDefinition.isExtendChildren())
+                .findFirst().isPresent())
+                .count());
+        return actual;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 4e29ec3..24e67c9 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -41,7 +41,9 @@
     <properties>
         <auth.server>undertow</auth.server>
         <auth.server.undertow>true</auth.server.undertow>
-
+        <auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
+        <auth.server.crossdc>false</auth.server.crossdc>
+        
         <auth.server.container>auth-server-${auth.server}</auth.server.container>
         <auth.server.home>${containers.home}/${auth.server.container}</auth.server.home>
         <auth.server.config.dir>${auth.server.home}</auth.server.config.dir>
@@ -62,10 +64,28 @@
         <auth.server.jboss.skip.unpack>${auth.server.undertow}</auth.server.jboss.skip.unpack>
         <auth.server.jboss.startup.timeout>300</auth.server.jboss.startup.timeout>
         
+        <!--debug properties-->
+        <auth.server.debug.port>5005</auth.server.debug.port>
+        <auth.server.debug.suspend>n</auth.server.debug.suspend>
+        <auth.server.jboss.jvm.debug.args>-agentlib:jdwp=transport=dt_socket,server=y,suspend=${auth.server.debug.suspend},address=${auth.server.host}:${auth.server.debug.port}</auth.server.jboss.jvm.debug.args>
+        
         <auth.server.remote>false</auth.server.remote>
         <auth.server.profile/>
         <auth.server.feature/>
-    
+
+        <cache.server>undefined</cache.server>
+        <cache.server.container>cache-server-${cache.server}</cache.server.container>
+        <cache.server.home>${containers.home}/${cache.server.container}</cache.server.home>
+        <cache.server.port.offset>1010</cache.server.port.offset>
+        <cache.server.management.port>11000</cache.server.management.port>
+        <cache.server.2.port.offset>2010</cache.server.2.port.offset>
+        <cache.server.2.management.port>12000</cache.server.2.management.port>
+        <cache.server.console.output>true</cache.server.console.output>
+        <keycloak.connectionsInfinispan.remoteStoreServer>localhost</keycloak.connectionsInfinispan.remoteStoreServer>
+        <keycloak.connectionsInfinispan.remoteStorePort>12232</keycloak.connectionsInfinispan.remoteStorePort>
+        <keycloak.connectionsInfinispan.remoteStorePort.2>13232</keycloak.connectionsInfinispan.remoteStorePort.2>
+        <keycloak.connectionsJpa.url.crossdc>jdbc:h2:mem:test-dc-shared</keycloak.connectionsJpa.url.crossdc>
+
         <adapter.test.props/>
         <migration.import.properties/>
         <examples.home>${project.build.directory}/examples</examples.home>
@@ -165,6 +185,23 @@
                     </executions>
                 </plugin>
                 <plugin>
+                    <artifactId>maven-antrun-plugin</artifactId>
+                    <executions>
+                        <execution>
+                            <id>clean-second-cache-server-arquillian-bug-workaround</id>
+                            <phase>process-test-resources</phase>
+                            <goals><goal>run</goal></goals>
+                            <configuration>
+                                <target>
+                                    <echo>${cache.server.home}/standalone-dc-2</echo>
+                                    <delete failonerror="false" dir="${cache.server.home}/standalone-dc-2" />
+                                    <mkdir dir="${cache.server.home}/standalone-dc-2/deployments" />
+                                </target>
+                            </configuration>
+                        </execution>
+                    </executions>
+                </plugin>
+                <plugin>
                     <artifactId>maven-surefire-plugin</artifactId>
                     <configuration>
                         <systemPropertyVariables>
@@ -194,6 +231,7 @@
                             <auth.server.config.property.name>${auth.server.config.property.name}</auth.server.config.property.name>
                             <auth.server.config.property.value>${auth.server.config.property.value}</auth.server.config.property.value>
                             <auth.server.adapter.impl.class>${auth.server.adapter.impl.class}</auth.server.adapter.impl.class>
+                            <auth.server.jboss.jvm.debug.args>${auth.server.jboss.jvm.debug.args}</auth.server.jboss.jvm.debug.args>
                             
                             <auth.server.profile>${auth.server.profile}</auth.server.profile>
                             <auth.server.feature>${auth.server.feature}</auth.server.feature>
@@ -229,6 +267,25 @@
                             <client.key.passphrase>${client.key.passphrase}</client.key.passphrase>
 
                             <auth.server.ocsp.responder.enabled>${auth.server.ocsp.responder.enabled}</auth.server.ocsp.responder.enabled>
+                            
+                            <!--cache server properties-->
+                            <auth.server.crossdc>${auth.server.crossdc}</auth.server.crossdc>
+                            <auth.server.undertow.crossdc>${auth.server.undertow.crossdc}</auth.server.undertow.crossdc>
+
+                            <cache.server>${cache.server}</cache.server>
+                            <cache.server.port.offset>${cache.server.port.offset}</cache.server.port.offset>
+                            <cache.server.container>${cache.server.container}</cache.server.container>
+                            <cache.server.home>${cache.server.home}</cache.server.home>
+                            <cache.server.console.output>${cache.server.console.output}</cache.server.console.output>
+                            <cache.server.management.port>${cache.server.management.port}</cache.server.management.port>
+                            <cache.server.2.port.offset>${cache.server.2.port.offset}</cache.server.2.port.offset>
+                            <cache.server.2.management.port>${cache.server.2.management.port}</cache.server.2.management.port>
+
+                            <keycloak.connectionsInfinispan.remoteStorePort>${keycloak.connectionsInfinispan.remoteStorePort}</keycloak.connectionsInfinispan.remoteStorePort>
+                            <keycloak.connectionsInfinispan.remoteStorePort.2>${keycloak.connectionsInfinispan.remoteStorePort.2}</keycloak.connectionsInfinispan.remoteStorePort.2>
+                            <keycloak.connectionsInfinispan.remoteStoreServer>${keycloak.connectionsInfinispan.remoteStoreServer}</keycloak.connectionsInfinispan.remoteStoreServer>
+
+                            <keycloak.connectionsJpa.url.crossdc>${keycloak.connectionsJpa.url.crossdc}</keycloak.connectionsJpa.url.crossdc>
                         </systemPropertyVariables>
                         <properties>
                             <property>
@@ -289,6 +346,7 @@
                 <auth.server>wildfly</auth.server>
                 <auth.server.jboss>true</auth.server.jboss>
                 <auth.server.undertow>false</auth.server.undertow>
+                <auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
                 <auth.server.config.property.value>standalone.xml</auth.server.config.property.value>
                 <auth.server.config.dir>${auth.server.home}/standalone/configuration</auth.server.config.dir>
                 <h2.version>1.3.173</h2.version>
@@ -307,6 +365,7 @@
                 <auth.server>eap</auth.server>
                 <auth.server.jboss>true</auth.server.jboss>
                 <auth.server.undertow>false</auth.server.undertow>
+                <auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
                 <auth.server.config.property.value>standalone.xml</auth.server.config.property.value>
                 <auth.server.config.dir>${auth.server.home}/standalone/configuration</auth.server.config.dir>
                 <h2.version>1.3.173</h2.version>
@@ -320,6 +379,154 @@
         </profile>
 
         <profile>
+            <id>cache-server-infinispan</id>
+            <properties>
+                <cache.server>infinispan</cache.server>
+                <auth.server.undertow.crossdc>true</auth.server.undertow.crossdc>
+                <auth.server.crossdc>true</auth.server.crossdc>
+                <cache.server.jboss>true</cache.server.jboss>
+                <cache.server.config.dir>${cache.server.home}/standalone/configuration</cache.server.config.dir>                
+            </properties>
+            <dependencies>
+                <dependency>
+                    <groupId>org.wildfly.arquillian</groupId>
+                    <artifactId>wildfly-arquillian-container-managed</artifactId>
+                </dependency>
+            </dependencies>
+
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-enforcer-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>enforce</goal>
+                                </goals>
+                                <configuration>
+                                    <rules>
+                                        <!--requireActiveProfile 'auth-server-wildfly/eap' doesn't work unless the profiles are defined in all submodule poms
+                                        using requireProperty instead-->
+                                        <requireProperty>
+                                            <property>cache.server</property>
+                                            <regex>(infinispan)|(jdg)</regex>
+                                            <regexMessage>Profile "cache-server-infinispan" requires activation of profile "cache-server-infinispan" or "cache-server-jdg".</regexMessage>
+                                        </requireProperty>
+                                    </rules>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-antrun-plugin</artifactId>
+                    </plugin>
+                </plugins>
+                <pluginManagement>
+                    <plugins>
+                        <plugin>
+                            <artifactId>maven-dependency-plugin</artifactId>
+                            <executions>
+                                <execution>
+                                    <id>unpack-cache-server-infinispan</id>
+                                    <phase>generate-resources</phase>
+                                    <goals>
+                                        <goal>unpack</goal>
+                                    </goals>
+                                    <configuration>
+                                        <artifactItems>
+                                            <artifactItem>
+                                                <groupId>org.keycloak.testsuite</groupId>
+                                                <artifactId>integration-arquillian-servers-cache-server-infinispan</artifactId>
+                                                <version>${project.version}</version>
+                                                <type>zip</type>
+                                                <outputDirectory>${containers.home}</outputDirectory>
+                                            </artifactItem>
+                                        </artifactItems>
+                                        <overWriteIfNewer>true</overWriteIfNewer>
+                                    </configuration>
+                                </execution>
+                            </executions>
+                        </plugin>
+                    </plugins>
+                </pluginManagement>
+            </build>
+        </profile>
+        
+        <profile>
+            <id>cache-server-jdg</id>
+            <properties>
+                <cache.server>jdg</cache.server>
+                <auth.server.undertow.crossdc>true</auth.server.undertow.crossdc>
+                <auth.server.crossdc>true</auth.server.crossdc>
+                <cache.server.jboss>true</cache.server.jboss>
+                <cache.server.config.dir>${cache.server.home}/standalone/configuration</cache.server.config.dir>
+            </properties>
+            <dependencies>
+                <dependency>
+                    <groupId>org.wildfly.arquillian</groupId>
+                    <artifactId>wildfly-arquillian-container-managed</artifactId>
+                </dependency>
+            </dependencies>
+
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-enforcer-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>enforce</goal>
+                                </goals>
+                                <configuration>
+                                    <rules>
+                                        <!--requireActiveProfile 'auth-server-wildfly/eap' doesn't work unless the profiles are defined in all submodule poms
+                                        using requireProperty instead-->
+                                        <requireProperty>
+                                            <property>cache.server</property>
+                                            <regex>(infinispan)|(jdg)</regex>
+                                            <regexMessage>Profile "cache-server-jdg" requires activation of profile "cache-server-infinispan" or "cache-server-jdg".</regexMessage>
+                                        </requireProperty>
+                                    </rules>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-antrun-plugin</artifactId>
+                    </plugin>
+                </plugins>
+                <pluginManagement>
+                    <plugins>
+                        <plugin>
+                            <artifactId>maven-dependency-plugin</artifactId>
+                            <executions>
+                                <execution>
+                                    <id>unpack-cache-server-jdg</id>
+                                    <phase>generate-resources</phase>
+                                    <goals>
+                                        <goal>unpack</goal>
+                                    </goals>
+                                    <configuration>
+                                        <artifactItems>
+                                            <artifactItem>
+                                                <groupId>org.keycloak.testsuite</groupId>
+                                                <artifactId>integration-arquillian-servers-cache-server-jdg</artifactId>
+                                                <version>${project.version}</version>
+                                                <type>zip</type>
+                                                <outputDirectory>${containers.home}</outputDirectory>
+                                            </artifactItem>
+                                        </artifactItems>
+                                        <overWriteIfNewer>true</overWriteIfNewer>
+                                    </configuration>
+                                </execution>
+                            </executions>
+                        </plugin>
+                    </plugins>
+                </pluginManagement>
+            </build>
+        </profile>
+
+        <profile>
             <id>auth-server-profile</id>
             <activation>
                 <property>
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index af76c9c..28a985c 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -1209,6 +1209,13 @@ authz-policy-js-code.tooltip=The JavaScript code providing the conditions for th
 authz-aggregated=Aggregated
 authz-add-aggregated-policy=Add Aggregated Policy
 
+# Authz Group Policy Detail
+authz-add-group-policy=Add Group Policy
+authz-no-groups-assigned=No groups assigned.
+authz-policy-group-claim=Groups Claim
+authz-policy-group-claim.tooltip=A claim to use as the source for user’s group. If the claim is present it must be an array of strings.
+authz-policy-group-groups.tooltip=Specifies the groups allowed by this policy.
+
 # Authz Permission List
 authz-no-permissions-available=No permissions available.
 
@@ -1305,9 +1312,35 @@ credential-reset-actions-timeout=Expires In
 credential-reset-actions-timeout.tooltip=Maximum time before the action permit expires.
 ldap-mappers=LDAP Mappers
 create-ldap-mapper=Create LDAP mapper
-
-
-
-
+map-role-mgmt-scope-description=Policies that decide if an admin can map this role to a user or group
+manage-authz-users-scope-description=Policies that decide if an admin can manage all users in the realm
+view-authz-users-scope-description=Policies that decide if an admin can view all users in realm
+permissions-enabled-role=Permissions Enabled
+permissions-enabled-role.tooltip=Whether or not to enable fine grain permissions for managing this role.  Disabling will delete all current permissions that have been set up.
+manage-permissions-role.tooltip=Fine grain permissions for managing roles.  For example, you can define different policies for who is allowed to map a role.
+lookup=Lookup
+manage-permissions-users.tooltip=Fine grain permssions for managing all users in realm.  You can define different policies for who is allowed to manage users in the realm.
+permissions-enabled-users=Permissions Enabled
+permissions-enabled-users.tooltip=Whether or not to enable fine grain permissions for managing users.  Disabling will delete all current permissions that have been set up.
+manage-permissions-client.tooltip=Fine grain permssions for admins that want to manage this client or apply roles defined by this client.
+manage-permissions-group.tooltip=Fine grain permssions for admins that want to manage this group or the members of this group.
+manage-authz-group-scope-description=Policies that decide if an admin can manage this group
+view-authz-group-scope-description=Policies that decide if an admin can view this group
+view-members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group
+manage-authz-client-scope-description=Policies that decide if an admin can manage this client
+configure-authz-client-scope-description=Reduced management permissions for admin.  Cannot set scope, template, or protocol mappers.
+view-authz-client-scope-description=Policies that decide if an admin can view this client
+map-roles-authz-client-scope-description=Policies that decide if an admin can map roles defined by this client
+map-roles-client-scope-authz-client-scope-description=Policies that decide if an admin can apply roles defined by this client to the client scope of another client
+map-roles-composite-authz-client-scope-description=Policies that decide if an admin can apply roles defined by this client as a composite to another role
+map-role-authz-role-scope-description=Policies that decide if an admin can map role this role to a user or group
+map-role-client-scope-authz-role-scope-description=Policies that decide if an admin can apply this role to the client scope of a client
+map-role-composite-authz-role-scope-description=Policies that decide if an admin can apply this role as a composite to another role
+manage-group-membership-authz-users-scope-description=Policies that decide if an admin can manage group membership for all users in the realm.  This is used in conjunction with specific group policy
+impersonate-authz-users-scope-description=Policies that decide if admin can impersonate other users
+map-roles-authz-users-scope-description=Policies that decide if admin can map roles for all users
+user-impersonated-authz-users-scope-description=Policies that decide which users can be impersonated.  These policies are applied to the user being impersonated.
+manage-membership-authz-group-scope-description=Policies that decide if admin can add or remove users from this group
+manage-members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group
 
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js
index 4451535..c4e870f 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -1398,9 +1398,6 @@ module.config([ '$routeProvider', function($routeProvider) {
                 realm : function(RealmLoader) {
                     return RealmLoader();
                 },
-                clients : function(ClientListLoader) {
-                    return ClientListLoader();
-                },
                 serverInfo : function(ServerInfoLoader) {
                     return ServerInfoLoader();
                 }
@@ -2358,6 +2355,24 @@ module.directive('kcTabsAuthentication', function () {
     }
 });
 
+module.directive('kcTabsRole', function () {
+    return {
+        scope: true,
+        restrict: 'E',
+        replace: true,
+        templateUrl: resourceUrl + '/templates/kc-tabs-role.html'
+    }
+});
+
+module.directive('kcTabsClientRole', function () {
+    return {
+        scope: true,
+        restrict: 'E',
+        replace: true,
+        templateUrl: resourceUrl + '/templates/kc-tabs-client-role.html'
+    }
+});
+
 module.directive('kcTabsUser', function () {
     return {
         scope: true,
@@ -2367,6 +2382,15 @@ module.directive('kcTabsUser', function () {
     }
 });
 
+module.directive('kcTabsUsers', function () {
+    return {
+        scope: true,
+        restrict: 'E',
+        replace: true,
+        templateUrl: resourceUrl + '/templates/kc-tabs-users.html'
+    }
+});
+
 module.directive('kcTabsGroup', function () {
     return {
         scope: true,
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js
index 7c5e7fa..a59ebf3 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js
@@ -324,7 +324,29 @@ module.config(['$routeProvider', function ($routeProvider) {
             }
         },
         controller: 'ResourceServerPolicyRoleDetailCtrl'
-    }).when('/realms/:realm/clients/:client/authz/resource-server/policy/js/create', {
+    }).when('/realms/:realm/clients/:client/authz/resource-server/policy/group/create', {
+          templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-group-detail.html',
+          resolve: {
+              realm: function (RealmLoader) {
+                  return RealmLoader();
+              },
+              client : function(ClientLoader) {
+                  return ClientLoader();
+              }
+          },
+          controller: 'ResourceServerPolicyGroupDetailCtrl'
+      }).when('/realms/:realm/clients/:client/authz/resource-server/policy/group/:id', {
+          templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-group-detail.html',
+          resolve: {
+              realm: function (RealmLoader) {
+                  return RealmLoader();
+              },
+              client : function(ClientLoader) {
+                  return ClientLoader();
+              }
+          },
+          controller: 'ResourceServerPolicyGroupDetailCtrl'
+      }).when('/realms/:realm/clients/:client/authz/resource-server/policy/js/create', {
         templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-js-detail.html',
         resolve: {
             realm: function (RealmLoader) {
@@ -390,7 +412,65 @@ module.config(['$routeProvider', function ($routeProvider) {
             }
         },
         controller: 'ResourceServerPolicyAggregateDetailCtrl'
-    });
+    }).when('/realms/:realm/roles/:role/permissions', {
+        templateUrl : resourceUrl + '/partials/authz/mgmt/realm-role-permissions.html',
+        resolve : {
+            realm : function(RealmLoader) {
+                return RealmLoader();
+            },
+            role : function(RoleLoader) {
+                return RoleLoader();
+            }
+        },
+        controller : 'RealmRolePermissionsCtrl'
+    }).when('/realms/:realm/clients/:client/roles/:role/permissions', {
+        templateUrl : resourceUrl + '/partials/authz/mgmt/client-role-permissions.html',
+        resolve : {
+            realm : function(RealmLoader) {
+                return RealmLoader();
+            },
+            client : function(ClientLoader) {
+                return ClientLoader();
+            },
+            role : function(RoleLoader) {
+                return RoleLoader();
+            }
+        },
+        controller : 'ClientRolePermissionsCtrl'
+    }).when('/realms/:realm/users-permissions', {
+        templateUrl : resourceUrl + '/partials/authz/mgmt/users-permissions.html',
+        resolve : {
+            realm : function(RealmLoader) {
+                return RealmLoader();
+            }
+        },
+        controller : 'UsersPermissionsCtrl'
+    })
+        .when('/realms/:realm/clients/:client/permissions', {
+            templateUrl : resourceUrl + '/partials/authz/mgmt/client-permissions.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                client : function(ClientLoader) {
+                    return ClientLoader();
+                }
+            },
+            controller : 'ClientPermissionsCtrl'
+        })
+        .when('/realms/:realm/groups/:group/permissions', {
+            templateUrl : resourceUrl + '/partials/authz/mgmt/group-permissions.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                group : function(GroupLoader) {
+                    return GroupLoader();
+                }
+            },
+            controller : 'GroupPermissionsCtrl'
+        })
+    ;
 }]);
 
 module.directive('kcTabsResourceServer', function () {
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
index 2cce138..034d595 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
@@ -79,72 +79,7 @@ module.controller('ResourceServerDetailCtrl', function($scope, $http, $route, $l
     });
 });
 
-var Resources = {
-    delete: function(ResourceServerResource, realm, client, $scope, AuthzDialog, $location, Notifications, $route) {
-        ResourceServerResource.permissions({
-            realm : realm,
-            client : client.id,
-            rsrid : $scope.resource._id
-        }, function (permissions) {
-            var msg = "";
-
-            if (permissions.length > 0 && !$scope.deleteConsent) {
-                msg = "<p>This resource is referenced in some permissions:</p>";
-                msg += "<ul>";
-                for (i = 0; i < permissions.length; i++) {
-                    msg+= "<li><strong>" + permissions[i].name + "</strong></li>";
-                }
-                msg += "</ul>";
-                msg += "<p>If you remove this resource, the permissions above will be affected and will not be associated with this resource anymore.</p>";
-            }
-
-            AuthzDialog.confirmDeleteWithMsg($scope.resource.name, "Resource", msg, function() {
-                ResourceServerResource.delete({realm : realm, client : $scope.client.id, rsrid : $scope.resource._id}, null, function() {
-                    $location.url("/realms/" + realm + "/clients/" + $scope.client.id + "/authz/resource-server/resource");
-                    $route.reload();
-                    Notifications.success("The resource has been deleted.");
-                });
-            });
-        });
-    }
-}
-
-var Policies = {
-    delete: function(service, realm, client, $scope, AuthzDialog, $location, Notifications, $route, isPermission) {
-        var msg = "";
-
-        service.dependentPolicies({
-            realm : realm,
-            client : client.id,
-            id : $scope.policy.id
-        }, function (dependentPolicies) {
-            if (dependentPolicies.length > 0 && !$scope.deleteConsent) {
-                msg = "<p>This policy is being used by other policies:</p>";
-                msg += "<ul>";
-                for (i = 0; i < dependentPolicies.length; i++) {
-                    msg+= "<li><strong>" + dependentPolicies[i].name + "</strong></li>";
-                }
-                msg += "</ul>";
-                msg += "<p>If you remove this policy, the policies above will be affected and will not be associated with this policy anymore.</p>";
-            }
-
-            AuthzDialog.confirmDeleteWithMsg($scope.policy.name, isPermission ? "Permission" : "Policy", msg, function() {
-                service.delete({realm : realm, client : $scope.client.id, id : $scope.policy.id}, null, function() {
-                    if (isPermission) {
-                        $location.url("/realms/" + realm + "/clients/" + $scope.client.id + "/authz/resource-server/permission");
-                        Notifications.success("The permission has been deleted.");
-                    } else {
-                        $location.url("/realms/" + realm + "/clients/" + $scope.client.id + "/authz/resource-server/policy");
-                        Notifications.success("The policy has been deleted.");
-                    }
-                    $route.reload();
-                });
-            });
-        });
-    }
-}
-
-module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerResource, client, AuthzDialog, Notifications) {
+module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerResource, client) {
     $scope.realm = realm;
     $scope.client = client;
 
@@ -236,11 +171,6 @@ module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, 
             }
         }
     };
-
-    $scope.delete = function(resource) {
-        $scope.resource = resource;
-        Resources.delete(ResourceServerResource, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route);
-    };
 });
 
 module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerResource, ResourceServerScope, AuthzDialog, Notifications) {
@@ -352,7 +282,30 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
                 }
 
                 $scope.remove = function() {
-                    Resources.delete(ResourceServerResource, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route);
+                    ResourceServerResource.permissions({
+                        realm : $route.current.params.realm,
+                        client : client.id,
+                        rsrid : $scope.resource._id
+                    }, function (permissions) {
+                        var msg = "";
+
+                        if (permissions.length > 0 && !$scope.deleteConsent) {
+                            msg = "<p>This resource is referenced in some policies:</p>";
+                            msg += "<ul>";
+                            for (i = 0; i < permissions.length; i++) {
+                                msg+= "<li><strong>" + permissions[i].name + "</strong></li>";
+                            }
+                            msg += "</ul>";
+                            msg += "<p>If you remove this resource, the policies above will be affected and will not be associated with this resource anymore.</p>";
+                        }
+
+                        AuthzDialog.confirmDeleteWithMsg($scope.resource.name, "Resource", msg, function() {
+                            ResourceServerResource.delete({realm : realm.realm, client : $scope.client.id, rsrid : $scope.resource._id}, null, function() {
+                                $location.url("/realms/" + realm.realm + "/clients/" + $scope.client.id + "/authz/resource-server/resource");
+                                Notifications.success("The resource has been deleted.");
+                            });
+                        });
+                    });
                 }
 
                 $scope.reset = function() {
@@ -367,6 +320,9 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
     }
 
     this.checkNameAvailability = function (onSuccess) {
+        if (!$scope.resource.name || $scope.resource.name.trim().length == 0) {
+            return;
+        }
         ResourceServerResource.search({
             realm : $route.current.params.realm,
             client : client.id,
@@ -382,37 +338,7 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
     }
 });
 
-var Scopes = {
-    delete: function(ResourceServerScope, realm, client, $scope, AuthzDialog, $location, Notifications, $route) {
-        ResourceServerScope.permissions({
-            realm : realm,
-            client : client.id,
-            id : $scope.scope.id
-        }, function (permissions) {
-            var msg = "";
-
-            if (permissions.length > 0 && !$scope.deleteConsent) {
-                msg = "<p>This scope is referenced in some permissions:</p>";
-                msg += "<ul>";
-                for (i = 0; i < permissions.length; i++) {
-                    msg+= "<li><strong>" + permissions[i].name + "</strong></li>";
-                }
-                msg += "</ul>";
-                msg += "<p>If you remove this scope, the permissions above will be affected and will not be associated with this scope anymore.</p>";
-            }
-
-            AuthzDialog.confirmDeleteWithMsg($scope.scope.name, "Scope", msg, function() {
-                ResourceServerScope.delete({realm : realm, client : $scope.client.id, id : $scope.scope.id}, null, function() {
-                    $location.url("/realms/" + realm + "/clients/" + $scope.client.id + "/authz/resource-server/scope");
-                    $route.reload();
-                    Notifications.success("The scope has been deleted.");
-                });
-            });
-        });
-    }
-}
-
-module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerScope,client, AuthzDialog, Notifications) {
+module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerScope, client) {
     $scope.realm = realm;
     $scope.client = client;
 
@@ -504,11 +430,6 @@ module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $lo
             }
         }
     };
-
-    $scope.delete = function(scope) {
-        $scope.scope = scope;
-        Scopes.delete(ResourceServerScope, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route);
-    };
 });
 
 module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerScope, AuthzDialog, Notifications) {
@@ -578,7 +499,30 @@ module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $rout
                 }
 
                 $scope.remove = function() {
-                    Scopes.delete(ResourceServerScope, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route);
+                    ResourceServerScope.permissions({
+                        realm : $route.current.params.realm,
+                        client : client.id,
+                        id : $scope.scope.id
+                    }, function (permissions) {
+                        var msg = "";
+
+                        if (permissions.length > 0 && !$scope.deleteConsent) {
+                            msg = "<p>This scope is referenced in some policies:</p>";
+                            msg += "<ul>";
+                            for (i = 0; i < permissions.length; i++) {
+                                msg+= "<li><strong>" + permissions[i].name + "</strong></li>";
+                            }
+                            msg += "</ul>";
+                            msg += "<p>If you remove this scope, the policies above will be affected and will not be associated with this scope anymore.</p>";
+                        }
+
+                        AuthzDialog.confirmDeleteWithMsg($scope.scope.name, "Scope", msg, function() {
+                            ResourceServerScope.delete({realm : realm.realm, client : $scope.client.id, id : $scope.scope.id}, null, function() {
+                                $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/scope");
+                                Notifications.success("The scope has been deleted.");
+                            });
+                        });
+                    });
                 }
 
                 $scope.reset = function() {
@@ -593,6 +537,9 @@ module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $rout
     }
 
     this.checkNameAvailability = function (onSuccess) {
+        if (!$scope.scope.name || $scope.scope.name.trim().length == 0) {
+            return;
+        }
         ResourceServerScope.search({
             realm : $route.current.params.realm,
             client : client.id,
@@ -607,7 +554,7 @@ module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $rout
     }
 });
 
-module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerPolicy, PolicyProvider, client, AuthzDialog, Notifications) {
+module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerPolicy, PolicyProvider, client) {
     $scope.realm = realm;
     $scope.client = client;
     $scope.policyProviders = [];
@@ -703,14 +650,9 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l
             }
         }
     };
-
-    $scope.delete = function(policy) {
-        $scope.policy = policy;
-        Policies.delete(ResourceServerPolicy, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route, false);
-    };
 });
 
-module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerPermission, PolicyProvider, client, AuthzDialog, Notifications) {
+module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerPermission, PolicyProvider, client) {
     $scope.realm = realm;
     $scope.client = client;
     $scope.policyProviders = [];
@@ -805,11 +747,6 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route
             }
         }
     };
-
-    $scope.delete = function(policy) {
-        $scope.policy = policy;
-        Policies.delete(ResourceServerPermission, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route, true);
-    };
 });
 
 module.controller('ResourceServerPolicyDroolsDetailCtrl', function($scope, $http, $route, realm, client, PolicyController) {
@@ -1254,19 +1191,17 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route
                 client : client.id,
                 id : policy.id
             }, function(policies) {
-                if (policies.length > 0) {
-                    $scope.selectedPolicies = [];
-                    for (i = 0; i < policies.length; i++) {
-                        policies[i].text = policies[i].name;
-                        $scope.selectedPolicies.push(policies[i]);
-                    }
-                    var copy = angular.copy($scope.selectedPolicies);
-                    $scope.$watch('selectedPolicies', function() {
-                        if (!angular.equals($scope.selectedPolicies, copy)) {
-                            $scope.changed = true;
-                        }
-                    }, true);
+                $scope.selectedPolicies = [];
+                for (i = 0; i < policies.length; i++) {
+                    policies[i].text = policies[i].name;
+                    $scope.selectedPolicies.push(policies[i]);
                 }
+                var copy = angular.copy($scope.selectedPolicies);
+                $scope.$watch('selectedPolicies', function() {
+                    if (!angular.equals($scope.selectedPolicies, copy)) {
+                        $scope.changed = true;
+                    }
+                }, true);
             });
         },
 
@@ -1728,6 +1663,119 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
     }
 });
 
+module.controller('ResourceServerPolicyGroupDetailCtrl', function($scope, $route, realm, client, Client, Groups, Group, PolicyController) {
+    PolicyController.onInit({
+        getPolicyType : function() {
+            return "group";
+        },
+
+        onInit : function() {
+            $scope.tree = [];
+
+            Groups.query({realm: $route.current.params.realm}, function(groups) {
+                $scope.groups = groups;
+                $scope.groupList = [
+                    {"id" : "realm", "name": "Groups",
+                                "subGroups" : groups}
+                ];
+            });
+
+            var isLeaf = function(node) {
+                return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0);
+            }
+
+            $scope.getGroupClass = function(node) {
+                if (node.id == "realm") {
+                    return 'pficon pficon-users';
+                }
+                if (isLeaf(node)) {
+                    return 'normal';
+                }
+                if (node.subGroups.length && node.collapsed) return 'collapsed';
+                if (node.subGroups.length && !node.collapsed) return 'expanded';
+                return 'collapsed';
+
+            }
+
+            $scope.getSelectedClass = function(node) {
+                if (node.selected) {
+                    return 'selected';
+                } else if ($scope.cutNode && $scope.cutNode.id == node.id) {
+                    return 'cut';
+                }
+                return undefined;
+            }
+
+            $scope.selectGroup = function(group) {
+                for (i = 0; i < $scope.selectedGroups.length; i++) {
+                    if ($scope.selectedGroups[i].id == group.id) {
+                        return
+                    }
+                }
+                $scope.selectedGroups.push({id: group.id, path: group.path});
+                $scope.changed = true;
+            }
+
+            $scope.extendChildren = function(group) {
+                $scope.changed = true;
+            }
+
+            $scope.removeFromList = function(group) {
+                var index = $scope.selectedGroups.indexOf(group);
+                if (index != -1) {
+                    $scope.selectedGroups.splice(index, 1);
+                    $scope.changed = true;
+                }
+            }
+        },
+
+        onInitCreate : function(policy) {
+            var selectedGroups = [];
+
+            $scope.selectedGroups = angular.copy(selectedGroups);
+
+            $scope.$watch('selectedGroups', function() {
+                if (!angular.equals($scope.selectedGroups, selectedGroups)) {
+                    $scope.changed = true;
+                } else {
+                    $scope.changed = false;
+                }
+            }, true);
+        },
+
+        onInitUpdate : function(policy) {
+            $scope.selectedGroups = policy.groups;
+
+            angular.forEach($scope.selectedGroups, function(group, index){
+               Group.get({realm: $route.current.params.realm, groupId: group.id}, function (existing) {
+                   group.path = existing.path;
+               });
+            });
+
+            $scope.$watch('selectedGroups', function() {
+                if (!$scope.changed) {
+                    return;
+                }
+                if (!angular.equals($scope.selectedGroups, selectedGroups)) {
+                    $scope.changed = true;
+                } else {
+                    $scope.changed = false;
+                }
+            }, true);
+        },
+
+        onUpdate : function() {
+            $scope.policy.groups = $scope.selectedGroups;
+            delete $scope.policy.config;
+        },
+
+        onCreate : function() {
+            $scope.policy.groups = $scope.selectedGroups;
+            delete $scope.policy.config;
+        }
+    }, realm, client, $scope);
+});
+
 module.controller('ResourceServerPolicyJSDetailCtrl', function($scope, $route, $location, realm, PolicyController, client) {
     PolicyController.onInit({
         getPolicyType : function() {
@@ -2050,7 +2098,35 @@ module.service("PolicyController", function($http, $route, $location, ResourceSe
                 });
 
                 $scope.remove = function() {
-                    Policies.delete(ResourceServerPolicy, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route, delegate.isPermission());
+                    var msg = "";
+
+                    service.dependentPolicies({
+                        realm : $route.current.params.realm,
+                        client : client.id,
+                        id : $scope.policy.id
+                    }, function (dependentPolicies) {
+                        if (dependentPolicies.length > 0 && !$scope.deleteConsent) {
+                            msg = "<p>This policy is being used by other policies:</p>";
+                            msg += "<ul>";
+                            for (i = 0; i < dependentPolicies.length; i++) {
+                                msg+= "<li><strong>" + dependentPolicies[i].name + "</strong></li>";
+                            }
+                            msg += "</ul>";
+                            msg += "<p>If you remove this policy, the policies above will be affected and will not be associated with this policy anymore.</p>";
+                        }
+
+                        AuthzDialog.confirmDeleteWithMsg($scope.policy.name, "Policy", msg, function() {
+                            service.delete({realm : $scope.realm.realm, client : $scope.client.id, id : $scope.policy.id}, null, function() {
+                                if (delegate.isPermission()) {
+                                    $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission");
+                                    Notifications.success("The permission has been deleted.");
+                                } else {
+                                    $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy");
+                                    Notifications.success("The policy has been deleted.");
+                                }
+                            });
+                        });
+                    });
                 }
             }
         });
@@ -2060,6 +2136,9 @@ module.service("PolicyController", function($http, $route, $location, ResourceSe
         }
 
         this.checkNameAvailability = function (onSuccess) {
+            if (!$scope.policy.name || $scope.policy.name.trim().length == 0) {
+                return;
+            }
             ResourceServerPolicy.search({
                 realm: $route.current.params.realm,
                 client: client.id,
@@ -2082,21 +2161,13 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio
     $scope.client = client;
     $scope.clients = clients;
     $scope.roles = roles;
-    authzRequest = {};
-    authzRequest.resources = [];
-    authzRequest.context = {};
-    authzRequest.context.attributes = {};
-    authzRequest.roleIds = [];
+    $scope.authzRequest = {};
+    $scope.authzRequest.resources = [];
+    $scope.authzRequest.context = {};
+    $scope.authzRequest.context.attributes = {};
+    $scope.authzRequest.roleIds = [];
     $scope.resultUrl = resourceUrl + '/partials/authz/policy/resource-server-policy-evaluate-result.html';
 
-    $scope.authzRequest = angular.copy(authzRequest);
-
-    $scope.$watch('authzRequest', function() {
-        if (!angular.equals($scope.authzRequest, authzRequest)) {
-            $scope.changed = true;
-        }
-    }, true);
-
     $scope.addContextAttribute = function() {
         if (!$scope.newContextAttribute.value || $scope.newContextAttribute.value == '') {
             Notifications.error("You must provide a value to a context attribute.");
@@ -2409,4 +2480,103 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio
         $scope.authzRequest = angular.copy(authzRequest);
         $scope.changed = false;
     }
-});
\ No newline at end of file
+});
+
+getManageClientId = function(realm) {
+    if (realm.realm == masterRealm) {
+        return 'master-realm';
+    } else {
+        return 'realm-management';
+    }
+}
+
+module.controller('RealmRolePermissionsCtrl', function($scope, $http, $route, $location, realm, role, RoleManagementPermissions, Client, Notifications) {
+    console.log('RealmRolePermissionsCtrl');
+    $scope.role = role;
+    $scope.realm = realm;
+    RoleManagementPermissions.get({realm: realm.realm, role: role.id}, function(data) {
+        $scope.permissions = data;
+    });
+    Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) {
+        $scope.realmManagementClientId = data[0].id;
+    });
+    $scope.setEnabled = function() {
+        var param = { enabled: $scope.permissions.enabled};
+        $scope.permissions= RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param);
+    };
+
+
+});
+module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $location, realm, client, role, Client, RoleManagementPermissions, Client, Notifications) {
+    console.log('RealmRolePermissionsCtrl');
+    $scope.client = client;
+    $scope.role = role;
+    $scope.realm = realm;
+    RoleManagementPermissions.get({realm: realm.realm, role: role.id}, function(data) {
+        $scope.permissions = data;
+    });
+    Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) {
+        $scope.realmManagementClientId = data[0].id;
+    });
+    $scope.setEnabled = function() {
+        console.log('perssions enabled: ' + $scope.permissions.enabled);
+        var param = { enabled: $scope.permissions.enabled};
+        $scope.permissions = RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param);
+    };
+
+
+});
+
+module.controller('UsersPermissionsCtrl', function($scope, $http, $route, $location, realm, UsersManagementPermissions, Client, Notifications) {
+    console.log('UsersPermissionsCtrl');
+    $scope.realm = realm;
+    UsersManagementPermissions.get({realm: realm.realm}, function(data) {
+        $scope.permissions = data;
+    });
+    Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) {
+        $scope.realmManagementClientId = data[0].id;
+    });
+    $scope.changeIt = function() {
+        console.log('before permissions.enabled=' + $scope.permissions.enabled);
+        var param = { enabled: $scope.permissions.enabled};
+        $scope.permissions = UsersManagementPermissions.update({realm: realm.realm}, param);
+    };
+
+
+});
+
+module.controller('ClientPermissionsCtrl', function($scope, $http, $route, $location, realm, client, Client, ClientManagementPermissions, Notifications) {
+    $scope.client = client;
+    $scope.realm = realm;
+    ClientManagementPermissions.get({realm: realm.realm, client: client.id}, function(data) {
+        $scope.permissions = data;
+    });
+    Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) {
+        $scope.realmManagementClientId = data[0].id;
+    });
+    $scope.setEnabled = function() {
+        var param = { enabled: $scope.permissions.enabled};
+        $scope.permissions = ClientManagementPermissions.update({realm: realm.realm, client: client.id}, param);
+    };
+
+
+});
+
+module.controller('GroupPermissionsCtrl', function($scope, $http, $route, $location, realm, group, GroupManagementPermissions, Client, Notifications) {
+    $scope.group = group;
+    $scope.realm = realm;
+    Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) {
+        $scope.realmManagementClientId = data[0].id;
+    });
+    GroupManagementPermissions.get({realm: realm.realm, group: group.id}, function(data) {
+        $scope.permissions = data;
+    });
+    $scope.setEnabled = function() {
+        var param = { enabled: $scope.permissions.enabled};
+        $scope.permissions = GroupManagementPermissions.update({realm: realm.realm, group: group.id}, param);
+    };
+
+
+});
+
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js
index 92219bb..f56ca8f 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js
@@ -144,4 +144,50 @@ module.service('AuthzDialog', function($modal) {
     }
 
     return dialog;
-});
\ No newline at end of file
+});
+
+module.factory('RoleManagementPermissions', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role/management/permissions', {
+        realm : '@realm',
+        role : '@role'
+    }, {
+        update: {
+            method: 'PUT'
+        }
+    });
+});
+
+module.factory('UsersManagementPermissions', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/users-management-permissions', {
+        realm : '@realm'
+    }, {
+        update: {
+            method: 'PUT'
+        }
+    });
+});
+
+module.factory('ClientManagementPermissions', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/clients/:client/management/permissions', {
+        realm : '@realm',
+        client : '@client'
+    }, {
+        update: {
+            method: 'PUT'
+        }
+    });
+});
+
+module.factory('GroupManagementPermissions', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/groups/:group/management/permissions', {
+        realm : '@realm',
+        group : '@group'
+    }, {
+        update: {
+            method: 'PUT'
+        }
+    });
+});
+
+
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 315b8a5..515eb99 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -728,9 +728,9 @@ module.controller('ClientImportCtrl', function($scope, $location, $upload, realm
 });
 
 
-module.controller('ClientListCtrl', function($scope, realm, clients, Client, serverInfo, $route, Dialog, Notifications, filterFilter) {
+module.controller('ClientListCtrl', function($scope, realm, Client, serverInfo, $route, Dialog, Notifications, filterFilter) {
     $scope.realm = realm;
-    $scope.clients = clients;
+    $scope.clients = Client.query({realm: realm.realm, viewableOnly: true});
     $scope.currentPage = 1;
     $scope.currentPageInput = 1;
     $scope.pageSize = 20;
@@ -1387,6 +1387,7 @@ module.controller('ClientScopeMappingCtrl', function($scope, $http, realm, clien
     }
 
     $scope.changeFlag = function() {
+        console.log('changeFlag');
         Client.update({
             realm : realm.realm,
             client : client.id
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 23f4ad8..ab4e72a 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -1,81 +1,95 @@
-module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location, Notifications, ServerInfo) {
-    $scope.authUrl = authUrl;
-    $scope.resourceUrl = resourceUrl;
-    $scope.auth = Auth;
-    $scope.serverInfo = ServerInfo.get();
-
-    function getAccess(role) {
-        if (!Current.realm) {
-            return false;
-        }
-
-        var realmAccess = Auth.user && Auth.user['realm_access'];
+function getAccess(Auth, Current, role) {
+    if (!Current.realm)return false;
+    var realmAccess = Auth.user && Auth.user['realm_access'];
+    if (realmAccess) {
+        realmAccess = realmAccess[Current.realm.realm];
         if (realmAccess) {
-            realmAccess = realmAccess[Current.realm.realm];
-            if (realmAccess) {
-                return realmAccess.indexOf(role) >= 0;
-            }
+            return realmAccess.indexOf(role) >= 0;
         }
-        return false;
     }
+    return false;
+}
 
-    $scope.access = {
+function getAccessObject(Auth, Current) {
+    return {
         get createRealm() {
             return Auth.user && Auth.user.createRealm;
         },
 
+        get queryUsers() {
+            return getAccess(Auth, Current, 'query-users') || this.viewUsers;
+        },
+
+        get queryGroups() {
+            return getAccess(Auth, Current, 'query-groups') || this.viewUsers;
+        },
+
+        get queryClients() {
+            return getAccess(Auth, Current, 'query-clients') || this.viewClients;
+        },
+
         get viewRealm() {
-            return getAccess('view-realm') || getAccess('manage-realm') || this.manageRealm;
+            return getAccess(Auth, Current, 'view-realm') || getAccess(Auth, Current, 'manage-realm') || this.manageRealm;
         },
 
         get viewClients() {
-            return getAccess('view-clients') || getAccess('manage-clients') || this.manageClients;
+            return getAccess(Auth, Current, 'view-clients') || getAccess(Auth, Current, 'manage-clients') || this.manageClients;
         },
 
         get viewUsers() {
-            return getAccess('view-users') || getAccess('manage-users') || this.manageClients;
+            return getAccess(Auth, Current, 'view-users') || getAccess(Auth, Current, 'manage-users') || this.manageClients;
         },
 
         get viewEvents() {
-            return getAccess('view-events') || getAccess('manage-events') || this.manageClients;
+            return getAccess(Auth, Current, 'view-events') || getAccess(Auth, Current, 'manage-events') || this.manageClients;
         },
 
         get viewIdentityProviders() {
-            return getAccess('view-identity-providers') || getAccess('manage-identity-providers') || this.manageIdentityProviders;
+            return getAccess(Auth, Current, 'view-identity-providers') || getAccess(Auth, Current, 'manage-identity-providers') || this.manageIdentityProviders;
         },
 
         get viewAuthorization() {
-            return getAccess('view-authorization') || this.manageAuthorization;
+            return getAccess(Auth, Current, 'view-authorization') || this.manageAuthorization;
         },
 
         get manageRealm() {
-            return getAccess('manage-realm');
+            return getAccess(Auth, Current, 'manage-realm');
         },
 
         get manageClients() {
-            return getAccess('manage-clients');
+            return getAccess(Auth, Current, 'manage-clients');
         },
 
         get manageUsers() {
-            return getAccess('manage-users');
+            return getAccess(Auth, Current, 'manage-users');
         },
 
         get manageEvents() {
-            return getAccess('manage-events');
+            return getAccess(Auth, Current, 'manage-events');
         },
 
         get manageIdentityProviders() {
-            return getAccess('manage-identity-providers');
+            return getAccess(Auth, Current, 'manage-identity-providers');
         },
 
         get manageAuthorization() {
-            return getAccess('manage-authorization');
+            return getAccess(Auth, Current, 'manage-authorization');
         },
 
         get impersonation() {
-            return getAccess('impersonation');
+            return getAccess(Auth, Current, 'impersonation');
         }
     };
+}
+
+
+module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location, Notifications, ServerInfo) {
+    $scope.authUrl = authUrl;
+    $scope.resourceUrl = resourceUrl;
+    $scope.auth = Auth;
+    $scope.serverInfo = ServerInfo.get();
+
+    $scope.access = getAccessObject(Auth, Current);
 
     $scope.$watch(function() {
         return $location.path();
@@ -85,20 +99,36 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
     });
 });
 
-module.controller('HomeCtrl', function(Realm, Auth, $location) {
+module.controller('HomeCtrl', function(Realm, Auth, Current, $location) {
+
     Realm.query(null, function(realms) {
         var realm;
         if (realms.length == 1) {
-            realm = realms[0].realm;
+            realm = realms[0];
         } else if (realms.length == 2) {
             if (realms[0].realm == Auth.user.realm) {
-                realm = realms[1].realm;
+                realm = realms[1];
             } else if (realms[1].realm == Auth.user.realm) {
-                realm = realms[0].realm;
+                realm = realms[0];
             }
         }
         if (realm) {
-            $location.url('/realms/' + realm);
+            Current.realms = realms;
+            Current.realm = realm;
+            var access = getAccessObject(Auth, Current);
+            if (access.viewRealm || access.manageRealm) {
+                $location.url('/realms/' + realm.realm );
+            } else if (access.queryClients) {
+                $location.url('/realms/' + realm.realm + "/clients");
+            } else if (access.viewIdentityProviders) {
+                $location.url('/realms/' + realm.realm + "/identity-provider-settings");
+            } else if (access.queryUsers) {
+                $location.url('/realms/' + realm.realm + "/users");
+            } else if (access.queryGroups) {
+                $location.url('/realms/' + realm.realm + "/groups");
+            } else if (access.viewEvents) {
+                $location.url('/realms/' + realm.realm + "/events");
+            }
         } else {
             $location.url('/realms');
         }
@@ -1322,6 +1352,21 @@ module.controller('RealmRevocationCtrl', function($scope, Realm, RealmPushRevoca
 });
 
 
+module.controller('RoleTabCtrl', function(Dialog, $scope, Current, Notifications, $location) {
+    $scope.removeRole = function() {
+        Dialog.confirmDelete($scope.role.name, 'role', function() {
+            RoleById.remove({
+                realm: realm.realm,
+                role: $scope.role.id
+            }, function () {
+                $route.reload();
+                Notifications.success("The role has been deleted.");
+            });
+        });
+    };
+});
+
+
 module.controller('RoleListCtrl', function($scope, $route, Dialog, Notifications, realm, roles, RoleById, filterFilter) {
     $scope.realm = realm;
     $scope.roles = roles;
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html
new file mode 100644
index 0000000..abc21a4
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html
@@ -0,0 +1,39 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/clients">{{:: 'clients' | translate}}</a></li>
+        <li>{{client.clientId}}</li>
+    </ol>
+
+    <kc-tabs-client></kc-tabs-client>
+
+    <form class=form-horizontal" name="enableForm" novalidate kc-read-only="!client.access.manage || !access.manageAuthorization">
+        <fieldset class="border-top">
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="permissionsEnabled">{{:: 'permissions-enabled-role' | translate}}</label>
+            <div class="col-md-6">
+                <input ng-model="permissions.enabled" ng-click="setEnabled()" name="permissionsEnabled" id="permissionsEnabled" ng-disabled="!access.manageAuthorization" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+            </div>
+            <kc-tooltip>{{:: 'permissions-enabled-role.tooltip' | translate}}</kc-tooltip>
+        </div>
+        </fieldset>
+    </form>
+    <table class="datatable table table-striped table-bordered dataTable no-footer" data-ng-show="permissions.enabled">
+        <thead>
+        <tr>
+            <th>{{:: 'scope-name' | translate}}</th>
+            <th>{{:: 'description' | translate}}</th>
+            <th colspan="2">{{:: 'actions' | translate}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="(scopeName, scopeId) in permissions.scopePermissions">
+            <td><a href="#/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{scopeName}}</a></td>
+            <td translate="{{scopeName}}-authz-client-scope-description"></td>
+            <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{:: 'edit' | translate}}</td>
+        </tr>
+        </tbody>
+    </table>
+
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html
new file mode 100644
index 0000000..c5f37ea
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html
@@ -0,0 +1,40 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/clients">{{:: 'clients' | translate}}</a></li>
+        <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></li>
+        <li>{{role.name}}</li>
+    </ol>
+
+    <kc-tabs-client-role></kc-tabs-client-role>
+
+    <form class=form-horizontal" name="enableForm" novalidate kc-read-only="!client.access.manage || !access.manageAuthorization">
+        <fieldset class="border-top">
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="permissionsEnabled">{{:: 'permissions-enabled-role' | translate}}</label>
+            <div class="col-md-6">
+                <input ng-model="permissions.enabled" ng-click="setEnabled()" name="permissionsEnabled" id="permissionsEnabled" ng-disabled="!access.manageAuthorization" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+            </div>
+            <kc-tooltip>{{:: 'permissions-enabled-role.tooltip' | translate}}</kc-tooltip>
+        </div>
+        </fieldset>
+    </form>
+    <table class="datatable table table-striped table-bordered dataTable no-footer" data-ng-show="permissions.enabled">
+        <thead>
+        <tr>
+            <th>{{:: 'scope-name' | translate}}</th>
+            <th>{{:: 'description' | translate}}</th>
+            <th colspan="2">{{:: 'actions' | translate}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="(scopeName, scopeId) in permissions.scopePermissions">
+            <td><a href="#/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{scopeName}}</a></td>
+            <td translate="{{scopeName}}-authz-role-scope-description"></td>
+            <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{:: 'edit' | translate}}</td>
+        </tr>
+        </tbody>
+    </table>
+
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/group-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/group-permissions.html
new file mode 100644
index 0000000..897a0ed
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/group-permissions.html
@@ -0,0 +1,39 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/groups">{{:: 'groups' | translate}}</a></li>
+        <li>{{group.name}}</li>
+    </ol>
+
+    <kc-tabs-group></kc-tabs-group>
+
+    <form class=form-horizontal" name="enableForm" novalidate kc-read-only="!group.access.manage || !access.manageAuthorization">
+        <fieldset class="border-top">
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="permissionsEnabled">{{:: 'permissions-enabled-role' | translate}}</label>
+            <div class="col-md-6">
+                <input ng-model="permissions.enabled" ng-click="setEnabled()" name="permissionsEnabled" id="permissionsEnabled" ng-disabled="!access.manageAuthorization" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+            </div>
+            <kc-tooltip>{{:: 'permissions-enabled-role.tooltip' | translate}}</kc-tooltip>
+        </div>
+        </fieldset>
+    </form>
+    <table class="datatable table table-striped table-bordered dataTable no-footer" data-ng-show="permissions.enabled">
+        <thead>
+        <tr>
+            <th>{{:: 'scope-name' | translate}}</th>
+            <th>{{:: 'description' | translate}}</th>
+            <th colspan="2">{{:: 'actions' | translate}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="(scopeName, scopeId) in permissions.scopePermissions">
+            <td><a href="#/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{scopeName}}</a></td>
+            <td translate="{{scopeName}}-authz-group-scope-description"></td>
+            <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{:: 'edit' | translate}}</td>
+        </tr>
+        </tbody>
+    </table>
+
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html
new file mode 100644
index 0000000..9c03333
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html
@@ -0,0 +1,39 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/roles">{{:: 'roles' | translate}}</a></li>
+        <li>{{role.name}}</li>
+    </ol>
+
+    <kc-tabs-role></kc-tabs-role>
+
+    <form class=form-horizontal" name="enableForm" novalidate kc-read-only="!access.manageAuthorization">
+        <fieldset class="border-top">
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="permissionsEnabled">{{:: 'permissions-enabled-role' | translate}}</label>
+            <div class="col-md-6">
+                <input ng-model="permissions.enabled" ng-click="setEnabled()" name="permissionsEnabled" id="permissionsEnabled" ng-disabled="!access.manageAuthorization" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+            </div>
+            <kc-tooltip>{{:: 'permissions-enabled-role.tooltip' | translate}}</kc-tooltip>
+        </div>
+        </fieldset>
+    </form>
+    <table class="datatable table table-striped table-bordered dataTable no-footer" data-ng-show="permissions.enabled">
+        <thead>
+        <tr>
+            <th>{{:: 'scope-name' | translate}}</th>
+            <th>{{:: 'description' | translate}}</th>
+            <th colspan="2">{{:: 'actions' | translate}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="(scopeName, scopeId) in permissions.scopePermissions">
+            <td><a href="#/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{scopeName}}</a></td>
+            <td translate="{{scopeName}}-authz-role-scope-description"></td>
+            <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{:: 'edit' | translate}}</td>
+        </tr>
+        </tbody>
+    </table>
+
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html
new file mode 100644
index 0000000..4a5661f
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html
@@ -0,0 +1,35 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+    <kc-tabs-users></kc-tabs-users>
+
+    <form class=form-horizontal" name="enableForm" novalidate kc-read-only="!access.manageAuthorization">
+        <fieldset class="border-top">
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="permissionsEnabled">{{:: 'permissions-enabled-users' | translate}}</label>
+            <div class="col-md-6">
+                <input ng-model="permissions.enabled" ng-click="changeIt()" name="permissionsEnabled" id="permissionsEnabled" ng-disabled="!access.manageAuthorization" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+            </div>
+            <kc-tooltip>{{:: 'permissions-enabled-users.tooltip' | translate}}</kc-tooltip>
+        </div>
+        </fieldset>
+    </form>
+    <table class="datatable table table-striped table-bordered dataTable no-footer" data-ng-show="permissions.enabled">
+        <thead>
+        <tr>
+            <th>{{:: 'scope-name' | translate}}</th>
+            <th>{{:: 'description' | translate}}</th>
+            <th colspan="2">{{:: 'actions' | translate}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="(scopeName, scopeId) in permissions.scopePermissions">
+            <td><a href="#/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{scopeName}}</a></td>
+            <td translate="{{scopeName}}-authz-users-scope-description"></td>
+            <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{realmManagementClientId}}/authz/resource-server/permission/scope/{{scopeId}}">{{:: 'edit' | translate}}</td>
+        </tr>
+        </tbody>
+    </table>
+
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-group-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-group-detail.html
new file mode 100644
index 0000000..61af0f1
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-group-detail.html
@@ -0,0 +1,124 @@
+<!--
+  ~ * Copyright 2017 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.
+  -->
+
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/clients">{{:: 'clients' | translate}}</a></li>
+        <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></li>
+        <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server">{{:: 'authz-authorization' | translate}}</a></li>
+        <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy">{{:: 'authz-policies' | translate}}</a></li>
+        <li data-ng-show="create">{{:: 'authz-add-group-policy' | translate}}</li>
+        <li data-ng-hide="create">{{:: 'groups' | translate}}</li>
+        <li data-ng-hide="create">{{originalPolicy.name}}</li>
+    </ol>
+
+    <h1 data-ng-show="create">{{:: 'authz-add-group-policy' | translate}}</h1>
+    <h1 data-ng-hide="create">{{originalPolicy.name|capitalize}}<i class="pficon pficon-delete clickable" data-ng-show="!create"
+                                                         data-ng-click="remove()"></i></h1>
+
+    <form class="form-horizontal" name="groupPolicyForm" novalidate>
+        <fieldset class="border-top">
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="name">{{:: 'name' | translate}} <span class="required">*</span></label>
+                <div class="col-sm-6">
+                    <input class="form-control" type="text" id="name" name="name" data-ng-model="policy.name" autofocus required data-ng-blur="checkNewNameAvailability()">
+                </div>
+                <kc-tooltip>{{:: 'authz-policy-name.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="description">{{:: 'description' | translate}} </label>
+                <div class="col-sm-6">
+                    <input class="form-control" type="text" id="description" name="description" data-ng-model="policy.description">
+                </div>
+                <kc-tooltip>{{:: 'authz-policy-description.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="groupsClaim">{{:: 'authz-policy-group-claim' | translate}} <span class="required">*</span></label>
+                <div class="col-sm-6">
+                    <input class="form-control" type="text" id="groupsClaim" name="groupsClaim" data-ng-model="policy.groupsClaim" required>
+                </div>
+                <kc-tooltip>{{:: 'authz-policy-group-claim.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="selectedGroups">{{:: 'groups' | translate}} <span class="required">*</span></label>
+                <div class="col-md-6">
+                    <div tree-id="tree"
+                        angular-treeview="true"
+                        tree-model="groupList"
+                        node-id="id"
+                        node-label="name"
+                        node-children="subGroups" >
+                    </div>
+                    <button data-ng-click="selectGroup(tree.currentNode)" id="selectGroup" class="btn btn-primary" data-ng-disabled="tree.currentNode == null">Select</button>
+                    <input class="form-control" type="text" id="selectedGroups" name="selectedGroups" data-ng-model="noop" data-ng-required="selectedGroups.length <= 0" autofocus required data-ng-show="false">
+                </div>
+                <kc-tooltip>{{:: 'authz-policy-user-users.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group" data-ng-if="selectedGroups.length > 0">
+                <label class="col-md-2 control-label"></label>
+                <div class="col-md-5">
+                    <table class="table table-striped table-bordered" id="selected-groups">
+                        <thead>
+                            <tr>
+                                <th>{{:: 'path' | translate}}</th>
+                                <th class="col-sm-3">Extend to Children</th>
+                                <th>{{:: 'actions' | translate}}</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr ng-repeat="group in selectedGroups | orderBy:'name' track by $index">
+                                <td>{{group.path}}</td>
+                                <td>
+                                    <input type="checkbox" ng-model="group.extendChildren" id="{{role.id}}" data-ng-click="extendChildren()">
+                                </td>
+                                <td class="kc-action-cell">
+                                    <button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(group);">{{:: 'remove' | translate}}</button>
+                                </td>
+                            </tr>
+                            <tr data-ng-show="!selectedGroups.length">
+                                <td class="text-muted" colspan="3">{{:: 'authz-no-groups-assigned' | translate}}</td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="logic">{{:: 'authz-policy-logic' | translate}}</label>
+
+                <div class="col-sm-1">
+                    <select class="form-control" id="logic"
+                            data-ng-model="policy.logic">
+                        <option value="POSITIVE">{{:: 'authz-policy-logic-positive' | translate}}</option>
+                        <option value="NEGATIVE">{{:: 'authz-policy-logic-negative' | translate}}</option>
+                    </select>
+                </div>
+
+                <kc-tooltip>{{:: 'authz-policy-logic.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <input type="hidden" data-ng-model="policy.type"/>
+        </fieldset>
+        <div class="form-group" data-ng-show="access.manageAuthorization">
+            <div class="col-md-10 col-md-offset-2">
+                <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
+                <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
+            </div>
+        </div>
+    </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html
index 4a6312b..72ff437 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html
@@ -7,7 +7,7 @@
 
     <kc-tabs-client></kc-tabs-client>
 
-    <form class="form-horizontal" name="clusteringForm" novalidate kc-read-only="!access.manageClients">
+    <form class="form-horizontal" name="clusteringForm" novalidate kc-read-only="!client.access.configure">
         <legend><span class="text">{{:: 'basic-configuration' | translate}}</span></legend>
         <fieldset >
             <div class="form-group clearfix">
@@ -31,7 +31,7 @@
             </div>
 
             <div class="form-group">
-                <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
+                <div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
                     <button data-kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
                     <button data-kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
                 </div>
@@ -43,7 +43,7 @@
             <table class="table table-striped table-bordered">
                 <thead>
                     <tr>
-                        <th class="kc-table-actions" colspan="5" data-ng-show="access.manageClients">
+                        <th class="kc-table-actions" colspan="5" data-ng-show="client.access.configure">
                             <div class="pull-right">
                                 <a class="btn btn-default" tooltip="Manually register cluster node. This is usually not needed as cluster node should be registered automatically by adapter"
                                    tooltip-trigger="mouseover mouseout" tooltip-placement="bottom" href="#/register-node/realms/{{realm.realm}}/clients/{{client.id}}/clustering">{{:: 'register-node-manually' | translate}}</a>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html
index 440994f..7df3a42 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html
@@ -10,10 +10,10 @@
     <h1 data-ng-show="create">{{:: 'add-node' | translate}}</h1>
     <h1 data-ng-hide="create">
         {{node.host|capitalize}}
-        <i id="removeClient" class="pficon pficon-delete clickable" data-ng-show="access.manageClients" data-ng-click="unregisterNode()"></i>
+        <i id="removeClient" class="pficon pficon-delete clickable" data-ng-show="client.access.configure" data-ng-click="unregisterNode()"></i>
     </h1>
 
-    <form class="form-horizontal" name="clusteringForm" novalidate kc-read-only="!access.manageClients" data-ng-show="create || registered">
+    <form class="form-horizontal" name="clusteringForm" novalidate kc-read-only="!client.access.configure" data-ng-show="create || registered">
         <div class="form-group">
             <label class="col-md-2 control-label" for="host">{{:: 'host' | translate}}</label>
             <div class="col-sm-6">
@@ -27,7 +27,7 @@
             </div>
         </div>
         <div class="form-group">
-            <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
                 <button data-kc-save data-ng-show="create">{{:: 'save' | translate}}</button>
             </div>
         </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html
index b1b1062..e6865a3 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html
@@ -7,7 +7,7 @@
 
     <kc-tabs-client></kc-tabs-client>
 
-    <form class="form-horizontal" name="clientForm" novalidate kc-read-only="!access.manageClients">
+    <form class="form-horizontal" name="clientForm" novalidate kc-read-only="!client.access.configure">
         <fieldset class="border-top">
             <div class="form-group clearfix">
                 <label class="col-md-2 control-label" for="clientAuthenticatorType"> {{:: 'client-authenticator' | translate}}</label>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html
index 1d59078..39e9ebc 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html
@@ -1,11 +1,11 @@
 <div>
-    <form class="form-horizontal no-margin-top" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-show="currentAuthenticatorConfigProperties.length > 0" data-ng-controller="ClientGenericCredentialsCtrl">
+    <form class="form-horizontal no-margin-top" name="credentialForm" novalidate kc-read-only="!client.access.configure" data-ng-show="currentAuthenticatorConfigProperties.length > 0" data-ng-controller="ClientGenericCredentialsCtrl">
         <fieldset>
             <kc-provider-config realm="realm" config="client.attributes" properties="currentAuthenticatorConfigProperties"></kc-provider-config>
         </fieldset>
 
         <div class="form-group">
-            <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
                 <button kc-save  data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
                 <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
             </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
index 29f6524..b2a5bf4 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
@@ -1,4 +1,4 @@
- <div class="form-horizontal no-margin-top" name="keyForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSignedJWTCtrl">
+ <div class="form-horizontal no-margin-top" name="keyForm" novalidate kc-read-only="!client.access.configure" data-ng-controller="ClientSignedJWTCtrl">
 
      <div class="form-group">
          <label class="col-md-2 control-label" for="useJwksUrl">{{:: 'use-jwks-url' | translate}}</label>
@@ -63,7 +63,7 @@
      </div>
 
      <div class="form-group">
-         <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
+         <div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
              <button class="btn btn-default" type="submit" data-ng-click="generateSigningKey()">{{:: 'gen-new-keys-and-cert' | translate}}</button>
              <button data-ng-disabled="useJwksUrl" class="btn btn-default" type="submit" data-ng-click="importCertificate()">{{:: 'import-certificate' | translate}}</button>
              <button kc-save  data-ng-disabled="!changed" data-ng-click="save()">{{:: 'save' | translate}}</button>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html
index 382f612..82e50b7 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html
@@ -9,7 +9,7 @@
 
     <h1>{{:: 'generate-private-key' | translate}}</h1>
 
-    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageRealm">
+    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!client.access.configure">
         <input type="text" readonly value="this is not a login form" style="display: none;">
         <input type="password" readonly value="this is not a login form" style="display: none;">
 
@@ -48,7 +48,7 @@
                 <kc-tooltip>{{:: 'store-password.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group">
-                <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
+                <div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
                     <button class="btn btn-primary" type="submit" data-ng-click="download()">{{:: 'generate-and-download' | translate}}</button>
                     <button class="btn btn-default" type="submit" data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
                 </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html
index 9968a31..ec88cd6 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html
@@ -9,7 +9,7 @@
 
     <h1>{{:: 'import-client-certificate' | translate}}</h1>
 
-    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageRealm">
+    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!client.access.configure">
         <input type="text" readonly value="this is not a login form" style="display: none;">
         <input type="password" readonly value="this is not a login form" style="display: none;">
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html
index 744ea80..7cf5bf1 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html
@@ -1,5 +1,5 @@
 <div>
-    <form class="form-horizontal no-margin-top" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSecretCtrl">
+    <form class="form-horizontal no-margin-top" name="credentialForm" novalidate kc-read-only="!client.access.configure" data-ng-controller="ClientSecretCtrl">
         <div class="form-group">
             <label class="col-md-2 control-label" for="secret">{{:: 'secret' | translate}}</label>
             <div class="col-sm-6">
@@ -7,7 +7,7 @@
                     <div class="col-sm-6">
                         <input readonly kc-select-action="click" class="form-control" type="text" id="secret" name="secret" data-ng-model="secret">
                     </div>
-                    <div class="col-sm-6" data-ng-show="access.manageClients">
+                    <div class="col-sm-6" data-ng-show="client.access.configure">
                         <button type="submit" data-ng-click="changePassword()" class="btn btn-default">{{:: 'regenerate-secret' | translate}}</button>
                     </div>
                 </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 3eb084f..cd6e271 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -7,7 +7,7 @@
 
     <kc-tabs-client></kc-tabs-client>
 
-    <form class="form-horizontal" name="clientForm" novalidate kc-read-only="!access.manageClients">
+    <form class="form-horizontal" name="clientForm" novalidate kc-read-only="!client.access.configure">
         <fieldset class="border-top">
             <div class="form-group">
                 <label class="col-md-2 control-label" for="clientId">{{:: 'client-id' | translate}}</label>
@@ -57,7 +57,7 @@
                 </div>
                 <kc-tooltip>{{:: 'client-protocol.tooltip' | translate}}</kc-tooltip>
             </div>
-            <div class="form-group">
+            <div class="form-group"  kc-read-only="!client.access.manage">
                 <label class="col-md-2 control-label" for="protocol">{{:: 'client-template' | translate}}</label>
                 <div class="col-sm-6">
                     <div>
@@ -391,7 +391,7 @@
         </fieldset>
 
         <div class="form-group">
-            <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
                 <button kc-save  data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
                 <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
             </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html
index e2bc22e..c74f54f 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html
@@ -7,7 +7,7 @@
 
     <kc-tabs-client></kc-tabs-client>
 
-    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageRealm">
+    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!client.access.configure">
         <input type="text" readonly value="this is not a login form" style="display: none;">
         <input type="password" readonly value="this is not a login form" style="display: none;">
 
@@ -137,7 +137,7 @@
                               kc-select-action="click" readonly>{{keyInfo.certificate}}</textarea>
                 </div>
             </div>
-            <div class="form-group" data-ng-show="access.manageRealm">
+            <div class="form-group" data-ng-show="client.access.configure">
                 <div class="pull-right">
                     <button class="btn btn-primary" type="submit" data-ng-click="generate()">Generate new keys</button>
                 </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
index 51aaa20..03ebf5c 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
@@ -42,7 +42,7 @@
                 </td>
                 <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}">{{:: 'edit' | translate}}</td>
                 <td class="kc-action-cell" data-ng-click="exportClient(client)">{{:: 'export' | translate}}</td>
-                <td class="kc-action-cell" data-ng-show="access.manageClients" data-ng-click="removeClient(client)">{{:: 'delete' | translate}}</td>
+                <td class="kc-action-cell" data-ng-show="client.access.manage" data-ng-click="removeClient(client)">{{:: 'delete' | translate}}</td>
             </tr>
             <tr data-ng-show="(clients | filter:search).length == 0">
                 <td class="text-muted" colspan="4" data-ng-show="search.clientId">{{:: 'no-results' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html
index ba9b9bf..5648c19 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html
@@ -7,7 +7,7 @@
 
     <kc-tabs-client></kc-tabs-client>
 
-    <form class="form-horizontal" name="allowScope" novalidate kc-read-only="!access.manageClients">
+    <form class="form-horizontal" name="allowScope" novalidate kc-read-only="!client.access.manage">
         <fieldset class="border-top">
             <div class="form-group" ng-show="client.clientTemplate">
                 <label class="col-md-2 control-label" for="useTemplateScope">Inherit Template Mappers</label>
@@ -35,7 +35,7 @@
                         </div>
                     </div>
 
-                    <div class="pull-right" data-ng-show="access.manageClients">
+                    <div class="pull-right" data-ng-show="client.access.manage">
                         <a class="btn btn-default" href="#/create/client/{{realm.realm}}/{{client.id}}/mappers">{{:: 'create' | translate}}</a>
                         <a class="btn btn-default" href="#/realms/{{realm.realm}}/clients/{{client.id}}/add-mappers">{{:: 'add-builtin' | translate}}</a>
                     </div>
@@ -55,7 +55,7 @@
             <td>{{mapperTypes[mapper.protocolMapper].category}}</td>
             <td>{{mapperTypes[mapper.protocolMapper].name}}</td>
             <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/mappers/{{mapper.id}}">{{:: 'edit' | translate}}</td>
-            <td class="kc-action-cell" data-ng-show="access.manageClients" data-ng-click="removeMapper(mapper)">{{:: 'delete' | translate}}</td>
+            <td class="kc-action-cell" data-ng-show="client.access.manage" data-ng-click="removeMapper(mapper)">{{:: 'delete' | translate}}</td>
         </tr>
         <tr data-ng-show="mappers.length == 0">
             <td>{{:: 'no-mappers-available' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html
index 04c2339..6537779 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html
@@ -45,7 +45,7 @@
         </tbody>
     </table>
 
-    <div data-ng-show="access.manageRealm">
+    <div data-ng-show="client.access.manage">
         <button class="btn btn-primary" data-ng-click="add()">{{:: 'add-selected' | translate}}</button>
     </div>
 </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html
index 55a3546..327b70d 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html
@@ -1,5 +1,5 @@
 <div>
-    <form class="form-horizontal" name="registrationAccessTokenForm" novalidate kc-read-only="!access.manageClients">
+    <form class="form-horizontal" name="registrationAccessTokenForm" novalidate kc-read-only="!client.access.configure">
         <div class="form-group">
             <label class="col-md-2 control-label" for="registrationAccessToken">{{:: 'registrationAccessToken' | translate}}</label>
             <div class="col-sm-6">
@@ -7,7 +7,7 @@
                     <div class="col-sm-6">
                         <input readonly kc-select-action="click" class="form-control" type="text" id="registrationAccessToken" name="registrationAccessToken" data-ng-model="client.registrationAccessToken">
                     </div>
-                    <div class="col-sm-6" data-ng-show="access.manageClients">
+                    <div class="col-sm-6" data-ng-show="client.access.configure">
                         <button type="submit" data-ng-click="regenerateRegistrationAccessToken()" class="btn btn-default">{{:: 'registrationAccessToken.regenerate' | translate}}</button>
                     </div>
                 </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html
index 38560e8..95db767 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html
@@ -7,7 +7,7 @@
 
     <kc-tabs-client></kc-tabs-client>
 
-    <form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageClients">
+    <form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!client.access.configure">
         <fieldset class="border-top">
             <div class="form-group">
                 <label class="col-md-2 control-label" for="notBefore">{{:: 'not-before' | translate}}</label>
@@ -18,7 +18,7 @@
             </div>
         </fieldset>
         <div class="form-group">
-            <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
                 <button type="submit" data-ng-click="clear()" class="btn btn-default">{{:: 'clear' | translate}}</button>
                 <button type="submit" data-ng-click="setNotBeforeNow()" class="btn btn-default">{{:: 'set-to-now' | translate}}</button>
                 <button type="submit" data-ng-click="pushRevocation()" class="btn btn-primary" tooltip-trigger="mouseover mouseout" tooltip="{{:: 'client-revoke.push.tooltip' | translate}}" tooltip-placement="bottom">{{:: 'push' | translate}}</button>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html
index de1a763..67c96fc 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html
@@ -8,11 +8,9 @@
         <li data-ng-hide="create">{{role.name}}</li>
     </ol>
 
-    <h1 data-ng-show="create">{{:: 'add-role' | translate}}</h1>
-    <h1 data-ng-hide="create">{{role.name|capitalize}}<i class="pficon pficon-delete clickable" data-ng-show="!create && access.manageClients" 
-    	data-ng-hide="changed" data-ng-click="remove()"></i></h1>
+    <kc-tabs-client-role></kc-tabs-client-role>
 
-    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageClients">
+    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!client.access.configure">
 
         <fieldset class="border-top">
             <div class="form-group">
@@ -49,13 +47,13 @@
         </fieldset>
 
         <div class="form-group">
-            <div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageClients">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="create && client.access.configure">
                 <button kc-save>{{:: 'save' | translate}}</button>
                 <button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
             </div>
         </div>
         <div class="form-group">
-            <div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageClients">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="!create && client.access.configure">
                 <button kc-save  data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
                 <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
             </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html
index da4d49d..9906074 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html
@@ -10,7 +10,7 @@
     <table class="table table-striped table-bordered">
         <thead>
         <tr>
-            <th class="kc-table-actions" colspan="5" data-ng-show="access.manageClients">
+            <th class="kc-table-actions" colspan="5" data-ng-show="client.access.configure">
                 <div class="pull-right">
                     <a class="btn btn-default" href="#/create/role/{{realm.realm}}/clients/{{client.id}}">{{:: 'add-role' | translate}}</a>
                 </div>
@@ -29,7 +29,7 @@
             <td translate="{{role.composite}}"></td>
             <td>{{role.description}}</td>
             <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/roles/{{role.id}}">{{:: 'edit' | translate}}</td>
-            <td class="kc-action-cell" data-ng-show="access.manageClients" data-ng-click="removeRole(role)">{{:: 'delete' | translate}}</td>
+            <td class="kc-action-cell" data-ng-show="client.access.configure" data-ng-click="removeRole(role)">{{:: 'delete' | translate}}</td>
         </tr>
         <tr data-ng-show="!roles || roles.length == 0">
             <td>{{:: 'no-client-roles-available' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html
index 2165165..ec51e96 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html
@@ -9,7 +9,7 @@
 
     <h1>{{:: 'export-saml-key' | translate}} {{client.clientId|capitalize}}</h1>
 
-    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageRealm">
+    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!client.access.configure">
         <input type="text" readonly value="this is not a login form" style="display: none;">
         <input type="password" readonly value="this is not a login form" style="display: none;">
 
@@ -55,7 +55,7 @@
                 <kc-tooltip>{{:: 'store-password.tooltip' | translate}}</kc-tooltip>
            </div>
             <div class="form-group">
-                <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
+                <div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
                     <button class="btn btn-primary" type="submit" data-ng-click="download()">{{:: 'download' | translate}}</button>
                 </div>
             </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html
index dec13a8..a580a61 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html
@@ -9,7 +9,7 @@
 
     <h1>{{:: 'import-saml-key' | translate}} {{client.clientId|capitalize}}</h1>
 
-    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageRealm">
+    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!client.access.configure">
         <input type="text" readonly value="this is not a login form" style="display: none;">
         <input type="password" readonly value="this is not a login form" style="display: none;">
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html
index 52e5468..776349d 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html
@@ -7,7 +7,7 @@
 
     <kc-tabs-client></kc-tabs-client>
 
-    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageRealm">
+    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!client.access.configure">
         <fieldset class="form-group col-sm-10" data-ng-show="client.attributes['saml.client.signature'] == 'true'">
             <legend uncollapsed><span class="text">{{:: 'signing-key' | translate}}</span>  <kc-tooltip>{{:: 'saml-signing-key' | translate}}</kc-tooltip></legend>
             <div class="form-group" data-ng-hide="!signingKeyInfo.privateKey">
@@ -27,7 +27,7 @@
                 </div>
             </div>
             <div class="form-group">
-                <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
+                <div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
                     <button class="btn btn-default" type="submit" data-ng-click="generateSigningKey()">{{:: 'generate-new-keys' | translate}}</button>
                     <button class="btn btn-default" type="submit" data-ng-click="importSigningKey()">{{:: 'import' | translate}}</button>
                     <button class="btn btn-default" type="submit" data-ng-hide="!signingKeyInfo.certificate" data-ng-click="exportSigningKey()">{{:: 'export' | translate}}</button>
@@ -53,7 +53,7 @@
                 </div>
             </div>
             <div class="form-group">
-                <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
+                <div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
                     <button class="btn btn-default" type="submit" data-ng-click="generateEncryptionKey()">{{:: 'generate-new-keys' | translate}}</button>
                     <button class="btn btn-default" type="submit" data-ng-click="importEncryptionKey()">{{:: 'import' | translate}}</button>
                     <button class="btn btn-default" type="submit" data-ng-hide="!encryptionKeyInfo.certificate" data-ng-click="exportEncryptionKey()">{{:: 'export' | translate}}</button>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html
index fc10e6b..6cb01f4 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html
@@ -9,7 +9,7 @@
 
         <h2><span>{{client.clientId}}</span> {{:: 'scope-mappings' | translate}} </h2>
         <p class="subtitle"></p>
-        <form class="form-horizontal" name="allowScope" novalidate kc-read-only="!access.manageClients">
+        <form class="form-horizontal" name="allowScope" novalidate kc-read-only="!client.access.manage">
             <fieldset class="border-top">
                 <div class="form-group" ng-show="client.clientTemplate">
                     <label class="col-md-2 control-label" for="useTemplateScope">Inherit Template Scope</label>
@@ -25,14 +25,14 @@
                     <label class="col-md-2 control-label" for="fullScopeAllowed">{{:: 'full-scope-allowed' | translate}}</label>
                     <kc-tooltip>{{:: 'full-scope-allowed.tooltip' | translate}}</kc-tooltip>
                     <div class="col-md-6">
-                        <input ng-model="client.fullScopeAllowed" ng-click="changeFlag()" name="fullScopeAllowed" id="fullScopeAllowed" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+                        <input kc-read-only="!client.access.manage" ng-model="client.fullScopeAllowed" ng-click="changeFlag()" name="fullScopeAllowed" id="fullScopeAllowed" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
                     </div>
                 </div>
                 <div class="form-group" ng-show="client.useTemplateScope && template && template.fullScopeAllowed">
                     <label class="col-md-2 control-label" for="fullScopeAllowed">{{:: 'full-scope-allowed' | translate}}</label>
                     <kc-tooltip>Client template has full scope allowed, which means this client will have the full scope of all roles.</kc-tooltip>
                     <div class="col-md-1">
-                        <input ng-model="template.fullScopeAllowed" name="fullScopeAllowed" id="fullScopeAllowed" ng-disabled="true" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+                        <input kc-read-only="!client.access.manage" ng-model="template.fullScopeAllowed" name="fullScopeAllowed" id="fullScopeAllowed" ng-disabled="true" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
                     </div>
                     <div class="col-md-1">
                         <i>inherited</i>
@@ -41,7 +41,7 @@
             </fieldset>
         </form>
 
-        <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageClients" data-ng-hide="hideRoleSelector()">
+        <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!client.access.manage" data-ng-hide="hideRoleSelector()">
             <div class="form-group">
                 <label class="col-md-2 control-label" class="control-label">{{:: 'realm-roles' | translate}}</label>
                 <div class="col-md-10">
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html
index 27f967d..d57873f 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html
@@ -10,7 +10,7 @@
     <h2><span>{{client.clientId}}</span> {{:: 'service-accounts' | translate}} </h2>
     <p class="subtitle"></p>
 
-    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageClients" data-ng-show="client.serviceAccountsEnabled">
+    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!client.access.configure" data-ng-show="client.serviceAccountsEnabled">
         <div class="form-group">
             <label class="col-md-2 control-label" class="control-label">{{:: 'realm-roles' | translate}}</label>
             <div class="col-md-10">
@@ -102,7 +102,7 @@
         </div>
     </form>
 
-    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageClients" data-ng-show="!client.serviceAccountsEnabled">
+    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!client.access.configure" data-ng-show="!client.serviceAccountsEnabled">
         <legend><span class="text" translate="service-account-is-not-enabled-for" translate-values="{client: client.clientId}"></span></legend>
     </form>
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html b/themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html
index c3962c5..e12c553 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html
@@ -6,7 +6,7 @@
 
     <kc-tabs-group></kc-tabs-group>
 
-    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageUsers">
+    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!group.access.manage">
         <table class="table table-striped table-bordered">
             <thead>
             <tr>
@@ -29,7 +29,7 @@
             </tbody>
         </table>
 
-        <div class="form-group" data-ng-show="access.manageUsers">
+        <div class="form-group" data-ng-show="group.access.manage">
             <div class="col-md-12">
                 <button kc-save  data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
                 <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/group-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/group-detail.html
index 677ea32..8fd6461 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/group-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/group-detail.html
@@ -16,7 +16,7 @@
             </div>
         </fieldset>
 
-        <div class="form-group" data-ng-show="access.manageUsers">
+        <div class="form-group" data-ng-show="group.access.manage">
             <div class="col-md-10 col-md-offset-2">
                 <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
                 <button kc-reset data-ng-disabled="!changed" data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html
index 9a1c5b8..62aa7d4 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html
@@ -7,7 +7,7 @@
     <kc-tabs-group></kc-tabs-group>
 
     <form class="form-horizontal" name="realmForm" novalidate>
-        <div class="form-group" kc-read-only="!access.manageUsers">
+        <div class="form-group" kc-read-only="!group.access.manage">
             <label class="col-md-2 control-label" class="control-label">{{:: 'realm-roles' | translate}}</label>
 
             <div class="col-md-10">
@@ -54,7 +54,7 @@
                     <span>{{:: 'client-roles' | translate}}</span>
                     <select class="form-control" id="clients" name="clients" ng-change="changeClient()" ng-model="targetClient" ng-options="a.clientId for a in clients | orderBy:'clientId'" ng-disabled="false"></select>
                 </label>
-                <div class="col-md-10" kc-read-only="!access.manageUsers">
+                <div class="col-md-10" kc-read-only="!group.access.manage">
                     <div class="row" data-ng-hide="targetClient">
                         <div class="col-md-4"><span class="text-muted">{{:: 'select-client-to-view-roles' | translate}}</span></div>
                     </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html b/themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html
index 45a5be6..17dcc05 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html
@@ -21,7 +21,7 @@
         <tr ng-repeat="requiredAction in requiredActions | orderBy : 'name'" data-ng-show="requiredActions.length > 0">
             <td>{{requiredAction.name}}</td>
             <td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.enabled"></td>
-            <td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.defaultAction"></td>
+            <td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)" ng-disabled="!requiredAction.enabled" ng-checked="requiredAction.enabled && requiredAction.defaultAction" id="{{requiredAction.alias}}.defaultAction"></td>
         </tr>
         <tr data-ng-show="requiredActions.length == 0">
             <td>{{:: 'no-required-actions-configured' | translate}}</td>
@@ -31,4 +31,4 @@
 
 </div>
 
-<kc-menu></kc-menu>
\ No newline at end of file
+<kc-menu></kc-menu>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html
index 97acab8..3bcaa66 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html
@@ -6,9 +6,7 @@
         <li data-ng-show="create">{{:: 'add-role' | translate}}</li>
     </ol>
 
-    <h1 data-ng-hide="create">{{role.name|capitalize}} <i id="removeRole" class="pficon pficon-delete clickable" data-ng-show="!create && access.manageRealm" 
-    	data-ng-hide="changed" data-ng-click="remove()"></i></h1>
-    <h1 data-ng-show="create">{{:: 'add-role' | translate}}</h1>
+    <kc-tabs-role></kc-tabs-role>
 
     <form class="form-horizontal clearfix" name="realmForm" novalidate kc-read-only="!access.manageRealm">
         <fieldset>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html
index b57b1d2..013dae7 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html
@@ -7,7 +7,7 @@
     <kc-tabs-user></kc-tabs-user>
 
     <form class="form-horizontal" name="realmForm" novalidate>
-        <div class="form-group" kc-read-only="!access.manageUsers">
+        <div class="form-group" kc-read-only="!user.access.mapRoles">
             <label class="col-md-2 control-label" class="control-label">{{:: 'realm-roles' | translate}}</label>
 
             <div class="col-md-10">
@@ -54,7 +54,7 @@
                     <span>{{:: 'client-roles' | translate}}</span>
                     <select class="form-control" id="clients" name="clients" ng-change="changeClient()" ng-model="targetClient" ng-options="a.clientId for a in clients | orderBy:'clientId'" ng-disabled="false"></select>
                 </label>
-                <div class="col-md-10" kc-read-only="!access.manageUsers">
+                <div class="col-md-10" kc-read-only="!user.access.mapRoles">
                     <div class="row" data-ng-hide="targetClient">
                         <div class="col-md-4"><span class="text-muted">{{:: 'select-client-to-view-roles' | translate}}</span></div>
                     </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html
index 26d30e1..10e3d8f 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html
@@ -6,7 +6,7 @@
 
     <kc-tabs-user></kc-tabs-user>
 
-    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageUsers">
+    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!user.access.manage">
         <table class="table table-striped table-bordered">
             <thead>
             <tr>
@@ -29,7 +29,7 @@
             </tbody>
         </table>
 
-        <div class="form-group" data-ng-show="access.manageUsers">
+        <div class="form-group" data-ng-show="user.access.manage">
             <div class="col-md-12">
                 <button kc-save  data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
                 <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html
index f1998f8..88e66e7 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html
@@ -7,7 +7,7 @@
 
     <kc-tabs-user></kc-tabs-user>
 
-    <form class="form-horizontal" name="userForm" novalidate kc-read-only="!access.manageUsers">
+    <form class="form-horizontal" name="userForm" novalidate kc-read-only="!create && !user.access.manage">
 
         <fieldset class="border-top">
             <div class="form-group">
@@ -62,7 +62,7 @@
             <div class="form-group clearfix block">
                 <label class="col-md-2 control-label" for="userEnabled">{{:: 'user-enabled' | translate}}</label>
                 <div class="col-md-6">
-                    <input ng-model="user.enabled" name="userEnabled" id="userEnabled" ng-disabled="!access.manageUsers" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+                    <input ng-model="user.enabled" name="userEnabled" id="userEnabled" ng-disabled="!create && !user.access.manage" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
                 </div>
                 <kc-tooltip>{{:: 'user-enabled.tooltip' | translate}}</kc-tooltip>
             </div>
@@ -93,7 +93,7 @@
             <div class="form-group clearfix block">
                 <label class="col-md-2 control-label" for="emailVerified">{{:: 'email-verified' | translate}}</label>
                 <div class="col-md-6">
-                    <input ng-model="user.emailVerified" name="emailVerified" id="emailVerified" ng-disabled="!access.manageUsers" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+                    <input ng-model="user.emailVerified" name="emailVerified" id="emailVerified" ng-disabled="!create && !user.access.manage" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
                 </div>
                 <kc-tooltip>{{:: 'email-verified.tooltip' | translate}}</kc-tooltip>
             </div>
@@ -138,7 +138,7 @@
                 <button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
             </div>
 
-            <div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageUsers">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="!create && user.access.manage">
                 <button kc-save  data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
                 <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
             </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html
index a9c0e6d..f7bf04e 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html
@@ -7,7 +7,7 @@
      <kc-tabs-user></kc-tabs-user>
 
      <form class="form-horizontal" name="realmForm" novalidate>
-         <div class="form-group" kc-read-only="!access.manageUsers">
+         <div class="form-group" kc-read-only="!user.access.manageGroupMembership">
              <label class="col-md-1 control-label" class="control-label"></label>
 
              <div class="col-md-8" >
@@ -21,7 +21,7 @@
                                          <label class="control-label">{{:: 'group-membership' | translate}}</label>
                                          <kc-tooltip>{{:: 'group-membership.tooltip' | translate}}</kc-tooltip>
 
-                                         <div class="pull-right" data-ng-show="access.manageUsers">
+                                         <div class="pull-right" data-ng-show="user.access.manageGroupMembership">
                                              <button id="leaveGroups" class="btn btn-default" ng-click="leaveGroup()">{{:: 'leave' | translate}}</button>
                                          </div>
                                      </div>
@@ -53,7 +53,7 @@
                                          <label class="control-label">{{:: 'available-groups' | translate}}</label>
                                          <kc-tooltip>{{:: 'membership.available-groups.tooltip' | translate}}</kc-tooltip>
 
-                                         <div class="pull-right" data-ng-show="access.manageUsers">
+                                         <div class="pull-right" data-ng-show="user.access.manageGroupMembership">
                                              <button id="joinGroup" class="btn btn-default" ng-click="joinGroup()">{{:: 'join' | translate}}</button>
                                          </div>
                                      </div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html
index 4864acf..569e1fc 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html
@@ -1,5 +1,6 @@
 <div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2" ng-init="init()">
-    <h1>{{:: 'users' | translate}}</h1>
+
+    <kc-tabs-users></kc-tabs-users>
 
     <table class="table table-striped table-bordered">
         <caption data-ng-show="users" class="hidden">{{:: 'table-of-realm-users' | translate}}</caption>
@@ -54,7 +55,7 @@
             <td class="clip">{{user.firstName}}</td>
             <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/users/{{user.id}}">{{:: 'edit' | translate}}</td>
             <td data-ng-show="serverInfo.profileInfo.disabledFeatures.indexOf('IMPERSONATION') == -1 && access.impersonation" class="kc-action-cell" data-ng-click="impersonate(user.id)">{{:: 'impersonate' | translate}}</td>
-            <td data-ng-show="access.manageUsers" class="kc-action-cell" data-ng-click="removeUser(user)">{{:: 'delete' | translate}}</td>
+            <td data-ng-show="user.access.manage" class="kc-action-cell" data-ng-click="removeUser(user)">{{:: 'delete' | translate}}</td>
         </tr>
         <tr data-ng-show="!users || users.length == 0">
             <td class="text-muted" data-ng-show="!users">{{:: 'users.instruction' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html
index 1fe11e9..02c1959 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html
@@ -8,9 +8,9 @@
 
     <table class="table table-striped table-bordered">
         <thead>
-        <tr data-ng-show="access.manageUsers">
+        <tr data-ng-show="user.access.manage">
             <th class="kc-table-actions" colspan="6">
-                <div class="pull-right" data-ng-show="access.manageUsers">
+                <div class="pull-right" data-ng-show="user.access.manage">
                     <a id="logoutAllSessions" class="btn btn-default" ng-click="logoutAll()">{{:: 'logout-all-sessions' | translate}}</a>
                 </div>
             </th>
@@ -20,7 +20,7 @@
             <th>{{:: 'started' | translate}}</th>
             <th>{{:: 'last-access' | translate}}</th>
             <th>{{:: 'clients' | translate}}</th>
-            <th data-ng-show="access.manageUsers">{{:: 'action' | translate}}</th>
+            <th data-ng-show="user.access.manage">{{:: 'action' | translate}}</th>
         </tr>
         </thead>
         <tbody>
@@ -34,7 +34,7 @@
                 </div>
             </ul>
             </td>
-            <td class="kc-action-cell" data-ng-show="access.manageUsers" ng-click="logoutSession(session.id)">{{:: 'logout' | translate}}</td>
+            <td class="kc-action-cell" data-ng-show="user.access.manage" ng-click="logoutSession(session.id)">{{:: 'logout' | translate}}</td>
         </tr>
         </tbody>
     </table>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
index 25d22d7..53b0a3d 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
@@ -16,7 +16,7 @@
       </div>
     </div>
 
-    <div class="nav-category" data-ng-show="current.realm">
+    <div class="nav-category" data-ng-show="current.realm && (access.viewRealm || access.queryClients || access.viewIdentityProviders)">
         <h2>{{:: 'configure' | translate}}</h2>
         <ul class="nav nav-pills nav-stacked">
             <li data-ng-show="access.viewRealm" data-ng-class="((!path[2]
@@ -32,7 +32,7 @@
     || path[2] == 'keys-settings' || path[2] == 'smtp-settings' || path[2] == 'ldap-settings' || path[2] == 'auth-settings') && path[3] != 'clients') && 'active'">
                 <a href="#/realms/{{realm.realm}}"><span class="pficon pficon-settings"></span> {{:: 'realm-settings' | translate}}</a>
             </li>
-            <li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'clients' || path[3] == 'clients') && 'active'"><a href="#/realms/{{realm.realm}}/clients"><i class="fa fa-cube"></i> {{:: 'clients' | translate}}</a></li>
+            <li data-ng-show="access.queryClients" data-ng-class="(path[2] == 'clients' || path[3] == 'clients') && 'active'"><a href="#/realms/{{realm.realm}}/clients"><i class="fa fa-cube"></i> {{:: 'clients' | translate}}</a></li>
             <li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'client-templates' || path[3] == 'client-templates') && 'active'"><a href="#/realms/{{realm.realm}}/client-templates"><i class="fa fa-cubes"></i> {{:: 'client-templates' | translate}}</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'roles' || path[2] == 'default-roles') && 'active'"><a href="#/realms/{{realm.realm}}/roles"><i class="fa fa-tasks"></i> {{:: 'roles' | translate}}</a></li>
             <li data-ng-show="access.viewIdentityProviders" data-ng-class="(path[2] == 'identity-provider-settings' || path[2] == 'identity-provider-mappers') && 'active'"><a href="#/realms/{{realm.realm}}/identity-provider-settings"><i class="fa fa-exchange"></i> {{:: 'identity-providers' | translate}}</a></li>
@@ -45,12 +45,12 @@
         </ul>
     </div>
 
-    <div class="nav-category" data-ng-show="current.realm">
+    <div class="nav-category" data-ng-show="current.realm && (access.viewRealm || access.queryGroups || access.queryUsers || access.viewEvents)">
         <h2>{{:: 'manage' | translate}}</h2>
         <ul class="nav nav-pills nav-stacked">
-            <li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'groups' 
+            <li data-ng-show="access.queryGroups" data-ng-class="(path[2] == 'groups'
                         || path[2] == 'default-groups') && 'active'"><a href="#/realms/{{realm.realm}}/groups"><span class="pficon pficon-users"></span> {{:: 'groups' | translate}}</a></li>
-            <li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users') && 'active'"><a href="#/realms/{{realm.realm}}/users"><span class="pficon pficon-user"></span> {{:: 'users' | translate}}</a></li>
+            <li data-ng-show="access.queryUsers" data-ng-class="(path[2] == 'users') && 'active'"><a href="#/realms/{{realm.realm}}/users"><span class="pficon pficon-user"></span> {{:: 'users' | translate}}</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/realm"><i class="fa fa-clock-o"></i> {{:: 'sessions' | translate}}</a></li>
             <li data-ng-show="access.viewEvents" data-ng-class="(path[2] == 'events' 
                         || path[2] == 'events-settings'
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
index 2a3be32..e5b7c21 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
@@ -3,7 +3,7 @@
     <h1 data-ng-show="create">{{:: 'add-client' | translate}}</h1>
     <h1 data-ng-hide="create">
         {{client.clientId|capitalize}}
-        <i id="removeClient" class="pficon pficon-delete clickable" data-ng-show="access.manageClients" data-ng-click="removeClient()"></i>
+        <i id="removeClient" class="pficon pficon-delete clickable" data-ng-show="client.access.manage" data-ng-click="removeClient()"></i>
     </h1>
 
     <ul class="nav nav-tabs"  data-ng-hide="create && !path[4]">
@@ -43,5 +43,9 @@
             <a href="#/realms/{{realm.realm}}/clients/{{client.id}}/service-account-roles">{{:: 'service-account-roles' | translate}}</a>
             <kc-tooltip>{{:: 'service-account-roles.tooltip' | translate}}</kc-tooltip>
         </li>
+        <li ng-class="{active: path[4] == 'permissions'}" data-ng-show="client.access.manage && access.manageAuthorization">
+            <a href="#/realms/{{realm.realm}}/clients/{{client.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
+            <kc-tooltip>{{:: 'manage-permissions-client.tooltip' | translate}}</kc-tooltip>
+        </li>
     </ul>
 </div>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-role.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-role.html
new file mode 100755
index 0000000..9637186
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-role.html
@@ -0,0 +1,13 @@
+<div data-ng-controller="RoleTabCtrl">
+    <h1 data-ng-show="create">{{:: 'add-role' | translate}}</h1>
+    <h1 data-ng-hide="create">{{role.name|capitalize}}<i class="pficon pficon-delete clickable" data-ng-show="!create && access.manageClients"
+                                                         data-ng-hide="changed" data-ng-click="remove()"></i></h1>
+
+    <ul class="nav nav-tabs" data-ng-show="!create">
+        <li ng-class="{active: !path[6]}"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/roles/{{role.id}}">{{:: 'details' | translate}}</a></li>
+        <li ng-class="{active: path[6] && path[6] == 'permissions'}">
+            <a href="#/realms/{{realm.realm}}/clients/{{client.id}}/roles/{{role.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
+            <kc-tooltip>{{:: 'manage-permissions-role.tooltip' | translate}}</kc-tooltip>
+        </li>
+    </ul>
+</div>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html
index c242336..e73d998 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html
@@ -1,7 +1,7 @@
 <div data-ng-controller="GroupTabCtrl">
     <h1>
         {{group.name|capitalize}}
-        <i id="removeGroup" class="pficon pficon-delete clickable" data-ng-show="access.manageUsers" data-ng-click="removeGroup()"></i>
+        <i id="removeGroup" class="pficon pficon-delete clickable" data-ng-show="group.access.manage" data-ng-click="removeGroup()"></i>
     </h1>
 
     <ul class="nav nav-tabs">
@@ -9,5 +9,9 @@
         <li ng-class="{active: path[4] == 'attributes'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/attributes">{{:: 'attributes' | translate}}</a></li>
         <li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/role-mappings">{{:: 'role-mappings' | translate}}</a></li>
         <li ng-class="{active: path[4] == 'members'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/members">{{:: 'members' | translate}}</a></li>
+        <li ng-class="{active: path[4] == 'permissions'}" data-ng-show="group.access.manage && access.manageAuthorization">
+            <a href="#/realms/{{realm.realm}}/groups/{{group.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
+            <kc-tooltip>{{:: 'manage-permissions-group.tooltip' | translate}}</kc-tooltip>
+        </li>
     </ul>
 </div>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-role.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-role.html
new file mode 100755
index 0000000..785c7e9
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-role.html
@@ -0,0 +1,13 @@
+<div data-ng-controller="RoleTabCtrl">
+    <h1 data-ng-hide="create">{{role.name|capitalize}} <i id="removeRole" class="pficon pficon-delete clickable" data-ng-show="!create && access.manageRealm"
+    	data-ng-hide="changed" data-ng-click="remove()"></i></h1>
+    <h1 data-ng-show="create">{{:: 'add-role' | translate}}</h1>
+
+    <ul class="nav nav-tabs" data-ng-show="!create">
+        <li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/roles/{{role.id}}">{{:: 'details' | translate}}</a></li>
+        <li ng-class="{active: path[4] == 'permissions'}" data-ng-show="access.manageRealm && access.manageAuthorization">
+            <a href="#/realms/{{realm.realm}}/roles/{{role.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
+            <kc-tooltip>{{:: 'manage-permissions-role.tooltip' | translate}}</kc-tooltip>
+        </li>
+    </ul>
+</div>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html
index 66dad39..9c372e3 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html
@@ -8,7 +8,7 @@
     <ul class="nav nav-tabs" data-ng-show="!create">
         <li ng-class="{active: !path[4] && path[0] != 'create'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}">{{:: 'details' | translate}}</a></li>
         <li ng-class="{active: path[4] == 'user-attributes'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-attributes">{{:: 'attributes' | translate}}</a></li>
-        <li ng-class="{active: path[4] == 'user-credentials'}" data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-credentials">{{:: 'credentials' | translate}}</a></li>
+        <li ng-class="{active: path[4] == 'user-credentials'}" data-ng-show="user.access.manage"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-credentials">{{:: 'credentials' | translate}}</a></li>
         <li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/role-mappings">{{:: 'role-mappings' | translate}}</a></li>
         <li ng-class="{active: path[4] == 'groups'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/groups">{{:: 'groups' | translate}}</a></li>
         <li ng-class="{active: path[4] == 'consents'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/consents">{{:: 'consents' | translate}}</a></li>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-users.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-users.html
new file mode 100755
index 0000000..f641dc0
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-users.html
@@ -0,0 +1,11 @@
+<div >
+    <h1>{{:: 'users' | translate}}</h1>
+
+    <ul class="nav nav-tabs">
+        <li ng-class="{active: path[2] == 'users'}"><a href="#/realms/{{realm.realm}}/users">{{:: 'lookup' | translate}}</a></li>
+        <li ng-class="{active: path[2] == 'users-permissions'}" data-ng-show="access.manageUsers && access.manageAuthorization">
+            <a href="#/realms/{{realm.realm}}/users-permissions">{{:: 'authz-permissions' | translate}}</a>
+            <kc-tooltip>{{:: 'manage-permissions-users.tooltip' | translate}}</kc-tooltip>
+        </li>
+    </ul>
+</div>
\ No newline at end of file
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
index 818626a..476449a 100755
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
@@ -113,10 +113,10 @@
                     <eviction max-entries="1000" strategy="LRU"/>
                     <expiration max-idle="3600000" />
                 </local-cache>
-                <local-cache name="actionTokens">
+                <distributed-cache name="actionTokens" mode="SYNC" owners="2">
                     <eviction max-entries="-1" strategy="NONE"/>
                     <expiration max-idle="-1" interval="300000"/>
-                </local-cache>
+                </distributed-cache>
             </cache-container>
             <cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
                 <transport lock-timeout="60000"/>
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml
index 839ecdf..ca80102 100755
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml
@@ -116,10 +116,10 @@
                     <eviction max-entries="1000" strategy="LRU"/>
                     <expiration max-idle="3600000" />
                 </local-cache>
-                <local-cache name="actionTokens">
+                <distributed-cache name="actionTokens" mode="SYNC" owners="2">
                     <eviction max-entries="-1" strategy="NONE"/>
                     <expiration max-idle="-1" interval="300000"/>
-                </local-cache>
+                </distributed-cache>
             </cache-container>
             <cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
                 <transport lock-timeout="60000"/>