keycloak-uncached

KEYCLOAK-1749 Rotate registration access token, add registration

11/17/2015 6:36:42 AM

Changes

services/src/main/java/org/keycloak/services/clientregistration/TokenGenerator.java 27(+0 -27)

Details

diff --git a/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java b/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
index e59de76..f932226 100644
--- a/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
+++ b/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
@@ -55,9 +55,10 @@ public class ClientRegistration {
         return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
     }
 
-    public void update(ClientRepresentation client) throws ClientRegistrationException {
+    public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
         String content = serialize(client);
-        httpUtil.doPut(content, DEFAULT, client.getClientId());
+        InputStream resultStream = httpUtil.doPut(content, DEFAULT, client.getClientId());
+        return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
     }
 
     public void delete(ClientRepresentation client) throws ClientRegistrationException {
diff --git a/client-api/src/main/java/org/keycloak/client/registration/HttpUtil.java b/client-api/src/main/java/org/keycloak/client/registration/HttpUtil.java
index 699d378..6444749 100644
--- a/client-api/src/main/java/org/keycloak/client/registration/HttpUtil.java
+++ b/client-api/src/main/java/org/keycloak/client/registration/HttpUtil.java
@@ -80,7 +80,9 @@ class HttpUtil {
                 responseStream.close();
                 return null;
             } else {
-                responseStream.close();
+                if (responseStream != null) {
+                    responseStream.close();
+                }
                 throw new HttpErrorException(response.getStatusLine());
             }
         } catch (IOException e) {
@@ -88,7 +90,7 @@ class HttpUtil {
         }
     }
 
-    void doPut(String content, String... path) throws ClientRegistrationException {
+    InputStream doPut(String content, String... path) throws ClientRegistrationException {
         try {
             HttpPut request = new HttpPut(getUrl(baseUri, path));
 
@@ -100,10 +102,20 @@ class HttpUtil {
 
             HttpResponse response = httpClient.execute(request);
             if (response.getEntity() != null) {
-                response.getEntity().getContent().close();
+                response.getEntity().getContent();
             }
 
-            if (response.getStatusLine().getStatusCode() != 200) {
+            InputStream responseStream = null;
+            if (response.getEntity() != null) {
+                responseStream = response.getEntity().getContent();
+            }
+
+            if (response.getStatusLine().getStatusCode() == 200) {
+                return responseStream;
+            } else {
+                if (responseStream != null) {
+                    responseStream.close();
+                }
                 throw new HttpErrorException(response.getStatusLine());
             }
         } catch (IOException e) {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index bcbc98d..a9e74f7 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -270,7 +270,10 @@ client-certificate-import=Client Certificate Import
 import-client-certificate=Import Client Certificate
 jwt-import.key-alias.tooltip=Archive alias for your certificate.
 secret=Secret
-regenerate-secret=Regenerate Secret
+regenerate-secret=Regenerate Secretsecret=Secret
+registrationAccessToken=Registration access token
+registrationAccessToken.regenerate=Regenerate registration access token
+registrationAccessToken.tooltip=The registration access token provides access for clients to the client registration service.
 add-role=Add Role
 role-name=Role Name
 composite=Composite
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index c5f6316..971b0c4 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -30,7 +30,7 @@ module.controller('ClientRoleListCtrl', function($scope, $location, realm, clien
     });
 });
 
-module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, clientConfigProperties, Client) {
+module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, clientConfigProperties, Client, ClientRegistrationAccessToken, Notifications) {
     $scope.realm = realm;
     $scope.client = angular.copy(client);
     $scope.clientAuthenticatorProviders = clientAuthenticatorProviders;
@@ -68,6 +68,17 @@ module.controller('ClientCredentialsCtrl', function($scope, $location, realm, cl
         }
     }, true);
 
+    $scope.regenerateRegistrationAccessToken = function() {
+        var secret = ClientRegistrationAccessToken.update({ realm : $scope.realm.realm, client : $scope.client.id },
+            function(data) {
+                Notifications.success('The registration access token has been updated.');
+                $scope.client['registrationAccessToken'] = data.registrationAccessToken;
+            },
+            function() {
+                Notifications.error('Failed to update the registration access token');
+            }
+        );
+    };
 });
 
 module.controller('ClientSecretCtrl', function($scope, $location, ClientSecret, Notifications) {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index 065f831..34fdc9d 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -981,6 +981,17 @@ module.factory('ClientSecret', function($resource) {
     });
 });
 
