keycloak-aplcache
Changes
connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProvider.java 86(+0 -86)
connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProviderFactory.java 210(+0 -210)
connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProviderFactory.java 25(+0 -25)
connections/file/src/main/resources/META-INF/services/org.keycloak.connections.file.FileConnectionProviderFactory 1(+0 -1)
connections/pom.xml 1(+0 -1)
dependencies/server-all/pom.xml 4(+0 -4)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-file/main/module.xml 20(+0 -20)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-file/main/module.xml 18(+0 -18)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml 4(+1 -3)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml 2(+0 -2)
distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml 4(+1 -3)
distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-file/main/module.xml 20(+0 -20)
distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-file/main/module.xml 16(+0 -16)
distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml 2(+0 -2)
federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java 2(+1 -1)
forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties 7(+5 -2)
forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js 13(+12 -1)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html 5(+5 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html 2(+1 -1)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html 2(+1 -1)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html 2(+1 -1)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html 18(+18 -0)
integration/jetty/jetty8.1/pom.xml 6(+3 -3)
integration/jetty/jetty9.1/pom.xml 6(+3 -3)
integration/jetty/jetty9.2/pom.xml 6(+3 -3)
integration/jetty/jetty-core/pom.xml 6(+3 -3)
integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java 3(+3 -0)
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java 9(+9 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java 6(+6 -0)
model/pom.xml 1(+0 -1)
pom.xml 16(+3 -13)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java 79(+79 -0)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProviderFactory.java 44(+13 -31)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java 4(+3 -1)
saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory 1(+1 -0)
services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java 132(+132 -0)
services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java 92(+92 -0)
services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProviderFactory.java 42(+9 -33)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java 2(+1 -1)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java 27(+20 -7)
services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java 107(+37 -70)
services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java 38(+38 -0)
services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java 99(+99 -0)
services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java 4(+3 -1)
services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java 77(+77 -0)
services/src/main/java/org/keycloak/services/clientregistration/OIDCClientRegistrationProvider.java 34(+0 -34)
services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory 4(+3 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java 28(+28 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java 29(+28 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java 9(+9 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java 96(+96 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java 125(+125 -0)
Details
diff --git a/client-api/src/main/java/org/keycloak/client/registration/Auth.java b/client-api/src/main/java/org/keycloak/client/registration/Auth.java
new file mode 100644
index 0000000..5b0e85f
--- /dev/null
+++ b/client-api/src/main/java/org/keycloak/client/registration/Auth.java
@@ -0,0 +1,58 @@
+package org.keycloak.client.registration;
+
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpRequest;
+import org.keycloak.common.util.Base64;
+import org.keycloak.representations.idm.ClientRepresentation;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class Auth {
+
+ public abstract void addAuth(HttpRequest request);
+
+ public static Auth token(String token) {
+ return new BearerTokenAuth(token);
+ }
+
+ public static Auth token(ClientRepresentation client) {
+ return new BearerTokenAuth(client.getRegistrationAccessToken());
+ }
+
+ public static Auth client(String clientId, String clientSecret) {
+ return new BasicAuth(clientId, clientSecret);
+ }
+
+ private static class BearerTokenAuth extends Auth {
+
+ private String token;
+
+ public BearerTokenAuth(String token) {
+ this.token = token;
+ }
+
+ @Override
+ public void addAuth(HttpRequest request) {
+ request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
+ }
+ }
+
+ private static class BasicAuth extends Auth {
+
+ private String username;
+ private String password;
+
+ public BasicAuth(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ @Override
+ public void addAuth(HttpRequest request) {
+ String val = Base64.encodeBytes((username + ":" + password).getBytes());
+ request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + val);
+ }
+ }
+
+}
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 82a3b37..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
@@ -1,18 +1,9 @@
package org.keycloak.client.registration;
-import org.apache.http.HttpHeaders;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpDelete;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpPut;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
+import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.common.util.Base64;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
@@ -23,160 +14,59 @@ import java.io.InputStream;
*/
public class ClientRegistration {
- private String clientRegistrationUrl;
- private HttpClient httpClient;
- private Auth auth;
+ private final String DEFAULT = "default";
+ private final String INSTALLATION = "install";
- public static ClientRegistrationBuilder create() {
- return new ClientRegistrationBuilder();
- }
+ private HttpUtil httpUtil;
- private ClientRegistration() {
+ public ClientRegistration(String authServerUrl, String realm) {
+ httpUtil = new HttpUtil(HttpClients.createDefault(), HttpUtil.getUrl(authServerUrl, "realms", realm, "clients"));
}
- public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
- String content = serialize(client);
- InputStream resultStream = doPost(content);
- return deserialize(resultStream, ClientRepresentation.class);
+ public ClientRegistration(String authServerUrl, String realm, HttpClient httpClient) {
+ httpUtil = new HttpUtil(httpClient, HttpUtil.getUrl(authServerUrl, "realms", realm, "clients"));
}
- public ClientRepresentation get() throws ClientRegistrationException {
- if (auth instanceof ClientIdSecretAuth) {
- String clientId = ((ClientIdSecretAuth) auth).clientId;
- return get(clientId);
- } else {
- throw new ClientRegistrationException("Requires client authentication");
+ public void close() throws ClientRegistrationException {
+ if (httpUtil != null) {
+ httpUtil.close();
}
+ httpUtil = null;
}
- public ClientRepresentation get(String clientId) throws ClientRegistrationException {
- InputStream resultStream = doGet(clientId);
- return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
+ public ClientRegistration auth(Auth auth) {
+ httpUtil.setAuth(auth);
+ return this;
}
- public void update(ClientRepresentation client) throws ClientRegistrationException {
+ public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
- doPut(content, client.getClientId());
- }
-
- public void delete() throws ClientRegistrationException {
- if (auth instanceof ClientIdSecretAuth) {
- String clientId = ((ClientIdSecretAuth) auth).clientId;
- delete(clientId);
- } else {
- throw new ClientRegistrationException("Requires client authentication");
- }
- }
-
- public void delete(String clientId) throws ClientRegistrationException {
- doDelete(clientId);
+ InputStream resultStream = httpUtil.doPost(content, DEFAULT);
+ return deserialize(resultStream, ClientRepresentation.class);
}
- public void close() throws ClientRegistrationException {
- if (httpClient instanceof CloseableHttpClient) {
- try {
- ((CloseableHttpClient) httpClient).close();
- } catch (IOException e) {
- throw new ClientRegistrationException("Failed to close http client", e);
- }
- }
+ public ClientRepresentation get(String clientId) throws ClientRegistrationException {
+ InputStream resultStream = httpUtil.doGet(DEFAULT, clientId);
+ return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
}
- private InputStream doPost(String content) throws ClientRegistrationException {
- try {
- HttpPost request = new HttpPost(clientRegistrationUrl);
-
- request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
- request.setHeader(HttpHeaders.ACCEPT, "application/json");
- request.setEntity(new StringEntity(content));
-
- auth.addAuth(request);
-
- HttpResponse response = httpClient.execute(request);
- InputStream responseStream = null;
- if (response.getEntity() != null) {
- responseStream = response.getEntity().getContent();
- }
-
- if (response.getStatusLine().getStatusCode() == 201) {
- return responseStream;
- } else {
- responseStream.close();
- throw new HttpErrorException(response.getStatusLine());
- }
- } catch (IOException e) {
- throw new ClientRegistrationException("Failed to send request", e);
- }
+ public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
+ InputStream resultStream = httpUtil.doGet(INSTALLATION, clientId);
+ return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
}
- private InputStream doGet(String endpoint) throws ClientRegistrationException {
- try {
- HttpGet request = new HttpGet(clientRegistrationUrl + "/" + endpoint);
-
- request.setHeader(HttpHeaders.ACCEPT, "application/json");
-
- auth.addAuth(request);
-
- HttpResponse response = httpClient.execute(request);
- InputStream responseStream = null;
- if (response.getEntity() != null) {
- responseStream = response.getEntity().getContent();
- }
-
- if (response.getStatusLine().getStatusCode() == 200) {
- return responseStream;
- } else if (response.getStatusLine().getStatusCode() == 404) {
- responseStream.close();
- return null;
- } else {
- responseStream.close();
- throw new HttpErrorException(response.getStatusLine());
- }
- } catch (IOException e) {
- throw new ClientRegistrationException("Failed to send request", e);
- }
+ public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
+ String content = serialize(client);
+ InputStream resultStream = httpUtil.doPut(content, DEFAULT, client.getClientId());
+ return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
}
- private void doPut(String content, String endpoint) throws ClientRegistrationException {
- try {
- HttpPut request = new HttpPut(clientRegistrationUrl + "/" + endpoint);
-
- request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
- request.setHeader(HttpHeaders.ACCEPT, "application/json");
- request.setEntity(new StringEntity(content));
-
- auth.addAuth(request);
-
- HttpResponse response = httpClient.execute(request);
- if (response.getEntity() != null) {
- response.getEntity().getContent().close();
- }
-
- if (response.getStatusLine().getStatusCode() != 200) {
- throw new HttpErrorException(response.getStatusLine());
- }
- } catch (IOException e) {
- throw new ClientRegistrationException("Failed to send request", e);
- }
+ public void delete(ClientRepresentation client) throws ClientRegistrationException {
+ delete(client.getClientId());
}
- private void doDelete(String endpoint) throws ClientRegistrationException {
- try {
- HttpDelete request = new HttpDelete(clientRegistrationUrl + "/" + endpoint);
-
- auth.addAuth(request);
-
- HttpResponse response = httpClient.execute(request);
- if (response.getEntity() != null) {
- response.getEntity().getContent().close();
- }
-
- if (response.getStatusLine().getStatusCode() != 200) {
- throw new HttpErrorException(response.getStatusLine());
- }
- } catch (IOException e) {
- throw new ClientRegistrationException("Failed to send request", e);
- }
+ public void delete(String clientId) throws ClientRegistrationException {
+ httpUtil.doDelete(DEFAULT, clientId);
}
private String serialize(ClientRepresentation client) throws ClientRegistrationException {
@@ -195,81 +85,4 @@ public class ClientRegistration {
}
}
- public static class ClientRegistrationBuilder {
-
- private String realm;
-
- private String authServerUrl;
-
- private Auth auth;
-
- private HttpClient httpClient;
-
- public ClientRegistrationBuilder realm(String realm) {
- this.realm = realm;
- return this;
- }
- public ClientRegistrationBuilder authServerUrl(String authServerUrl) {
- this.authServerUrl = authServerUrl;
- return this;
- }
-
- public ClientRegistrationBuilder auth(String token) {
- this.auth = new TokenAuth(token);
- return this;
- }
-
- public ClientRegistrationBuilder auth(String clientId, String clientSecret) {
- this.auth = new ClientIdSecretAuth(clientId, clientSecret);
- return this;
- }
-
- public ClientRegistrationBuilder httpClient(HttpClient httpClient) {
- this.httpClient = httpClient;
- return this;
- }
-
- public ClientRegistration build() {
- ClientRegistration clientRegistration = new ClientRegistration();
- clientRegistration.clientRegistrationUrl = authServerUrl + "/realms/" + realm + "/client-registration/default";
-
- clientRegistration.httpClient = httpClient != null ? httpClient : HttpClients.createDefault();
- clientRegistration.auth = auth;
-
- return clientRegistration;
- }
-
- }
-
- public interface Auth {
- void addAuth(HttpRequest httpRequest);
- }
-
- public static class AuthorizationHeaderAuth implements Auth {
- private String credentials;
-
- public AuthorizationHeaderAuth(String credentials) {
- this.credentials = credentials;
- }
-
- public void addAuth(HttpRequest httpRequest) {
- httpRequest.setHeader(HttpHeaders.AUTHORIZATION, credentials);
- }
- }
-
- public static class TokenAuth extends AuthorizationHeaderAuth {
- public TokenAuth(String token) {
- super("Bearer " + token);
- }
- }
-
- public static class ClientIdSecretAuth extends AuthorizationHeaderAuth {
- private String clientId;
-
- public ClientIdSecretAuth(String clientId, String clientSecret) {
- super("Basic " + Base64.encodeBytes((clientId + ":" + clientSecret).getBytes()));
- this.clientId = clientId;
- }
- }
-
}
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
new file mode 100644
index 0000000..6444749
--- /dev/null
+++ b/client-api/src/main/java/org/keycloak/client/registration/HttpUtil.java
@@ -0,0 +1,171 @@
+package org.keycloak.client.registration;
+
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.*;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.client.registration.HttpErrorException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+class HttpUtil {
+
+ private HttpClient httpClient;
+
+ private String baseUri;
+
+ private Auth auth;
+
+ HttpUtil(HttpClient httpClient, String baseUri) {
+ this.httpClient = httpClient;
+ this.baseUri = baseUri;
+ }
+
+ void setAuth(Auth auth) {
+ this.auth = auth;
+ }
+
+ InputStream doPost(String content, String... path) throws ClientRegistrationException {
+ try {
+ HttpPost request = new HttpPost(getUrl(baseUri, path));
+
+ request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
+ request.setHeader(HttpHeaders.ACCEPT, "application/json");
+ request.setEntity(new StringEntity(content));
+
+ addAuth(request);
+
+ HttpResponse response = httpClient.execute(request);
+ InputStream responseStream = null;
+ if (response.getEntity() != null) {
+ responseStream = response.getEntity().getContent();
+ }
+
+ if (response.getStatusLine().getStatusCode() == 201) {
+ return responseStream;
+ } else {
+ responseStream.close();
+ throw new HttpErrorException(response.getStatusLine());
+ }
+ } catch (IOException e) {
+ throw new ClientRegistrationException("Failed to send request", e);
+ }
+ }
+
+ InputStream doGet(String... path) throws ClientRegistrationException {
+ try {
+ HttpGet request = new HttpGet(getUrl(baseUri, path));
+
+ request.setHeader(HttpHeaders.ACCEPT, "application/json");
+
+ addAuth(request);
+
+ HttpResponse response = httpClient.execute(request);
+ InputStream responseStream = null;
+ if (response.getEntity() != null) {
+ responseStream = response.getEntity().getContent();
+ }
+
+ if (response.getStatusLine().getStatusCode() == 200) {
+ return responseStream;
+ } else if (response.getStatusLine().getStatusCode() == 404) {
+ responseStream.close();
+ return null;
+ } else {
+ if (responseStream != null) {
+ responseStream.close();
+ }
+ throw new HttpErrorException(response.getStatusLine());
+ }
+ } catch (IOException e) {
+ throw new ClientRegistrationException("Failed to send request", e);
+ }
+ }
+
+ InputStream doPut(String content, String... path) throws ClientRegistrationException {
+ try {
+ HttpPut request = new HttpPut(getUrl(baseUri, path));
+
+ request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
+ request.setHeader(HttpHeaders.ACCEPT, "application/json");
+ request.setEntity(new StringEntity(content));
+
+ addAuth(request);
+
+ HttpResponse response = httpClient.execute(request);
+ if (response.getEntity() != null) {
+ response.getEntity().getContent();
+ }
+
+ 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) {
+ throw new ClientRegistrationException("Failed to send request", e);
+ }
+ }
+
+ void doDelete(String... path) throws ClientRegistrationException {
+ try {
+ HttpDelete request = new HttpDelete(getUrl(baseUri, path));
+
+ addAuth(request);
+
+ HttpResponse response = httpClient.execute(request);
+ if (response.getEntity() != null) {
+ response.getEntity().getContent().close();
+ }
+
+ if (response.getStatusLine().getStatusCode() != 200) {
+ throw new HttpErrorException(response.getStatusLine());
+ }
+ } catch (IOException e) {
+ throw new ClientRegistrationException("Failed to send request", e);
+ }
+ }
+
+ void close() throws ClientRegistrationException {
+ if (httpClient instanceof CloseableHttpClient) {
+ try {
+ ((CloseableHttpClient) httpClient).close();
+ } catch (IOException e) {
+ throw new ClientRegistrationException("Failed to close http client", e);
+ }
+ }
+ }
+
+ static String getUrl(String baseUri, String... path) {
+ StringBuilder s = new StringBuilder();
+ s.append(baseUri);
+ for (String p : path) {
+ s.append('/');
+ s.append(p);
+ }
+ return s.toString();
+ }
+
+ private void addAuth(HttpRequestBase request) {
+ if (auth != null) {
+ auth.addAuth(request);
+ }
+ }
+
+}
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
index 1120252..aed99fc 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
@@ -58,6 +58,9 @@
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
+ <addColumn tableName="CLIENT">
+ <column name="REGISTRATION_SECRET" type="VARCHAR(255)"/>
+ </addColumn>
</changeSet>
</databaseChangeLog>
\ No newline at end of file
connections/pom.xml 1(+0 -1)
diff --git a/connections/pom.xml b/connections/pom.xml
index 891cd59..e74e99c 100755
--- a/connections/pom.xml
+++ b/connections/pom.xml
@@ -17,7 +17,6 @@
<module>jpa-liquibase</module>
<module>infinispan</module>
<module>mongo</module>
- <module>file</module>
<module>mongo-update</module>
<module>http-client</module>
</modules>
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 0999505..514c0fb 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
@@ -19,6 +19,7 @@ public class ClientRepresentation {
protected Boolean enabled;
protected String clientAuthenticatorType;
protected String secret;
+ protected String registrationAccessToken;
protected String[] defaultRoles;
protected List<String> redirectUris;
protected List<String> webOrigins;
@@ -124,6 +125,14 @@ public class ClientRepresentation {
this.secret = secret;
}
+ public String getRegistrationAccessToken() {
+ return registrationAccessToken;
+ }
+
+ public void setRegistrationAccessToken(String registrationAccessToken) {
+ this.registrationAccessToken = registrationAccessToken;
+ }
+
public List<String> getRedirectUris() {
return redirectUris;
}
@@ -251,4 +260,5 @@ public class ClientRepresentation {
public void setProtocolMappers(List<ProtocolMapperRepresentation> protocolMappers) {
this.protocolMappers = protocolMappers;
}
+
}
dependencies/server-all/pom.xml 4(+0 -4)
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 81ce095..ad786a3 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -38,10 +38,6 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-model-file</artifactId>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-sessions-infinispan</artifactId>
</dependency>
<dependency>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
index c339f44..7e61fb4 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
@@ -8,7 +8,6 @@
<module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
- <module name="org.keycloak.keycloak-connections-file" services="import"/>
<module name="org.keycloak.keycloak-common" services="import"/>
<module name="org.keycloak.keycloak-core" services="import"/>
<module name="org.keycloak.keycloak-email-api" services="import"/>
@@ -33,7 +32,6 @@
<module name="org.keycloak.keycloak-model-api" services="import"/>
<module name="org.keycloak.keycloak-model-jpa" services="import"/>
<module name="org.keycloak.keycloak-model-mongo" services="import"/>
- <module name="org.keycloak.keycloak-model-file" services="import"/>
<module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
@@ -70,4 +68,4 @@
<subsystem name="weld"/>
</exclude-subsystems>
</deployment>
-</jboss-deployment-structure>
\ No newline at end of file
+</jboss-deployment-structure>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
index 77ce3ad..340c3bf 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
@@ -18,7 +18,6 @@
<module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
- <module name="org.keycloak.keycloak-connections-file" services="import"/>
<module name="org.keycloak.keycloak-common" services="import"/>
<module name="org.keycloak.keycloak-core" services="import"/>
<module name="org.keycloak.keycloak-email-api" services="import"/>
@@ -43,7 +42,6 @@
<module name="org.keycloak.keycloak-model-api" services="import"/>
<module name="org.keycloak.keycloak-model-jpa" services="import"/>
<module name="org.keycloak.keycloak-model-mongo" services="import"/>
- <module name="org.keycloak.keycloak-model-file" services="import"/>
<module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
<module name="org.keycloak.keycloak-saml-core" services="import"/>
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
index 60ccb65..9f60836 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
@@ -173,10 +173,6 @@
<maven-resource group="org.keycloak" artifact="keycloak-connections-jpa-liquibase"/>
</module-def>
- <module-def name="org.keycloak.keycloak-connections-file">
- <maven-resource group="org.keycloak" artifact="keycloak-connections-file"/>
- </module-def>
-
<module-def name="org.keycloak.keycloak-connections-infinispan">
<maven-resource group="org.keycloak" artifact="keycloak-connections-infinispan"/>
</module-def>
@@ -224,11 +220,11 @@
<module-def name="org.keycloak.keycloak-social-facebook">
<maven-resource group="org.keycloak" artifact="keycloak-social-facebook"/>
</module-def>
-
+
<module-def name="org.keycloak.keycloak-social-linkedin">
<maven-resource group="org.keycloak" artifact="keycloak-social-linkedin"/>
</module-def>
-
+
<module-def name="org.keycloak.keycloak-social-stackoverflow">
<maven-resource group="org.keycloak" artifact="keycloak-social-stackoverflow"/>
</module-def>
@@ -250,12 +246,6 @@
<maven-resource group="org.keycloak" artifact="keycloak-saml-protocol"/>
</module-def>
- <!-- file -->
-
- <module-def name="org.keycloak.keycloak-model-file">
- <maven-resource group="org.keycloak" artifact="keycloak-model-file"/>
- </module-def>
-
<!-- mongo -->
<module-def name="org.keycloak.keycloak-connections-mongo">
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
index c339f44..7e61fb4 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
@@ -8,7 +8,6 @@
<module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
- <module name="org.keycloak.keycloak-connections-file" services="import"/>
<module name="org.keycloak.keycloak-common" services="import"/>
<module name="org.keycloak.keycloak-core" services="import"/>
<module name="org.keycloak.keycloak-email-api" services="import"/>
@@ -33,7 +32,6 @@
<module name="org.keycloak.keycloak-model-api" services="import"/>
<module name="org.keycloak.keycloak-model-jpa" services="import"/>
<module name="org.keycloak.keycloak-model-mongo" services="import"/>
- <module name="org.keycloak.keycloak-model-file" services="import"/>
<module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
@@ -70,4 +68,4 @@
<subsystem name="weld"/>
</exclude-subsystems>
</deployment>
-</jboss-deployment-structure>
\ No newline at end of file
+</jboss-deployment-structure>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
index 44703f8..aa895e8 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
@@ -18,7 +18,6 @@
<module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
- <module name="org.keycloak.keycloak-connections-file" services="import"/>
<module name="org.keycloak.keycloak-common" services="import"/>
<module name="org.keycloak.keycloak-core" services="import"/>
<module name="org.keycloak.keycloak-email-api" services="import"/>
@@ -43,7 +42,6 @@
<module name="org.keycloak.keycloak-model-api" services="import"/>
<module name="org.keycloak.keycloak-model-jpa" services="import"/>
<module name="org.keycloak.keycloak-model-mongo" services="import"/>
- <module name="org.keycloak.keycloak-model-file" services="import"/>
<module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
<module name="org.keycloak.keycloak-saml-core" services="import"/>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml b/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml
index 10cb89d..12e2b1a 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml
@@ -866,6 +866,14 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
</para>
</section>
+ <section>
+ <title>Modifying First Broker Login Flow</title>
+ <para>
+ First Broker Login flow is used during first login with some identity provider. Term <literal>First Login</literal> means that there is not yet existing Keycloak account
+ linked with the particular authenticated identity provider account. More details about this flow are in the <link linkend="identity-broker-first-login">Identity provider chapter</link>.
+ </para>
+ </section>
+
<section id="client_authentication">
<title>Authentication of clients</title>
<para>Keycloak actually supports pluggable authentication for <ulink url="http://openid.net/specs/openid-connect-core-1_0.html">OpenID Connect</ulink>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml b/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml
index 7aefdb6..49d58ad 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml
@@ -8,7 +8,7 @@
<para>
<orderedlist>
<listitem>
- Create a new theme within the <literal>themes/admin/mytheme</literal> directory in your distribution.
+ Create a new theme within the <literal>themes/mytheme/admin</literal> directory in your distribution.
Where <literal>mytheme</literal> is whatever you want to name your theme.
</listitem>
<listitem>
@@ -19,15 +19,15 @@ import=common/keycloak
]]></programlisting>
</listitem>
<listitem>
- Copy the file <literal>themes/admin/base/resources/partials/user-attribute-entry.html</literal> into the
- a mirror directory in your theme: <literal>themes/admin/mytheme/resources/partials/user-attribute-entry.html</literal>.
+ Copy the file <literal>themes/base/admin/resources/partials/user-attributes.html</literal> into the
+ a mirror directory in your theme: <literal>themes/mytheme/admin/resources/partials/user-attributes.html</literal>.
What you are doing here is overriding the user attribute entry page in the admin console and putting in
what attributes you want. This file already contains an example of entering address data. You can remove
this if you want and replace it with something else. Also, if you want to edit this file directly instead
of creating a new theme, you can.
</listitem>
<listitem>
- In the <literal>user-attribute-entry.html</literal> file add your custom user attribute entry form item. For example
+ In the <literal>user-attributes.html</literal> file add your custom user attribute entry form item. For example
<programlisting><![CDATA[ <div class="form-group clearfix block">
<label class="col-sm-2 control-label" for="mobile">Mobile</label>
<div class="col-sm-6">
@@ -52,7 +52,7 @@ import=common/keycloak
<para>
<orderedlist>
<listitem>
- Create a new theme within the <literal>themes/login/mytheme</literal> directory in your distribution.
+ Create a new theme within the <literal>themes/mytheme/login</literal> directory in your distribution.
Where <literal>mytheme</literal> is whatever you want to name your theme.
</listitem>
<listitem>
@@ -63,8 +63,8 @@ import=common/keycloak
styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/login.css ../patternfly/lib/zocial/zocial.css css/login.css]]></programlisting>
</listitem>
<listitem>
- Copy the file <literal>themes/login/base/register.ftl</literal> into the
- a mirror directory in your theme: <literal>themes/login/mytheme/register.ftl</literal>.
+ Copy the file <literal>themes/base/login/register.ftl</literal> into the
+ a mirror directory in your theme: <literal>themes/mytheme/login/register.ftl</literal>.
What you are doing here is overriding the registration page and adding
what attributes you want. This file already contains an example of entering address data. You can remove
this if you want and replace it with something else. Also, if you want to edit this file directly instead
@@ -101,7 +101,7 @@ styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/login.
<para>
<orderedlist>
<listitem>
- Create a new theme within the <literal>themes/account/mytheme</literal> directory in your distribution.
+ Create a new theme within the <literal>themes/mytheme/account</literal> directory in your distribution.
Where <literal>mytheme</literal> is whatever you want to name your theme.
</listitem>
<listitem>
@@ -113,8 +113,8 @@ import=common/keycloak
styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/account.css css/account.css]]></programlisting>
</listitem>
<listitem>
- Copy the file <literal>themes/account/base/account.ftl</literal> into the
- a mirror directory in your theme: <literal>themes/account/mytheme/account.ftl</literal>.
+ Copy the file <literal>themes/base/account/account.ftl</literal> into the
+ a mirror directory in your theme: <literal>themes/mytheme/account/account.ftl</literal>.
What you are doing here is overriding the profile page and adding
what attributes you want to manage. This file already contains an example of entering address data. You can remove
this if you want and replace it with something else. Also, if you want to edit this file directly instead
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml b/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml
index a262b85..41c36f0 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml
@@ -66,7 +66,7 @@
</itemizedlist>
- <section>
+ <section id="identity-broker-overview">
<title>Overview</title>
<para>
@@ -127,10 +127,11 @@
<listitem>
<para>
Now Keycloak is going to check if the response from the identity provider is valid. If valid, it will create an user
- or just skip that if the user already exists. If it is a new user, Keycloak will ask informations about the user to the identity provider
+ or just skip that if the user already exists. If it is a new user, Keycloak may ask informations about the user to the identity provider
(or just read that from a security token) and create the user locally. This is what we call <emphasis>identity federation</emphasis>.
- If the user already exists Keycloak will ask him to link the identity returned from the identity provider
- with his existing account. A process that we call <emphasis>account linking</emphasis>.
+ If the user already exists Keycloak may ask him to link the identity returned from the identity provider
+ with his existing account. A process that we call <emphasis>account linking</emphasis>. What exactly is done is configurable
+ and can be specified by setup of <link linkend="identity-broker-first-login">First Login Flow</link> .
At the end of this step, Keycloak authenticates the user and issues its own token in order to access
the requested resource in the service provider.
</para>
@@ -210,7 +211,7 @@
<para>
Social providers allows you to enable social authentication to your realm.
Keycloak makes it easy to let users log in to your application using an existing account with a social network.
- Currently Facebook, Google and Twitter are supported with more planned for the future.
+ Currently Facebook, Google, Twitter, GitHub, LinkedIn and StackOverflow are supported with more planned for the future.
</para>
</listitem>
</varlistentry>
@@ -274,6 +275,15 @@
be used by any other means.
</entry>
</row>
+ <row>
+ <entry>
+ <literal>Authenticate By Default</literal>
+ </entry>
+ <entry>
+ If enabled, Keycloak will automatically redirect to this identity provider even before displaying login screen.
+ In other words, steps 3 and 4 from <link linkend="identity-broker-overview">the base flow</link> are skipped.
+ </entry>
+ </row>
<row>
<entry>
<literal>Store Tokens</literal>
@@ -295,20 +305,6 @@
</row>
<row>
<entry>
- <literal>Update Profile on First Login</literal>
- </entry>
- <entry>
- Allows you to force users to update their profile right after the authentication finishes and
- before the account is actually created in Keycloak. When "On", users will be always presented with the
- <emphasis>update profile page</emphasis> asking for additional information in order to federate their identities.
- When "On missing info", users will be presented with the <emphasis>update profile page</emphasis> only if some
- mandatory information (email, first name, last name) is not provided by identity provider.
- If "Off", the account will be created with the minimal information obtained from the identity provider
- during the authentication process.
- </entry>
- </row>
- <row>
- <entry>
<literal>Trust email</literal>
</entry>
<entry>
@@ -326,6 +322,16 @@
You can put number into this field, providers with lower numbers are shown first.
</entry>
</row>
+ <row>
+ <entry>
+ <literal>First Login Flow</literal>
+ </entry>
+ <entry>
+ Alias of authentication flow, which is triggered during first login with this identity provider. Term <literal>First Login</literal>
+ means that there is not yet existing Keycloak account linked with the authenticated identity provider account.
+ More details in <link linkend="identity-broker-first-login">First Login section</link>.
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -340,8 +346,8 @@
Forcing users to register to your realm when they want to access applications is hard.
So is trying to remember yet another username and password combination.
Social identity providers makes it easy for users to register on your realm and quickly sign in using a social network.
- Keycloak provides built-in support for the most common social networks out there, such as Google, Facebook, Twitter and
- even Github.
+ Keycloak provides built-in support for the most common social networks out there, such as Google, Facebook, Twitter,
+ Github, LinkedId and StackOverflow.
</para>
<section>
@@ -1211,7 +1217,13 @@ Authorization: Bearer {keycloak_access_token}]]></programlisting>
<section>
<title>Automatically Select and Identity Provider</title>
<para>
- Applications can automatically select an identity provider in order to authenticate an user. In this case, the user will not be presented to the login page but automatically redirected to the identity provider.
+ Each Identity provider has option <literal>Authenticate By Default</literal>, which allows that Identity provider is automatically
+ selected during authentication. User won't even see Keycloak login page, but is automatically redirected to the identity provider.
+ </para>
+ <para>
+ Applications can also automatically select an identity provider in order to authenticate an user.
+ Selection per application is preferred over <literal>Authenticate By Default</literal> option if you need more control
+ on when exactly is Identity provider automatically selected.
</para>
<para>
Keycloak supports a specific HTTP query parameter that you can use as a hint to tell the server which identity provider should be used to authenticate the user.
@@ -1283,6 +1295,122 @@ keycloak.createLoginUrl({
</para>
</section>
+ <section id="identity-broker-first-login">
+ <title>First Login Flow</title>
+ <para>
+ When Keycloak successfully authenticates user through identity provider (step 8 in <link linkend="identity-broker-overview">Overview</link> chapter),
+ there can be two situations:
+ <orderedlist>
+ <listitem>
+ <para>
+ There is already Keycloak user account linked with the authenticated identity provider account. In this case,
+ Keycloak will just authenticate as the existing user and redirect back to application (step 9 in <link linkend="identity-broker-overview">Overview</link> chapter).
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ There is not yet existing Keycloak user account linked with the identity provider account. This situation is more tricky.
+ Usually you just want to register new account into Keycloak database, but what if there is existing Keycloak account with same email like the identity provider account?
+ Automatically link identity provider account with existing Keycloak account is not very good option as there are possible security flaws related to that...
+ </para>
+ </listitem>
+ </orderedlist>
+ </para>
+ <para>
+ Because we had various requirements what to do in second case, we changed the behaviour to be flexible and configurable
+ through <link linkend="auth_spi">Authentication Flows SPI</link>. In admin console in Identity provider settings, there is option
+ <literal>First Login Flow</literal>, which allows you to choose, which workflow will be used after "first login" with this identity provider account.
+ By default it points to <literal>first broker login</literal> flow, but you can configure and use your own flow and use different flows for different identity providers etc.
+ </para>
+ <para>
+ The flow itself is configured in admin console under <literal>Authentication</literal> tab. When you choose <literal>First Broker Login</literal> flow,
+ you will see what authenticators are used by default. You can either re-configure existing flow (For example disable some authenticators,
+ mark some of them as <literal>required</literal>, configure some authenticators etc). Or you can even create new authentication flow and/or
+ write your own Authenticator implementations and use it in your flow. See <link linkend="auth_spi">Authentication Flows SPI</link> for more details on how to do it.
+ </para>
+ <para>
+ For <literal>First Broker Login</literal> case, it might be useful if your Authenticator is subclass of <literal>org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator</literal>
+ so you have access to all details about authenticated Identity provider account. But it's not a requirement.
+ </para>
+ <section>
+ <title>Default First Login Flow</title>
+ <para>
+ Let's describe the default behaviour provided by <literal>First Broker Login</literal> flow. There are those authenticators:
+ <variablelist>
+ <varlistentry>
+ <term>Review Profile</term>
+ <listitem>
+ <para>
+ This authenticator might display the profile info page, where user can review his profile retrieved from identity provider.
+ The authenticator is configurable. You can set <literal>Update Profile On First Login</literal> option.
+ When <literal>On</literal>, users will be always presented with the profile page asking for additional information
+ in order to federate their identities. When <literal>missing</literal>, users will be presented with
+ the profile page only if some mandatory information (email, first name, last name) is not provided by identity provider.
+ If <literal>Off</literal>, the profile page won't be displayed, unless user clicks in later phase on <literal>Review profile info</literal>
+ link (page displayed in later phase by <literal>Confirm Link Existing Account</literal> authenticator)
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Create User If Unique</term>
+ <listitem>
+ <para>
+ This authenticator checks if there is already existing Keycloak account with same email or username like
+ the account from identity provider. If it's not, then authenticator just creates new Keyclok account and
+ link it with identity provider and whole flow is finished. Otherwise it goes to the next <literal>Handle Existing Account</literal> subflow.
+ If you always want to ensure that there is no duplicated account, you can mark this authenticator as <literal>REQUIRED</literal> .
+ In this case, the user will see the error page if there is existing Keycloak account and user needs
+ to link his identity provider account through Account management.
+ </para>
+ <para>
+ This authenticator also has config option <literal>Require Password Update After Registration</literal> .
+ When enabled, user is required to update password after account is created.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Confirm Link Existing Account</term>
+ <listitem>
+ <para>
+ User will see the info page, that there is existing Keycloak account with same email. He can either
+ review his profile again and use different email or username (flow is restarted and goes back to <literal>Review Profile</literal> authenticator).
+ Or he can confirm that he wants to link identity provider account with his existing Keycloak account.
+ Disable this authenticator if you don't want users to see this confirmation page, but go straight
+ to linking identity provider account by email verification or re-authentication.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Verify Existing Account By Email</term>
+ <listitem>
+ <para>
+ This authenticator is <literal>ALTERNATIVE</literal> by default, so it's used only if realm has SMTP setup configured.
+ It will send mail to user, where he can confirm that he wants to link identity provider with his Keycloak account.
+ Disable this if you don't want to confirm linking by email, but instead you always want users to reauthenticate with their password (and alternatively OTP).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Verify Existing Account By Re-authentication</term>
+ <listitem>
+ <para>
+ This authenticator is used if email authenticator is disabled or non-available (SMTP not configured for realm). It
+ will display login screen where user needs to authenticate with his password to link his Keycloak account with Identity provider.
+ User can also re-authenticate with some different identity provider, which is already linked to his keycloak account.
+ You can also force users to use OTP, otherwise it's optional and used only if OTP is already set for user account.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </para>
+ </section>
+ </section>
+
<section>
<title>Examples</title>
<para>
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
index 09a4da7..65831f2 100755
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
@@ -116,7 +116,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
- return kerberosPrincipal.equals(local.getFirstAttribute(KERBEROS_PRINCIPAL));
+ return kerberosPrincipal.equalsIgnoreCase(local.getFirstAttribute(KERBEROS_PRINCIPAL));
}
@Override
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
index 3d2eeea..4980104 100755
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -133,6 +133,7 @@ federatedIdentityLinkNotActiveMessage=This identity is not active anymore.
federatedIdentityRemovingLastProviderMessage=You can''t remove last federated identity as you don''t have password.
identityProviderRedirectErrorMessage=Failed to redirect to identity provider.
identityProviderRemovedMessage=Identity provider removed successfully.
+identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
accountDisabledMessage=Account is disabled, contact admin.
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 ebf3a83..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
@@ -394,7 +397,7 @@ update-profile-on-first-login.tooltip=Define conditions under which a user has t
trust-email=Trust Email
trust-email.tooltip=If enabled then email provided by this provider is not verified even if verification is enabled for the realm.
gui-order.tooltip=Number defining order of the provider in GUI (eg. on Login page).
-first-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after first login with this identity provider.
+first-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that there is not yet existing Keycloak account linked with the authenticated identity provider account.
openid-connect-config=OpenID Connect Config
openid-connect-config.tooltip=OIDC SP and external IDP configuration.
authorization-url=Authorization URL
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/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 803ef2d..6885308 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -185,7 +185,6 @@ resetCredentialNotAllowedMessage=Reset Credential not allowed
permissionNotApprovedMessage=Permission not approved.
noRelayStateInResponseMessage=No relay state in response from identity provider.
-identityProviderAlreadyLinkedMessage=The identity returned by the identity provider is already linked to another user.
insufficientPermissionMessage=Insufficient permissions to link identities.
couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider.
couldNotObtainTokenMessage=Could not obtain token from identity provider.
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 ***********/
integration/jetty/jetty8.1/pom.xml 6(+3 -3)
diff --git a/integration/jetty/jetty8.1/pom.xml b/integration/jetty/jetty8.1/pom.xml
index 580f9c0..b7577ec 100755
--- a/integration/jetty/jetty8.1/pom.xml
+++ b/integration/jetty/jetty8.1/pom.xml
@@ -70,21 +70,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
integration/jetty/jetty9.1/pom.xml 6(+3 -3)
diff --git a/integration/jetty/jetty9.1/pom.xml b/integration/jetty/jetty9.1/pom.xml
index f0c4a5b..0ad77ea 100755
--- a/integration/jetty/jetty9.1/pom.xml
+++ b/integration/jetty/jetty9.1/pom.xml
@@ -81,21 +81,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
integration/jetty/jetty9.2/pom.xml 6(+3 -3)
diff --git a/integration/jetty/jetty9.2/pom.xml b/integration/jetty/jetty9.2/pom.xml
index ab82620..7ae5028 100755
--- a/integration/jetty/jetty9.2/pom.xml
+++ b/integration/jetty/jetty9.2/pom.xml
@@ -67,21 +67,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
integration/jetty/jetty-core/pom.xml 6(+3 -3)
diff --git a/integration/jetty/jetty-core/pom.xml b/integration/jetty/jetty-core/pom.xml
index f1029a9..5c0700c 100755
--- a/integration/jetty/jetty-core/pom.xml
+++ b/integration/jetty/jetty-core/pom.xml
@@ -71,21 +71,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
diff --git a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
index aad0ec5..641ca70 100755
--- a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
+++ b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
@@ -90,6 +90,9 @@ public class FilterSessionStore implements AdapterSessionStore {
MultivaluedHashMap<String, String> getParams() {
if (parameters != null) return parameters;
+
+ if (body == null) return new MultivaluedHashMap<String, String>();
+
String contentType = getContentType();
contentType = contentType.toLowerCase();
if (contentType.startsWith("application/x-www-form-urlencoded")) {
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index 8753f45..c421aea 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -90,6 +90,9 @@ public interface ClientModel extends RoleContainerModel {
String getSecret();
public void setSecret(String secret);
+ String getRegistrationSecret();
+ void setRegistrationSecret(String registrationSecret);
+
boolean isFullScopeAllowed();
void setFullScopeAllowed(boolean value);
diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
index aa4f725..f15614f 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
@@ -17,6 +17,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private boolean enabled;
private String clientAuthenticatorType;
private String secret;
+ private String registrationSecret;
private String protocol;
private int notBefore;
private boolean publicClient;
@@ -90,6 +91,14 @@ public class ClientEntity extends AbstractIdentifiableEntity {
this.secret = secret;
}
+ public String getRegistrationSecret() {
+ return registrationSecret;
+ }
+
+ public void setRegistrationSecret(String registrationSecret) {
+ this.registrationSecret = registrationSecret;
+ }
+
public int getNotBefore() {
return notBefore;
}
diff --git a/model/api/src/main/java/org/keycloak/models/KeycloakContext.java b/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
index cf8bc6d..f430e5f 100755
--- a/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
+++ b/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
@@ -5,6 +5,7 @@ import org.keycloak.models.utils.RealmImporter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
+import java.net.URI;
import java.util.Locale;
/**
@@ -12,6 +13,8 @@ import java.util.Locale;
*/
public interface KeycloakContext {
+ URI getAuthServerUrl();
+
String getContextPath();
UriInfo getUri();
diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
index 98e960c..40a8999 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
@@ -1,6 +1,8 @@
package org.keycloak.models.utils;
import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import org.keycloak.models.AuthenticationExecutionModel;
@@ -36,7 +38,7 @@ public class DefaultAuthenticationFlows {
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
- if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm);
+ if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, false);
}
public static void migrateFlows(RealmModel realm) {
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm, true);
@@ -44,7 +46,7 @@ public class DefaultAuthenticationFlows {
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
- if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm);
+ if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, true);
}
public static void registrationFlow(RealmModel realm) {
@@ -322,7 +324,7 @@ public class DefaultAuthenticationFlows {
realm.addAuthenticatorExecution(execution);
}
- public static void firstBrokerLoginFlow(RealmModel realm) {
+ public static void firstBrokerLoginFlow(RealmModel realm, boolean migrate) {
AuthenticationFlowModel firstBrokerLogin = new AuthenticationFlowModel();
firstBrokerLogin.setAlias(FIRST_BROKER_LOGIN_FLOW);
firstBrokerLogin.setDescription("Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account");
@@ -423,10 +425,19 @@ public class DefaultAuthenticationFlows {
execution = new AuthenticationExecutionModel();
execution.setParentFlow(verifyByReauthenticationAccountFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
- // TODO: read the requirement from browser authenticator
-// if (migrate && hasCredentialType(realm, RequiredCredentialModel.TOTP.getType())) {
-// execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
-// }
+
+ if (migrate) {
+ // Try to read OTP requirement from browser flow
+ AuthenticationFlowModel browserFlow = realm.getBrowserFlow();
+ List<AuthenticationExecutionModel> browserExecutions = new LinkedList<>();
+ KeycloakModelUtils.deepFindAuthenticationExecutions(realm, browserFlow, browserExecutions);
+ for (AuthenticationExecutionModel browserExecution : browserExecutions) {
+ if (browserExecution.getAuthenticator().equals("auth-otp-form")) {
+ execution.setRequirement(browserExecution.getRequirement());
+ }
+ }
+ }
+
execution.setAuthenticator("auth-otp-form");
execution.setPriority(20);
execution.setAuthenticatorFlow(false);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java b/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java
index b840de6..9f5c5de 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java
@@ -23,6 +23,9 @@ public class FormMessage {
private String message;
private Object[] parameters;
+ public FormMessage() {
+ }
+
/**
* Create message.
*
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 dd058c6..43f2b93 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,7 +1,9 @@
package org.keycloak.models.utils;
import org.bouncycastle.openssl.PEMWriter;
-import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.GroupModel;
@@ -17,6 +19,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.PemUtils;
@@ -30,9 +33,11 @@ import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
+import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -45,6 +50,8 @@ import java.util.UUID;
*/
public final class KeycloakModelUtils {
+ private static final int RANDOM_PASSWORD_BYTES = 32;
+
private KeycloakModelUtils() {
}
@@ -176,12 +183,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";
}
@@ -389,6 +406,26 @@ public final class KeycloakModelUtils {
}
}
+
+ /**
+ * Recursively find all AuthenticationExecutionModel from specified flow or all it's subflows
+ *
+ * @param realm
+ * @param flow
+ * @param result input should be empty list. At the end will be all executions added to this list
+ */
+ public static void deepFindAuthenticationExecutions(RealmModel realm, AuthenticationFlowModel flow, List<AuthenticationExecutionModel> result) {
+ List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flow.getId());
+ for (AuthenticationExecutionModel execution : executions) {
+ if (execution.isAuthenticatorFlow()) {
+ AuthenticationFlowModel subFlow = realm.getAuthenticationFlowById(execution.getFlowId());
+ deepFindAuthenticationExecutions(realm, subFlow, result);
+ } else {
+ result.add(execution);
+ }
+ }
+ }
+
public static String resolveFirstAttribute(GroupModel group, String name) {
String value = group.getFirstAttribute(name);
if (value != null) return value;
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 176cb76..457d13c 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -11,8 +11,6 @@ import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.OTPPolicy;
-import org.keycloak.models.session.PersistentClientSessionModel;
-import org.keycloak.models.session.PersistentUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
@@ -46,14 +44,7 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.common.util.Time;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -301,19 +292,50 @@ public class ModelToRepresentation {
public static void exportAuthenticationFlows(RealmModel realm, RealmRepresentation rep) {
rep.setAuthenticationFlows(new LinkedList<AuthenticationFlowRepresentation>());
rep.setAuthenticatorConfig(new LinkedList<AuthenticatorConfigRepresentation>());
- for (AuthenticationFlowModel model : realm.getAuthenticationFlows()) {
+
+ List<AuthenticationFlowModel> authenticationFlows = new ArrayList<>(realm.getAuthenticationFlows());
+ //ensure consistent ordering of authenticationFlows.
+ Collections.sort(authenticationFlows, new Comparator<AuthenticationFlowModel>() {
+ @Override
+ public int compare(AuthenticationFlowModel left, AuthenticationFlowModel right) {
+ return left.getAlias().compareTo(right.getAlias());
+ }
+ });
+
+ for (AuthenticationFlowModel model : authenticationFlows) {
AuthenticationFlowRepresentation flowRep = toRepresentation(realm, model);
rep.getAuthenticationFlows().add(flowRep);
}
- for (AuthenticatorConfigModel model : realm.getAuthenticatorConfigs()) {
+
+ List<AuthenticatorConfigModel> authenticatorConfigs = new ArrayList<>(realm.getAuthenticatorConfigs());
+ //ensure consistent ordering of authenticatorConfigs.
+ Collections.sort(authenticatorConfigs, new Comparator<AuthenticatorConfigModel>() {
+ @Override
+ public int compare(AuthenticatorConfigModel left, AuthenticatorConfigModel right) {
+ return left.getAlias().compareTo(right.getAlias());
+ }
+ });
+
+ for (AuthenticatorConfigModel model : authenticatorConfigs) {
rep.getAuthenticatorConfig().add(toRepresentation(model));
}
}
public static void exportRequiredActions(RealmModel realm, RealmRepresentation rep) {
+
rep.setRequiredActions(new LinkedList<RequiredActionProviderRepresentation>());
- for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
+
+ List<RequiredActionProviderModel> requiredActionProviders = realm.getRequiredActionProviders();
+ //ensure consistent ordering of requiredActionProviders.
+ Collections.sort(requiredActionProviders, new Comparator<RequiredActionProviderModel>() {
+ @Override
+ public int compare(RequiredActionProviderModel left, RequiredActionProviderModel right) {
+ return left.getAlias().compareTo(right.getAlias());
+ }
+ });
+
+ for (RequiredActionProviderModel model : requiredActionProviders) {
RequiredActionProviderRepresentation action = toRepresentation(model);
rep.getRequiredActions().add(action);
}
@@ -396,6 +418,7 @@ public class ModelToRepresentation {
rep.setNotBefore(clientModel.getNotBefore());
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());
+ rep.setRegistrationAccessToken(clientModel.getRegistrationSecret());
Set<String> redirectUris = clientModel.getRedirectUris();
if (redirectUris != null) {
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index bb9dbc8..baa2b13 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -797,6 +797,8 @@ public class RepresentationToModel {
KeycloakModelUtils.generateSecret(client);
}
+ client.setRegistrationSecret(resourceRep.getRegistrationAccessToken());
+
if (resourceRep.getAttributes() != null) {
for (Map.Entry<String, String> entry : resourceRep.getAttributes().entrySet()) {
client.setAttribute(entry.getKey(), entry.getValue());
@@ -873,6 +875,7 @@ public class RepresentationToModel {
if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout());
if (rep.getClientAuthenticatorType() != null) resource.setClientAuthenticatorType(rep.getClientAuthenticatorType());
+ if (rep.getRegistrationAccessToken() != null) resource.setRegistrationSecret(rep.getRegistrationAccessToken());
resource.updateClient();
if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol());
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index 477c5d6..7a87373 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -120,6 +120,15 @@ public class ClientAdapter implements ClientModel {
getDelegateForUpdate();
updated.setSecret(secret);
}
+ public String getRegistrationSecret() {
+ if (updated != null) return updated.getRegistrationSecret();
+ return cached.getRegistrationSecret();
+ }
+
+ public void setRegistrationSecret(String registrationsecret) {
+ getDelegateForUpdate();
+ updated.setRegistrationSecret(registrationsecret);
+ }
public boolean isPublicClient() {
if (updated != null) return updated.isPublicClient();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index 2d58222..1c04b9d 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -31,6 +31,7 @@ public class CachedClient implements Serializable {
private boolean enabled;
private String clientAuthenticatorType;
private String secret;
+ private String registrationSecret;
private String protocol;
private Map<String, String> attributes = new HashMap<String, String>();
private boolean publicClient;
@@ -57,6 +58,7 @@ public class CachedClient implements Serializable {
id = model.getId();
clientAuthenticatorType = model.getClientAuthenticatorType();
secret = model.getSecret();
+ registrationSecret = model.getRegistrationSecret();
clientId = model.getClientId();
name = model.getName();
description = model.getDescription();
@@ -129,6 +131,10 @@ public class CachedClient implements Serializable {
return secret;
}
+ public String getRegistrationSecret() {
+ return registrationSecret;
+ }
+
public boolean isPublicClient() {
return publicClient;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 3baddd1..5ea0b11 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -178,6 +178,16 @@ public class ClientAdapter implements ClientModel {
}
@Override
+ public String getRegistrationSecret() {
+ return entity.getRegistrationSecret();
+ }
+
+ @Override
+ public void setRegistrationSecret(String registrationSecret) {
+ entity.setRegistrationSecret(registrationSecret);
+ }
+
+ @Override
public boolean validateSecret(String secret) {
return secret.equals(entity.getSecret());
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index f26f651..881b129 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -42,6 +42,8 @@ public class ClientEntity {
private boolean enabled;
@Column(name="SECRET")
private String secret;
+ @Column(name="REGISTRATION_SECRET")
+ private String registrationSecret;
@Column(name="CLIENT_AUTHENTICATOR_TYPE")
private String clientAuthenticatorType;
@Column(name="NOT_BEFORE")
@@ -201,6 +203,14 @@ public class ClientEntity {
this.secret = secret;
}
+ public String getRegistrationSecret() {
+ return registrationSecret;
+ }
+
+ public void setRegistrationSecret(String registrationSecret) {
+ this.registrationSecret = registrationSecret;
+ }
+
public int getNotBefore() {
return notBefore;
}
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 c375084..ef6c32b 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
@@ -1,5 +1,6 @@
package org.keycloak.models.jpa;
+import org.keycloak.Config;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.models.AuthenticationExecutionModel;
@@ -1195,8 +1196,13 @@ public class RealmAdapter implements RealmModel {
@Override
public ClientModel getMasterAdminClient() {
- ClientEntity client = realm.getMasterAdminClient();
- return client!=null ? new ClientAdapter(this, em, session, realm.getMasterAdminClient()) : null;
+ ClientEntity masterAdminClient = realm.getMasterAdminClient();
+ if (masterAdminClient == null) {
+ return null;
+ }
+
+ RealmAdapter masterRealm = new RealmAdapter(session, em, masterAdminClient.getRealm());
+ return new ClientAdapter(masterRealm, em, session, masterAdminClient);
}
@Override
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index e99f142..cbacd09 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -178,6 +178,17 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
}
@Override
+ public String getRegistrationSecret() {
+ return getMongoEntity().getRegistrationSecret();
+ }
+
+ @Override
+ public void setRegistrationSecret(String registrationSecretsecret) {
+ getMongoEntity().setRegistrationSecret(registrationSecretsecret);
+ updateMongoEntity();
+ }
+
+ @Override
public boolean isPublicClient() {
return getMongoEntity().isPublicClient();
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 3581a0a..cab0ddf 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -1230,7 +1230,13 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public ClientModel getMasterAdminClient() {
MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, realm.getMasterAdminClient(), invocationContext);
- return appData != null ? new ClientAdapter(session, this, appData, invocationContext) : null;
+ if (appData == null) {
+ return null;
+ }
+
+ MongoRealmEntity masterRealm = getMongoStore().loadEntity(MongoRealmEntity.class, appData.getRealmId(), invocationContext);
+ RealmModel masterRealmModel = new RealmAdapter(session, masterRealm, invocationContext);
+ return new ClientAdapter(session, masterRealmModel, appData, invocationContext);
}
@Override
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 87729d4..83979c3 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -453,7 +453,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
@Override
public Set<GroupModel> getGroups() {
- if (user.getGroupIds() == null && user.getGroupIds().size() == 0) return Collections.EMPTY_SET;
+ if (user.getGroupIds() == null || user.getGroupIds().size() == 0) return Collections.EMPTY_SET;
Set<GroupModel> groups = new HashSet<>();
for (String id : user.getGroupIds()) {
groups.add(realm.getGroupById(id));
model/pom.xml 1(+0 -1)
diff --git a/model/pom.xml b/model/pom.xml
index 2f78805..0322abb 100755
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -29,7 +29,6 @@
<module>invalidation-cache</module>
<module>jpa</module>
<module>mongo</module>
- <module>file</module>
<module>sessions-infinispan</module>
</modules>
</project>
pom.xml 16(+3 -13)
diff --git a/pom.xml b/pom.xml
index 85409fe..d545efd 100755
--- a/pom.xml
+++ b/pom.xml
@@ -599,11 +599,6 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-connections-file</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
<artifactId>keycloak-connections-infinispan</artifactId>
<version>${project.version}</version>
</dependency>
@@ -838,7 +833,7 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wf9-server-subsystem</artifactId>
<version>${project.version}</version>
- </dependency>
+ </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-subsystem</artifactId>
@@ -961,11 +956,6 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-model-file</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
<artifactId>keycloak-invalidation-cache-infinispan</artifactId>
<version>${project.version}</version>
</dependency>
@@ -1441,7 +1431,7 @@
<groupId>org.wildfly.build</groupId>
<artifactId>wildfly-feature-pack-build-maven-plugin</artifactId>
<version>${wildfly.build-tools.version}</version>
- </plugin>
+ </plugin>
<plugin>
<groupId>org.wildfly.build</groupId>
<artifactId>wildfly-server-provisioning-maven-plugin</artifactId>
@@ -1462,7 +1452,7 @@
<requireMavenVersion>
<version>3.1.1</version>
</requireMavenVersion>
- </rules>
+ </rules>
</configuration>
</execution>
</executions>
diff --git a/saml/client-adapter/jetty/jetty8.1/pom.xml b/saml/client-adapter/jetty/jetty8.1/pom.xml
index 937b8ee..1e888f5 100755
--- a/saml/client-adapter/jetty/jetty8.1/pom.xml
+++ b/saml/client-adapter/jetty/jetty8.1/pom.xml
@@ -54,21 +54,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
diff --git a/saml/client-adapter/jetty/jetty9.1/pom.xml b/saml/client-adapter/jetty/jetty9.1/pom.xml
index f9bc11b..b8ca67b 100755
--- a/saml/client-adapter/jetty/jetty9.1/pom.xml
+++ b/saml/client-adapter/jetty/jetty9.1/pom.xml
@@ -69,21 +69,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
diff --git a/saml/client-adapter/jetty/jetty9.2/pom.xml b/saml/client-adapter/jetty/jetty9.2/pom.xml
index d7dc8b8..9382c4a 100755
--- a/saml/client-adapter/jetty/jetty9.2/pom.xml
+++ b/saml/client-adapter/jetty/jetty9.2/pom.xml
@@ -69,21 +69,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
diff --git a/saml/client-adapter/jetty/jetty-core/pom.xml b/saml/client-adapter/jetty/jetty-core/pom.xml
index 52cc273..87bf21a 100755
--- a/saml/client-adapter/jetty/jetty-core/pom.xml
+++ b/saml/client-adapter/jetty/jetty-core/pom.xml
@@ -59,21 +59,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
<dependency>
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
new file mode 100644
index 0000000..298623d
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
@@ -0,0 +1,79 @@
+package org.keycloak.protocol.saml.clientregistration;
+
+import org.jboss.logging.Logger;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.exportimport.ClientDescriptionConverter;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.clientregistration.ClientRegAuth;
+import org.keycloak.services.clientregistration.ClientRegistrationProvider;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class EntityDescriptorClientRegistrationProvider implements ClientRegistrationProvider {
+
+ private static final Logger logger = Logger.getLogger(EntityDescriptorClientRegistrationProvider.class);
+
+ private KeycloakSession session;
+ private EventBuilder event;
+ private ClientRegAuth auth;
+
+ public EntityDescriptorClientRegistrationProvider(KeycloakSession session) {
+ this.session = session;
+ }
+
+// @POST
+// @Consumes(MediaType.APPLICATION_XML)
+// @Produces(MediaType.APPLICATION_JSON)
+// public Response create(String descriptor) {
+// event.event(EventType.CLIENT_REGISTER);
+//
+// auth.requireCreate();
+//
+// ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor);
+//
+// try {
+// ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
+// client = ModelToRepresentation.toRepresentation(clientModel);
+// URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
+//
+// logger.infov("Created client {0}", client.getClientId());
+//
+// event.client(client.getClientId()).success();
+//
+// return Response.created(uri).entity(client).build();
+// } catch (ModelDuplicateException e) {
+// return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
+// }
+// }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void setAuth(ClientRegAuth auth) {
+ this.auth = auth;
+ }
+
+ @Override
+ public void setEvent(EventBuilder event) {
+ this.event = event;
+ }
+
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
index da99613..42ff3ee 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
@@ -33,6 +33,8 @@ import java.util.Map;
*/
public class EntityDescriptorDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
+ public static final String ID = "saml2-entity-descriptor";
+
@Override
public boolean isSupported(String description) {
description = description.trim();
@@ -161,7 +163,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo
@Override
public String getId() {
- return "saml2-entity-descriptor";
+ return ID;
}
}
diff --git a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
new file mode 100644
index 0000000..e4f8117
--- /dev/null
+++ b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
@@ -0,0 +1 @@
+org.keycloak.protocol.saml.clientregistration.EntityDescriptorClientRegistrationProviderFactory
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
index 9b862ef..2250dd0 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -190,7 +190,7 @@ public class LogoutEndpoint {
}
private ClientModel authorizeClient() {
- ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
+ ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient();
if (client.isBearerOnly()) {
throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);
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 1fb3e4a..34161d8 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -145,7 +145,7 @@ public class TokenEndpoint {
}
private void checkClient() {
- AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
+ AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event);
client = clientAuth.getClient();
clientAuthAttributes = clientAuth.getClientAuthAttributes();
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
index 56b47bb..955dfe4 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
@@ -8,6 +8,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.clientregistration.oidc.DescriptionConverter;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
@@ -17,6 +18,8 @@ import java.io.IOException;
*/
public class OIDCClientDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
+ public static final String ID = "openid-connect";
+
@Override
public boolean isSupported(String description) {
description = description.trim();
@@ -26,15 +29,8 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte
@Override
public ClientRepresentation convertToInternal(String description) {
try {
- OIDCClientRepresentation oidcRep = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
-
- ClientRepresentation client = new ClientRepresentation();
- client.setClientId(KeycloakModelUtils.generateId());
- client.setName(oidcRep.getClientName());
- client.setRedirectUris(oidcRep.getRedirectUris());
- client.setBaseUrl(oidcRep.getClientUri());
-
- return client;
+ OIDCClientRepresentation clientOIDC = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
+ return DescriptionConverter.toInternal(clientOIDC);
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -59,7 +55,7 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte
@Override
public String getId() {
- return "openid-connect";
+ return ID;
}
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java
index 7de415c..4a83ebd 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java
@@ -12,12 +12,48 @@ public class OIDCClientRepresentation {
@JsonProperty("redirect_uris")
private List<String> redirectUris;
+ @JsonProperty("token_endpoint_auth_method")
+ private String tokenEndpointAuthMethod;
+
+ @JsonProperty("grant_types")
+ private String grantTypes;
+
+ @JsonProperty("response_types")
+ private String responseTypes;
+
@JsonProperty("client_name")
private String clientName;
@JsonProperty("client_uri")
private String clientUri;
+ @JsonProperty("logo_uri")
+ private String logoUri;
+
+ @JsonProperty("scope")
+ private String scope;
+
+ @JsonProperty("contacts")
+ private String contacts;
+
+ @JsonProperty("tos_uri")
+ private String tos_uri;
+
+ @JsonProperty("policy_uri")
+ private String policy_uri;
+
+ @JsonProperty("jwks_uri")
+ private String jwks_uri;
+
+ @JsonProperty("jwks")
+ private String jwks;
+
+ @JsonProperty("software_id")
+ private String softwareId;
+
+ @JsonProperty("software_version")
+ private String softwareVersion;
+
public List<String> getRedirectUris() {
return redirectUris;
}
@@ -26,6 +62,30 @@ public class OIDCClientRepresentation {
this.redirectUris = redirectUris;
}
+ public String getTokenEndpointAuthMethod() {
+ return tokenEndpointAuthMethod;
+ }
+
+ public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) {
+ this.tokenEndpointAuthMethod = tokenEndpointAuthMethod;
+ }
+
+ public String getGrantTypes() {
+ return grantTypes;
+ }
+
+ public void setGrantTypes(String grantTypes) {
+ this.grantTypes = grantTypes;
+ }
+
+ public String getResponseTypes() {
+ return responseTypes;
+ }
+
+ public void setResponseTypes(String responseTypes) {
+ this.responseTypes = responseTypes;
+ }
+
public String getClientName() {
return clientName;
}
@@ -42,4 +102,76 @@ public class OIDCClientRepresentation {
this.clientUri = clientUri;
}
+ public String getLogoUri() {
+ return logoUri;
+ }
+
+ public void setLogoUri(String logoUri) {
+ this.logoUri = logoUri;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public void setScope(String scope) {
+ this.scope = scope;
+ }
+
+ public String getContacts() {
+ return contacts;
+ }
+
+ public void setContacts(String contacts) {
+ this.contacts = contacts;
+ }
+
+ public String getTos_uri() {
+ return tos_uri;
+ }
+
+ public void setTos_uri(String tos_uri) {
+ this.tos_uri = tos_uri;
+ }
+
+ public String getPolicy_uri() {
+ return policy_uri;
+ }
+
+ public void setPolicy_uri(String policy_uri) {
+ this.policy_uri = policy_uri;
+ }
+
+ public String getJwks_uri() {
+ return jwks_uri;
+ }
+
+ public void setJwks_uri(String jwks_uri) {
+ this.jwks_uri = jwks_uri;
+ }
+
+ public String getJwks() {
+ return jwks;
+ }
+
+ public void setJwks(String jwks) {
+ this.jwks = jwks;
+ }
+
+ public String getSoftwareId() {
+ return softwareId;
+ }
+
+ public void setSoftwareId(String softwareId) {
+ this.softwareId = softwareId;
+ }
+
+ public String getSoftwareVersion() {
+ return softwareVersion;
+ }
+
+ public void setSoftwareVersion(String softwareVersion) {
+ this.softwareVersion = softwareVersion;
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java
index c017ba3..a2d0d6c 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java
@@ -19,18 +19,8 @@ import javax.ws.rs.core.Response;
*/
public class AuthorizeClientUtil {
- public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event, RealmModel realm) {
- AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
- String flowId = clientAuthFlow.getId();
-
- AuthenticationProcessor processor = new AuthenticationProcessor();
- processor.setFlowId(flowId)
- .setConnection(session.getContext().getConnection())
- .setEventBuilder(event)
- .setRealm(realm)
- .setSession(session)
- .setUriInfo(session.getContext().getUri())
- .setRequest(session.getContext().getContextObject(HttpRequest.class));
+ public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event) {
+ AuthenticationProcessor processor = getAuthenticationProcessor(session, event);
Response response = processor.authenticateClient();
if (response != null) {
@@ -45,6 +35,24 @@ public class AuthorizeClientUtil {
return new ClientAuthResult(client, processor.getClientAuthAttributes());
}
+ public static AuthenticationProcessor getAuthenticationProcessor(KeycloakSession session, EventBuilder event) {
+ RealmModel realm = session.getContext().getRealm();
+
+ AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
+ String flowId = clientAuthFlow.getId();
+
+ AuthenticationProcessor processor = new AuthenticationProcessor();
+ processor.setFlowId(flowId)
+ .setConnection(session.getContext().getConnection())
+ .setEventBuilder(event)
+ .setRealm(realm)
+ .setSession(session)
+ .setUriInfo(session.getContext().getUri())
+ .setRequest(session.getContext().getContextObject(HttpRequest.class));
+
+ return processor;
+ }
+
public static class ClientAuthResult {
private final ClientModel client;
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
new file mode 100644
index 0000000..feffc5f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
@@ -0,0 +1,92 @@
+package org.keycloak.services.clientregistration;
+
+import org.jboss.resteasy.spi.UnauthorizedException;
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.managers.ClientManager;
+import org.keycloak.services.managers.RealmManager;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AdapterInstallationClientRegistrationProvider implements ClientRegistrationProvider {
+
+ private KeycloakSession session;
+ private EventBuilder event;
+ private ClientRegAuth auth;
+
+ public AdapterInstallationClientRegistrationProvider(KeycloakSession session) {
+ this.session = session;
+ }
+
+ @GET
+ @Path("{clientId}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response get(@PathParam("clientId") String clientId) {
+ event.event(EventType.CLIENT_INFO);
+
+ ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+
+ if (auth.isAuthenticated()) {
+ auth.requireView(client);
+ } else {
+ authenticateClient(client);
+ }
+
+ ClientManager clientManager = new ClientManager(new RealmManager(session));
+ Object rep = clientManager.toInstallationRepresentation(session.getContext().getRealm(), client, session.getContext().getAuthServerUrl());
+
+ event.client(client.getClientId()).success();
+ return Response.ok(rep).build();
+ }
+
+ @Override
+ public void setAuth(ClientRegAuth auth) {
+ this.auth = auth;
+ }
+
+ @Override
+ public void setEvent(EventBuilder event) {
+ this.event = event;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ private void authenticateClient(ClientModel client) {
+ if (client.isPublicClient()) {
+ return;
+ }
+
+ AuthenticationProcessor processor = AuthorizeClientUtil.getAuthenticationProcessor(session, event);
+
+ Response response = processor.authenticateClient();
+ if (response != null) {
+ event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
+ throw new ForbiddenException();
+ }
+
+ ClientModel authClient = processor.getClient();
+ if (client == null) {
+ event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
+ throw new ForbiddenException();
+ }
+
+ if (!authClient.getClientId().equals(client.getClientId())) {
+ event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
+ throw new ForbiddenException();
+ }
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java
new file mode 100644
index 0000000..cf13235
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java
@@ -0,0 +1,134 @@
+package org.keycloak.services.clientregistration;
+
+import org.jboss.resteasy.spi.UnauthorizedException;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.*;
+import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.managers.AppAuthManager;
+import org.keycloak.services.managers.AuthenticationManager;
+
+import javax.ws.rs.core.HttpHeaders;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientRegAuth {
+
+ private KeycloakSession session;
+ private EventBuilder event;
+
+ private String token;
+ private AccessToken.Access bearerRealmAccess;
+
+ private boolean authenticated = false;
+ private boolean registrationAccessToken = false;
+
+ public ClientRegAuth(KeycloakSession session, EventBuilder event) {
+ this.session = session;
+ this.event = event;
+
+ init();
+ }
+
+ private void init() {
+ RealmModel realm = session.getContext().getRealm();
+
+ String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+ if (authorizationHeader == null) {
+ return;
+ }
+
+ String[] split = authorizationHeader.split(" ");
+ if (!split[0].equalsIgnoreCase("bearer")) {
+ return;
+ }
+
+ 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);
+ authenticated = true;
+ }
+ }
+
+ public boolean isAuthenticated() {
+ return authenticated;
+ }
+
+ public boolean isRegistrationAccessToken() {
+ return registrationAccessToken;
+ }
+
+ public void requireCreate() {
+ if (!authenticated) {
+ event.error(Errors.NOT_ALLOWED);
+ throw new UnauthorizedException();
+ }
+
+ if (bearerRealmAccess != null) {
+ if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) {
+ return;
+ }
+ }
+
+ event.error(Errors.NOT_ALLOWED);
+ throw new ForbiddenException();
+ }
+
+ public void requireView(ClientModel client) {
+ if (!authenticated) {
+ event.error(Errors.NOT_ALLOWED);
+ throw new UnauthorizedException();
+ }
+
+ if (client == null) {
+ event.error(Errors.NOT_ALLOWED);
+ throw new ForbiddenException();
+ }
+
+ if (bearerRealmAccess != null) {
+ if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) {
+ return;
+ }
+ } else if (token != null) {
+ if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) {
+ return;
+ }
+ }
+
+ event.error(Errors.NOT_ALLOWED);
+ throw new ForbiddenException();
+ }
+
+ public void requireUpdate(ClientModel client) {
+ if (!authenticated) {
+ event.error(Errors.NOT_ALLOWED);
+ throw new UnauthorizedException();
+ }
+
+ if (client == null) {
+ event.error(Errors.NOT_ALLOWED);
+ throw new ForbiddenException();
+ }
+
+ if (bearerRealmAccess != null) {
+ if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) {
+ return;
+ }
+ } else if (token != null) {
+ if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) {
+ return;
+ }
+ }
+
+ event.error(Errors.NOT_ALLOWED);
+ throw new ForbiddenException();
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
index d1d6648..0c6bc4e 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
@@ -9,7 +9,7 @@ import org.keycloak.provider.Provider;
*/
public interface ClientRegistrationProvider extends Provider {
- void setRealm(RealmModel realm);
+ void setAuth(ClientRegAuth auth);
void setEvent(EventBuilder event);
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 8b215fa..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,37 +1,50 @@
package org.keycloak.services.clientregistration;
+import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.services.managers.AppAuthManager;
+import org.keycloak.services.ErrorResponseException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientRegistrationService {
- private RealmModel realm;
-
private EventBuilder event;
@Context
private KeycloakSession session;
- public ClientRegistrationService(RealmModel realm, EventBuilder event) {
- this.realm = realm;
+ public ClientRegistrationService(EventBuilder event) {
this.event = event;
}
@Path("{provider}")
public Object getProvider(@PathParam("provider") String providerId) {
+ checkSsl();
+
ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
- provider.setRealm(realm);
+
+ if (provider == null) {
+ throw new NotFoundException("Client registration provider not found");
+ }
+
provider.setEvent(event);
+ provider.setAuth(new ClientRegAuth(session, event));
return provider;
}
+ private void checkSsl() {
+ if (!session.getContext().getUri().getBaseUri().getScheme().equals("https")) {
+ if (session.getContext().getRealm().getSslRequired().isRequired(session.getContext().getConnection())) {
+ throw new ErrorResponseException("invalid_request", "HTTPS required", Response.Status.FORBIDDEN);
+ }
+ }
+ }
+
}
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 04cb46a..0fad9c2 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
@@ -1,22 +1,17 @@
package org.keycloak.services.clientregistration;
-import org.jboss.logging.Logger;
-import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
-import org.keycloak.models.*;
+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.protocol.oidc.utils.AuthorizeClientUtil;
-import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ErrorResponse;
-import org.keycloak.services.ForbiddenException;
-import org.keycloak.services.managers.AppAuthManager;
-import org.keycloak.services.managers.AuthenticationManager;
import javax.ws.rs.*;
-import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
@@ -26,31 +21,29 @@ import java.net.URI;
*/
public class DefaultClientRegistrationProvider implements ClientRegistrationProvider {
- private static final Logger logger = Logger.getLogger(DefaultClientRegistrationProvider.class);
-
private KeycloakSession session;
private EventBuilder event;
- private RealmModel realm;
+ private ClientRegAuth auth;
public DefaultClientRegistrationProvider(KeycloakSession session) {
this.session = session;
}
-
@POST
@Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
public Response create(ClientRepresentation client) {
event.event(EventType.CLIENT_REGISTER);
- authenticate(true, null);
+ auth.requireCreate();
try {
- ClientModel clientModel = RepresentationToModel.createClient(session, realm, client, true);
+ ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
+ KeycloakModelUtils.generateRegistrationAccessToken(clientModel);
+
client = ModelToRepresentation.toRepresentation(clientModel);
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
- logger.infov("Created client {0}", client.getClientId());
-
event.client(client.getClientId()).success();
return Response.created(uri).entity(client).build();
} catch (ModelDuplicateException e) {
@@ -64,10 +57,14 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
public Response get(@PathParam("clientId") String clientId) {
event.event(EventType.CLIENT_INFO);
- ClientModel client = authenticate(false, clientId);
- if (client == null) {
- return Response.status(Response.Status.NOT_FOUND).build();
+ 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();
}
@@ -77,13 +74,19 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
public Response update(@PathParam("clientId") String clientId, ClientRepresentation rep) {
event.event(EventType.CLIENT_UPDATE).client(clientId);
- ClientModel client = authenticate(false, clientId);
+ ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+ auth.requireUpdate(client);
+
RepresentationToModel.updateClient(rep, client);
- logger.infov("Updated client {0}", rep.getClientId());
+ if (auth.isRegistrationAccessToken()) {
+ KeycloakModelUtils.generateRegistrationAccessToken(client);
+ }
+
+ rep = ModelToRepresentation.toRepresentation(client);
- event.success();
- return Response.status(Response.Status.OK).build();
+ event.client(client.getClientId()).success();
+ return Response.ok(rep).build();
}
@DELETE
@@ -91,9 +94,11 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
public Response delete(@PathParam("clientId") String clientId) {
event.event(EventType.CLIENT_DELETE).client(clientId);
- ClientModel client = authenticate(false, clientId);
- if (realm.removeClient(client.getId())) {
- event.success();
+ ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+ auth.requireUpdate(client);
+
+ if (session.getContext().getRealm().removeClient(client.getId())) {
+ event.client(client.getClientId()).success();
return Response.ok().build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
@@ -101,55 +106,17 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
}
@Override
- public void close() {
-
- }
-
-
- private ClientModel authenticate(boolean create, String clientId) {
- String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
-
- boolean bearer = authorizationHeader != null && authorizationHeader.split(" ")[0].equalsIgnoreCase("Bearer");
-
- if (bearer) {
- AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm);
- AccessToken.Access realmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID);
- if (realmAccess != null) {
- if (realmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS)) {
- return create ? null : realm.getClientByClientId(clientId);
- }
-
- if (create && realmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) {
- return create ? null : realm.getClientByClientId(clientId);
- }
- }
- } else if (!create) {
- ClientModel client;
-
- try {
- AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
- client = clientAuth.getClient();
-
- if (client != null && !client.isPublicClient() && client.getClientId().equals(clientId)) {
- return client;
- }
- } catch (Throwable t) {
- }
- }
-
- event.error(Errors.NOT_ALLOWED);
-
- throw new ForbiddenException();
+ public void setAuth(ClientRegAuth auth) {
+ this.auth = auth;
}
@Override
- public void setRealm(RealmModel realm) {
-this.realm = realm;
+ public void setEvent(EventBuilder event) {
+ this.event = event;
}
@Override
- public void setEvent(EventBuilder event) {
- this.event = event;
+ public void close() {
}
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
new file mode 100644
index 0000000..1e0784c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
@@ -0,0 +1,38 @@
+package org.keycloak.services.clientregistration.oidc;
+
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DescriptionConverter {
+
+ public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) {
+ ClientRepresentation client = new ClientRepresentation();
+ client.setClientId(KeycloakModelUtils.generateId());
+ client.setName(clientOIDC.getClientName());
+ client.setRedirectUris(clientOIDC.getRedirectUris());
+ client.setBaseUrl(clientOIDC.getClientUri());
+ return client;
+ }
+
+ public static OIDCClientResponseRepresentation toExternalResponse(ClientRepresentation client) {
+ OIDCClientResponseRepresentation response = new OIDCClientResponseRepresentation();
+ response.setClientId(client.getClientId());
+
+ response.setClientName(client.getName());
+ response.setClientUri(client.getBaseUrl());
+
+ response.setClientSecret(client.getSecret());
+ response.setClientSecretExpiresAt(0);
+
+ response.setRedirectUris(client.getRedirectUris());
+
+ response.setRegistrationAccessToken(client.getRegistrationAccessToken());
+
+ return response;
+ }
+
+}
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
new file mode 100644
index 0000000..b27ddb0
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
@@ -0,0 +1,99 @@
+package org.keycloak.services.clientregistration.oidc;
+
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.Time;
+import org.keycloak.events.EventBuilder;
+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.ModelToRepresentation;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.protocol.oidc.OIDCClientDescriptionConverter;
+import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.clientregistration.ClientRegAuth;
+import org.keycloak.services.clientregistration.ClientRegistrationProvider;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OIDCClientRegistrationProvider implements ClientRegistrationProvider {
+
+ private static final Logger logger = Logger.getLogger(OIDCClientRegistrationProvider.class);
+
+ private KeycloakSession session;
+ private EventBuilder event;
+ private ClientRegAuth auth;
+
+ public OIDCClientRegistrationProvider(KeycloakSession session) {
+ this.session = session;
+ }
+
+// @POST
+// @Consumes(MediaType.APPLICATION_JSON)
+// @Produces(MediaType.APPLICATION_JSON)
+// public Response create(OIDCClientRepresentation clientOIDC) {
+// event.event(EventType.CLIENT_REGISTER);
+//
+// auth.requireCreate();
+//
+// ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
+//
+// try {
+// ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
+//
+// client = ModelToRepresentation.toRepresentation(clientModel);
+//
+// String registrationAccessToken = TokenGenerator.createRegistrationAccessToken();
+//
+// clientModel.setRegistrationSecret(registrationAccessToken);
+//
+// URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
+//
+// logger.infov("Created client {0}", client.getClientId());
+//
+// event.client(client.getClientId()).success();
+//
+// OIDCClientResponseRepresentation response = DescriptionConverter.toExternalResponse(client);
+//
+// response.setClientName(client.getName());
+// response.setClientUri(client.getBaseUrl());
+//
+// response.setClientSecret(client.getSecret());
+// response.setClientSecretExpiresAt(0);
+//
+// response.setRedirectUris(client.getRedirectUris());
+//
+// response.setRegistrationAccessToken(registrationAccessToken);
+// response.setRegistrationClientUri(uri.toString());
+//
+// return Response.created(uri).entity(response).build();
+// } catch (ModelDuplicateException e) {
+// return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
+// }
+// }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void setAuth(ClientRegAuth auth) {
+ this.auth = auth;
+ }
+
+ @Override
+ public void setEvent(EventBuilder event) {
+ this.event = event;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java
new file mode 100644
index 0000000..cd4eea4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java
@@ -0,0 +1,77 @@
+package org.keycloak.services.clientregistration.oidc;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OIDCClientResponseRepresentation extends OIDCClientRepresentation {
+
+ @JsonProperty("client_id")
+ private String clientId;
+
+ @JsonProperty("client_secret")
+ private String clientSecret;
+
+ @JsonProperty("client_id_issued_at")
+ private int clientIdIssuedAt;
+
+ @JsonProperty("client_secret_expires_at")
+ private int clientSecretExpiresAt;
+
+ @JsonProperty("registration_client_uri")
+ private String registrationClientUri;
+
+ @JsonProperty("registration_access_token")
+ private String registrationAccessToken;
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getClientSecret() {
+ return clientSecret;
+ }
+
+ public void setClientSecret(String clientSecret) {
+ this.clientSecret = clientSecret;
+ }
+
+ public int getClientIdIssuedAt() {
+ return clientIdIssuedAt;
+ }
+
+ public void setClientIdIssuedAt(int clientIdIssuedAt) {
+ this.clientIdIssuedAt = clientIdIssuedAt;
+ }
+
+ public int getClientSecretExpiresAt() {
+ return clientSecretExpiresAt;
+ }
+
+ public void setClientSecretExpiresAt(int clientSecretExpiresAt) {
+ this.clientSecretExpiresAt = clientSecretExpiresAt;
+ }
+
+ public String getRegistrationClientUri() {
+ return registrationClientUri;
+ }
+
+ public void setRegistrationClientUri(String registrationClientUri) {
+ this.registrationClientUri = registrationClientUri;
+ }
+
+ public String getRegistrationAccessToken() {
+ return registrationAccessToken;
+ }
+
+ public void setRegistrationAccessToken(String registrationAccessToken) {
+ this.registrationAccessToken = registrationAccessToken;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
index a78f99e..26aba4b 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
@@ -10,6 +10,7 @@ import org.keycloak.services.util.LocaleHelper;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
+import java.net.URI;
import java.util.Locale;
/**
@@ -30,6 +31,13 @@ public class DefaultKeycloakContext implements KeycloakContext {
}
@Override
+ public URI getAuthServerUrl() {
+ UriInfo uri = getUri();
+ KeycloakApplication keycloakApplication = getContextObject(KeycloakApplication.class);
+ return keycloakApplication.getBaseUri(uri);
+ }
+
+ @Override
public String getContextPath() {
KeycloakApplication app = getContextObject(KeycloakApplication.class);
return app.getContextPath();
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 688c242..d44a622 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -50,6 +50,7 @@ import org.keycloak.timer.TimerProvider;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import static java.lang.Boolean.TRUE;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.protocol.ProtocolMapperUtils;
@@ -221,9 +222,12 @@ public class RealmManager implements RealmImporter {
if(rep.getEnabledEventTypes() != null) {
realm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes()));
}
-
- realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
- realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
+ if(rep.isAdminEventsEnabled() != null) {
+ realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
+ }
+ if(rep.isAdminEventsDetailsEnabled() != null){
+ realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
+ }
}
private void setupMasterAdminManagement(RealmModel realm) {
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 2bebec3..af24034 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -61,6 +61,7 @@ import org.keycloak.services.messages.Messages;
import org.keycloak.services.util.ResolveRelative;
import org.keycloak.services.validation.Validation;
import org.keycloak.common.util.UriUtils;
+import org.keycloak.util.JsonSerialization;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -74,6 +75,8 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.Variant;
+
+import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.HashSet;
@@ -116,6 +119,9 @@ public class AccountService extends AbstractSecuredLocalService {
public static final String KEYCLOAK_STATE_CHECKER = "KEYCLOAK_STATE_CHECKER";
+ // Used when some other context (ie. IdentityBrokerService) wants to forward error to account management and display it here
+ public static final String ACCOUNT_MGMT_FORWARDED_ERROR_NOTE = "ACCOUNT_MGMT_FORWARDED_ERROR";
+
private final AppAuthManager authManager;
private EventBuilder event;
private AccountProvider account;
@@ -217,6 +223,17 @@ public class AccountService extends AbstractSecuredLocalService {
setReferrerOnPage();
+ String forwardedError = auth.getClientSession().getNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE);
+ if (forwardedError != null) {
+ try {
+ FormMessage errorMessage = JsonSerialization.readValue(forwardedError, FormMessage.class);
+ account.setError(errorMessage.getMessage(), errorMessage.getParameters());
+ auth.getClientSession().removeNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE);
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
return account.createResponse(page);
} else {
return login(path);
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..f729c7f 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
@@ -50,6 +50,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import static java.lang.Boolean.TRUE;
+
/**
* Base resource class for managing one particular client of a realm.
@@ -103,7 +105,7 @@ public class ClientResource {
auth.requireManage();
try {
- if (rep.isServiceAccountsEnabled() && !client.isServiceAccountsEnabled()) {
+ if (TRUE.equals(rep.isServiceAccountsEnabled()) && !client.isServiceAccountsEnabled()) {
new ClientManager(new RealmManager(session)).enableServiceAccount(client);;
}
@@ -215,6 +217,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/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
index ddb301d..a933712 100755
--- a/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
+++ b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
@@ -153,7 +153,7 @@ public class ClientsManagementService {
}
protected ClientModel authorizeClient() {
- ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
+ ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient();
if (client.isPublicClient()) {
Map<String, String> error = new HashMap<String, String>();
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index c8784bd..5912536 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -65,6 +65,7 @@ import org.keycloak.services.Urls;
import org.keycloak.services.validation.Validation;
import org.keycloak.social.SocialIdentityProvider;
import org.keycloak.common.util.ObjectUtil;
+import org.keycloak.util.JsonSerialization;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
@@ -74,6 +75,7 @@ import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
@@ -419,7 +421,6 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return finishBrokerAuthentication(context, federatedUser, clientSession, providerId);
}
} catch (Exception e) {
- // TODO?
return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
}
}
@@ -465,7 +466,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
this.event.event(EventType.FEDERATED_IDENTITY_LINK);
if (federatedUser != null) {
- return redirectToErrorPage(Messages.IDENTITY_PROVIDER_ALREADY_LINKED, context.getIdpConfig().getAlias());
+ return redirectToAccountErrorPage(clientSession, Messages.IDENTITY_PROVIDER_ALREADY_LINKED, context.getIdpConfig().getAlias());
}
UserModel authenticatedUser = clientSession.getUserSession().getUser();
@@ -475,12 +476,10 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
if (!authenticatedUser.isEnabled()) {
- fireErrorEvent(Errors.USER_DISABLED);
- return redirectToErrorPage(Messages.ACCOUNT_DISABLED);
+ return redirectToAccountErrorPage(clientSession, Messages.ACCOUNT_DISABLED);
}
if (!authenticatedUser.hasRole(this.realmModel.getClientByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).getRole(MANAGE_ACCOUNT))) {
- fireErrorEvent(Errors.NOT_ALLOWED);
return redirectToErrorPage(Messages.INSUFFICIENT_PERMISSION);
}
@@ -581,6 +580,20 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return ErrorPage.error(this.session, message, parameters);
}
+ private Response redirectToAccountErrorPage(ClientSessionModel clientSession, String message, Object ... parameters) {
+ fireErrorEvent(message);
+
+ FormMessage errorMessage = new FormMessage(message, parameters);
+ try {
+ String serializedError = JsonSerialization.writeValueAsString(errorMessage);
+ clientSession.setNote(AccountService.ACCOUNT_MGMT_FORWARDED_ERROR_NOTE, serializedError);
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+
+ return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
+ }
+
private Response redirectToLoginPage(Throwable t, ClientSessionCode clientCode) {
String message = t.getMessage();
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index f18f000..4b5e4f2 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -113,11 +113,11 @@ public class RealmsResource {
return service;
}
- @Path("{realm}/client-registration")
+ @Path("{realm}/clients")
public ClientRegistrationService getClientsService(final @PathParam("realm") String name) {
RealmModel realm = init(name);
EventBuilder event = new EventBuilder(realm, session, clientConnection);
- ClientRegistrationService service = new ClientRegistrationService(realm, event);
+ ClientRegistrationService service = new ClientRegistrationService(event);
ResteasyProviderFactory.getInstance().injectProperties(service);
return service;
}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
index 3e8773a..d9b8c41 100644
--- a/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
@@ -1 +1,3 @@
-org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory
\ No newline at end of file
+org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory
+org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory
+org.keycloak.services.clientregistration.AdapterInstallationClientRegistrationProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
index 904caf6..1d2e5b6 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -389,6 +389,34 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
this.updateProfilePage.assertCurrent();
}
+
+ // KEYCLOAK-1822
+ @Test
+ public void testAccountManagementLinkedIdentityAlreadyExists() {
+ // Login as "test-user" through broker
+ IdentityProviderModel identityProvider = getIdentityProviderModel();
+ assertSuccessfulAuthentication(identityProvider, "test-user", "test-user@localhost", false);
+
+ // Login as pedroigor to account management
+ accountFederatedIdentityPage.realm("realm-with-broker");
+ accountFederatedIdentityPage.open();
+ assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+ loginPage.login("pedroigor", "password");
+ assertTrue(accountFederatedIdentityPage.isCurrent());
+
+ // Try to link my "pedroigor" identity with "test-user" from brokered Keycloak.
+ accountFederatedIdentityPage.clickAddProvider(identityProvider.getAlias());
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+ this.loginPage.login("test-user", "password");
+ doAfterProviderAuthentication();
+
+ // Error is displayed in account management because federated identity"test-user" already linked to local account "test-user"
+ assertTrue(accountFederatedIdentityPage.isCurrent());
+ assertEquals("Federated identity returned by " + getProviderId() + " is already linked to another user.", accountFederatedIdentityPage.getError());
+ }
+
+
@Test(expected = NoSuchElementException.class)
public void testIdentityProviderNotAllowed() {
this.driver.navigate().to("http://localhost:8081/test-app/");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
index ac858dd..4e52db0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
@@ -127,7 +127,34 @@ public abstract class AbstractKerberosTest {
spnegoResponse.close();
events.clear();
- }
+ }
+
+ // KEYCLOAK-2102
+ @Test
+ public void spnegoCaseInsensitiveTest() throws Exception {
+ KeycloakRule keycloakRule = getKeycloakRule();
+ AssertEvents events = getAssertEvents();
+
+ Response spnegoResponse = spnegoLogin("MyDuke", "theduke");
+ Assert.assertEquals(302, spnegoResponse.getStatus());
+
+ events.expectLogin()
+ .client("kerberos-app")
+ .user(keycloakRule.getUser("test", "myduke").getId())
+ .detail(Details.REDIRECT_URI, KERBEROS_APP_URL)
+ //.detail(Details.AUTH_METHOD, "spnego")
+ .detail(Details.USERNAME, "myduke")
+ .assertEvent();
+
+ String location = spnegoResponse.getLocation().toString();
+ driver.navigate().to(location);
+
+ String pageSource = driver.getPageSource();
+ Assert.assertTrue(pageSource.contains("Kerberos Test") && pageSource.contains("Kerberos servlet secured content"));
+
+ spnegoResponse.close();
+ events.clear();
+ }
@Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
index 4e771df..8c84581 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
@@ -4,6 +4,7 @@ import org.junit.Assert;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
+import org.keycloak.Config;
import org.keycloak.models.ClientModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.ModelDuplicateException;
@@ -146,11 +147,11 @@ public class AdapterTest extends AbstractModelTest {
Assert.assertTrue(userProvider.validCredentials(realmModel, user, UserCredentialModel.password("geheim")));
List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
Assert.assertEquals(creds.get(0).getHashIterations(), 1);
- realmModel.setPasswordPolicy( new PasswordPolicy("hashIterations(200)"));
+ realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(200)"));
Assert.assertTrue(userProvider.validCredentials(realmModel, user, UserCredentialModel.password("geheim")));
creds = user.getCredentialsDirectly();
Assert.assertEquals(creds.get(0).getHashIterations(), 200);
- realmModel.setPasswordPolicy( new PasswordPolicy("hashIterations(1)"));
+ realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(1)"));
}
@Test
@@ -797,6 +798,22 @@ public class AdapterTest extends AbstractModelTest {
}
+ // KEYCLOAK-2026
+ @Test
+ public void testMasterAdminClient() {
+ realmModel = realmManager.createRealm("foo-realm");
+ ClientModel masterAdminClient = realmModel.getMasterAdminClient();
+ Assert.assertEquals(Config.getAdminRealm(), masterAdminClient.getRealm().getId());
+
+ commit();
+
+ realmModel = realmManager.getRealmByName("foo-realm");
+ masterAdminClient = realmModel.getMasterAdminClient();
+ Assert.assertEquals(Config.getAdminRealm(), masterAdminClient.getRealm().getId());
+
+ realmManager.removeRealm(realmModel);
+ }
+
private KeyPair generateKeypair() throws NoSuchAlgorithmException {
return KeyPairGenerator.getInstance("RSA").generateKeyPair();
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java
index 4c02798..54a5cbb 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java
@@ -5,12 +5,17 @@ import javax.ws.rs.core.UriBuilder;
import org.keycloak.services.Urls;
import org.keycloak.testsuite.Constants;
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 class AccountFederatedIdentityPage extends AbstractAccountPage {
+ @FindBy(className = "alert-error")
+ private WebElement errorMessage;
+
public AccountFederatedIdentityPage() {};
private String realmName = "test";
@@ -39,4 +44,8 @@ public class AccountFederatedIdentityPage extends AbstractAccountPage {
public void clickRemoveProvider(String providerId) {
driver.findElement(By.id("remove-" + providerId)).click();
}
+
+ public String getError() {
+ return errorMessage.getText();
+ }
}
diff --git a/testsuite/integration/src/test/resources/kerberos/users-kerberos.ldif b/testsuite/integration/src/test/resources/kerberos/users-kerberos.ldif
index fd9936c..9d55092 100644
--- a/testsuite/integration/src/test/resources/kerberos/users-kerberos.ldif
+++ b/testsuite/integration/src/test/resources/kerberos/users-kerberos.ldif
@@ -76,6 +76,20 @@ userPassword: theduke
krb5PrincipalName: jduke@KEYCLOAK.ORG
krb5KeyVersionNumber: 0
+dn: uid=MyDuke,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: My
+sn: Duke
+mail: MyDuke@keycloak.org
+uid: MyDuke
+userPassword: theduke
+krb5PrincipalName: MyDuke@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
dn: uid=gsstestserver,ou=People,dc=keycloak,dc=org
objectClass: top
objectClass: person
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
new file mode 100644
index 0000000..8b8dfad
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
@@ -0,0 +1,96 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.After;
+import org.junit.Before;
+import org.keycloak.client.registration.ClientRegistration;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.Constants;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTest {
+
+ static final String REALM_NAME = "test";
+
+ ClientRegistration reg;
+
+ @Before
+ public void before() throws Exception {
+ reg = new ClientRegistration(testContext.getAuthServerContextRoot() + "/auth", "test");
+ }
+
+ @After
+ public void after() throws Exception {
+ reg.close();
+ }
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ RealmRepresentation rep = new RealmRepresentation();
+ rep.setEnabled(true);
+ rep.setRealm(REALM_NAME);
+ rep.setUsers(new LinkedList<UserRepresentation>());
+
+ LinkedList<CredentialRepresentation> credentials = new LinkedList<>();
+ CredentialRepresentation password = new CredentialRepresentation();
+ password.setType(CredentialRepresentation.PASSWORD);
+ password.setValue("password");
+ credentials.add(password);
+
+ UserRepresentation user = new UserRepresentation();
+ user.setEnabled(true);
+ user.setUsername("manage-clients");
+ user.setCredentials(credentials);
+ user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.MANAGE_CLIENTS)));
+
+ rep.getUsers().add(user);
+
+ UserRepresentation user2 = new UserRepresentation();
+ user2.setEnabled(true);
+ user2.setUsername("create-clients");
+ user2.setCredentials(credentials);
+ user2.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.CREATE_CLIENT)));
+
+ rep.getUsers().add(user2);
+
+ UserRepresentation user3 = new UserRepresentation();
+ user3.setEnabled(true);
+ user3.setUsername("no-access");
+ user3.setCredentials(credentials);
+
+ rep.getUsers().add(user3);
+
+ testRealms.add(rep);
+ }
+
+ public ClientRepresentation createClient(ClientRepresentation client) {
+ Response response = adminClient.realm(REALM_NAME).clients().create(client);
+ String id = response.getLocation().toString();
+ id = id.substring(id.lastIndexOf('/') + 1);
+ client.setId(id);
+ response.close();
+ return client;
+ }
+
+ public ClientRepresentation getClient(String clientId) {
+ try {
+ return adminClient.realm(REALM_NAME).clients().get(clientId).toRepresentation();
+ } catch (NotFoundException e) {
+ return null;
+ }
+ }
+
+}
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
new file mode 100644
index 0000000..bf98364
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
@@ -0,0 +1,125 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.Before;
+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;
+
+import javax.ws.rs.core.Response;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AdapterInstallationConfigTest extends AbstractClientRegistrationTest {
+
+ 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");
+ client.setSecret("RegistrationAccessTokenTestClientSecret");
+ client.setPublicClient(false);
+ client.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
+ client.setRootUrl("http://root");
+ client = createClient(client);
+
+ client2 = new ClientRepresentation();
+ client2.setEnabled(true);
+ client2.setClientId("RegistrationAccessTokenTest2");
+ client2.setSecret("RegistrationAccessTokenTestClientSecret");
+ client2.setPublicClient(false);
+ client2.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
+ client2.setRootUrl("http://root");
+ client2 = createClient(client2);
+
+ clientPublic = new ClientRepresentation();
+ clientPublic.setEnabled(true);
+ clientPublic.setClientId("RegistrationAccessTokenTestPublic");
+ clientPublic.setPublicClient(true);
+ clientPublic.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessTokenPublic");
+ clientPublic.setRootUrl("http://root");
+ clientPublic = createClient(clientPublic);
+ }
+
+ @Test
+ public void getConfigWithRegistrationAccessToken() throws ClientRegistrationException {
+ reg.auth(Auth.token(client.getRegistrationAccessToken()));
+
+ AdapterConfig config = reg.getAdapterConfig(client.getClientId());
+ assertNotNull(config);
+ }
+
+ @Test
+ public void getConfig() throws ClientRegistrationException {
+ reg.auth(Auth.client(client.getClientId(), client.getSecret()));
+
+ 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
+ public void getConfigMissingSecret() throws ClientRegistrationException {
+ reg.auth(null);
+
+ try {
+ reg.getAdapterConfig(client.getClientId());
+ fail("Expected 403");
+ } catch (ClientRegistrationException e) {
+ assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ }
+ }
+
+ @Test
+ public void getConfigWrongClient() throws ClientRegistrationException {
+ reg.auth(Auth.client(client.getClientId(), client.getSecret()));
+
+ try {
+ reg.getAdapterConfig(client2.getClientId());
+ fail("Expected 403");
+ } catch (ClientRegistrationException e) {
+ assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ }
+ }
+
+ @Test
+ public void getConfigPublicClient() throws ClientRegistrationException {
+ reg.auth(null);
+
+ 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/ClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
index 1d4b011..8b84b10 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
@@ -1,302 +1,211 @@
package org.keycloak.testsuite.client;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
-import org.keycloak.client.registration.ClientRegistration;
+import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.client.registration.HttpErrorException;
-import org.keycloak.models.AdminRoles;
-import org.keycloak.models.Constants;
-import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.testsuite.AbstractKeycloakTest;
+import javax.ws.rs.NotFoundException;
import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
import static org.junit.Assert.*;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class ClientRegistrationTest extends AbstractKeycloakTest {
+public class ClientRegistrationTest extends AbstractClientRegistrationTest {
- private static final String REALM_NAME = "test";
private static final String CLIENT_ID = "test-client";
private static final String CLIENT_SECRET = "test-client-secret";
- private ClientRegistration clientRegistrationAsAdmin;
- private ClientRegistration clientRegistrationAsClient;
-
- @Before
- public void before() throws ClientRegistrationException {
- clientRegistrationAsAdmin = clientBuilder().auth(getToken("manage-clients", "password")).build();
- clientRegistrationAsClient = clientBuilder().auth(CLIENT_ID, CLIENT_SECRET).build();
- }
-
- @After
- public void after() throws ClientRegistrationException {
- clientRegistrationAsAdmin.close();
- clientRegistrationAsClient.close();
- }
-
- @Override
- public void addTestRealms(List<RealmRepresentation> testRealms) {
- RealmRepresentation rep = new RealmRepresentation();
- rep.setEnabled(true);
- rep.setRealm(REALM_NAME);
- rep.setUsers(new LinkedList<UserRepresentation>());
-
- LinkedList<CredentialRepresentation> credentials = new LinkedList<>();
- CredentialRepresentation password = new CredentialRepresentation();
- password.setType(CredentialRepresentation.PASSWORD);
- password.setValue("password");
- credentials.add(password);
-
- UserRepresentation user = new UserRepresentation();
- user.setEnabled(true);
- user.setUsername("manage-clients");
- user.setCredentials(credentials);
- user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.MANAGE_CLIENTS)));
-
- rep.getUsers().add(user);
-
- UserRepresentation user2 = new UserRepresentation();
- user2.setEnabled(true);
- user2.setUsername("create-clients");
- user2.setCredentials(credentials);
- user2.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.CREATE_CLIENT)));
-
- rep.getUsers().add(user2);
-
- UserRepresentation user3 = new UserRepresentation();
- user3.setEnabled(true);
- user3.setUsername("no-access");
- user3.setCredentials(credentials);
-
- rep.getUsers().add(user3);
-
- testRealms.add(rep);
- }
-
- private void registerClient(ClientRegistration clientRegistration) throws ClientRegistrationException {
+ private ClientRepresentation registerClient() throws ClientRegistrationException {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(CLIENT_ID);
client.setSecret(CLIENT_SECRET);
- ClientRepresentation createdClient = clientRegistration.create(client);
+ ClientRepresentation createdClient = reg.create(client);
assertEquals(CLIENT_ID, createdClient.getClientId());
client = adminClient.realm(REALM_NAME).clients().get(createdClient.getId()).toRepresentation();
assertEquals(CLIENT_ID, client.getClientId());
- AccessTokenResponse token2 = oauthClient.getToken(REALM_NAME, CLIENT_ID, CLIENT_SECRET, "manage-clients", "password");
- assertNotNull(token2.getToken());
+ return client;
}
@Test
public void registerClientAsAdmin() throws ClientRegistrationException {
- registerClient(clientRegistrationAsAdmin);
+ authManageClients();
+ registerClient();
}
@Test
public void registerClientAsAdminWithCreateOnly() throws ClientRegistrationException {
- ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
- try {
- registerClient(clientRegistration);
- } finally {
- clientRegistration.close();
- }
+ authCreateClients();
+ registerClient();
}
@Test
public void registerClientAsAdminWithNoAccess() throws ClientRegistrationException {
- ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
+ authNoAccess();
try {
- registerClient(clientRegistration);
+ registerClient();
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
- } finally {
- clientRegistration.close();
}
}
@Test
+ public void getClientAsAdmin() throws ClientRegistrationException {
+ registerClientAsAdmin();
+ ClientRepresentation rep = reg.get(CLIENT_ID);
+ assertNotNull(rep);
+ }
+
+ @Test
public void getClientAsAdminWithCreateOnly() throws ClientRegistrationException {
- registerClient(clientRegistrationAsAdmin);
- ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
+ registerClientAsAdmin();
+ authCreateClients();
try {
- clientRegistration.get(CLIENT_ID);
+ reg.get(CLIENT_ID);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
- } finally {
- clientRegistration.close();
}
}
@Test
- public void wrongClient() throws ClientRegistrationException {
- registerClient(clientRegistrationAsAdmin);
-
- ClientRepresentation client = new ClientRepresentation();
- client.setClientId("test-client-2");
- client.setSecret("test-client-2-secret");
-
- clientRegistrationAsAdmin.create(client);
-
- ClientRegistration clientRegistration = clientBuilder().auth("test-client-2", "test-client-2-secret").build();
-
- client = clientRegistration.get("test-client-2");
- assertNotNull(client);
- assertEquals("test-client-2", client.getClientId());
-
+ public void getClientAsAdminWithNoAccess() throws ClientRegistrationException {
+ registerClientAsAdmin();
+ authNoAccess();
try {
- try {
- clientRegistration.get(CLIENT_ID);
- fail("Expected 403");
- } catch (ClientRegistrationException e) {
- assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
- }
-
- client = clientRegistrationAsAdmin.get(CLIENT_ID);
- try {
- clientRegistration.update(client);
- fail("Expected 403");
- } catch (ClientRegistrationException e) {
- assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
- }
-
- try {
- clientRegistration.delete(CLIENT_ID);
- fail("Expected 403");
- } catch (ClientRegistrationException e) {
- assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
- }
- }
- finally {
- clientRegistration.close();
+ reg.get(CLIENT_ID);
+ fail("Expected 403");
+ } catch (ClientRegistrationException e) {
+ assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
@Test
- public void getClientAsAdminWithNoAccess() throws ClientRegistrationException {
- registerClient(clientRegistrationAsAdmin);
- ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
+ public void getClientNotFound() throws ClientRegistrationException {
+ authManageClients();
try {
- clientRegistration.get(CLIENT_ID);
+ reg.get(CLIENT_ID);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
- } finally {
- clientRegistration.close();
}
}
- private void updateClient(ClientRegistration clientRegistration) throws ClientRegistrationException {
- ClientRepresentation client = clientRegistration.get(CLIENT_ID);
+ private void updateClient() throws ClientRegistrationException {
+ ClientRepresentation client = reg.get(CLIENT_ID);
client.setRedirectUris(Collections.singletonList("http://localhost:8080/app"));
- clientRegistration.update(client);
+ reg.update(client);
- ClientRepresentation updatedClient = clientRegistration.get(CLIENT_ID);
+ ClientRepresentation updatedClient = reg.get(CLIENT_ID);
assertEquals(1, updatedClient.getRedirectUris().size());
assertEquals("http://localhost:8080/app", updatedClient.getRedirectUris().get(0));
}
+
@Test
public void updateClientAsAdmin() throws ClientRegistrationException {
- registerClient(clientRegistrationAsAdmin);
- updateClient(clientRegistrationAsAdmin);
+ registerClientAsAdmin();
+
+ authManageClients();
+ updateClient();
}
@Test
public void updateClientAsAdminWithCreateOnly() throws ClientRegistrationException {
- ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
+ authCreateClients();
try {
- updateClient(clientRegistration);
+ updateClient();
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
- } finally {
- clientRegistration.close();
}
}
@Test
public void updateClientAsAdminWithNoAccess() throws ClientRegistrationException {
- ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
+ authNoAccess();
try {
- updateClient(clientRegistration);
+ updateClient();
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
- } finally {
- clientRegistration.close();
}
}
@Test
- public void updateClientAsClient() throws ClientRegistrationException {
- registerClient(clientRegistrationAsAdmin);
- updateClient(clientRegistrationAsClient);
+ public void updateClientNotFound() throws ClientRegistrationException {
+ authManageClients();
+ try {
+ updateClient();
+ fail("Expected 403");
+ } catch (ClientRegistrationException e) {
+ assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ }
}
- private void deleteClient(ClientRegistration clientRegistration) throws ClientRegistrationException {
- clientRegistration.delete(CLIENT_ID);
-
- // Can't authenticate as client after client is deleted
- ClientRepresentation client = clientRegistrationAsAdmin.get(CLIENT_ID);
- assertNull(client);
+ private void deleteClient(ClientRepresentation client) throws ClientRegistrationException {
+ reg.delete(CLIENT_ID);
+ try {
+ adminClient.realm("test").clients().get(client.getId()).toRepresentation();
+ fail("Expected 403");
+ } catch (NotFoundException e) {
+ }
}
@Test
public void deleteClientAsAdmin() throws ClientRegistrationException {
- registerClient(clientRegistrationAsAdmin);
- deleteClient(clientRegistrationAsAdmin);
+ authCreateClients();
+ ClientRepresentation client = registerClient();
+
+ authManageClients();
+ deleteClient(client);
}
@Test
public void deleteClientAsAdminWithCreateOnly() throws ClientRegistrationException {
- ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
+ authManageClients();
+ ClientRepresentation client = registerClient();
try {
- deleteClient(clientRegistration);
+ authCreateClients();
+ deleteClient(client);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
- } finally {
- clientRegistration.close();
}
}
@Test
public void deleteClientAsAdminWithNoAccess() throws ClientRegistrationException {
- ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
+ authManageClients();
+ ClientRepresentation client = registerClient();
try {
- deleteClient(clientRegistration);
+ authNoAccess();
+ deleteClient(client);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
- } finally {
- clientRegistration.close();
}
}
- @Test
- public void deleteClientAsClient() throws ClientRegistrationException {
- registerClient(clientRegistrationAsAdmin);
- deleteClient(clientRegistrationAsClient);
+ private void authCreateClients() {
+ reg.auth(Auth.token(getToken("create-clients", "password")));
+ }
+
+ private void authManageClients() {
+ reg.auth(Auth.token(getToken("manage-clients", "password")));
}
- private ClientRegistration.ClientRegistrationBuilder clientBuilder() {
- return ClientRegistration.create().realm("test").authServerUrl(testContext.getAuthServerContextRoot() + "/auth");
+ private void authNoAccess() {
+ reg.auth(Auth.token(getToken("no-access", "password")));
}
private String getToken(String username, String password) {
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
new file mode 100644
index 0000000..be880bf
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
@@ -0,0 +1,122 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.Before;
+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.representations.idm.ClientRepresentation;
+
+import javax.ws.rs.core.Response;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest {
+
+ private ClientRepresentation client;
+
+ @Before
+ public void before() throws Exception {
+ super.before();
+
+ client = new ClientRepresentation();
+ client.setEnabled(true);
+ client.setClientId("RegistrationAccessTokenTest");
+ client.setSecret("RegistrationAccessTokenTestClientSecret");
+ client.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
+ client.setRootUrl("http://root");
+ client = createClient(client);
+
+ 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
+ public void getClientWithBadRegistrationToken() throws ClientRegistrationException {
+ reg.auth(Auth.token("invalid"));
+ try {
+ reg.get(client.getClientId());
+ fail("Expected 403");
+ } catch (ClientRegistrationException e) {
+ assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ }
+ }
+
+ @Test
+ public void updateClientWithRegistrationToken() throws ClientRegistrationException {
+ client.setRootUrl("http://newroot");
+ 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
+ public void updateClientWithBadRegistrationToken() throws ClientRegistrationException {
+ client.setRootUrl("http://newroot");
+
+ reg.auth(Auth.token("invalid"));
+ try {
+ reg.update(client);
+ fail("Expected 403");
+ } catch (ClientRegistrationException e) {
+ assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ }
+
+ assertEquals("http://root", getClient(client.getId()).getRootUrl());
+ }
+
+ @Test
+ public void deleteClientWithRegistrationToken() throws ClientRegistrationException {
+ reg.delete(client);
+ assertNull(getClient(client.getId()));
+ }
+
+ @Test
+ public void deleteClientWithBadRegistrationToken() throws ClientRegistrationException {
+ reg.auth(Auth.token("invalid"));
+ try {
+ reg.delete(client);
+ fail("Expected 403");
+ } catch (ClientRegistrationException e) {
+ assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ }
+ assertNotNull(getClient(client.getId()));
+ }
+
+}