Merge pull request #1845 from stianst/client-reg

Client reg
This commit is contained in:
Stian Thorgersen 2015-11-24 16:33:55 +01:00
commit c901fc7480
25 changed files with 818 additions and 484 deletions

View file

@ -5,6 +5,7 @@ import org.apache.http.HttpRequest;
import org.keycloak.common.util.Base64;
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -21,11 +22,14 @@ public abstract class Auth {
return new BearerTokenAuth(initialAccess.getToken());
}
public static Auth token(ClientRepresentation client) {
return new BearerTokenAuth(client.getRegistrationAccessToken());
}
public static Auth token(OIDCClientRepresentation client) {
return new BearerTokenAuth(client.getRegistrationAccessToken());
}
public static Auth client(String clientId, String clientSecret) {
return new BasicAuth(clientId, clientSecret);
}

View file

@ -3,8 +3,10 @@ package org.keycloak.client.registration;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClients;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
@ -18,10 +20,17 @@ public class ClientRegistration {
public static final ObjectMapper outputMapper = new ObjectMapper();
static {
outputMapper.getSerializationConfig().addMixInAnnotations(ClientRepresentation.class, ClientRepresentationMixIn.class);
outputMapper.getSerializationConfig().addMixInAnnotations(OIDCClientRepresentation.class, OIDCClientRepresentationMixIn.class);
outputMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
}
private final String JSON = "application/json";
private final String XML = "application/xml";
private final String DEFAULT = "default";
private final String INSTALLATION = "install";
private final String OIDC = "openid-connect";
private final String SAML = "saml2-entity-descriptor";
private HttpUtil httpUtil;
@ -47,23 +56,23 @@ public class ClientRegistration {
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
InputStream resultStream = httpUtil.doPost(content, DEFAULT);
InputStream resultStream = httpUtil.doPost(content, JSON, JSON, DEFAULT);
return deserialize(resultStream, ClientRepresentation.class);
}
public ClientRepresentation get(String clientId) throws ClientRegistrationException {
InputStream resultStream = httpUtil.doGet(DEFAULT, clientId);
InputStream resultStream = httpUtil.doGet(JSON, DEFAULT, clientId);
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
}
public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
InputStream resultStream = httpUtil.doGet(INSTALLATION, clientId);
InputStream resultStream = httpUtil.doGet(JSON, INSTALLATION, clientId);
return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
}
public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
InputStream resultStream = httpUtil.doPut(content, DEFAULT, client.getClientId());
InputStream resultStream = httpUtil.doPut(content, JSON, JSON, DEFAULT, client.getClientId());
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
}
@ -75,10 +84,17 @@ public class ClientRegistration {
httpUtil.doDelete(DEFAULT, clientId);
}
public static String serialize(ClientRepresentation client) throws ClientRegistrationException {
try {
public OIDCClientRegistration oidc() {
return new OIDCClientRegistration();
}
return outputMapper.writeValueAsString(client);
public SAMLClientRegistration saml() {
return new SAMLClientRegistration();
}
public static String serialize(Object obj) throws ClientRegistrationException {
try {
return outputMapper.writeValueAsString(obj);
} catch (IOException e) {
throw new ClientRegistrationException("Failed to write json object", e);
}
@ -92,6 +108,44 @@ public class ClientRegistration {
}
}
public class OIDCClientRegistration {
public OIDCClientRepresentation create(OIDCClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
InputStream resultStream = httpUtil.doPost(content, JSON, JSON, OIDC);
return deserialize(resultStream, OIDCClientRepresentation.class);
}
public OIDCClientRepresentation get(String clientId) throws ClientRegistrationException {
InputStream resultStream = httpUtil.doGet(JSON, OIDC, clientId);
return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
}
public OIDCClientRepresentation update(OIDCClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
InputStream resultStream = httpUtil.doPut(content, JSON, JSON, OIDC, client.getClientId());
return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
}
public void delete(OIDCClientRepresentation client) throws ClientRegistrationException {
delete(client.getClientId());
}
public void delete(String clientId) throws ClientRegistrationException {
httpUtil.doDelete(OIDC, clientId);
}
}
public class SAMLClientRegistration {
public ClientRepresentation create(String entityDescriptor) throws ClientRegistrationException {
InputStream resultStream = httpUtil.doPost(entityDescriptor, XML, JSON, SAML);
return deserialize(resultStream, ClientRepresentation.class);
}
}
public static class ClientRegistrationBuilder {
private String url;

View file

@ -33,12 +33,12 @@ class HttpUtil {
this.auth = auth;
}
InputStream doPost(String content, String... path) throws ClientRegistrationException {
InputStream doPost(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
try {
HttpPost request = new HttpPost(getUrl(baseUri, path));
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setHeader(HttpHeaders.ACCEPT, "application/json");
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
request.setHeader(HttpHeaders.ACCEPT, acceptType);
request.setEntity(new StringEntity(content));
addAuth(request);
@ -60,11 +60,11 @@ class HttpUtil {
}
}
InputStream doGet(String... path) throws ClientRegistrationException {
InputStream doGet(String acceptType, String... path) throws ClientRegistrationException {
try {
HttpGet request = new HttpGet(getUrl(baseUri, path));
request.setHeader(HttpHeaders.ACCEPT, "application/json");
request.setHeader(HttpHeaders.ACCEPT, acceptType);
addAuth(request);
@ -90,12 +90,12 @@ class HttpUtil {
}
}
InputStream doPut(String content, String... path) throws ClientRegistrationException {
InputStream doPut(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
try {
HttpPut request = new HttpPut(getUrl(baseUri, path));
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setHeader(HttpHeaders.ACCEPT, "application/json");
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
request.setHeader(HttpHeaders.ACCEPT, acceptType);
request.setEntity(new StringEntity(content));
addAuth(request);
@ -134,7 +134,7 @@ class HttpUtil {
response.getEntity().getContent().close();
}
if (response.getStatusLine().getStatusCode() != 200) {
if (response.getStatusLine().getStatusCode() != 204) {
throw new HttpErrorException(response.getStatusLine());
}
} catch (IOException e) {

View file

@ -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;
}

View file

@ -62,6 +62,8 @@ public class ClientRegistrationCLI {
CommandContainer command = registry.getCommand(args[0], null);
ParserGenerator.parseAndPopulate(command, args[0], Arrays.copyOfRange(args, 1, args.length));
}*/
//commandInvocation.getCommandRegistry().getAllCommandNames()
}
}

View file

@ -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;
}
}

View file

@ -10,15 +10,15 @@
<para>
The Client Registration Service provides built-in support for Keycloak Client Representations, OpenID Connect
Client Meta Data and SAML Entity Descriptors. It's also possible to plugin custom client registration providers
if required. The Client Registration Service endpoint is <literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;</literal>.
if required. The Client Registration Service endpoint is <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;</literal>.
</para>
<para>
The built-in supported <literal>providers</literal> are:
<itemizedlist>
<listitem><literal>default</literal> Keycloak Representations</listitem>
<listitem><literal>install</literal> Keycloak Adapter Configuration</listitem>
<!--<listitem><literal>openid-connect</literal> OpenID Connect Dynamic Client Registration</listitem>-->
<!--<listitem><literal>saml-ed</literal> SAML Entity Descriptors</listitem>-->
<listitem><literal>openid-connect</literal> OpenID Connect Dynamic Client Registration</listitem>
<listitem><literal>saml2-entity-descriptor</literal> SAML Entity Descriptors</listitem>
</itemizedlist>
The following sections will describe how to use the different providers.
</para>
@ -106,30 +106,30 @@ Authorization: bearer eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJmMjJmNzQyYy04ZjNlLTQ2M....
</para>
<para>
To create a client create a Client Representation (JSON) then do a HTTP POST to:
<literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;/default</literal>. It will return a Client Representation
<literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/default</literal>. It will return a Client Representation
that also includes the registration access token. You should save the registration access token somewhere
if you want to retrieve the config, update or delete the client later.
</para>
<para>
To retrieve the Client Representation then do a HTTP GET to:
<literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>. It will also
<literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>. It will also
return a new registration access token.
</para>
<para>
To update the Client Representation then do a HTTP PUT to with the updated Client Representation to:
<literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>. It will also
<literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>. It will also
return a new registration access token.
</para>
<para>
To delete the Client Representation then do a HTTP DELETE to:
<literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>
<literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>
</para>
</section>
<section>
<title>Keycloak Adapter Configuration</title>
<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
HTTP basic authentication. To do this include the following header in the request:
<programlisting><![CDATA[
@ -138,7 +138,7 @@ Authorization: basic BASE64(client-id + ':' + client-secret)
</para>
<para>
To retrieve the Adapter Configuration then do a HTTP GET to:
<literal>&lt;KEYCLOAK URL&gt;/clients/&lt;provider&gt;/installation/&lt;client id&gt;</literal>
<literal>&lt;KEYCLOAK URL&gt;//realms/&lt;realm&gt;clients/&lt;provider&gt;/installation/&lt;client id&gt;</literal>
</para>
<para>
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>
</section>
<!--
<section>
<title>OpenID Connect Dynamic Client Registration</title>
<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>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/oidc[/&lt;client id&gt;]</literal>.
</para>
<para>
This endpoints can also be found in the OpenID Connect Discovery endpoint for the realm:
<literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/.well-known/openid-configuration</literal>.
</para>
</section>
-->
<!--
<section>
<title>SAML Entity Descriptors</title>
<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>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/saml2-entity-descriptor</literal>.
</para>
</section>
-->
<section>
<title>Client Registration Java API</title>

View file

@ -1,63 +1,35 @@
package org.keycloak.protocol.saml.clientregistration;
import org.jboss.logging.Logger;
import org.keycloak.events.EventBuilder;
import org.keycloak.exportimport.ClientDescriptionConverter;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.clientregistration.ClientRegistrationAuth;
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class EntityDescriptorClientRegistrationProvider implements ClientRegistrationProvider {
private static final Logger logger = Logger.getLogger(EntityDescriptorClientRegistrationProvider.class);
private KeycloakSession session;
private EventBuilder event;
private ClientRegistrationAuth auth;
public class EntityDescriptorClientRegistrationProvider extends AbstractClientRegistrationProvider {
public EntityDescriptorClientRegistrationProvider(KeycloakSession session) {
this.session = session;
super(session);
}
// @POST
// @Consumes(MediaType.APPLICATION_XML)
// @Produces(MediaType.APPLICATION_JSON)
// public Response create(String descriptor) {
// event.event(EventType.CLIENT_REGISTER);
//
// auth.requireCreate();
//
// ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor);
//
// try {
// ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
// client = ModelToRepresentation.toRepresentation(clientModel);
// URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
//
// logger.infov("Created client {0}", client.getClientId());
//
// event.client(client.getClientId()).success();
//
// return Response.created(uri).entity(client).build();
// } catch (ModelDuplicateException e) {
// return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
// }
// }
@Override
public void close() {
}
@Override
public void setAuth(ClientRegistrationAuth auth) {
this.auth = auth;
}
@Override
public void setEvent(EventBuilder event) {
this.event = event;
@POST
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_JSON)
public Response createSaml(String descriptor) {
ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor);
client = create(client);
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
return Response.created(uri).entity(client).build();
}
}

View file

@ -5,8 +5,7 @@ import org.keycloak.exportimport.ClientDescriptionConverter;
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientregistration.oidc.DescriptionConverter;
import org.keycloak.util.JsonSerialization;

View file

@ -4,6 +4,8 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.services.clientregistration.ClientRegistrationService;
import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.Urls;
import org.keycloak.wellknown.WellKnownProvider;
@ -48,6 +50,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
config.setUserinfoEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setLogoutEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "logout").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setJwksUri(uriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
config.setRegistrationEndpoint(RealmsResource.clientRegistrationUrl(uriInfo).path(ClientRegistrationService.class, "provider").build(realm.getName(), OIDCClientRegistrationProviderFactory.ID).toString());
config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);

View file

@ -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;
}
}

View file

@ -7,7 +7,6 @@ import org.codehaus.jackson.annotate.JsonProperty;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -48,6 +47,9 @@ public class OIDCConfigurationRepresentation {
@JsonProperty("response_modes_supported")
private List<String> responseModesSupported;
@JsonProperty("registration_endpoint")
private String registrationEndpoint;
protected Map<String, Object> otherClaims = new HashMap<String, Object>();
public String getIssuer() {
@ -138,6 +140,14 @@ public class OIDCConfigurationRepresentation {
this.responseModesSupported = responseModesSupported;
}
public String getRegistrationEndpoint() {
return registrationEndpoint;
}
public void setRegistrationEndpoint(String registrationEndpoint) {
this.registrationEndpoint = registrationEndpoint;
}
@JsonAnyGetter
public Map<String, Object> getOtherClaims() {
return otherClaims;

View file

@ -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() {
}
}

View file

@ -25,7 +25,7 @@ public class ClientRegistrationService {
}
@Path("{provider}")
public Object getProvider(@PathParam("provider") String providerId) {
public Object provider(@PathParam("provider") String providerId) {
checkSsl();
ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);

View file

@ -2,14 +2,11 @@ package org.keycloak.services.clientregistration;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientInitialAccessModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ErrorResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
@ -19,101 +16,41 @@ import java.net.URI;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class DefaultClientRegistrationProvider implements ClientRegistrationProvider {
private KeycloakSession session;
private EventBuilder event;
private ClientRegistrationAuth auth;
public class DefaultClientRegistrationProvider extends AbstractClientRegistrationProvider {
public DefaultClientRegistrationProvider(KeycloakSession session) {
this.session = session;
super(session);
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response create(ClientRepresentation client) {
event.event(EventType.CLIENT_REGISTER);
auth.requireCreate();
try {
ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
client = ModelToRepresentation.toRepresentation(clientModel);
String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, clientModel);
client.setRegistrationAccessToken(registrationAccessToken);
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
if (auth.isInitialAccessToken()) {
ClientInitialAccessModel initialAccessModel = auth.getInitialAccessModel();
initialAccessModel.decreaseRemainingCount();
}
event.client(client.getClientId()).success();
return Response.created(uri).entity(client).build();
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
}
public Response createDefault(ClientRepresentation client) {
client = create(client);
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
return Response.created(uri).entity(client).build();
}
@GET
@Path("{clientId}")
@Produces(MediaType.APPLICATION_JSON)
public Response get(@PathParam("clientId") String clientId) {
event.event(EventType.CLIENT_INFO);
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
auth.requireView(client);
ClientRepresentation rep = ModelToRepresentation.toRepresentation(client);
if (auth.isRegistrationAccessToken()) {
String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
rep.setRegistrationAccessToken(registrationAccessToken);
}
event.client(client.getClientId()).success();
return Response.ok(rep).build();
public Response getDefault(@PathParam("clientId") String clientId) {
ClientRepresentation client = get(clientId);
return Response.ok(client).build();
}
@PUT
@Path("{clientId}")
@Consumes(MediaType.APPLICATION_JSON)
public Response update(@PathParam("clientId") String clientId, ClientRepresentation rep) {
event.event(EventType.CLIENT_UPDATE).client(clientId);
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
auth.requireUpdate(client);
RepresentationToModel.updateClient(rep, client);
rep = ModelToRepresentation.toRepresentation(client);
if (auth.isRegistrationAccessToken()) {
String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
rep.setRegistrationAccessToken(registrationAccessToken);
}
event.client(client.getClientId()).success();
return Response.ok(rep).build();
public Response updateDefault(@PathParam("clientId") String clientId, ClientRepresentation client) {
client = update(clientId, client);
return Response.ok(client).build();
}
@DELETE
@Path("{clientId}")
public Response delete(@PathParam("clientId") String clientId) {
event.event(EventType.CLIENT_DELETE).client(clientId);
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
auth.requireUpdate(client);
if (session.getContext().getRealm().removeClient(client.getId())) {
event.client(client.getClientId()).success();
return Response.ok().build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
public void deleteDefault(@PathParam("clientId") String clientId) {
delete(clientId);
}
@Override

View file

@ -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";
}

View file

@ -1,8 +1,9 @@
package org.keycloak.services.clientregistration.oidc;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import java.net.URI;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -11,27 +12,22 @@ public class DescriptionConverter {
public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(KeycloakModelUtils.generateId());
client.setClientId(clientOIDC.getClientId());
client.setName(clientOIDC.getClientName());
client.setRedirectUris(clientOIDC.getRedirectUris());
client.setBaseUrl(clientOIDC.getClientUri());
return client;
}
public static OIDCClientResponseRepresentation toExternalResponse(ClientRepresentation client) {
OIDCClientResponseRepresentation response = new OIDCClientResponseRepresentation();
public static OIDCClientRepresentation toExternalResponse(ClientRepresentation client, URI uri) {
OIDCClientRepresentation response = new OIDCClientRepresentation();
response.setClientId(client.getClientId());
response.setClientName(client.getName());
response.setClientUri(client.getBaseUrl());
response.setClientSecret(client.getSecret());
response.setClientSecretExpiresAt(0);
response.setRedirectUris(client.getRedirectUris());
response.setRegistrationAccessToken(client.getRegistrationAccessToken());
response.setRegistrationClientUri(uri.toString());
return response;
}

View file

@ -1,72 +1,70 @@
package org.keycloak.services.clientregistration.oidc;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
import org.keycloak.services.clientregistration.ClientRegistrationAuth;
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
import org.keycloak.services.clientregistration.ErrorCodes;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class OIDCClientRegistrationProvider implements ClientRegistrationProvider {
private static final Logger logger = Logger.getLogger(OIDCClientRegistrationProvider.class);
private KeycloakSession session;
private EventBuilder event;
private ClientRegistrationAuth auth;
public class OIDCClientRegistrationProvider extends AbstractClientRegistrationProvider {
public OIDCClientRegistrationProvider(KeycloakSession session) {
this.session = session;
super(session);
}
// @POST
// @Consumes(MediaType.APPLICATION_JSON)
// @Produces(MediaType.APPLICATION_JSON)
// public Response create(OIDCClientRepresentation clientOIDC) {
// event.event(EventType.CLIENT_REGISTER);
//
// auth.requireCreate();
//
// ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
//
// try {
// ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
//
// client = ModelToRepresentation.toRepresentation(clientModel);
//
// String registrationAccessToken = TokenGenerator.createRegistrationAccessToken();
//
// clientModel.setRegistrationToken(registrationAccessToken);
//
// URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
//
// logger.infov("Created client {0}", client.getClientId());
//
// event.client(client.getClientId()).success();
//
// OIDCClientResponseRepresentation response = DescriptionConverter.toExternalResponse(client);
//
// response.setClientName(client.getName());
// response.setClientUri(client.getBaseUrl());
//
// response.setClientSecret(client.getSecret());
// response.setClientSecretExpiresAt(0);
//
// response.setRedirectUris(client.getRedirectUris());
//
// response.setRegistrationAccessToken(registrationAccessToken);
// response.setRegistrationClientUri(uri.toString());
//
// return Response.created(uri).entity(response).build();
// } catch (ModelDuplicateException e) {
// return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
// }
// }
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createOIDC(OIDCClientRepresentation clientOIDC) {
if (clientOIDC.getClientId() != null) {
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier included", Response.Status.BAD_REQUEST);
}
@Override
public void close() {
ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
client = create(client);
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
clientOIDC.setClientIdIssuedAt(Time.currentTime());
return Response.created(uri).entity(clientOIDC).build();
}
@GET
@Path("{clientId}")
@Produces(MediaType.APPLICATION_JSON)
public Response getOIDC(@PathParam("clientId") String clientId) {
ClientRepresentation client = get(clientId);
OIDCClientRepresentation clientOIDC = DescriptionConverter.toExternalResponse(client, session.getContext().getUri().getRequestUri());
return Response.ok(clientOIDC).build();
}
@PUT
@Path("{clientId}")
@Consumes(MediaType.APPLICATION_JSON)
public Response updateOIDC(@PathParam("clientId") String clientId, OIDCClientRepresentation clientOIDC) {
ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
client = update(clientId, client);
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
return Response.ok(clientOIDC).build();
}
@DELETE
@Path("{clientId}")
public void deleteOIDC(@PathParam("clientId") String clientId) {
delete(clientId);
}
@Override
@ -79,4 +77,8 @@ public class OIDCClientRegistrationProvider implements ClientRegistrationProvide
this.event = event;
}
@Override
public void close() {
}
}

View file

@ -11,6 +11,8 @@ import org.keycloak.services.clientregistration.ClientRegistrationProviderFactor
*/
public class OIDCClientRegistrationProviderFactory implements ClientRegistrationProviderFactory {
public static final String ID = "openid-connect";
@Override
public ClientRegistrationProvider create(KeycloakSession session) {
return new OIDCClientRegistrationProvider(session);
@ -30,7 +32,7 @@ public class OIDCClientRegistrationProviderFactory implements ClientRegistration
@Override
public String getId() {
return "openid-connect";
return ID;
}
}

View file

@ -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;
}
}

View file

@ -59,6 +59,10 @@ public class RealmsResource {
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getProtocol");
}
public static UriBuilder clientRegistrationUrl(UriInfo uriInfo) {
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getClientsService");
}
public static UriBuilder brokerUrl(UriInfo uriInfo) {
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getBrokerService");
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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>

View file

@ -180,7 +180,6 @@ public class RealmTest extends AbstractClientTest {
String description = IOUtils.toString(getClass().getResourceAsStream("/client-descriptions/client-oidc.json"));
ClientRepresentation converted = realm.convertClientDescription(description);
assertEquals(36, converted.getClientId().length());
assertEquals(1, converted.getRedirectUris().size());
assertEquals("http://localhost", converted.getRedirectUris().get(0));
}