diff --git a/.travis.yml b/.travis.yml index 8146d6e1ed..5dee839fcb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,14 +3,8 @@ language: java jdk: - oraclejdk8 -cache: - directories: - - $HOME/.m2 - -before_cache: - - rm -rf $HOME/.m2/repository/org/keycloak - -install: mvn install -Pdistribution -DskipTests=true -B -V +install: + - travis_wait mvn install -Pdistribution -DskipTests=true -B -V -q script: - mvn test -B diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java b/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java index 31ad4e882f..c3dd313921 100644 --- a/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java +++ b/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java @@ -5,6 +5,7 @@ import org.apache.http.HttpRequest; import org.keycloak.common.util.Base64; import org.keycloak.representations.idm.ClientInitialAccessPresentation; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; /** * @author Stian Thorgersen @@ -21,11 +22,14 @@ public abstract class Auth { return new BearerTokenAuth(initialAccess.getToken()); } - public static Auth token(ClientRepresentation client) { return new BearerTokenAuth(client.getRegistrationAccessToken()); } + public static Auth token(OIDCClientRepresentation client) { + return new BearerTokenAuth(client.getRegistrationAccessToken()); + } + public static Auth client(String clientId, String clientSecret) { return new BasicAuth(clientId, clientSecret); } diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java index 2b1a991c6f..f2215f19b9 100644 --- a/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java +++ b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java @@ -3,8 +3,10 @@ package org.keycloak.client.registration; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClients; import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.annotate.JsonSerialize; import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.util.JsonSerialization; import java.io.IOException; @@ -18,10 +20,17 @@ public class ClientRegistration { public static final ObjectMapper outputMapper = new ObjectMapper(); static { outputMapper.getSerializationConfig().addMixInAnnotations(ClientRepresentation.class, ClientRepresentationMixIn.class); + outputMapper.getSerializationConfig().addMixInAnnotations(OIDCClientRepresentation.class, OIDCClientRepresentationMixIn.class); + outputMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL); } + private final String JSON = "application/json"; + private final String XML = "application/xml"; + private final String DEFAULT = "default"; private final String INSTALLATION = "install"; + private final String OIDC = "openid-connect"; + private final String SAML = "saml2-entity-descriptor"; private HttpUtil httpUtil; @@ -47,23 +56,23 @@ public class ClientRegistration { public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException { String content = serialize(client); - InputStream resultStream = httpUtil.doPost(content, DEFAULT); + InputStream resultStream = httpUtil.doPost(content, JSON, JSON, DEFAULT); return deserialize(resultStream, ClientRepresentation.class); } public ClientRepresentation get(String clientId) throws ClientRegistrationException { - InputStream resultStream = httpUtil.doGet(DEFAULT, clientId); + InputStream resultStream = httpUtil.doGet(JSON, DEFAULT, clientId); return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null; } public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException { - InputStream resultStream = httpUtil.doGet(INSTALLATION, clientId); + InputStream resultStream = httpUtil.doGet(JSON, INSTALLATION, clientId); return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null; } public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException { String content = serialize(client); - InputStream resultStream = httpUtil.doPut(content, DEFAULT, client.getClientId()); + InputStream resultStream = httpUtil.doPut(content, JSON, JSON, DEFAULT, client.getClientId()); return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null; } @@ -75,10 +84,17 @@ public class ClientRegistration { httpUtil.doDelete(DEFAULT, clientId); } - public static String serialize(ClientRepresentation client) throws ClientRegistrationException { - try { + public OIDCClientRegistration oidc() { + return new OIDCClientRegistration(); + } - return outputMapper.writeValueAsString(client); + public SAMLClientRegistration saml() { + return new SAMLClientRegistration(); + } + + public static String serialize(Object obj) throws ClientRegistrationException { + try { + return outputMapper.writeValueAsString(obj); } catch (IOException e) { throw new ClientRegistrationException("Failed to write json object", e); } @@ -92,6 +108,44 @@ public class ClientRegistration { } } + public class OIDCClientRegistration { + + public OIDCClientRepresentation create(OIDCClientRepresentation client) throws ClientRegistrationException { + String content = serialize(client); + InputStream resultStream = httpUtil.doPost(content, JSON, JSON, OIDC); + return deserialize(resultStream, OIDCClientRepresentation.class); + } + + public OIDCClientRepresentation get(String clientId) throws ClientRegistrationException { + InputStream resultStream = httpUtil.doGet(JSON, OIDC, clientId); + return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null; + } + + public OIDCClientRepresentation update(OIDCClientRepresentation client) throws ClientRegistrationException { + String content = serialize(client); + InputStream resultStream = httpUtil.doPut(content, JSON, JSON, OIDC, client.getClientId()); + return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null; + } + + public void delete(OIDCClientRepresentation client) throws ClientRegistrationException { + delete(client.getClientId()); + } + + public void delete(String clientId) throws ClientRegistrationException { + httpUtil.doDelete(OIDC, clientId); + } + + } + + public class SAMLClientRegistration { + + public ClientRepresentation create(String entityDescriptor) throws ClientRegistrationException { + InputStream resultStream = httpUtil.doPost(entityDescriptor, XML, JSON, SAML); + return deserialize(resultStream, ClientRepresentation.class); + } + + } + public static class ClientRegistrationBuilder { private String url; diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java b/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java index 6444749782..4d44710f5b 100644 --- a/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java +++ b/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java @@ -33,12 +33,12 @@ class HttpUtil { this.auth = auth; } - InputStream doPost(String content, String... path) throws ClientRegistrationException { + InputStream doPost(String content, String contentType, String acceptType, 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.setHeader(HttpHeaders.CONTENT_TYPE, contentType); + request.setHeader(HttpHeaders.ACCEPT, acceptType); request.setEntity(new StringEntity(content)); addAuth(request); @@ -60,11 +60,11 @@ class HttpUtil { } } - InputStream doGet(String... path) throws ClientRegistrationException { + InputStream doGet(String acceptType, String... path) throws ClientRegistrationException { try { HttpGet request = new HttpGet(getUrl(baseUri, path)); - request.setHeader(HttpHeaders.ACCEPT, "application/json"); + request.setHeader(HttpHeaders.ACCEPT, acceptType); addAuth(request); @@ -90,12 +90,12 @@ class HttpUtil { } } - InputStream doPut(String content, String... path) throws ClientRegistrationException { + InputStream doPut(String content, String contentType, String acceptType, 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.setHeader(HttpHeaders.CONTENT_TYPE, contentType); + request.setHeader(HttpHeaders.ACCEPT, acceptType); request.setEntity(new StringEntity(content)); addAuth(request); @@ -134,7 +134,7 @@ class HttpUtil { response.getEntity().getContent().close(); } - if (response.getStatusLine().getStatusCode() != 200) { + if (response.getStatusLine().getStatusCode() != 204) { throw new HttpErrorException(response.getStatusLine()); } } catch (IOException e) { diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java b/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java new file mode 100644 index 0000000000..b0dfed637e --- /dev/null +++ b/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java @@ -0,0 +1,22 @@ +package org.keycloak.client.registration; + +import org.codehaus.jackson.annotate.JsonIgnore; + +/** + * @author Stian Thorgersen + */ +abstract class OIDCClientRepresentationMixIn { + + @JsonIgnore + private Integer client_id_issued_at; + + @JsonIgnore + private Integer client_secret_expires_at; + + @JsonIgnore + private String registration_client_uri; + + @JsonIgnore + private String registration_access_token; + +} diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java index f0b0857760..986faac648 100644 --- a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java +++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java @@ -62,6 +62,8 @@ public class ClientRegistrationCLI { CommandContainer command = registry.getCommand(args[0], null); ParserGenerator.parseAndPopulate(command, args[0], Arrays.copyOfRange(args, 1, args.length)); }*/ + + //commandInvocation.getCommandRegistry().getAllCommandNames() } } 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 db8b8d0091..c81b062099 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 @@ -73,5 +73,7 @@ + + \ No newline at end of file diff --git a/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java new file mode 100644 index 0000000000..1ff32fc096 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java @@ -0,0 +1,223 @@ +package org.keycloak.representations.oidc; + +import org.codehaus.jackson.annotate.JsonAutoDetect; + +import java.util.List; + +/** + * @author Stian Thorgersen + */ +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) +public class OIDCClientRepresentation { + + private List redirect_uris; + + private String token_endpoint_auth_method; + + private String grant_types; + + private String response_types; + + private String client_id; + + private String client_secret; + + private String client_name; + + private String client_uri; + + private String logo_uri; + + private String scope; + + private String contacts; + + private String tos_uri; + + private String policy_uri; + + private String jwks_uri; + + private String jwks; + + private String software_id; + + private String software_version; + + private Integer client_id_issued_at; + + private Integer client_secret_expires_at; + + private String registration_client_uri; + + private String registration_access_token; + + public List getRedirectUris() { + return redirect_uris; + } + + public void setRedirectUris(List redirectUris) { + this.redirect_uris = redirectUris; + } + + public String getTokenEndpointAuthMethod() { + return token_endpoint_auth_method; + } + + public void setTokenEndpointAuthMethod(String token_endpoint_auth_method) { + this.token_endpoint_auth_method = token_endpoint_auth_method; + } + + public String getGrantTypes() { + return grant_types; + } + + public void setGrantTypes(String grantTypes) { + this.grant_types = grantTypes; + } + + public String getResponseTypes() { + return response_types; + } + + public void setResponseTypes(String responseTypes) { + this.response_types = responseTypes; + } + + public String getClientId() { + return client_id; + } + + public void setClientId(String clientId) { + this.client_id = clientId; + } + + public String getClientSecret() { + return client_secret; + } + + public void setClientSecret(String clientSecret) { + this.client_secret = clientSecret; + } + + public String getClientName() { + return client_name; + } + + public void setClientName(String client_name) { + this.client_name = client_name; + } + + public String getClientUri() { + return client_uri; + } + + public void setClientUri(String client_uri) { + this.client_uri = client_uri; + } + + public String getLogoUri() { + return logo_uri; + } + + public void setLogoUri(String logo_uri) { + this.logo_uri = logo_uri; + } + + 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 getTosUri() { + return tos_uri; + } + + public void setTosUri(String tos_uri) { + this.tos_uri = tos_uri; + } + + public String getPolicyUri() { + return policy_uri; + } + + public void setPolicyUri(String policy_uri) { + this.policy_uri = policy_uri; + } + + public String getJwksUri() { + return jwks_uri; + } + + public void setJwksUri(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 software_id; + } + + public void setSoftwareId(String softwareId) { + this.software_id = softwareId; + } + + public String getSoftwareVersion() { + return software_version; + } + + public void setSoftwareVersion(String softwareVersion) { + this.software_version = softwareVersion; + } + + public Integer getClientIdIssuedAt() { + return client_id_issued_at; + } + + public void setClientIdIssuedAt(Integer clientIdIssuedAt) { + this.client_id_issued_at = clientIdIssuedAt; + } + + public Integer getClientSecretExpiresAt() { + return client_secret_expires_at; + } + + public void setClientSecretExpiresAt(Integer client_secret_expires_at) { + this.client_secret_expires_at = client_secret_expires_at; + } + + public String getRegistrationClientUri() { + return registration_client_uri; + } + + public void setRegistrationClientUri(String registrationClientUri) { + this.registration_client_uri = registrationClientUri; + } + + public String getRegistrationAccessToken() { + return registration_access_token; + } + + public void setRegistrationAccessToken(String registrationAccessToken) { + this.registration_access_token = registrationAccessToken; + } + +} diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml index 0070cc004b..fab7119e4f 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml @@ -10,15 +10,15 @@ The Client Registration Service provides built-in support for Keycloak Client Representations, OpenID Connect Client Meta Data and SAML Entity Descriptors. It's also possible to plugin custom client registration providers - if required. The Client Registration Service endpoint is <KEYCLOAK URL>/clients/<provider>. + if required. The Client Registration Service endpoint is <KEYCLOAK URL>/realms/<realm>/clients/<provider>. The built-in supported providers are: default Keycloak Representations install Keycloak Adapter Configuration - - + openid-connect OpenID Connect Dynamic Client Registration + saml2-entity-descriptor SAML Entity Descriptors The following sections will describe how to use the different providers. @@ -106,30 +106,30 @@ Authorization: bearer eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJmMjJmNzQyYy04ZjNlLTQ2M.... To create a client create a Client Representation (JSON) then do a HTTP POST to: - <KEYCLOAK URL>/clients/<provider>/default. It will return a Client Representation + <KEYCLOAK URL>/realms/<realm>/clients/<provider>/default. It will return a Client Representation that also includes the registration access token. You should save the registration access token somewhere if you want to retrieve the config, update or delete the client later. To retrieve the Client Representation then do a HTTP GET to: - <KEYCLOAK URL>/clients/<provider>/default/<client id>. It will also + <KEYCLOAK URL>/realms/<realm>clients/<provider>/default/<client id>. It will also return a new registration access token. To update the Client Representation then do a HTTP PUT to with the updated Client Representation to: - <KEYCLOAK URL>/clients/<provider>/default/<client id>. It will also + <KEYCLOAK URL>/realms/<realm>/clients/<provider>/default/<client id>. It will also return a new registration access token. To delete the Client Representation then do a HTTP DELETE to: - <KEYCLOAK URL>/clients/<provider>/default/<client id> + <KEYCLOAK URL>/realms/<realm>/clients/<provider>/default/<client id> Keycloak Adapter Configuration - The installation client registration provider can be used to retrieve the adapter configuration + The installation client registration provider can be used to retrieve the adapter configuration for a client. In addition to token authentication you can also authenticate with client credentials using HTTP basic authentication. To do this include the following header in the request: To retrieve the Adapter Configuration then do a HTTP GET to: - <KEYCLOAK URL>/clients/<provider>/installation/<client id> + <KEYCLOAK URL>//realms/<realm>clients/<provider>/installation/<client id> No authentication is required for public clients. This means that for the JavaScript adapter you can @@ -146,23 +146,36 @@ Authorization: basic BASE64(client-id + ':' + client-secret) - - Client Registration Java API diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java index 020d349150..0a253d1fb9 100644 --- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java @@ -153,6 +153,10 @@ public class ExtendingThemeManager implements ThemeProvider { private List themes; + private Properties properties; + + private ConcurrentHashMap> messages = new ConcurrentHashMap<>(); + public ExtendingTheme(List themes) { this.themes = themes; } @@ -229,28 +233,41 @@ public class ExtendingThemeManager implements ThemeProvider { @Override public Properties getMessages(String baseBundlename, Locale locale) throws IOException { - Properties messages = new Properties(); - ListIterator itr = themes.listIterator(themes.size()); - while (itr.hasPrevious()) { - Properties m = itr.previous().getMessages(baseBundlename, locale); - if (m != null) { - messages.putAll(m); + if (messages.get(baseBundlename) == null || messages.get(baseBundlename).get(locale) == null) { + Properties messages = new Properties(); + ListIterator itr = themes.listIterator(themes.size()); + while (itr.hasPrevious()) { + Properties m = itr.previous().getMessages(baseBundlename, locale); + if (m != null) { + messages.putAll(m); + } } + + this.messages.putIfAbsent(baseBundlename, new ConcurrentHashMap()); + this.messages.get(baseBundlename).putIfAbsent(locale, messages); + + return messages; + } else { + return messages.get(baseBundlename).get(locale); } - return messages; } @Override public Properties getProperties() throws IOException { - Properties properties = new Properties(); - ListIterator itr = themes.listIterator(themes.size()); - while (itr.hasPrevious()) { - Properties p = itr.previous().getProperties(); - if (p != null) { - properties.putAll(p); + if (properties == null) { + Properties properties = new Properties(); + ListIterator itr = themes.listIterator(themes.size()); + while (itr.hasPrevious()) { + Properties p = itr.previous().getProperties(); + if (p != null) { + properties.putAll(p); + } } + this.properties = properties; + return properties; + } else { + return properties; } - return properties; } } 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 09192ae702..e274ca71d0 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 @@ -271,7 +271,7 @@ 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 Secretsecret=Secret +regenerate-secret=Regenerate 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. diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html index 1f8890e7d7..eaaf8116b0 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html @@ -51,18 +51,18 @@ How far ahead should the server look just in case the token generator and server are out of time sync or counter sync? - + Initial Counter - + What should the initial counter value be? - + OTP Token Period - + How many seconds should an OTP token be valid? Defaults to 30 seconds. diff --git a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java index aff7d37622..3674334ffe 100755 --- a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java +++ b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java @@ -76,6 +76,8 @@ public class PasswordPolicy implements Serializable { list.add(new PasswordHistory(arg)); } else if (name.equals(ForceExpiredPasswordChange.NAME)) { list.add(new ForceExpiredPasswordChange(arg)); + } else { + throw new IllegalArgumentException("Unsupported policy"); } } return list; diff --git a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java index 7ce15d8eee..af9b6b562b 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java @@ -51,6 +51,10 @@ public class CredentialValidation { } public static boolean validateHashedCredential(RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) { + if(unhashedCredValue == null){ + return false; + } + boolean validated = new Pbkdf2PasswordEncoder(credential.getSalt()).verify(unhashedCredValue, credential.getValue(), credential.getHashIterations()); if (validated) { int iterations = hashIterations(realm); 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 dde4462b97..3916fd16a7 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 @@ -69,12 +69,12 @@ public class RepresentationToModel { private static Logger logger = Logger.getLogger(RepresentationToModel.class); public static OTPPolicy toPolicy(RealmRepresentation rep) { OTPPolicy policy = new OTPPolicy(); - policy.setType(rep.getOtpPolicyType()); - policy.setLookAheadWindow(rep.getOtpPolicyLookAheadWindow()); - policy.setInitialCounter(rep.getOtpPolicyInitialCounter()); - policy.setAlgorithm(rep.getOtpPolicyAlgorithm()); - policy.setDigits(rep.getOtpPolicyDigits()); - policy.setPeriod(rep.getOtpPolicyPeriod()); + if (rep.getOtpPolicyType() != null) policy.setType(rep.getOtpPolicyType()); + if (rep.getOtpPolicyLookAheadWindow() != null) policy.setLookAheadWindow(rep.getOtpPolicyLookAheadWindow()); + if (rep.getOtpPolicyInitialCounter() != null) policy.setInitialCounter(rep.getOtpPolicyInitialCounter()); + if (rep.getOtpPolicyAlgorithm() != null) policy.setAlgorithm(rep.getOtpPolicyAlgorithm()); + if (rep.getOtpPolicyDigits() != null) policy.setDigits(rep.getOtpPolicyDigits()); + if (rep.getOtpPolicyPeriod() != null) policy.setPeriod(rep.getOtpPolicyPeriod()); return policy; } diff --git a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java index df76588a3d..8c662fb8af 100755 --- a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java +++ b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java @@ -83,6 +83,15 @@ public class PasswordPolicyTest { Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage()); Assert.assertNull(policy.validate("jdoe", "ab&d1234")); } + + @Test + public void testInvalidPolicyName() { + try { + PasswordPolicy policy = new PasswordPolicy("noSuchPolicy"); + Assert.fail("Expected exception"); + } catch (IllegalArgumentException e) { + } + } @Test public void testRegexPatterns() { 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 index 81c2df4d9e..1ee3cd2bff 100644 --- 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 @@ -1,63 +1,35 @@ package org.keycloak.protocol.saml.clientregistration; -import org.jboss.logging.Logger; -import org.keycloak.events.EventBuilder; +import org.keycloak.exportimport.ClientDescriptionConverter; import org.keycloak.models.KeycloakSession; -import org.keycloak.services.clientregistration.ClientRegistrationAuth; -import org.keycloak.services.clientregistration.ClientRegistrationProvider; +import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider; + +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 Stian Thorgersen */ -public class EntityDescriptorClientRegistrationProvider implements ClientRegistrationProvider { - - private static final Logger logger = Logger.getLogger(EntityDescriptorClientRegistrationProvider.class); - - private KeycloakSession session; - private EventBuilder event; - private ClientRegistrationAuth auth; +public class EntityDescriptorClientRegistrationProvider extends AbstractClientRegistrationProvider { public EntityDescriptorClientRegistrationProvider(KeycloakSession session) { - this.session = session; + super(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(ClientRegistrationAuth auth) { - this.auth = auth; - } - - @Override - public void setEvent(EventBuilder event) { - this.event = event; + @POST + @Consumes(MediaType.APPLICATION_XML) + @Produces(MediaType.APPLICATION_JSON) + public Response createSaml(String descriptor) { + ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor); + client = create(client); + URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build(); + return Response.created(uri).entity(client).build(); } } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java index 0506f2156b..5afaa3d6b7 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java @@ -148,24 +148,17 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap inputData) { List credentials = new LinkedList<>(); String password = inputData.getFirst(CredentialRepresentation.PASSWORD); - if (password == null || password.isEmpty()) { - invalidPassword(context, user); - return false; - } credentials.add(UserCredentialModel.password(password)); boolean valid = context.getSession().users().validCredentials(context.getRealm(), user, credentials); if (!valid) { - invalidPassword(context, user); + context.getEvent().user(user); + context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); + Response challengeResponse = invalidCredentials(context); + context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse); + context.clearUser(); return false; } return true; } - private void invalidPassword(AuthenticationFlowContext context, UserModel user) { - context.getEvent().user(user); - context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); - Response challengeResponse = invalidCredentials(context); - context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse); - context.clearUser(); - } } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java index cff7f37196..21aa18d259 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java @@ -31,15 +31,6 @@ public class ValidatePassword extends AbstractDirectGrantAuthenticator { MultivaluedMap inputData = context.getHttpRequest().getDecodedFormParameters(); List credentials = new LinkedList<>(); String password = inputData.getFirst(CredentialRepresentation.PASSWORD); - if (password == null || password.isEmpty()) { - if (context.getUser() != null) { - context.getEvent().user(context.getUser()); - } - context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); - Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials"); - context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse); - return; - } credentials.add(UserCredentialModel.password(password)); boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); if (!valid) { 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 955dfe4769..6caacd6f31 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java @@ -5,8 +5,7 @@ import org.keycloak.exportimport.ClientDescriptionConverter; import org.keycloak.exportimport.ClientDescriptionConverterFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.clientregistration.oidc.DescriptionConverter; import org.keycloak.util.JsonSerialization; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java index 714c0d1da9..60e9f9d36a 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java @@ -4,6 +4,8 @@ import org.keycloak.OAuth2Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; +import org.keycloak.services.clientregistration.ClientRegistrationService; +import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory; import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.Urls; import org.keycloak.wellknown.WellKnownProvider; @@ -48,6 +50,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider { config.setUserinfoEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()); config.setLogoutEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "logout").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()); config.setJwksUri(uriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()); + config.setRegistrationEndpoint(RealmsResource.clientRegistrationUrl(uriInfo).path(ClientRegistrationService.class, "provider").build(realm.getName(), OIDCClientRegistrationProviderFactory.ID).toString()); config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED); config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED); 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 deleted file mode 100644 index 4a83ebddce..0000000000 --- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.keycloak.protocol.oidc.representations; - -import org.codehaus.jackson.annotate.JsonProperty; - -import java.util.List; - -/** - * @author Stian Thorgersen - */ -public class OIDCClientRepresentation { - - @JsonProperty("redirect_uris") - private List 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 getRedirectUris() { - return redirectUris; - } - - public void setRedirectUris(List redirectUris) { - 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; - } - - public void setClientName(String clientName) { - this.clientName = clientName; - } - - public String getClientUri() { - return clientUri; - } - - public void setClientUri(String clientUri) { - 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/representations/OIDCConfigurationRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java index 9245e588b9..02263317ca 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java @@ -7,7 +7,6 @@ import org.codehaus.jackson.annotate.JsonProperty; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; /** * @author Stian Thorgersen @@ -48,6 +47,9 @@ public class OIDCConfigurationRepresentation { @JsonProperty("response_modes_supported") private List responseModesSupported; + @JsonProperty("registration_endpoint") + private String registrationEndpoint; + protected Map otherClaims = new HashMap(); public String getIssuer() { @@ -138,6 +140,14 @@ public class OIDCConfigurationRepresentation { this.responseModesSupported = responseModesSupported; } + public String getRegistrationEndpoint() { + return registrationEndpoint; + } + + public void setRegistrationEndpoint(String registrationEndpoint) { + this.registrationEndpoint = registrationEndpoint; + } + @JsonAnyGetter public Map getOtherClaims() { return otherClaims; diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java new file mode 100644 index 0000000000..0c95a37ce1 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java @@ -0,0 +1,125 @@ +package org.keycloak.services.clientregistration; + +import org.keycloak.events.EventBuilder; +import org.keycloak.events.EventType; +import org.keycloak.models.ClientInitialAccessModel; +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.representations.idm.ClientRepresentation; +import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.ForbiddenException; + +import javax.ws.rs.core.Response; + +/** + * @author Stian Thorgersen + */ +public abstract class AbstractClientRegistrationProvider implements ClientRegistrationProvider { + + protected KeycloakSession session; + protected EventBuilder event; + protected ClientRegistrationAuth auth; + + public AbstractClientRegistrationProvider(KeycloakSession session) { + this.session = session; + } + + public ClientRepresentation create(ClientRepresentation client) { + event.event(EventType.CLIENT_REGISTER); + + auth.requireCreate(); + + try { + ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true); + if (client.getClientId() == null) { + clientModel.setClientId(clientModel.getId()); + } + + client = ModelToRepresentation.toRepresentation(clientModel); + + String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, clientModel); + + client.setRegistrationAccessToken(registrationAccessToken); + + if (auth.isInitialAccessToken()) { + ClientInitialAccessModel initialAccessModel = auth.getInitialAccessModel(); + initialAccessModel.decreaseRemainingCount(); + } + + event.client(client.getClientId()).success(); + return client; + } catch (ModelDuplicateException e) { + throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier in use", Response.Status.BAD_REQUEST); + } + } + + public ClientRepresentation get(String clientId) { + event.event(EventType.CLIENT_INFO); + + ClientModel client = session.getContext().getRealm().getClientByClientId(clientId); + auth.requireView(client); + + ClientRepresentation rep = ModelToRepresentation.toRepresentation(client); + + if (auth.isRegistrationAccessToken()) { + String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client); + rep.setRegistrationAccessToken(registrationAccessToken); + } + + event.client(client.getClientId()).success(); + return rep; + } + + public ClientRepresentation update(String clientId, ClientRepresentation rep) { + event.event(EventType.CLIENT_UPDATE).client(clientId); + + ClientModel client = session.getContext().getRealm().getClientByClientId(clientId); + auth.requireUpdate(client); + + if (!client.getClientId().equals(rep.getClientId())) { + throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier modified", Response.Status.BAD_REQUEST); + } + + RepresentationToModel.updateClient(rep, client); + rep = ModelToRepresentation.toRepresentation(client); + + if (auth.isRegistrationAccessToken()) { + String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client); + rep.setRegistrationAccessToken(registrationAccessToken); + } + + event.client(client.getClientId()).success(); + return rep; + } + + public void delete(String clientId) { + event.event(EventType.CLIENT_DELETE).client(clientId); + + ClientModel client = session.getContext().getRealm().getClientByClientId(clientId); + auth.requireUpdate(client); + + if (session.getContext().getRealm().removeClient(client.getId())) { + event.client(client.getClientId()).success(); + } else { + throw new ForbiddenException(); + } + } + + @Override + public void setAuth(ClientRegistrationAuth auth) { + this.auth = auth; + } + + @Override + public void setEvent(EventBuilder event) { + this.event = event; + } + + @Override + public void close() { + } + +} 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 0581388a1c..621d5fb91c 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java @@ -25,7 +25,7 @@ public class ClientRegistrationService { } @Path("{provider}") - public Object getProvider(@PathParam("provider") String providerId) { + public Object provider(@PathParam("provider") String providerId) { checkSsl(); ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId); 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 38d71f227c..126d00af49 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java @@ -2,14 +2,11 @@ package org.keycloak.services.clientregistration; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; -import org.keycloak.models.ClientInitialAccessModel; 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.representations.idm.ClientRepresentation; -import org.keycloak.services.ErrorResponse; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; @@ -19,101 +16,41 @@ import java.net.URI; /** * @author Stian Thorgersen */ -public class DefaultClientRegistrationProvider implements ClientRegistrationProvider { - - private KeycloakSession session; - private EventBuilder event; - private ClientRegistrationAuth auth; +public class DefaultClientRegistrationProvider extends AbstractClientRegistrationProvider { public DefaultClientRegistrationProvider(KeycloakSession session) { - this.session = session; + super(session); } @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public Response create(ClientRepresentation client) { - event.event(EventType.CLIENT_REGISTER); - - auth.requireCreate(); - - try { - ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true); - client = ModelToRepresentation.toRepresentation(clientModel); - - String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, clientModel); - - client.setRegistrationAccessToken(registrationAccessToken); - - URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build(); - - if (auth.isInitialAccessToken()) { - ClientInitialAccessModel initialAccessModel = auth.getInitialAccessModel(); - initialAccessModel.decreaseRemainingCount(); - } - - event.client(client.getClientId()).success(); - return Response.created(uri).entity(client).build(); - } catch (ModelDuplicateException e) { - return ErrorResponse.exists("Client " + client.getClientId() + " already exists"); - } + public Response createDefault(ClientRepresentation client) { + client = create(client); + URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build(); + return Response.created(uri).entity(client).build(); } @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); - auth.requireView(client); - - ClientRepresentation rep = ModelToRepresentation.toRepresentation(client); - - if (auth.isRegistrationAccessToken()) { - String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client); - rep.setRegistrationAccessToken(registrationAccessToken); - } - - event.client(client.getClientId()).success(); - return Response.ok(rep).build(); + public Response getDefault(@PathParam("clientId") String clientId) { + ClientRepresentation client = get(clientId); + return Response.ok(client).build(); } @PUT @Path("{clientId}") @Consumes(MediaType.APPLICATION_JSON) - public Response update(@PathParam("clientId") String clientId, ClientRepresentation rep) { - event.event(EventType.CLIENT_UPDATE).client(clientId); - - ClientModel client = session.getContext().getRealm().getClientByClientId(clientId); - auth.requireUpdate(client); - - RepresentationToModel.updateClient(rep, client); - rep = ModelToRepresentation.toRepresentation(client); - - if (auth.isRegistrationAccessToken()) { - String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client); - rep.setRegistrationAccessToken(registrationAccessToken); - } - - event.client(client.getClientId()).success(); - return Response.ok(rep).build(); + public Response updateDefault(@PathParam("clientId") String clientId, ClientRepresentation client) { + client = update(clientId, client); + return Response.ok(client).build(); } @DELETE @Path("{clientId}") - public Response delete(@PathParam("clientId") String clientId) { - event.event(EventType.CLIENT_DELETE).client(clientId); - - 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(); - } + public void deleteDefault(@PathParam("clientId") String clientId) { + delete(clientId); } @Override diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ErrorCodes.java b/services/src/main/java/org/keycloak/services/clientregistration/ErrorCodes.java new file mode 100644 index 0000000000..ed491decaf --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/ErrorCodes.java @@ -0,0 +1,12 @@ +package org.keycloak.services.clientregistration; + +/** + * @author Stian Thorgersen + */ +public interface ErrorCodes { + + String INVALID_REDIRECT_URI = "invalid_redirect_uri"; + + String INVALID_CLIENT_METADATA = "invalid_client_metadata"; + +} 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 index 1e0784c288..a7f9f2c82f 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java @@ -1,8 +1,9 @@ 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; +import org.keycloak.representations.oidc.OIDCClientRepresentation; + +import java.net.URI; /** * @author Stian Thorgersen @@ -11,27 +12,22 @@ public class DescriptionConverter { public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) { ClientRepresentation client = new ClientRepresentation(); - client.setClientId(KeycloakModelUtils.generateId()); + client.setClientId(clientOIDC.getClientId()); client.setName(clientOIDC.getClientName()); client.setRedirectUris(clientOIDC.getRedirectUris()); client.setBaseUrl(clientOIDC.getClientUri()); return client; } - public static OIDCClientResponseRepresentation toExternalResponse(ClientRepresentation client) { - OIDCClientResponseRepresentation response = new OIDCClientResponseRepresentation(); + public static OIDCClientRepresentation toExternalResponse(ClientRepresentation client, URI uri) { + OIDCClientRepresentation response = new OIDCClientRepresentation(); 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()); - + response.setRegistrationClientUri(uri.toString()); 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 index 961f28de78..e60720bc87 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java @@ -1,72 +1,70 @@ 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.models.KeycloakSession; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.representations.oidc.OIDCClientRepresentation; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider; import org.keycloak.services.clientregistration.ClientRegistrationAuth; -import org.keycloak.services.clientregistration.ClientRegistrationProvider; +import org.keycloak.services.clientregistration.ErrorCodes; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.net.URI; /** * @author Stian Thorgersen */ -public class OIDCClientRegistrationProvider implements ClientRegistrationProvider { - - private static final Logger logger = Logger.getLogger(OIDCClientRegistrationProvider.class); - - private KeycloakSession session; - private EventBuilder event; - private ClientRegistrationAuth auth; +public class OIDCClientRegistrationProvider extends AbstractClientRegistrationProvider { public OIDCClientRegistrationProvider(KeycloakSession session) { - this.session = session; + super(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.setRegistrationToken(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"); -// } -// } + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response createOIDC(OIDCClientRepresentation clientOIDC) { + if (clientOIDC.getClientId() != null) { + throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier included", Response.Status.BAD_REQUEST); + } - @Override - public void close() { + ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC); + client = create(client); + URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build(); + clientOIDC = DescriptionConverter.toExternalResponse(client, uri); + clientOIDC.setClientIdIssuedAt(Time.currentTime()); + return Response.created(uri).entity(clientOIDC).build(); + } + + @GET + @Path("{clientId}") + @Produces(MediaType.APPLICATION_JSON) + public Response getOIDC(@PathParam("clientId") String clientId) { + ClientRepresentation client = get(clientId); + OIDCClientRepresentation clientOIDC = DescriptionConverter.toExternalResponse(client, session.getContext().getUri().getRequestUri()); + return Response.ok(clientOIDC).build(); + } + + @PUT + @Path("{clientId}") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateOIDC(@PathParam("clientId") String clientId, OIDCClientRepresentation clientOIDC) { + ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC); + client = update(clientId, client); + URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build(); + clientOIDC = DescriptionConverter.toExternalResponse(client, uri); + return Response.ok(clientOIDC).build(); + } + + @DELETE + @Path("{clientId}") + public void deleteOIDC(@PathParam("clientId") String clientId) { + delete(clientId); } @Override @@ -79,4 +77,8 @@ public class OIDCClientRegistrationProvider implements ClientRegistrationProvide this.event = event; } + @Override + public void close() { + } + } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java index 6f112f8816..0144e790a8 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java @@ -11,6 +11,8 @@ import org.keycloak.services.clientregistration.ClientRegistrationProviderFactor */ public class OIDCClientRegistrationProviderFactory implements ClientRegistrationProviderFactory { + public static final String ID = "openid-connect"; + @Override public ClientRegistrationProvider create(KeycloakSession session) { return new OIDCClientRegistrationProvider(session); @@ -30,7 +32,7 @@ public class OIDCClientRegistrationProviderFactory implements ClientRegistration @Override public String getId() { - return "openid-connect"; + return ID; } } 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 deleted file mode 100644 index cd4eea4949..0000000000 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.keycloak.services.clientregistration.oidc; - -import org.codehaus.jackson.annotate.JsonProperty; -import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation; - -/** - * @author Stian Thorgersen - */ -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/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java index 4b5e4f201b..cc1e49ae38 100755 --- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java @@ -59,6 +59,10 @@ public class RealmsResource { return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getProtocol"); } + public static UriBuilder clientRegistrationUrl(UriInfo uriInfo) { + return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getClientsService"); + } + public static UriBuilder brokerUrl(UriInfo uriInfo) { return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getBrokerService"); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index d874d1e2d5..90eab4a54e 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -236,7 +236,7 @@ public class RealmAdminResource { } catch (ModelDuplicateException e) { throw e; } catch (Exception e) { - logger.error(e); + logger.error(e.getMessage(), e); return ErrorResponse.error("Failed to update " + rep.getRealm() + " Realm.", Response.Status.INTERNAL_SERVER_ERROR); } } diff --git a/testsuite/integration-arquillian/tests/adapters/as7/pom.xml b/testsuite/integration-arquillian/tests/adapters/as7/pom.xml index 313c154429..15b575cd80 100644 --- a/testsuite/integration-arquillian/tests/adapters/as7/pom.xml +++ b/testsuite/integration-arquillian/tests/adapters/as7/pom.xml @@ -25,14 +25,13 @@ zip - org.keycloak - keycloak-as7-adapter-dist - zip + org.wildfly + wildfly-arquillian-container-managed - org.jboss.as - jboss-as-arquillian-container-managed - 7.2.0.Final + org.keycloak + keycloak-eap6-adapter-dist + zip @@ -67,18 +66,20 @@ org.keycloak keycloak-as7-adapter-dist + ${project.version} zip ${adapter.libs.as7} - true + org.apache.maven.plugins maven-surefire-plugin + 2.18.1 true @@ -90,7 +91,6 @@ - adapter-libs-provided @@ -133,5 +133,4 @@ - diff --git a/testsuite/integration-arquillian/tests/adapters/as7/src/main/xslt/arquillian.xsl b/testsuite/integration-arquillian/tests/adapters/as7/src/main/xslt/arquillian.xsl index 8970850487..9ba1e9403f 100644 --- a/testsuite/integration-arquillian/tests/adapters/as7/src/main/xslt/arquillian.xsl +++ b/testsuite/integration-arquillian/tests/adapters/as7/src/main/xslt/arquillian.xsl @@ -18,6 +18,7 @@ ${app.server.as7.home} -Djboss.socket.binding.port-offset=${app.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props} localhost + remote ${app.server.management.port.jmx} diff --git a/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7DemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7DemoServletsAdapterTest.java index f0258b23d0..1a4a68c30c 100644 --- a/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7DemoServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7DemoServletsAdapterTest.java @@ -1,5 +1,6 @@ package org.keycloak.testsuite.adapter.servlet; +import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty; import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; /** @@ -7,6 +8,7 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; * @author tkyjovsk */ @AppServerContainer("app-server-as7") +@AdapterLibsLocationProperty("adapter.libs.as7") public class AS7DemoServletsAdapterTest extends AbstractDemoServletsAdapterTest { } diff --git a/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7SessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7SessionServletAdapterTest.java index 9362ecd02d..4b88033f35 100644 --- a/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7SessionServletAdapterTest.java +++ b/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7SessionServletAdapterTest.java @@ -1,5 +1,6 @@ package org.keycloak.testsuite.adapter.servlet; +import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty; import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; /** @@ -7,6 +8,7 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; * @author tkyjovsk */ @AppServerContainer("app-server-as7") +@AdapterLibsLocationProperty("adapter.libs.as7") public class AS7SessionServletAdapterTest extends AbstractSessionServletAdapterTest { } diff --git a/testsuite/integration-arquillian/tests/adapters/eap6/pom.xml b/testsuite/integration-arquillian/tests/adapters/eap6/pom.xml new file mode 100644 index 0000000000..433ca63ae7 --- /dev/null +++ b/testsuite/integration-arquillian/tests/adapters/eap6/pom.xml @@ -0,0 +1,135 @@ + + + + org.keycloak.testsuite + integration-arquillian-tests-adapters + 1.7.0.Final-SNAPSHOT + + 4.0.0 + + integration-arquillian-adapters-eap6 + Adapter Tests on EAP 6 + + + ${containers.home}/jboss-eap-6.4 + ${containers.home}/keycloak-eap6-adapter-dist + + + + + org.jboss.as + jboss-as-dist + ${jboss.version} + zip + + + org.wildfly + wildfly-arquillian-container-managed + + + org.keycloak + keycloak-eap6-adapter-dist + zip + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack-as7-and-adapter + generate-resources + + unpack + + + + + org.jboss.as + jboss-as-dist + ${jboss.version} + zip + ${containers.home} + + + org.keycloak + keycloak-eap6-adapter-dist + ${project.version} + zip + ${adapter.libs.eap6} + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18.1 + + + true + ${app.server.eap6.home} + ${adapter.libs.eap6} + + + + + + + + + adapter-libs-provided + + + !adapter.libs.bundled + + + + ${app.server.eap6.home} + + + + + org.codehaus.mojo + xml-maven-plugin + + + configure-adapter-subsystem + process-resources + + transform + + + + + ${app.server.eap6.home}/standalone/configuration + + standalone.xml + + src/main/xslt/standalone.xsl + ${app.server.eap6.home}/standalone/configuration + + + + + + + + + + + diff --git a/testsuite/integration-arquillian/tests/adapters/eap6/src/main/xslt/arquillian.xsl b/testsuite/integration-arquillian/tests/adapters/eap6/src/main/xslt/arquillian.xsl new file mode 100644 index 0000000000..fbfd50db20 --- /dev/null +++ b/testsuite/integration-arquillian/tests/adapters/eap6/src/main/xslt/arquillian.xsl @@ -0,0 +1,37 @@ + + + + + + + + + + + + ${app.server.eap6} + org.jboss.as.arquillian.container.managed.ManagedDeployableContainer + ${app.server.eap6.home} + -Djboss.socket.binding.port-offset=${app.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props} + localhost + remote + ${app.server.management.port.jmx} + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/adapters/eap6/src/main/xslt/standalone.xsl b/testsuite/integration-arquillian/tests/adapters/eap6/src/main/xslt/standalone.xsl new file mode 100644 index 0000000000..fb3612beff --- /dev/null +++ b/testsuite/integration-arquillian/tests/adapters/eap6/src/main/xslt/standalone.xsl @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6DemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6DemoServletsAdapterTest.java new file mode 100644 index 0000000000..5eb363e0f0 --- /dev/null +++ b/testsuite/integration-arquillian/tests/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6DemoServletsAdapterTest.java @@ -0,0 +1,14 @@ +package org.keycloak.testsuite.adapter.servlet; + +import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty; +import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; + +/** + * + * @author tkyjovsk + */ +@AppServerContainer("app-server-eap6") +@AdapterLibsLocationProperty("adapter.libs.eap6") +public class EAP6DemoServletsAdapterTest extends AbstractDemoServletsAdapterTest { + +} diff --git a/testsuite/integration-arquillian/tests/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6SessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6SessionServletAdapterTest.java new file mode 100644 index 0000000000..c187910a0e --- /dev/null +++ b/testsuite/integration-arquillian/tests/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6SessionServletAdapterTest.java @@ -0,0 +1,14 @@ +package org.keycloak.testsuite.adapter.servlet; + +import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty; +import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; + +/** + * + * @author tkyjovsk + */ +@AppServerContainer("app-server-eap6") +@AdapterLibsLocationProperty("adapter.libs.eap6") +public class EAP6SessionServletAdapterTest extends AbstractSessionServletAdapterTest { + +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java index 7b0fdb14b8..00aaed308f 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java @@ -17,6 +17,7 @@ */ package org.keycloak.testsuite.util; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import static org.jboss.arquillian.graphene.Graphene.waitAjax; @@ -31,24 +32,35 @@ import org.openqa.selenium.WebElement; */ public final class WaitUtils { + public static final String PAGELOAD_TIMEOUT_PROP = "pageload.timeout"; + public static final String IMPLICIT_TIMEOUT_PROP = "implicit.timeout"; + public static final String SCRIPT_TIMEOUT_PROP = "script.timeout"; + public static final String POLLING_INTERVAL_PROP = "polling.interval"; + + public static final Integer PAGELOAD_TIMEOUT = Integer.parseInt(System.getProperty(PAGELOAD_TIMEOUT_PROP, "5000")); + public static final Integer IMPLICIT_TIMEOUT = Integer.parseInt(System.getProperty(IMPLICIT_TIMEOUT_PROP, "3000")); + public static final Integer SCRIPT_TIMEOUT = Integer.parseInt(System.getProperty(SCRIPT_TIMEOUT_PROP, "3000")); + + public static final Integer POLLING_INTERVAL = Integer.parseInt(System.getProperty(POLLING_INTERVAL_PROP, "1000")); + public static void waitAjaxForElement(WebElement element) { - waitAjax().until() - .element(element).is().present(); + waitAjax().withTimeout(SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS) + .until().element(element).is().present(); } public static void waitAjaxForElementNotPresent(WebElement element) { - waitAjax().until() - .element(element).is().not().present(); + waitAjax().withTimeout(SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS) + .until().element(element).is().not().present(); } public static void waitAjaxForElementNotVisible(WebElement element) { - waitAjax().until() - .element(element).is().not().visible(); + waitAjax().withTimeout(SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS) + .until().element(element).is().not().visible(); } public static void waitGuiForElement(By element, String message) { - waitGui().until(message) - .element(element).is().present(); + waitGui().withTimeout(IMPLICIT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS) + .until(message).element(element).is().present(); } public static void waitGuiForElement(By element) { @@ -60,11 +72,13 @@ public final class WaitUtils { } public static void waitGuiForElementPresent(WebElement element, String message) { - waitGui().until(message).element(element).is().present(); + waitGui().withTimeout(IMPLICIT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS) + .until(message).element(element).is().present(); } public static void waitGuiForElementNotPresent(WebElement element) { - waitGui().until().element(element).is().not().present(); + waitGui().withTimeout(IMPLICIT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS) + .until().element(element).is().not().present(); } public static void pause(long millis) { @@ -74,5 +88,5 @@ public final class WaitUtils { Logger.getLogger(WaitUtils.class.getName()).log(Level.SEVERE, null, ex); } } - + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index 8ed6dcdd16..e553b3dbb1 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -32,6 +32,7 @@ import org.keycloak.testsuite.auth.page.account.Account; import org.keycloak.testsuite.auth.page.login.OIDCLogin; import org.keycloak.testsuite.auth.page.login.UpdatePassword; import org.keycloak.testsuite.util.Timer; +import org.keycloak.testsuite.util.WaitUtils; /** * @@ -116,9 +117,9 @@ public abstract class AbstractKeycloakTest { } protected void driverSettings() { - driver.manage().timeouts().pageLoadTimeout(5, TimeUnit.SECONDS); - driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS); - driver.manage().timeouts().setScriptTimeout(3, TimeUnit.SECONDS); + driver.manage().timeouts().pageLoadTimeout(WaitUtils.PAGELOAD_TIMEOUT, TimeUnit.MILLISECONDS); + driver.manage().timeouts().implicitlyWait(WaitUtils.IMPLICIT_TIMEOUT, TimeUnit.MILLISECONDS); + driver.manage().timeouts().setScriptTimeout(WaitUtils.SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS); driver.manage().window().maximize(); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java new file mode 100644 index 0000000000..9696274241 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java @@ -0,0 +1,85 @@ +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.representations.idm.ClientInitialAccessCreatePresentation; +import org.keycloak.representations.idm.ClientInitialAccessPresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; + +import java.util.Collections; + +import static org.junit.Assert.*; + +/** + * @author Stian Thorgersen + */ +public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { + + @Before + public void before() throws Exception { + super.before(); + + ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10)); + reg.auth(Auth.token(token)); + } + + public OIDCClientRepresentation create() throws ClientRegistrationException { + OIDCClientRepresentation client = new OIDCClientRepresentation(); + client.setClientName("RegistrationAccessTokenTest"); + client.setClientUri("http://root"); + client.setRedirectUris(Collections.singletonList("http://redirect")); + + OIDCClientRepresentation response = reg.oidc().create(client); + + return response; + } + + @Test + public void createClient() throws ClientRegistrationException { + OIDCClientRepresentation response = create(); + + assertNotNull(response.getRegistrationAccessToken()); + assertNotNull(response.getClientIdIssuedAt()); + assertNotNull(response.getClientId()); + assertNull(response.getClientSecretExpiresAt()); + assertNotNull(response.getRegistrationClientUri()); + assertEquals("RegistrationAccessTokenTest", response.getClientName()); + assertEquals("http://root", response.getClientUri()); + assertEquals(1, response.getRedirectUris().size()); + assertEquals("http://redirect", response.getRedirectUris().get(0)); + } + + @Test + public void getClient() throws ClientRegistrationException { + OIDCClientRepresentation response = create(); + reg.auth(Auth.token(response)); + + OIDCClientRepresentation rep = reg.oidc().get(response.getClientId()); + assertNotNull(rep); + assertNotEquals(response.getRegistrationAccessToken(), rep.getRegistrationAccessToken()); + } + + @Test + public void updateClient() throws ClientRegistrationException { + OIDCClientRepresentation response = create(); + reg.auth(Auth.token(response)); + + response.setRedirectUris(Collections.singletonList("http://newredirect")); + + OIDCClientRepresentation updated = reg.oidc().update(response); + + assertEquals(1, updated.getRedirectUris().size()); + assertEquals("http://newredirect", updated.getRedirectUris().get(0)); + } + + @Test + public void deleteClient() throws ClientRegistrationException { + OIDCClientRepresentation response = create(); + reg.auth(Auth.token(response)); + + reg.oidc().delete(response); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/SAMLClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/SAMLClientRegistrationTest.java new file mode 100644 index 0000000000..1f5eab8a98 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/SAMLClientRegistrationTest.java @@ -0,0 +1,42 @@ +package org.keycloak.testsuite.client; + +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.client.registration.Auth; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; +import org.keycloak.representations.idm.ClientInitialAccessPresentation; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; + +import java.io.IOException; +import java.util.Collections; + +import static org.junit.Assert.*; + +/** + * @author Stian Thorgersen + */ +public class SAMLClientRegistrationTest extends AbstractClientRegistrationTest { + + @Before + public void before() throws Exception { + super.before(); + + ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10)); + reg.auth(Auth.token(token)); + } + + @Test + public void createClient() throws ClientRegistrationException, IOException { + String entityDescriptor = IOUtils.toString(getClass().getResourceAsStream("/clientreg-test/saml-entity-descriptor.xml")); + ClientRepresentation response = reg.saml().create(entityDescriptor); + + assertNotNull(response.getRegistrationAccessToken()); + assertEquals("loadbalancer-9.siroe.com", response.getClientId()); + assertEquals(1, response.getRedirectUris().size()); + assertEquals("https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp", response.getRedirectUris().get(0)); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/clientreg-test/saml-entity-descriptor.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/clientreg-test/saml-entity-descriptor.xml new file mode 100644 index 0000000000..b00ab251a4 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/clientreg-test/saml-entity-descriptor.xml @@ -0,0 +1,82 @@ + + + + + + +MIICYDCCAgqgAwIBAgICBoowDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz +dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh +dGUgTWFuYWdlcjAeFw0wNjExMDIxOTExMzRaFw0xMDA3MjkxOTExMzRaMDcxEjAQBgNVBAoTCXNp +cm9lLmNvbTEhMB8GA1UEAxMYbG9hZGJhbGFuY2VyLTkuc2lyb2UuY29tMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQCjOwa5qoaUuVnknqf5pdgAJSEoWlvx/jnUYbkSDpXLzraEiy2UhvwpoBgB +EeTSUaPPBvboCItchakPI6Z/aFdH3Wmjuij9XD8r1C+q//7sUO0IGn0ORycddHhoo0aSdnnxGf9V +tREaqKm9dJ7Yn7kQHjo2eryMgYxtr/Z5Il5F+wIDAQABo2AwXjARBglghkgBhvhCAQEEBAMCBkAw +DgYDVR0PAQH/BAQDAgTwMB8GA1UdIwQYMBaAFDugITflTCfsWyNLTXDl7cMDUKuuMBgGA1UdEQQR +MA+BDW1hbGxhQHN1bi5jb20wDQYJKoZIhvcNAQEEBQADQQB/6DOB6sRqCZu2OenM9eQR0gube85e +nTTxU4a7x1naFxzYXK1iQ1vMARKMjDb19QEJIEJKZlDK4uS7yMlf1nFS + + + + + + + + +MIICTDCCAfagAwIBAgICBo8wDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz +dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh +dGUgTWFuYWdlcjAeFw0wNjExMDcyMzU2MTdaFw0xMDA4MDMyMzU2MTdaMCMxITAfBgNVBAMTGGxv +YWRiYWxhbmNlci05LnNpcm9lLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAw574iRU6 +HsSO4LXW/OGTXyfsbGv6XRVOoy3v+J1pZ51KKejcDjDJXNkKGn3/356AwIaqbcymWd59T0zSqYfR +Hn+45uyjYxRBmVJseLpVnOXLub9jsjULfGx0yjH4w+KsZSZCXatoCHbj/RJtkzuZY6V9to/hkH3S +InQB4a3UAgMCAwEAAaNgMF4wEQYJYIZIAYb4QgEBBAQDAgZAMA4GA1UdDwEB/wQEAwIE8DAfBgNV +HSMEGDAWgBQ7oCE35Uwn7FsjS01w5e3DA1CrrjAYBgNVHREEETAPgQ1tYWxsYUBzdW4uY29tMA0G +CSqGSIb3DQEBBAUAA0EAMlbfBg/ff0Xkv4DOR5LEqmfTZKqgdlD81cXynfzlF7XfnOqI6hPIA90I +x5Ql0ejivIJAYcMGUyA+/YwJg2FGoA== + + + + + 128 + + + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java index 126d0e592f..28f0915189 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java @@ -170,6 +170,10 @@ public class KeycloakServer { System.setProperty("keycloak.theme.cacheTemplates", "false"); } + if (!System.getProperties().containsKey("keycloak.theme.cacheThemes")) { + System.setProperty("keycloak.theme.cacheThemes", "false"); + } + if (!System.getProperties().containsKey("keycloak.theme.staticMaxAge")) { System.setProperty("keycloak.theme.staticMaxAge", "-1"); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java index bb3351542e..42870a6642 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java @@ -180,7 +180,6 @@ public class RealmTest extends AbstractClientTest { String description = IOUtils.toString(getClass().getResourceAsStream("/client-descriptions/client-oidc.json")); ClientRepresentation converted = realm.convertClientDescription(description); - assertEquals(36, converted.getClientId().length()); assertEquals(1, converted.getRedirectUris().size()); assertEquals("http://localhost", converted.getRedirectUris().get(0)); }