+module.factory('ClientRegistrationAccessToken', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/clients/:client/registration-access-token', {
+        realm : '@realm',
+        client : '@client'
+    },  {
+        update : {
+            method : 'POST'
+        }
+    });
+});
+
 module.factory('ClientOrigins', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/clients/:client/allowed-origins', {
         realm : '@realm',
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html
index 96f16ca..b1b1062 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html
@@ -28,6 +28,11 @@
     <div data-ng-include="resourceUrl + '/partials/' + clientAuthenticatorConfigPartial">
     </div>
 
+    <hr/>
+
+    <div data-ng-include="resourceUrl + '/partials/client-registration-access-token.html'">
+    </div>
+
 </div>
 
 <kc-menu></kc-menu>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html
index 631939c..1d59078 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html
@@ -1,5 +1,5 @@
 <div>
-    <form class="form-horizontal" 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="!access.manageClients" 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>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
index aa03203..8c581d7 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
@@ -1,5 +1,5 @@
 <div>
-    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSignedJWTCtrl">
+    <form class="form-horizontal no-margin-top" name="keyForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSignedJWTCtrl">
         <div class="form-group">
             <label class="col-md-2 control-label" for="signingCert">{{:: 'certificate' | translate}}</label>
             <kc-tooltip>{{:: 'certificate.tooltip' | translate}}</kc-tooltip>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html
index 2bd53db..744ea80 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html
@@ -1,5 +1,5 @@
 <div>
-    <form class="form-horizontal" 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="!access.manageClients" 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">
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html
new file mode 100644
index 0000000..55a3546
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html
@@ -0,0 +1,18 @@
+<div>
+    <form class="form-horizontal" name="registrationAccessTokenForm" novalidate kc-read-only="!access.manageClients">
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="registrationAccessToken">{{:: 'registrationAccessToken' | translate}}</label>
+            <div class="col-sm-6">
+                <div class="row">
+                    <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">
+                        <button type="submit" data-ng-click="regenerateRegistrationAccessToken()" class="btn btn-default">{{:: 'registrationAccessToken.regenerate' | translate}}</button>
+                    </div>
+                </div>
+            </div>
+            <kc-tooltip>{{:: 'registrationAccessToken.tooltip' | translate}}</kc-tooltip>
+        </div>
+    </form>
+</div>
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css b/forms/common-themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
index c0e8fb2..500ed89 100644
--- a/forms/common-themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
+++ b/forms/common-themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
@@ -22,6 +22,11 @@ table {
     margin-top: 20px;
 }
 
+.no-margin-top {
+    margin-top: 0px !important;
+}
+
+
 
 /*********** Loading ***********/
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index ad5997a..c35c58e 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -1,6 +1,7 @@
 package org.keycloak.models.utils;
 
 import org.bouncycastle.openssl.PEMWriter;
+import org.keycloak.common.util.Base64Url;
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientModel;
@@ -26,12 +27,7 @@ import org.keycloak.common.util.PemUtils;
 import javax.crypto.spec.SecretKeySpec;
 import java.io.IOException;
 import java.io.StringWriter;
-import java.security.Key;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
+import java.security.*;
 import java.security.cert.X509Certificate;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -47,6 +43,8 @@ import java.util.UUID;
  */
 public final class KeycloakModelUtils {
 
+    private static final int RANDOM_PASSWORD_BYTES = 32;
+
     private KeycloakModelUtils() {
     }
 
@@ -178,12 +176,22 @@ public final class KeycloakModelUtils {
         return rep;
     }
 
-    public static UserCredentialModel generateSecret(ClientModel app) {
+    public static UserCredentialModel generateSecret(ClientModel client) {
         UserCredentialModel secret = UserCredentialModel.generateSecret();
-        app.setSecret(secret.getValue());
+        client.setSecret(secret.getValue());
         return secret;
     }
 
+    public static void generateRegistrationAccessToken(ClientModel client) {
+        client.setRegistrationSecret(generatePassword());
+    }
+
+    public static String generatePassword() {
+        byte[] buf = new byte[RANDOM_PASSWORD_BYTES];
+        new SecureRandom().nextBytes(buf);
+        return Base64Url.encode(buf);
+    }
+
     public static String getDefaultClientAuthenticatorType() {
         return "client-secret";
     }
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java
index 4964349..cf13235 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java
@@ -24,6 +24,7 @@ public class ClientRegAuth {
     private AccessToken.Access bearerRealmAccess;
 
     private boolean authenticated = false;
+    private boolean registrationAccessToken = false;
 
     public ClientRegAuth(KeycloakSession session, EventBuilder event) {
         this.session = session;
@@ -48,6 +49,7 @@ public class ClientRegAuth {
         if (split[1].indexOf('.') == -1) {
             token = split[1];
             authenticated = true;
+            registrationAccessToken = true;
         } else {
             AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm);
             bearerRealmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID);
@@ -59,6 +61,10 @@ public class ClientRegAuth {
         return authenticated;
     }
 
+    public boolean isRegistrationAccessToken() {
+        return registrationAccessToken;
+    }
+
     public void requireCreate() {
         if (!authenticated) {
             event.error(Errors.NOT_ALLOWED);
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
index ea508de..2aed3f1 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
@@ -1,5 +1,6 @@
 package org.keycloak.services.clientregistration;
 
+import org.jboss.resteasy.spi.NotFoundException;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.services.ErrorResponseException;
@@ -28,6 +29,11 @@ public class ClientRegistrationService {
         checkSsl();
 
         ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
+
+        if (provider == null) {
+            throw new NotFoundException("Client registration provider not found");
+        }
+
         provider.setEvent(event);
         provider.setAuth(new ClientRegAuth(session, event));
         return provider;
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
index 603ed08..0fad9c2 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
@@ -5,6 +5,7 @@ import org.keycloak.events.EventType;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.ClientRepresentation;
@@ -38,7 +39,7 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
 
         try {
             ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
-            clientModel.setRegistrationSecret(TokenGenerator.createRegistrationAccessToken());
+            KeycloakModelUtils.generateRegistrationAccessToken(clientModel);
 
             client = ModelToRepresentation.toRepresentation(clientModel);
             URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
@@ -59,6 +60,10 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
         ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
         auth.requireView(client);
 
+        if (auth.isRegistrationAccessToken()) {
+            KeycloakModelUtils.generateRegistrationAccessToken(client);
+        }
+
         event.client(client.getClientId()).success();
         return Response.ok(ModelToRepresentation.toRepresentation(client)).build();
     }
@@ -74,8 +79,14 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
 
         RepresentationToModel.updateClient(rep, client);
 
+        if (auth.isRegistrationAccessToken()) {
+            KeycloakModelUtils.generateRegistrationAccessToken(client);
+        }
+
+        rep = ModelToRepresentation.toRepresentation(client);
+
         event.client(client.getClientId()).success();
-        return Response.status(Response.Status.OK).build();
+        return Response.ok(rep).build();
     }
 
     @DELETE
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
index 4d131cc..b27ddb0 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
@@ -15,7 +15,6 @@ import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.services.ErrorResponse;
 import org.keycloak.services.clientregistration.ClientRegAuth;
 import org.keycloak.services.clientregistration.ClientRegistrationProvider;
-import org.keycloak.services.clientregistration.TokenGenerator;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.POST;
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 5d76778..da3eeb0 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
@@ -215,6 +215,24 @@ public class ClientResource {
     }
 
     /**
+     * Generate a new registration access token for the client
+     *
+     * @return
+     */
+    @Path("registration-access-token")
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public ClientRepresentation regenerateRegistrationAccessToken() {
+        auth.requireManage();
+
+        KeycloakModelUtils.generateRegistrationAccessToken(client);
+        ClientRepresentation rep = ModelToRepresentation.toRepresentation(client);
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).representation(rep).success();
+        return rep;
+    }
+
+    /**
      * Get the client secret
      *
      * @return
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
index b8bdb41..bf98364 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
@@ -5,6 +5,7 @@ import org.junit.Test;
 import org.keycloak.client.registration.Auth;
 import org.keycloak.client.registration.ClientRegistrationException;
 import org.keycloak.client.registration.HttpErrorException;
+import org.keycloak.common.enums.SslRequired;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.representations.idm.ClientRepresentation;
 
@@ -20,11 +21,14 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
     private ClientRepresentation client;
     private ClientRepresentation client2;
     private ClientRepresentation clientPublic;
+    private String publicKey;
 
     @Before
     public void before() throws Exception {
         super.before();
 
+        publicKey = adminClient.realm(REALM_NAME).toRepresentation().getPublicKey();
+
         client = new ClientRepresentation();
         client.setEnabled(true);
         client.setClientId("RegistrationAccessTokenTest");
@@ -66,6 +70,16 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
 
         AdapterConfig config = reg.getAdapterConfig(client.getClientId());
         assertNotNull(config);
+
+        assertEquals(testContext.getAuthServerContextRoot() + "/auth", config.getAuthServerUrl());
+        assertEquals("test", config.getRealm());
+
+        assertEquals(1, config.getCredentials().size());
+        assertEquals(client.getSecret(), config.getCredentials().get("secret"));
+
+        assertEquals(publicKey, config.getRealmKey());
+        assertEquals(client.getClientId(), config.getResource());
+        assertEquals(SslRequired.EXTERNAL.name().toLowerCase(), config.getSslRequired());
     }
 
     @Test
@@ -98,6 +112,14 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
 
         AdapterConfig config = reg.getAdapterConfig(clientPublic.getClientId());
         assertNotNull(config);
+
+        assertEquals("test", config.getRealm());
+
+        assertEquals(0, config.getCredentials().size());
+
+        assertEquals(publicKey, config.getRealmKey());
+        assertEquals(clientPublic.getClientId(), config.getResource());
+        assertEquals(SslRequired.EXTERNAL.name().toLowerCase(), config.getSslRequired());
     }
 
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
index d76da19..be880bf 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
@@ -33,10 +33,33 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest 
         reg.auth(Auth.token(client.getRegistrationAccessToken()));
     }
 
+    private ClientRepresentation assertRead(String id, String registrationAccess, boolean expectSuccess) throws ClientRegistrationException {
+        if (expectSuccess) {
+            reg.auth(Auth.token(registrationAccess));
+            ClientRepresentation rep = reg.get(client.getClientId());
+            assertNotNull(rep);
+            return rep;
+        } else {
+            reg.auth(Auth.token(registrationAccess));
+            try {
+                reg.get(client.getClientId());
+                fail("Expected 403");
+            } catch (ClientRegistrationException e) {
+                assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+            }
+        }
+        return null;
+    }
+
     @Test
     public void getClientWithRegistrationToken() throws ClientRegistrationException {
         ClientRepresentation rep = reg.get(client.getClientId());
         assertNotNull(rep);
+        assertNotEquals(client.getRegistrationAccessToken(), rep.getRegistrationAccessToken());
+
+        // check registration access token is updated
+        assertRead(client.getClientId(), client.getRegistrationAccessToken(), false);
+        assertRead(client.getClientId(), rep.getRegistrationAccessToken(), true);
     }
 
     @Test
@@ -53,9 +76,14 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest 
     @Test
     public void updateClientWithRegistrationToken() throws ClientRegistrationException {
         client.setRootUrl("http://newroot");
-        reg.update(client);
+        ClientRepresentation rep = reg.update(client);
 
         assertEquals("http://newroot", getClient(client.getId()).getRootUrl());
+        assertNotEquals(client.getRegistrationAccessToken(), rep.getRegistrationAccessToken());
+
+        // check registration access token is updated
+        assertRead(client.getClientId(), client.getRegistrationAccessToken(), false);
+        assertRead(client.getClientId(), rep.getRegistrationAccessToken(), true);
     }
 
     @Test