Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
980a1eca5e
49 changed files with 1182 additions and 562 deletions
10
.travis.yml
10
.travis.yml
|
@ -3,14 +3,8 @@ language: java
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
|
|
||||||
cache:
|
install:
|
||||||
directories:
|
- travis_wait mvn install -Pdistribution -DskipTests=true -B -V -q
|
||||||
- $HOME/.m2
|
|
||||||
|
|
||||||
before_cache:
|
|
||||||
- rm -rf $HOME/.m2/repository/org/keycloak
|
|
||||||
|
|
||||||
install: mvn install -Pdistribution -DskipTests=true -B -V
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- mvn test -B
|
- mvn test -B
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.apache.http.HttpRequest;
|
||||||
import org.keycloak.common.util.Base64;
|
import org.keycloak.common.util.Base64;
|
||||||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -21,11 +22,14 @@ public abstract class Auth {
|
||||||
return new BearerTokenAuth(initialAccess.getToken());
|
return new BearerTokenAuth(initialAccess.getToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Auth token(ClientRepresentation client) {
|
public static Auth token(ClientRepresentation client) {
|
||||||
return new BearerTokenAuth(client.getRegistrationAccessToken());
|
return new BearerTokenAuth(client.getRegistrationAccessToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Auth token(OIDCClientRepresentation client) {
|
||||||
|
return new BearerTokenAuth(client.getRegistrationAccessToken());
|
||||||
|
}
|
||||||
|
|
||||||
public static Auth client(String clientId, String clientSecret) {
|
public static Auth client(String clientId, String clientSecret) {
|
||||||
return new BasicAuth(clientId, clientSecret);
|
return new BasicAuth(clientId, clientSecret);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@ package org.keycloak.client.registration;
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.apache.http.impl.client.HttpClients;
|
import org.apache.http.impl.client.HttpClients;
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
|
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -18,10 +20,17 @@ public class ClientRegistration {
|
||||||
public static final ObjectMapper outputMapper = new ObjectMapper();
|
public static final ObjectMapper outputMapper = new ObjectMapper();
|
||||||
static {
|
static {
|
||||||
outputMapper.getSerializationConfig().addMixInAnnotations(ClientRepresentation.class, ClientRepresentationMixIn.class);
|
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 DEFAULT = "default";
|
||||||
private final String INSTALLATION = "install";
|
private final String INSTALLATION = "install";
|
||||||
|
private final String OIDC = "openid-connect";
|
||||||
|
private final String SAML = "saml2-entity-descriptor";
|
||||||
|
|
||||||
private HttpUtil httpUtil;
|
private HttpUtil httpUtil;
|
||||||
|
|
||||||
|
@ -47,23 +56,23 @@ public class ClientRegistration {
|
||||||
|
|
||||||
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
|
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
|
||||||
String content = serialize(client);
|
String content = serialize(client);
|
||||||
InputStream resultStream = httpUtil.doPost(content, DEFAULT);
|
InputStream resultStream = httpUtil.doPost(content, JSON, JSON, DEFAULT);
|
||||||
return deserialize(resultStream, ClientRepresentation.class);
|
return deserialize(resultStream, ClientRepresentation.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientRepresentation get(String clientId) throws ClientRegistrationException {
|
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;
|
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
|
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;
|
return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
|
public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
|
||||||
String content = serialize(client);
|
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;
|
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,10 +84,17 @@ public class ClientRegistration {
|
||||||
httpUtil.doDelete(DEFAULT, clientId);
|
httpUtil.doDelete(DEFAULT, clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String serialize(ClientRepresentation client) throws ClientRegistrationException {
|
public OIDCClientRegistration oidc() {
|
||||||
try {
|
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) {
|
} catch (IOException e) {
|
||||||
throw new ClientRegistrationException("Failed to write json object", 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 {
|
public static class ClientRegistrationBuilder {
|
||||||
|
|
||||||
private String url;
|
private String url;
|
||||||
|
|
|
@ -33,12 +33,12 @@ class HttpUtil {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream doPost(String content, String... path) throws ClientRegistrationException {
|
InputStream doPost(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
|
||||||
try {
|
try {
|
||||||
HttpPost request = new HttpPost(getUrl(baseUri, path));
|
HttpPost request = new HttpPost(getUrl(baseUri, path));
|
||||||
|
|
||||||
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
|
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
|
||||||
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
request.setHeader(HttpHeaders.ACCEPT, acceptType);
|
||||||
request.setEntity(new StringEntity(content));
|
request.setEntity(new StringEntity(content));
|
||||||
|
|
||||||
addAuth(request);
|
addAuth(request);
|
||||||
|
@ -60,11 +60,11 @@ class HttpUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream doGet(String... path) throws ClientRegistrationException {
|
InputStream doGet(String acceptType, String... path) throws ClientRegistrationException {
|
||||||
try {
|
try {
|
||||||
HttpGet request = new HttpGet(getUrl(baseUri, path));
|
HttpGet request = new HttpGet(getUrl(baseUri, path));
|
||||||
|
|
||||||
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
request.setHeader(HttpHeaders.ACCEPT, acceptType);
|
||||||
|
|
||||||
addAuth(request);
|
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 {
|
try {
|
||||||
HttpPut request = new HttpPut(getUrl(baseUri, path));
|
HttpPut request = new HttpPut(getUrl(baseUri, path));
|
||||||
|
|
||||||
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
|
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
|
||||||
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
request.setHeader(HttpHeaders.ACCEPT, acceptType);
|
||||||
request.setEntity(new StringEntity(content));
|
request.setEntity(new StringEntity(content));
|
||||||
|
|
||||||
addAuth(request);
|
addAuth(request);
|
||||||
|
@ -134,7 +134,7 @@ class HttpUtil {
|
||||||
response.getEntity().getContent().close();
|
response.getEntity().getContent().close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.getStatusLine().getStatusCode() != 200) {
|
if (response.getStatusLine().getStatusCode() != 204) {
|
||||||
throw new HttpErrorException(response.getStatusLine());
|
throw new HttpErrorException(response.getStatusLine());
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.keycloak.client.registration;
|
||||||
|
|
||||||
|
import org.codehaus.jackson.annotate.JsonIgnore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
|
@ -62,6 +62,8 @@ public class ClientRegistrationCLI {
|
||||||
CommandContainer command = registry.getCommand(args[0], null);
|
CommandContainer command = registry.getCommand(args[0], null);
|
||||||
ParserGenerator.parseAndPopulate(command, args[0], Arrays.copyOfRange(args, 1, args.length));
|
ParserGenerator.parseAndPopulate(command, args[0], Arrays.copyOfRange(args, 1, args.length));
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
//commandInvocation.getCommandRegistry().getAllCommandNames()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,5 +73,7 @@
|
||||||
<column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
|
<column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
|
||||||
</addColumn>
|
</addColumn>
|
||||||
|
|
||||||
|
<modifyDataType tableName="REALM" columnName="PASSWORD_POLICY" newDataType="VARCHAR(2550)"/>
|
||||||
|
|
||||||
</changeSet>
|
</changeSet>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
|
@ -0,0 +1,223 @@
|
||||||
|
package org.keycloak.representations.oidc;
|
||||||
|
|
||||||
|
import org.codehaus.jackson.annotate.JsonAutoDetect;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||||
|
public class OIDCClientRepresentation {
|
||||||
|
|
||||||
|
private List<String> 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<String> getRedirectUris() {
|
||||||
|
return redirect_uris;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRedirectUris(List<String> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,15 +10,15 @@
|
||||||
<para>
|
<para>
|
||||||
The Client Registration Service provides built-in support for Keycloak Client Representations, OpenID Connect
|
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
|
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 <literal><KEYCLOAK URL>/clients/<provider></literal>.
|
if required. The Client Registration Service endpoint is <literal><KEYCLOAK URL>/realms/<realm>/clients/<provider></literal>.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
The built-in supported <literal>providers</literal> are:
|
The built-in supported <literal>providers</literal> are:
|
||||||
<itemizedlist>
|
<itemizedlist>
|
||||||
<listitem><literal>default</literal> Keycloak Representations</listitem>
|
<listitem><literal>default</literal> Keycloak Representations</listitem>
|
||||||
<listitem><literal>install</literal> Keycloak Adapter Configuration</listitem>
|
<listitem><literal>install</literal> Keycloak Adapter Configuration</listitem>
|
||||||
<!--<listitem><literal>openid-connect</literal> OpenID Connect Dynamic Client Registration</listitem>-->
|
<listitem><literal>openid-connect</literal> OpenID Connect Dynamic Client Registration</listitem>
|
||||||
<!--<listitem><literal>saml-ed</literal> SAML Entity Descriptors</listitem>-->
|
<listitem><literal>saml2-entity-descriptor</literal> SAML Entity Descriptors</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
The following sections will describe how to use the different providers.
|
The following sections will describe how to use the different providers.
|
||||||
</para>
|
</para>
|
||||||
|
@ -106,30 +106,30 @@ Authorization: bearer eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJmMjJmNzQyYy04ZjNlLTQ2M....
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
To create a client create a Client Representation (JSON) then do a HTTP POST to:
|
To create a client create a Client Representation (JSON) then do a HTTP POST to:
|
||||||
<literal><KEYCLOAK URL>/clients/<provider>/default</literal>. It will return a Client Representation
|
<literal><KEYCLOAK URL>/realms/<realm>/clients/<provider>/default</literal>. It will return a Client Representation
|
||||||
that also includes the registration access token. You should save the registration access token somewhere
|
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.
|
if you want to retrieve the config, update or delete the client later.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
To retrieve the Client Representation then do a HTTP GET to:
|
To retrieve the Client Representation then do a HTTP GET to:
|
||||||
<literal><KEYCLOAK URL>/clients/<provider>/default/<client id></literal>. It will also
|
<literal><KEYCLOAK URL>/realms/<realm>clients/<provider>/default/<client id></literal>. It will also
|
||||||
return a new registration access token.
|
return a new registration access token.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
To update the Client Representation then do a HTTP PUT to with the updated Client Representation to:
|
To update the Client Representation then do a HTTP PUT to with the updated Client Representation to:
|
||||||
<literal><KEYCLOAK URL>/clients/<provider>/default/<client id></literal>. It will also
|
<literal><KEYCLOAK URL>/realms/<realm>/clients/<provider>/default/<client id></literal>. It will also
|
||||||
return a new registration access token.
|
return a new registration access token.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
To delete the Client Representation then do a HTTP DELETE to:
|
To delete the Client Representation then do a HTTP DELETE to:
|
||||||
<literal><KEYCLOAK URL>/clients/<provider>/default/<client id></literal>
|
<literal><KEYCLOAK URL>/realms/<realm>/clients/<provider>/default/<client id></literal>
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<title>Keycloak Adapter Configuration</title>
|
<title>Keycloak Adapter Configuration</title>
|
||||||
<para>
|
<para>
|
||||||
The <default>installation</default> client registration provider can be used to retrieve the adapter configuration
|
The <literal>installation</literal> 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
|
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:
|
HTTP basic authentication. To do this include the following header in the request:
|
||||||
<programlisting><![CDATA[
|
<programlisting><![CDATA[
|
||||||
|
@ -138,7 +138,7 @@ Authorization: basic BASE64(client-id + ':' + client-secret)
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
To retrieve the Adapter Configuration then do a HTTP GET to:
|
To retrieve the Adapter Configuration then do a HTTP GET to:
|
||||||
<literal><KEYCLOAK URL>/clients/<provider>/installation/<client id></literal>
|
<literal><KEYCLOAK URL>//realms/<realm>clients/<provider>/installation/<client id></literal>
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
No authentication is required for public clients. This means that for the JavaScript adapter you can
|
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)
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!--
|
|
||||||
<section>
|
<section>
|
||||||
<title>OpenID Connect Dynamic Client Registration</title>
|
<title>OpenID Connect Dynamic Client Registration</title>
|
||||||
<para>
|
<para>
|
||||||
TODO
|
Keycloak implements <ulink url="https://openid.net/specs/openid-connect-registration-1_0.html">OpenID Connect Dynamic Client Registration</ulink>,
|
||||||
|
which extends <ulink url="https://tools.ietf.org/html/rfc7591">OAuth 2.0 Dynamic Client Registration Protocol</ulink> and
|
||||||
|
<ulink url="https://tools.ietf.org/html/rfc7592">OAuth 2.0 Dynamic Client Registration Management Protocol</ulink>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The endpoint to use these specifications to register clients in Keycloak is:
|
||||||
|
<literal><KEYCLOAK URL>/realms/<realm>/clients/<provider>/oidc[/<client id>]</literal>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
This endpoints can also be found in the OpenID Connect Discovery endpoint for the realm:
|
||||||
|
<literal><KEYCLOAK URL>/realms/<realm>/.well-known/openid-configuration</literal>.
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<section>
|
<section>
|
||||||
<title>SAML Entity Descriptors</title>
|
<title>SAML Entity Descriptors</title>
|
||||||
<para>
|
<para>
|
||||||
TODO
|
The SAML Entity Descriptor endpoint only supports using SAML v2 Entity Descriptors to create clients. It
|
||||||
|
doesn't support retrieving, updating or deleting clients. For those operations the Keycloak representation
|
||||||
|
endpoints should be used. When creating a client a Keycloak Client Representation is returned with details
|
||||||
|
about the created client, including a registration access token.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
To create a client do a HTTP POST with the SAML Entity Descriptor to:
|
||||||
|
<literal><KEYCLOAK URL>/realms/<realm>/clients/<provider>/saml2-entity-descriptor</literal>.
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
-->
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<title>Client Registration Java API</title>
|
<title>Client Registration Java API</title>
|
||||||
|
|
|
@ -153,6 +153,10 @@ public class ExtendingThemeManager implements ThemeProvider {
|
||||||
|
|
||||||
private List<Theme> themes;
|
private List<Theme> themes;
|
||||||
|
|
||||||
|
private Properties properties;
|
||||||
|
|
||||||
|
private ConcurrentHashMap<String, ConcurrentHashMap<Locale, Properties>> messages = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public ExtendingTheme(List<Theme> themes) {
|
public ExtendingTheme(List<Theme> themes) {
|
||||||
this.themes = themes;
|
this.themes = themes;
|
||||||
}
|
}
|
||||||
|
@ -229,28 +233,41 @@ public class ExtendingThemeManager implements ThemeProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
|
public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
|
||||||
Properties messages = new Properties();
|
if (messages.get(baseBundlename) == null || messages.get(baseBundlename).get(locale) == null) {
|
||||||
ListIterator<Theme> itr = themes.listIterator(themes.size());
|
Properties messages = new Properties();
|
||||||
while (itr.hasPrevious()) {
|
ListIterator<Theme> itr = themes.listIterator(themes.size());
|
||||||
Properties m = itr.previous().getMessages(baseBundlename, locale);
|
while (itr.hasPrevious()) {
|
||||||
if (m != null) {
|
Properties m = itr.previous().getMessages(baseBundlename, locale);
|
||||||
messages.putAll(m);
|
if (m != null) {
|
||||||
|
messages.putAll(m);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.messages.putIfAbsent(baseBundlename, new ConcurrentHashMap<Locale, Properties>());
|
||||||
|
this.messages.get(baseBundlename).putIfAbsent(locale, messages);
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
} else {
|
||||||
|
return messages.get(baseBundlename).get(locale);
|
||||||
}
|
}
|
||||||
return messages;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Properties getProperties() throws IOException {
|
public Properties getProperties() throws IOException {
|
||||||
Properties properties = new Properties();
|
if (properties == null) {
|
||||||
ListIterator<Theme> itr = themes.listIterator(themes.size());
|
Properties properties = new Properties();
|
||||||
while (itr.hasPrevious()) {
|
ListIterator<Theme> itr = themes.listIterator(themes.size());
|
||||||
Properties p = itr.previous().getProperties();
|
while (itr.hasPrevious()) {
|
||||||
if (p != null) {
|
Properties p = itr.previous().getProperties();
|
||||||
properties.putAll(p);
|
if (p != null) {
|
||||||
|
properties.putAll(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this.properties = properties;
|
||||||
|
return properties;
|
||||||
|
} else {
|
||||||
|
return properties;
|
||||||
}
|
}
|
||||||
return properties;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -271,7 +271,7 @@ client-certificate-import=Client Certificate Import
|
||||||
import-client-certificate=Import Client Certificate
|
import-client-certificate=Import Client Certificate
|
||||||
jwt-import.key-alias.tooltip=Archive alias for your certificate.
|
jwt-import.key-alias.tooltip=Archive alias for your certificate.
|
||||||
secret=Secret
|
secret=Secret
|
||||||
regenerate-secret=Regenerate Secretsecret=Secret
|
regenerate-secret=Regenerate Secret
|
||||||
registrationAccessToken=Registration access token
|
registrationAccessToken=Registration access token
|
||||||
registrationAccessToken.regenerate=Regenerate registration access token
|
registrationAccessToken.regenerate=Regenerate registration access token
|
||||||
registrationAccessToken.tooltip=The registration access token provides access for clients to the client registration service.
|
registrationAccessToken.tooltip=The registration access token provides access for clients to the client registration service.
|
||||||
|
|
|
@ -51,18 +51,18 @@
|
||||||
<kc-tooltip>How far ahead should the server look just in case the token generator and server are out of time sync or counter sync?</kc-tooltip>
|
<kc-tooltip>How far ahead should the server look just in case the token generator and server are out of time sync or counter sync?</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" data-ng-show="realm.otpPolicyType == 'hotp'">
|
<div class="form-group" data-ng-if="realm.otpPolicyType == 'hotp'">
|
||||||
<label class="col-md-2 control-label" for="counter">Initial Counter</label>
|
<label class="col-md-2 control-label" for="counter">Initial Counter</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" type="number" required min="1" max="120" id="counter" name="counter" data-ng-model="realm.otpPolicyInitialCounter" autofocus>
|
<input class="form-control" type="number" data-ng-required="realm.otpPolicyType == 'hotp'" min="1" max="120" id="counter" name="counter" data-ng-model="realm.otpPolicyInitialCounter" autofocus>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>What should the initial counter value be?</kc-tooltip>
|
<kc-tooltip>What should the initial counter value be?</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" data-ng-show="realm.otpPolicyType == 'totp'">
|
<div class="form-group" data-ng-if="realm.otpPolicyType == 'totp'">
|
||||||
<label class="col-md-2 control-label" for="counter">OTP Token Period</label>
|
<label class="col-md-2 control-label" for="counter">OTP Token Period</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" type="number" required min="1" max="120" id="period" name="period" data-ng-model="realm.otpPolicyPeriod">
|
<input class="form-control" type="number" data-ng-required="realm.otpPolicyType == 'totp'" min="1" max="120" id="period" name="period" data-ng-model="realm.otpPolicyPeriod">
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>How many seconds should an OTP token be valid? Defaults to 30 seconds.</kc-tooltip>
|
<kc-tooltip>How many seconds should an OTP token be valid? Defaults to 30 seconds.</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -76,6 +76,8 @@ public class PasswordPolicy implements Serializable {
|
||||||
list.add(new PasswordHistory(arg));
|
list.add(new PasswordHistory(arg));
|
||||||
} else if (name.equals(ForceExpiredPasswordChange.NAME)) {
|
} else if (name.equals(ForceExpiredPasswordChange.NAME)) {
|
||||||
list.add(new ForceExpiredPasswordChange(arg));
|
list.add(new ForceExpiredPasswordChange(arg));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported policy");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
|
|
@ -51,6 +51,10 @@ public class CredentialValidation {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean validateHashedCredential(RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) {
|
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());
|
boolean validated = new Pbkdf2PasswordEncoder(credential.getSalt()).verify(unhashedCredValue, credential.getValue(), credential.getHashIterations());
|
||||||
if (validated) {
|
if (validated) {
|
||||||
int iterations = hashIterations(realm);
|
int iterations = hashIterations(realm);
|
||||||
|
|
|
@ -69,12 +69,12 @@ public class RepresentationToModel {
|
||||||
private static Logger logger = Logger.getLogger(RepresentationToModel.class);
|
private static Logger logger = Logger.getLogger(RepresentationToModel.class);
|
||||||
public static OTPPolicy toPolicy(RealmRepresentation rep) {
|
public static OTPPolicy toPolicy(RealmRepresentation rep) {
|
||||||
OTPPolicy policy = new OTPPolicy();
|
OTPPolicy policy = new OTPPolicy();
|
||||||
policy.setType(rep.getOtpPolicyType());
|
if (rep.getOtpPolicyType() != null) policy.setType(rep.getOtpPolicyType());
|
||||||
policy.setLookAheadWindow(rep.getOtpPolicyLookAheadWindow());
|
if (rep.getOtpPolicyLookAheadWindow() != null) policy.setLookAheadWindow(rep.getOtpPolicyLookAheadWindow());
|
||||||
policy.setInitialCounter(rep.getOtpPolicyInitialCounter());
|
if (rep.getOtpPolicyInitialCounter() != null) policy.setInitialCounter(rep.getOtpPolicyInitialCounter());
|
||||||
policy.setAlgorithm(rep.getOtpPolicyAlgorithm());
|
if (rep.getOtpPolicyAlgorithm() != null) policy.setAlgorithm(rep.getOtpPolicyAlgorithm());
|
||||||
policy.setDigits(rep.getOtpPolicyDigits());
|
if (rep.getOtpPolicyDigits() != null) policy.setDigits(rep.getOtpPolicyDigits());
|
||||||
policy.setPeriod(rep.getOtpPolicyPeriod());
|
if (rep.getOtpPolicyPeriod() != null) policy.setPeriod(rep.getOtpPolicyPeriod());
|
||||||
return policy;
|
return policy;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,15 @@ public class PasswordPolicyTest {
|
||||||
Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage());
|
Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage());
|
||||||
Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
|
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
|
@Test
|
||||||
public void testRegexPatterns() {
|
public void testRegexPatterns() {
|
||||||
|
|
|
@ -1,63 +1,35 @@
|
||||||
package org.keycloak.protocol.saml.clientregistration;
|
package org.keycloak.protocol.saml.clientregistration;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||||
import org.keycloak.events.EventBuilder;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.services.clientregistration.ClientRegistrationAuth;
|
import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
|
||||||
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
|
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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class EntityDescriptorClientRegistrationProvider implements ClientRegistrationProvider {
|
public class EntityDescriptorClientRegistrationProvider extends AbstractClientRegistrationProvider {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(EntityDescriptorClientRegistrationProvider.class);
|
|
||||||
|
|
||||||
private KeycloakSession session;
|
|
||||||
private EventBuilder event;
|
|
||||||
private ClientRegistrationAuth auth;
|
|
||||||
|
|
||||||
public EntityDescriptorClientRegistrationProvider(KeycloakSession session) {
|
public EntityDescriptorClientRegistrationProvider(KeycloakSession session) {
|
||||||
this.session = session;
|
super(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @POST
|
@POST
|
||||||
// @Consumes(MediaType.APPLICATION_XML)
|
@Consumes(MediaType.APPLICATION_XML)
|
||||||
// @Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
// public Response create(String descriptor) {
|
public Response createSaml(String descriptor) {
|
||||||
// event.event(EventType.CLIENT_REGISTER);
|
ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor);
|
||||||
//
|
client = create(client);
|
||||||
// auth.requireCreate();
|
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
|
||||||
//
|
return Response.created(uri).entity(client).build();
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,24 +148,17 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
|
||||||
public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap<String, String> inputData) {
|
public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap<String, String> inputData) {
|
||||||
List<UserCredentialModel> credentials = new LinkedList<>();
|
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||||
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
||||||
if (password == null || password.isEmpty()) {
|
|
||||||
invalidPassword(context, user);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
credentials.add(UserCredentialModel.password(password));
|
credentials.add(UserCredentialModel.password(password));
|
||||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), user, credentials);
|
boolean valid = context.getSession().users().validCredentials(context.getRealm(), user, credentials);
|
||||||
if (!valid) {
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,15 +31,6 @@ public class ValidatePassword extends AbstractDirectGrantAuthenticator {
|
||||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
List<UserCredentialModel> credentials = new LinkedList<>();
|
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||||
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
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));
|
credentials.add(UserCredentialModel.password(password));
|
||||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
|
|
|
@ -5,8 +5,7 @@ import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||||
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
|
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||||
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
|
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.services.clientregistration.oidc.DescriptionConverter;
|
import org.keycloak.services.clientregistration.oidc.DescriptionConverter;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
|
@ -4,6 +4,8 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
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.resources.RealmsResource;
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
import org.keycloak.wellknown.WellKnownProvider;
|
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.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.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.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.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
|
||||||
config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);
|
config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);
|
||||||
|
|
|
@ -1,177 +0,0 @@
|
||||||
package org.keycloak.protocol.oidc.representations;
|
|
||||||
|
|
||||||
import org.codehaus.jackson.annotate.JsonProperty;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRedirectUris(List<String> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -7,7 +7,6 @@ import org.codehaus.jackson.annotate.JsonProperty;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -48,6 +47,9 @@ public class OIDCConfigurationRepresentation {
|
||||||
@JsonProperty("response_modes_supported")
|
@JsonProperty("response_modes_supported")
|
||||||
private List<String> responseModesSupported;
|
private List<String> responseModesSupported;
|
||||||
|
|
||||||
|
@JsonProperty("registration_endpoint")
|
||||||
|
private String registrationEndpoint;
|
||||||
|
|
||||||
protected Map<String, Object> otherClaims = new HashMap<String, Object>();
|
protected Map<String, Object> otherClaims = new HashMap<String, Object>();
|
||||||
|
|
||||||
public String getIssuer() {
|
public String getIssuer() {
|
||||||
|
@ -138,6 +140,14 @@ public class OIDCConfigurationRepresentation {
|
||||||
this.responseModesSupported = responseModesSupported;
|
this.responseModesSupported = responseModesSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRegistrationEndpoint() {
|
||||||
|
return registrationEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegistrationEndpoint(String registrationEndpoint) {
|
||||||
|
this.registrationEndpoint = registrationEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonAnyGetter
|
@JsonAnyGetter
|
||||||
public Map<String, Object> getOtherClaims() {
|
public Map<String, Object> getOtherClaims() {
|
||||||
return otherClaims;
|
return otherClaims;
|
||||||
|
|
|
@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ public class ClientRegistrationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("{provider}")
|
@Path("{provider}")
|
||||||
public Object getProvider(@PathParam("provider") String providerId) {
|
public Object provider(@PathParam("provider") String providerId) {
|
||||||
checkSsl();
|
checkSsl();
|
||||||
|
|
||||||
ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
|
ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
|
||||||
|
|
|
@ -2,14 +2,11 @@ package org.keycloak.services.clientregistration;
|
||||||
|
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.ClientInitialAccessModel;
|
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.RepresentationToModel;
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.services.ErrorResponse;
|
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
@ -19,101 +16,41 @@ import java.net.URI;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class DefaultClientRegistrationProvider implements ClientRegistrationProvider {
|
public class DefaultClientRegistrationProvider extends AbstractClientRegistrationProvider {
|
||||||
|
|
||||||
private KeycloakSession session;
|
|
||||||
private EventBuilder event;
|
|
||||||
private ClientRegistrationAuth auth;
|
|
||||||
|
|
||||||
public DefaultClientRegistrationProvider(KeycloakSession session) {
|
public DefaultClientRegistrationProvider(KeycloakSession session) {
|
||||||
this.session = session;
|
super(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response create(ClientRepresentation client) {
|
public Response createDefault(ClientRepresentation client) {
|
||||||
event.event(EventType.CLIENT_REGISTER);
|
client = create(client);
|
||||||
|
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
|
||||||
auth.requireCreate();
|
return Response.created(uri).entity(client).build();
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{clientId}")
|
@Path("{clientId}")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response get(@PathParam("clientId") String clientId) {
|
public Response getDefault(@PathParam("clientId") String clientId) {
|
||||||
event.event(EventType.CLIENT_INFO);
|
ClientRepresentation client = get(clientId);
|
||||||
|
return Response.ok(client).build();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
@Path("{clientId}")
|
@Path("{clientId}")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public Response update(@PathParam("clientId") String clientId, ClientRepresentation rep) {
|
public Response updateDefault(@PathParam("clientId") String clientId, ClientRepresentation client) {
|
||||||
event.event(EventType.CLIENT_UPDATE).client(clientId);
|
client = update(clientId, client);
|
||||||
|
return Response.ok(client).build();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
@Path("{clientId}")
|
@Path("{clientId}")
|
||||||
public Response delete(@PathParam("clientId") String clientId) {
|
public void deleteDefault(@PathParam("clientId") String clientId) {
|
||||||
event.event(EventType.CLIENT_DELETE).client(clientId);
|
delete(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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.keycloak.services.clientregistration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public interface ErrorCodes {
|
||||||
|
|
||||||
|
String INVALID_REDIRECT_URI = "invalid_redirect_uri";
|
||||||
|
|
||||||
|
String INVALID_CLIENT_METADATA = "invalid_client_metadata";
|
||||||
|
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
package org.keycloak.services.clientregistration.oidc;
|
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.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -11,27 +12,22 @@ public class DescriptionConverter {
|
||||||
|
|
||||||
public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) {
|
public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) {
|
||||||
ClientRepresentation client = new ClientRepresentation();
|
ClientRepresentation client = new ClientRepresentation();
|
||||||
client.setClientId(KeycloakModelUtils.generateId());
|
client.setClientId(clientOIDC.getClientId());
|
||||||
client.setName(clientOIDC.getClientName());
|
client.setName(clientOIDC.getClientName());
|
||||||
client.setRedirectUris(clientOIDC.getRedirectUris());
|
client.setRedirectUris(clientOIDC.getRedirectUris());
|
||||||
client.setBaseUrl(clientOIDC.getClientUri());
|
client.setBaseUrl(clientOIDC.getClientUri());
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OIDCClientResponseRepresentation toExternalResponse(ClientRepresentation client) {
|
public static OIDCClientRepresentation toExternalResponse(ClientRepresentation client, URI uri) {
|
||||||
OIDCClientResponseRepresentation response = new OIDCClientResponseRepresentation();
|
OIDCClientRepresentation response = new OIDCClientRepresentation();
|
||||||
response.setClientId(client.getClientId());
|
response.setClientId(client.getClientId());
|
||||||
|
|
||||||
response.setClientName(client.getName());
|
response.setClientName(client.getName());
|
||||||
response.setClientUri(client.getBaseUrl());
|
response.setClientUri(client.getBaseUrl());
|
||||||
|
|
||||||
response.setClientSecret(client.getSecret());
|
response.setClientSecret(client.getSecret());
|
||||||
response.setClientSecretExpiresAt(0);
|
|
||||||
|
|
||||||
response.setRedirectUris(client.getRedirectUris());
|
response.setRedirectUris(client.getRedirectUris());
|
||||||
|
|
||||||
response.setRegistrationAccessToken(client.getRegistrationAccessToken());
|
response.setRegistrationAccessToken(client.getRegistrationAccessToken());
|
||||||
|
response.setRegistrationClientUri(uri.toString());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,72 +1,70 @@
|
||||||
package org.keycloak.services.clientregistration.oidc;
|
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.EventBuilder;
|
||||||
import org.keycloak.models.KeycloakSession;
|
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.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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class OIDCClientRegistrationProvider implements ClientRegistrationProvider {
|
public class OIDCClientRegistrationProvider extends AbstractClientRegistrationProvider {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(OIDCClientRegistrationProvider.class);
|
|
||||||
|
|
||||||
private KeycloakSession session;
|
|
||||||
private EventBuilder event;
|
|
||||||
private ClientRegistrationAuth auth;
|
|
||||||
|
|
||||||
public OIDCClientRegistrationProvider(KeycloakSession session) {
|
public OIDCClientRegistrationProvider(KeycloakSession session) {
|
||||||
this.session = session;
|
super(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @POST
|
@POST
|
||||||
// @Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
// @Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
// public Response create(OIDCClientRepresentation clientOIDC) {
|
public Response createOIDC(OIDCClientRepresentation clientOIDC) {
|
||||||
// event.event(EventType.CLIENT_REGISTER);
|
if (clientOIDC.getClientId() != null) {
|
||||||
//
|
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier included", Response.Status.BAD_REQUEST);
|
||||||
// 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");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Override
|
ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
|
||||||
public void close() {
|
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
|
@Override
|
||||||
|
@ -79,4 +77,8 @@ public class OIDCClientRegistrationProvider implements ClientRegistrationProvide
|
||||||
this.event = event;
|
this.event = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ import org.keycloak.services.clientregistration.ClientRegistrationProviderFactor
|
||||||
*/
|
*/
|
||||||
public class OIDCClientRegistrationProviderFactory implements ClientRegistrationProviderFactory {
|
public class OIDCClientRegistrationProviderFactory implements ClientRegistrationProviderFactory {
|
||||||
|
|
||||||
|
public static final String ID = "openid-connect";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientRegistrationProvider create(KeycloakSession session) {
|
public ClientRegistrationProvider create(KeycloakSession session) {
|
||||||
return new OIDCClientRegistrationProvider(session);
|
return new OIDCClientRegistrationProvider(session);
|
||||||
|
@ -30,7 +32,7 @@ public class OIDCClientRegistrationProviderFactory implements ClientRegistration
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return "openid-connect";
|
return ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -59,6 +59,10 @@ public class RealmsResource {
|
||||||
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getProtocol");
|
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) {
|
public static UriBuilder brokerUrl(UriInfo uriInfo) {
|
||||||
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getBrokerService");
|
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getBrokerService");
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,7 +236,7 @@ public class RealmAdminResource {
|
||||||
} catch (ModelDuplicateException e) {
|
} catch (ModelDuplicateException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception 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);
|
return ErrorResponse.error("Failed to update " + rep.getRealm() + " Realm.", Response.Status.INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,13 @@
|
||||||
<type>zip</type>
|
<type>zip</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.wildfly</groupId>
|
||||||
<artifactId>keycloak-as7-adapter-dist</artifactId>
|
<artifactId>wildfly-arquillian-container-managed</artifactId>
|
||||||
<type>zip</type>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.as</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>jboss-as-arquillian-container-managed</artifactId>
|
<artifactId>keycloak-eap6-adapter-dist</artifactId>
|
||||||
<version>7.2.0.Final</version>
|
<type>zip</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -67,18 +66,20 @@
|
||||||
<artifactItem>
|
<artifactItem>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-as7-adapter-dist</artifactId>
|
<artifactId>keycloak-as7-adapter-dist</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
<type>zip</type>
|
<type>zip</type>
|
||||||
<outputDirectory>${adapter.libs.as7}</outputDirectory>
|
<outputDirectory>${adapter.libs.as7}</outputDirectory>
|
||||||
</artifactItem>
|
</artifactItem>
|
||||||
</artifactItems>
|
</artifactItems>
|
||||||
<overWriteIfNewer>true</overWriteIfNewer>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.18.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<app.server.as7>true</app.server.as7>
|
<app.server.as7>true</app.server.as7>
|
||||||
|
@ -90,7 +91,6 @@
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
<profile>
|
<profile>
|
||||||
<id>adapter-libs-provided</id>
|
<id>adapter-libs-provided</id>
|
||||||
|
@ -133,5 +133,4 @@
|
||||||
</build>
|
</build>
|
||||||
</profile>
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
<property name="jbossHome">${app.server.as7.home}</property>
|
<property name="jbossHome">${app.server.as7.home}</property>
|
||||||
<property name="javaVmArguments">-Djboss.socket.binding.port-offset=${app.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
|
<property name="javaVmArguments">-Djboss.socket.binding.port-offset=${app.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
|
||||||
<property name="managementAddress">localhost</property>
|
<property name="managementAddress">localhost</property>
|
||||||
|
<property name="managementProtocol">remote</property>
|
||||||
<property name="managementPort">${app.server.management.port.jmx}</property>
|
<property name="managementPort">${app.server.management.port.jmx}</property>
|
||||||
</configuration>
|
</configuration>
|
||||||
</container>
|
</container>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.testsuite.adapter.servlet;
|
package org.keycloak.testsuite.adapter.servlet;
|
||||||
|
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,6 +8,7 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
*/
|
*/
|
||||||
@AppServerContainer("app-server-as7")
|
@AppServerContainer("app-server-as7")
|
||||||
|
@AdapterLibsLocationProperty("adapter.libs.as7")
|
||||||
public class AS7DemoServletsAdapterTest extends AbstractDemoServletsAdapterTest {
|
public class AS7DemoServletsAdapterTest extends AbstractDemoServletsAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.testsuite.adapter.servlet;
|
package org.keycloak.testsuite.adapter.servlet;
|
||||||
|
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,6 +8,7 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
*/
|
*/
|
||||||
@AppServerContainer("app-server-as7")
|
@AppServerContainer("app-server-as7")
|
||||||
|
@AdapterLibsLocationProperty("adapter.libs.as7")
|
||||||
public class AS7SessionServletAdapterTest extends AbstractSessionServletAdapterTest {
|
public class AS7SessionServletAdapterTest extends AbstractSessionServletAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
135
testsuite/integration-arquillian/tests/adapters/eap6/pom.xml
Normal file
135
testsuite/integration-arquillian/tests/adapters/eap6/pom.xml
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
|
<artifactId>integration-arquillian-tests-adapters</artifactId>
|
||||||
|
<version>1.7.0.Final-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>integration-arquillian-adapters-eap6</artifactId>
|
||||||
|
<name>Adapter Tests on EAP 6</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<app.server.eap6.home>${containers.home}/jboss-eap-6.4</app.server.eap6.home>
|
||||||
|
<adapter.libs.eap6>${containers.home}/keycloak-eap6-adapter-dist</adapter.libs.eap6>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.as</groupId>
|
||||||
|
<artifactId>jboss-as-dist</artifactId>
|
||||||
|
<version>${jboss.version}</version>
|
||||||
|
<type>zip</type>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.wildfly</groupId>
|
||||||
|
<artifactId>wildfly-arquillian-container-managed</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-eap6-adapter-dist</artifactId>
|
||||||
|
<type>zip</type>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<skip>true</skip>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>unpack-as7-and-adapter</id>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>unpack</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<artifactItems>
|
||||||
|
<artifactItem>
|
||||||
|
<groupId>org.jboss.as</groupId>
|
||||||
|
<artifactId>jboss-as-dist</artifactId>
|
||||||
|
<version>${jboss.version}</version>
|
||||||
|
<type>zip</type>
|
||||||
|
<outputDirectory>${containers.home}</outputDirectory>
|
||||||
|
</artifactItem>
|
||||||
|
<artifactItem>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-eap6-adapter-dist</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<type>zip</type>
|
||||||
|
<outputDirectory>${adapter.libs.eap6}</outputDirectory>
|
||||||
|
</artifactItem>
|
||||||
|
</artifactItems>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.18.1</version>
|
||||||
|
<configuration>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<app.server.eap6>true</app.server.eap6>
|
||||||
|
<app.server.eap6.home>${app.server.eap6.home}</app.server.eap6.home>
|
||||||
|
<adapter.libs.eap6>${adapter.libs.eap6}</adapter.libs.eap6>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>adapter-libs-provided</id>
|
||||||
|
<activation>
|
||||||
|
<property>
|
||||||
|
<name>!adapter.libs.bundled</name>
|
||||||
|
</property>
|
||||||
|
</activation>
|
||||||
|
<properties>
|
||||||
|
<adapter.libs.eap6>${app.server.eap6.home}</adapter.libs.eap6>
|
||||||
|
</properties>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>xml-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>configure-adapter-subsystem</id>
|
||||||
|
<phase>process-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>transform</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<transformationSets>
|
||||||
|
<transformationSet>
|
||||||
|
<dir>${app.server.eap6.home}/standalone/configuration</dir>
|
||||||
|
<includes>
|
||||||
|
<include>standalone.xml</include>
|
||||||
|
</includes>
|
||||||
|
<stylesheet>src/main/xslt/standalone.xsl</stylesheet>
|
||||||
|
<outputDir>${app.server.eap6.home}/standalone/configuration</outputDir>
|
||||||
|
</transformationSet>
|
||||||
|
</transformationSets>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
</project>
|
|
@ -0,0 +1,37 @@
|
||||||
|
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:xalan="http://xml.apache.org/xalan"
|
||||||
|
xmlns:a="http://jboss.org/schema/arquillian"
|
||||||
|
version="2.0"
|
||||||
|
exclude-result-prefixes="xalan a">
|
||||||
|
|
||||||
|
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
|
||||||
|
<xsl:strip-space elements="*"/>
|
||||||
|
|
||||||
|
<xsl:template match="/a:arquillian">
|
||||||
|
<xsl:copy>
|
||||||
|
<xsl:apply-templates select="node()|@*"/>
|
||||||
|
|
||||||
|
<container qualifier="app-server-eap6" mode="manual" >
|
||||||
|
<configuration>
|
||||||
|
<property name="enabled">${app.server.eap6}</property>
|
||||||
|
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
|
||||||
|
<property name="jbossHome">${app.server.eap6.home}</property>
|
||||||
|
<property name="javaVmArguments">-Djboss.socket.binding.port-offset=${app.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
|
||||||
|
<property name="managementAddress">localhost</property>
|
||||||
|
<property name="managementProtocol">remote</property>
|
||||||
|
<property name="managementPort">${app.server.management.port.jmx}</property>
|
||||||
|
</configuration>
|
||||||
|
</container>
|
||||||
|
|
||||||
|
</xsl:copy>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
|
||||||
|
<xsl:template match="@*|node()">
|
||||||
|
<xsl:copy>
|
||||||
|
<xsl:apply-templates select="@*|node()" />
|
||||||
|
</xsl:copy>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
|
@ -0,0 +1,51 @@
|
||||||
|
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:xalan="http://xml.apache.org/xalan"
|
||||||
|
xmlns:j="urn:jboss:domain:1.7"
|
||||||
|
xmlns:ds="urn:jboss:domain:datasources:1.2"
|
||||||
|
xmlns:k="urn:jboss:domain:keycloak:1.1"
|
||||||
|
xmlns:sec="urn:jboss:domain:security:1.2"
|
||||||
|
version="2.0"
|
||||||
|
exclude-result-prefixes="xalan j ds k sec">
|
||||||
|
|
||||||
|
<xsl:param name="config"/>
|
||||||
|
|
||||||
|
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
|
||||||
|
<xsl:strip-space elements="*"/>
|
||||||
|
|
||||||
|
<xsl:template match="//j:extensions">
|
||||||
|
<xsl:copy>
|
||||||
|
<xsl:apply-templates select="node()|@*"/>
|
||||||
|
<extension module="org.keycloak.keycloak-adapter-subsystem"/>
|
||||||
|
</xsl:copy>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template match="//j:profile">
|
||||||
|
<xsl:copy>
|
||||||
|
<xsl:apply-templates select="node()|@*"/>
|
||||||
|
<subsystem xmlns="urn:jboss:domain:keycloak:1.1"/>
|
||||||
|
</xsl:copy>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template match="//sec:security-domains">
|
||||||
|
<xsl:copy>
|
||||||
|
<xsl:apply-templates select="node()[name(.)='security-domain']"/>
|
||||||
|
<security-domain name="keycloak">
|
||||||
|
<authentication>
|
||||||
|
<login-module code="org.keycloak.adapters.jboss.KeycloakLoginModule" flag="required"/>
|
||||||
|
</authentication>
|
||||||
|
</security-domain>
|
||||||
|
<security-domain name="sp" cache-type="default">
|
||||||
|
<authentication>
|
||||||
|
<login-module code="org.picketlink.identity.federation.bindings.wildfly.SAML2LoginModule" flag="required"/>
|
||||||
|
</authentication>
|
||||||
|
</security-domain>
|
||||||
|
</xsl:copy>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template match="@*|node()">
|
||||||
|
<xsl:copy>
|
||||||
|
<xsl:apply-templates select="@*|node()" />
|
||||||
|
</xsl:copy>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.util;
|
package org.keycloak.testsuite.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import static org.jboss.arquillian.graphene.Graphene.waitAjax;
|
import static org.jboss.arquillian.graphene.Graphene.waitAjax;
|
||||||
|
@ -31,24 +32,35 @@ import org.openqa.selenium.WebElement;
|
||||||
*/
|
*/
|
||||||
public final class WaitUtils {
|
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) {
|
public static void waitAjaxForElement(WebElement element) {
|
||||||
waitAjax().until()
|
waitAjax().withTimeout(SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS)
|
||||||
.element(element).is().present();
|
.until().element(element).is().present();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void waitAjaxForElementNotPresent(WebElement element) {
|
public static void waitAjaxForElementNotPresent(WebElement element) {
|
||||||
waitAjax().until()
|
waitAjax().withTimeout(SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS)
|
||||||
.element(element).is().not().present();
|
.until().element(element).is().not().present();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void waitAjaxForElementNotVisible(WebElement element) {
|
public static void waitAjaxForElementNotVisible(WebElement element) {
|
||||||
waitAjax().until()
|
waitAjax().withTimeout(SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS)
|
||||||
.element(element).is().not().visible();
|
.until().element(element).is().not().visible();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void waitGuiForElement(By element, String message) {
|
public static void waitGuiForElement(By element, String message) {
|
||||||
waitGui().until(message)
|
waitGui().withTimeout(IMPLICIT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS)
|
||||||
.element(element).is().present();
|
.until(message).element(element).is().present();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void waitGuiForElement(By element) {
|
public static void waitGuiForElement(By element) {
|
||||||
|
@ -60,11 +72,13 @@ public final class WaitUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void waitGuiForElementPresent(WebElement element, String message) {
|
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) {
|
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) {
|
public static void pause(long millis) {
|
||||||
|
@ -74,5 +88,5 @@ public final class WaitUtils {
|
||||||
Logger.getLogger(WaitUtils.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(WaitUtils.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.OIDCLogin;
|
||||||
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
|
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
|
||||||
import org.keycloak.testsuite.util.Timer;
|
import org.keycloak.testsuite.util.Timer;
|
||||||
|
import org.keycloak.testsuite.util.WaitUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -116,9 +117,9 @@ public abstract class AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void driverSettings() {
|
protected void driverSettings() {
|
||||||
driver.manage().timeouts().pageLoadTimeout(5, TimeUnit.SECONDS);
|
driver.manage().timeouts().pageLoadTimeout(WaitUtils.PAGELOAD_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||||
driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
|
driver.manage().timeouts().implicitlyWait(WaitUtils.IMPLICIT_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||||
driver.manage().timeouts().setScriptTimeout(3, TimeUnit.SECONDS);
|
driver.manage().timeouts().setScriptTimeout(WaitUtils.SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||||
driver.manage().window().maximize();
|
driver.manage().window().maximize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<EntityDescriptor
|
||||||
|
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
entityID="loadbalancer-9.siroe.com">
|
||||||
|
<SPSSODescriptor
|
||||||
|
AuthnRequestsSigned="false"
|
||||||
|
WantAssertionsSigned="false"
|
||||||
|
protocolSupportEnumeration=
|
||||||
|
"urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
|
<KeyDescriptor use="signing">
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>
|
||||||
|
MIICYDCCAgqgAwIBAgICBoowDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||||
|
EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz
|
||||||
|
dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh
|
||||||
|
dGUgTWFuYWdlcjAeFw0wNjExMDIxOTExMzRaFw0xMDA3MjkxOTExMzRaMDcxEjAQBgNVBAoTCXNp
|
||||||
|
cm9lLmNvbTEhMB8GA1UEAxMYbG9hZGJhbGFuY2VyLTkuc2lyb2UuY29tMIGfMA0GCSqGSIb3DQEB
|
||||||
|
AQUAA4GNADCBiQKBgQCjOwa5qoaUuVnknqf5pdgAJSEoWlvx/jnUYbkSDpXLzraEiy2UhvwpoBgB
|
||||||
|
EeTSUaPPBvboCItchakPI6Z/aFdH3Wmjuij9XD8r1C+q//7sUO0IGn0ORycddHhoo0aSdnnxGf9V
|
||||||
|
tREaqKm9dJ7Yn7kQHjo2eryMgYxtr/Z5Il5F+wIDAQABo2AwXjARBglghkgBhvhCAQEEBAMCBkAw
|
||||||
|
DgYDVR0PAQH/BAQDAgTwMB8GA1UdIwQYMBaAFDugITflTCfsWyNLTXDl7cMDUKuuMBgGA1UdEQQR
|
||||||
|
MA+BDW1hbGxhQHN1bi5jb20wDQYJKoZIhvcNAQEEBQADQQB/6DOB6sRqCZu2OenM9eQR0gube85e
|
||||||
|
nTTxU4a7x1naFxzYXK1iQ1vMARKMjDb19QEJIEJKZlDK4uS7yMlf1nFS
|
||||||
|
</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
</KeyDescriptor>
|
||||||
|
<KeyDescriptor use="encryption">
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>
|
||||||
|
MIICTDCCAfagAwIBAgICBo8wDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||||
|
EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz
|
||||||
|
dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh
|
||||||
|
dGUgTWFuYWdlcjAeFw0wNjExMDcyMzU2MTdaFw0xMDA4MDMyMzU2MTdaMCMxITAfBgNVBAMTGGxv
|
||||||
|
YWRiYWxhbmNlci05LnNpcm9lLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAw574iRU6
|
||||||
|
HsSO4LXW/OGTXyfsbGv6XRVOoy3v+J1pZ51KKejcDjDJXNkKGn3/356AwIaqbcymWd59T0zSqYfR
|
||||||
|
Hn+45uyjYxRBmVJseLpVnOXLub9jsjULfGx0yjH4w+KsZSZCXatoCHbj/RJtkzuZY6V9to/hkH3S
|
||||||
|
InQB4a3UAgMCAwEAAaNgMF4wEQYJYIZIAYb4QgEBBAQDAgZAMA4GA1UdDwEB/wQEAwIE8DAfBgNV
|
||||||
|
HSMEGDAWgBQ7oCE35Uwn7FsjS01w5e3DA1CrrjAYBgNVHREEETAPgQ1tYWxsYUBzdW4uY29tMA0G
|
||||||
|
CSqGSIb3DQEBBAUAA0EAMlbfBg/ff0Xkv4DOR5LEqmfTZKqgdlD81cXynfzlF7XfnOqI6hPIA90I
|
||||||
|
x5Ql0ejivIJAYcMGUyA+/YwJg2FGoA==
|
||||||
|
</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
<EncryptionMethod Algorithm=
|
||||||
|
"https://www.w3.org/2001/04/xmlenc#aes128-cbc">
|
||||||
|
<KeySize xmlns="https://www.w3.org/2001/04/xmlenc#">128</KeySize>
|
||||||
|
</EncryptionMethod>
|
||||||
|
</KeyDescriptor>
|
||||||
|
<SingleLogoutService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"
|
||||||
|
ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"/>
|
||||||
|
<SingleLogoutService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPSloSoap/metaAlias/sp"/>
|
||||||
|
<ManageNameIDService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPMniRedirect/metaAlias/sp"
|
||||||
|
ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPMniRedirect/metaAlias/sp"/>
|
||||||
|
<ManageNameIDService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPMniSoap/metaAlias/sp"
|
||||||
|
ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPMniSoap/metaAlias/sp"/>
|
||||||
|
<NameIDFormat>
|
||||||
|
urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
|
||||||
|
</NameIDFormat>
|
||||||
|
<NameIDFormat>
|
||||||
|
urn:oasis:names:tc:SAML:2.0:nameid-format:transient
|
||||||
|
</NameIDFormat>
|
||||||
|
<AssertionConsumerService
|
||||||
|
isDefault="true"
|
||||||
|
index="0"
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp"/>
|
||||||
|
<AssertionConsumerService
|
||||||
|
index="1"
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp"/>
|
||||||
|
</SPSSODescriptor>
|
||||||
|
</EntityDescriptor>
|
|
@ -170,6 +170,10 @@ public class KeycloakServer {
|
||||||
System.setProperty("keycloak.theme.cacheTemplates", "false");
|
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")) {
|
if (!System.getProperties().containsKey("keycloak.theme.staticMaxAge")) {
|
||||||
System.setProperty("keycloak.theme.staticMaxAge", "-1");
|
System.setProperty("keycloak.theme.staticMaxAge", "-1");
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,6 @@ public class RealmTest extends AbstractClientTest {
|
||||||
String description = IOUtils.toString(getClass().getResourceAsStream("/client-descriptions/client-oidc.json"));
|
String description = IOUtils.toString(getClass().getResourceAsStream("/client-descriptions/client-oidc.json"));
|
||||||
|
|
||||||
ClientRepresentation converted = realm.convertClientDescription(description);
|
ClientRepresentation converted = realm.convertClientDescription(description);
|
||||||
assertEquals(36, converted.getClientId().length());
|
|
||||||
assertEquals(1, converted.getRedirectUris().size());
|
assertEquals(1, converted.getRedirectUris().size());
|
||||||
assertEquals("http://localhost", converted.getRedirectUris().get(0));
|
assertEquals("http://localhost", converted.getRedirectUris().get(0));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue