Merge pull request #1825 from stianst/client-reg

KEYCLOAK-1749 Client registration service
This commit is contained in:
Stian Thorgersen 2015-11-16 20:23:55 +01:00
commit 83ff02ea53
44 changed files with 1542 additions and 543 deletions

View file

@ -0,0 +1,58 @@
package org.keycloak.client.registration;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.keycloak.common.util.Base64;
import org.keycloak.representations.idm.ClientRepresentation;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class Auth {
public abstract void addAuth(HttpRequest request);
public static Auth token(String token) {
return new BearerTokenAuth(token);
}
public static Auth token(ClientRepresentation client) {
return new BearerTokenAuth(client.getRegistrationAccessToken());
}
public static Auth client(String clientId, String clientSecret) {
return new BasicAuth(clientId, clientSecret);
}
private static class BearerTokenAuth extends Auth {
private String token;
public BearerTokenAuth(String token) {
this.token = token;
}
@Override
public void addAuth(HttpRequest request) {
request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
}
}
private static class BasicAuth extends Auth {
private String username;
private String password;
public BasicAuth(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public void addAuth(HttpRequest request) {
String val = Base64.encodeBytes((username + ":" + password).getBytes());
request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + val);
}
}
}

View file

@ -1,18 +1,9 @@
package org.keycloak.client.registration;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.common.util.Base64;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
@ -23,160 +14,58 @@ import java.io.InputStream;
*/
public class ClientRegistration {
private String clientRegistrationUrl;
private HttpClient httpClient;
private Auth auth;
private final String DEFAULT = "default";
private final String INSTALLATION = "install";
public static ClientRegistrationBuilder create() {
return new ClientRegistrationBuilder();
private HttpUtil httpUtil;
public ClientRegistration(String authServerUrl, String realm) {
httpUtil = new HttpUtil(HttpClients.createDefault(), HttpUtil.getUrl(authServerUrl, "realms", realm, "clients"));
}
private ClientRegistration() {
public ClientRegistration(String authServerUrl, String realm, HttpClient httpClient) {
httpUtil = new HttpUtil(httpClient, HttpUtil.getUrl(authServerUrl, "realms", realm, "clients"));
}
public void close() throws ClientRegistrationException {
if (httpUtil != null) {
httpUtil.close();
}
httpUtil = null;
}
public ClientRegistration auth(Auth auth) {
httpUtil.setAuth(auth);
return this;
}
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
InputStream resultStream = doPost(content);
InputStream resultStream = httpUtil.doPost(content, DEFAULT);
return deserialize(resultStream, ClientRepresentation.class);
}
public ClientRepresentation get() throws ClientRegistrationException {
if (auth instanceof ClientIdSecretAuth) {
String clientId = ((ClientIdSecretAuth) auth).clientId;
return get(clientId);
} else {
throw new ClientRegistrationException("Requires client authentication");
}
public ClientRepresentation get(String clientId) throws ClientRegistrationException {
InputStream resultStream = httpUtil.doGet(DEFAULT, clientId);
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
}
public ClientRepresentation get(String clientId) throws ClientRegistrationException {
InputStream resultStream = doGet(clientId);
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
InputStream resultStream = httpUtil.doGet(INSTALLATION, clientId);
return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
}
public void update(ClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
doPut(content, client.getClientId());
httpUtil.doPut(content, DEFAULT, client.getClientId());
}
public void delete() throws ClientRegistrationException {
if (auth instanceof ClientIdSecretAuth) {
String clientId = ((ClientIdSecretAuth) auth).clientId;
delete(clientId);
} else {
throw new ClientRegistrationException("Requires client authentication");
}
public void delete(ClientRepresentation client) throws ClientRegistrationException {
delete(client.getClientId());
}
public void delete(String clientId) throws ClientRegistrationException {
doDelete(clientId);
}
public void close() throws ClientRegistrationException {
if (httpClient instanceof CloseableHttpClient) {
try {
((CloseableHttpClient) httpClient).close();
} catch (IOException e) {
throw new ClientRegistrationException("Failed to close http client", e);
}
}
}
private InputStream doPost(String content) throws ClientRegistrationException {
try {
HttpPost request = new HttpPost(clientRegistrationUrl);
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setHeader(HttpHeaders.ACCEPT, "application/json");
request.setEntity(new StringEntity(content));
auth.addAuth(request);
HttpResponse response = httpClient.execute(request);
InputStream responseStream = null;
if (response.getEntity() != null) {
responseStream = response.getEntity().getContent();
}
if (response.getStatusLine().getStatusCode() == 201) {
return responseStream;
} else {
responseStream.close();
throw new HttpErrorException(response.getStatusLine());
}
} catch (IOException e) {
throw new ClientRegistrationException("Failed to send request", e);
}
}
private InputStream doGet(String endpoint) throws ClientRegistrationException {
try {
HttpGet request = new HttpGet(clientRegistrationUrl + "/" + endpoint);
request.setHeader(HttpHeaders.ACCEPT, "application/json");
auth.addAuth(request);
HttpResponse response = httpClient.execute(request);
InputStream responseStream = null;
if (response.getEntity() != null) {
responseStream = response.getEntity().getContent();
}
if (response.getStatusLine().getStatusCode() == 200) {
return responseStream;
} else if (response.getStatusLine().getStatusCode() == 404) {
responseStream.close();
return null;
} else {
responseStream.close();
throw new HttpErrorException(response.getStatusLine());
}
} catch (IOException e) {
throw new ClientRegistrationException("Failed to send request", e);
}
}
private void doPut(String content, String endpoint) throws ClientRegistrationException {
try {
HttpPut request = new HttpPut(clientRegistrationUrl + "/" + endpoint);
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setHeader(HttpHeaders.ACCEPT, "application/json");
request.setEntity(new StringEntity(content));
auth.addAuth(request);
HttpResponse response = httpClient.execute(request);
if (response.getEntity() != null) {
response.getEntity().getContent().close();
}
if (response.getStatusLine().getStatusCode() != 200) {
throw new HttpErrorException(response.getStatusLine());
}
} catch (IOException e) {
throw new ClientRegistrationException("Failed to send request", e);
}
}
private void doDelete(String endpoint) throws ClientRegistrationException {
try {
HttpDelete request = new HttpDelete(clientRegistrationUrl + "/" + endpoint);
auth.addAuth(request);
HttpResponse response = httpClient.execute(request);
if (response.getEntity() != null) {
response.getEntity().getContent().close();
}
if (response.getStatusLine().getStatusCode() != 200) {
throw new HttpErrorException(response.getStatusLine());
}
} catch (IOException e) {
throw new ClientRegistrationException("Failed to send request", e);
}
httpUtil.doDelete(DEFAULT, clientId);
}
private String serialize(ClientRepresentation client) throws ClientRegistrationException {
@ -195,81 +84,4 @@ public class ClientRegistration {
}
}
public static class ClientRegistrationBuilder {
private String realm;
private String authServerUrl;
private Auth auth;
private HttpClient httpClient;
public ClientRegistrationBuilder realm(String realm) {
this.realm = realm;
return this;
}
public ClientRegistrationBuilder authServerUrl(String authServerUrl) {
this.authServerUrl = authServerUrl;
return this;
}
public ClientRegistrationBuilder auth(String token) {
this.auth = new TokenAuth(token);
return this;
}
public ClientRegistrationBuilder auth(String clientId, String clientSecret) {
this.auth = new ClientIdSecretAuth(clientId, clientSecret);
return this;
}
public ClientRegistrationBuilder httpClient(HttpClient httpClient) {
this.httpClient = httpClient;
return this;
}
public ClientRegistration build() {
ClientRegistration clientRegistration = new ClientRegistration();
clientRegistration.clientRegistrationUrl = authServerUrl + "/realms/" + realm + "/client-registration/default";
clientRegistration.httpClient = httpClient != null ? httpClient : HttpClients.createDefault();
clientRegistration.auth = auth;
return clientRegistration;
}
}
public interface Auth {
void addAuth(HttpRequest httpRequest);
}
public static class AuthorizationHeaderAuth implements Auth {
private String credentials;
public AuthorizationHeaderAuth(String credentials) {
this.credentials = credentials;
}
public void addAuth(HttpRequest httpRequest) {
httpRequest.setHeader(HttpHeaders.AUTHORIZATION, credentials);
}
}
public static class TokenAuth extends AuthorizationHeaderAuth {
public TokenAuth(String token) {
super("Bearer " + token);
}
}
public static class ClientIdSecretAuth extends AuthorizationHeaderAuth {
private String clientId;
public ClientIdSecretAuth(String clientId, String clientSecret) {
super("Basic " + Base64.encodeBytes((clientId + ":" + clientSecret).getBytes()));
this.clientId = clientId;
}
}
}

View file

@ -0,0 +1,159 @@
package org.keycloak.client.registration;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.*;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.client.registration.HttpErrorException;
import java.io.IOException;
import java.io.InputStream;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
class HttpUtil {
private HttpClient httpClient;
private String baseUri;
private Auth auth;
HttpUtil(HttpClient httpClient, String baseUri) {
this.httpClient = httpClient;
this.baseUri = baseUri;
}
void setAuth(Auth auth) {
this.auth = auth;
}
InputStream doPost(String content, String... path) throws ClientRegistrationException {
try {
HttpPost request = new HttpPost(getUrl(baseUri, path));
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setHeader(HttpHeaders.ACCEPT, "application/json");
request.setEntity(new StringEntity(content));
addAuth(request);
HttpResponse response = httpClient.execute(request);
InputStream responseStream = null;
if (response.getEntity() != null) {
responseStream = response.getEntity().getContent();
}
if (response.getStatusLine().getStatusCode() == 201) {
return responseStream;
} else {
responseStream.close();
throw new HttpErrorException(response.getStatusLine());
}
} catch (IOException e) {
throw new ClientRegistrationException("Failed to send request", e);
}
}
InputStream doGet(String... path) throws ClientRegistrationException {
try {
HttpGet request = new HttpGet(getUrl(baseUri, path));
request.setHeader(HttpHeaders.ACCEPT, "application/json");
addAuth(request);
HttpResponse response = httpClient.execute(request);
InputStream responseStream = null;
if (response.getEntity() != null) {
responseStream = response.getEntity().getContent();
}
if (response.getStatusLine().getStatusCode() == 200) {
return responseStream;
} else if (response.getStatusLine().getStatusCode() == 404) {
responseStream.close();
return null;
} else {
responseStream.close();
throw new HttpErrorException(response.getStatusLine());
}
} catch (IOException e) {
throw new ClientRegistrationException("Failed to send request", e);
}
}
void doPut(String content, String... path) throws ClientRegistrationException {
try {
HttpPut request = new HttpPut(getUrl(baseUri, path));
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setHeader(HttpHeaders.ACCEPT, "application/json");
request.setEntity(new StringEntity(content));
addAuth(request);
HttpResponse response = httpClient.execute(request);
if (response.getEntity() != null) {
response.getEntity().getContent().close();
}
if (response.getStatusLine().getStatusCode() != 200) {
throw new HttpErrorException(response.getStatusLine());
}
} catch (IOException e) {
throw new ClientRegistrationException("Failed to send request", e);
}
}
void doDelete(String... path) throws ClientRegistrationException {
try {
HttpDelete request = new HttpDelete(getUrl(baseUri, path));
addAuth(request);
HttpResponse response = httpClient.execute(request);
if (response.getEntity() != null) {
response.getEntity().getContent().close();
}
if (response.getStatusLine().getStatusCode() != 200) {
throw new HttpErrorException(response.getStatusLine());
}
} catch (IOException e) {
throw new ClientRegistrationException("Failed to send request", e);
}
}
void close() throws ClientRegistrationException {
if (httpClient instanceof CloseableHttpClient) {
try {
((CloseableHttpClient) httpClient).close();
} catch (IOException e) {
throw new ClientRegistrationException("Failed to close http client", e);
}
}
}
static String getUrl(String baseUri, String... path) {
StringBuilder s = new StringBuilder();
s.append(baseUri);
for (String p : path) {
s.append('/');
s.append(p);
}
return s.toString();
}
private void addAuth(HttpRequestBase request) {
if (auth != null) {
auth.addAuth(request);
}
}
}

View file

@ -58,6 +58,9 @@
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
<addColumn tableName="CLIENT">
<column name="REGISTRATION_SECRET" type="VARCHAR(255)"/>
</addColumn>
</changeSet>
</databaseChangeLog>

View file

@ -19,6 +19,7 @@ public class ClientRepresentation {
protected Boolean enabled;
protected String clientAuthenticatorType;
protected String secret;
protected String registrationAccessToken;
protected String[] defaultRoles;
protected List<String> redirectUris;
protected List<String> webOrigins;
@ -124,6 +125,14 @@ public class ClientRepresentation {
this.secret = secret;
}
public String getRegistrationAccessToken() {
return registrationAccessToken;
}
public void setRegistrationAccessToken(String registrationAccessToken) {
this.registrationAccessToken = registrationAccessToken;
}
public List<String> getRedirectUris() {
return redirectUris;
}
@ -251,4 +260,5 @@ public class ClientRepresentation {
public void setProtocolMappers(List<ProtocolMapperRepresentation> protocolMappers) {
this.protocolMappers = protocolMappers;
}
}

View file

@ -90,6 +90,9 @@ public interface ClientModel extends RoleContainerModel {
String getSecret();
public void setSecret(String secret);
String getRegistrationSecret();
void setRegistrationSecret(String registrationSecret);
boolean isFullScopeAllowed();
void setFullScopeAllowed(boolean value);

View file

@ -5,6 +5,7 @@ import org.keycloak.models.utils.RealmImporter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.Locale;
/**
@ -12,6 +13,8 @@ import java.util.Locale;
*/
public interface KeycloakContext {
URI getAuthServerUrl();
String getContextPath();
UriInfo getUri();

View file

@ -17,6 +17,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private boolean enabled;
private String clientAuthenticatorType;
private String secret;
private String registrationSecret;
private String protocol;
private int notBefore;
private boolean publicClient;
@ -90,6 +91,14 @@ public class ClientEntity extends AbstractIdentifiableEntity {
this.secret = secret;
}
public String getRegistrationSecret() {
return registrationSecret;
}
public void setRegistrationSecret(String registrationSecret) {
this.registrationSecret = registrationSecret;
}
public int getNotBefore() {
return notBefore;
}

View file

@ -11,8 +11,6 @@ import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.session.PersistentClientSessionModel;
import org.keycloak.models.session.PersistentUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
@ -47,7 +45,6 @@ import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.common.util.Time;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@ -390,6 +387,7 @@ public class ModelToRepresentation {
rep.setNotBefore(clientModel.getNotBefore());
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());
rep.setRegistrationAccessToken(clientModel.getRegistrationSecret());
Set<String> redirectUris = clientModel.getRedirectUris();
if (redirectUris != null) {

View file

@ -737,6 +737,8 @@ public class RepresentationToModel {
KeycloakModelUtils.generateSecret(client);
}
client.setRegistrationSecret(resourceRep.getRegistrationAccessToken());
if (resourceRep.getAttributes() != null) {
for (Map.Entry<String, String> entry : resourceRep.getAttributes().entrySet()) {
client.setAttribute(entry.getKey(), entry.getValue());
@ -813,6 +815,7 @@ public class RepresentationToModel {
if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout());
if (rep.getClientAuthenticatorType() != null) resource.setClientAuthenticatorType(rep.getClientAuthenticatorType());
if (rep.getRegistrationAccessToken() != null) resource.setRegistrationSecret(rep.getRegistrationAccessToken());
resource.updateClient();
if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol());

View file

@ -120,6 +120,15 @@ public class ClientAdapter implements ClientModel {
getDelegateForUpdate();
updated.setSecret(secret);
}
public String getRegistrationSecret() {
if (updated != null) return updated.getRegistrationSecret();
return cached.getRegistrationSecret();
}
public void setRegistrationSecret(String registrationsecret) {
getDelegateForUpdate();
updated.setRegistrationSecret(registrationsecret);
}
public boolean isPublicClient() {
if (updated != null) return updated.isPublicClient();

View file

@ -31,6 +31,7 @@ public class CachedClient implements Serializable {
private boolean enabled;
private String clientAuthenticatorType;
private String secret;
private String registrationSecret;
private String protocol;
private Map<String, String> attributes = new HashMap<String, String>();
private boolean publicClient;
@ -57,6 +58,7 @@ public class CachedClient implements Serializable {
id = model.getId();
clientAuthenticatorType = model.getClientAuthenticatorType();
secret = model.getSecret();
registrationSecret = model.getRegistrationSecret();
clientId = model.getClientId();
name = model.getName();
description = model.getDescription();
@ -129,6 +131,10 @@ public class CachedClient implements Serializable {
return secret;
}
public String getRegistrationSecret() {
return registrationSecret;
}
public boolean isPublicClient() {
return publicClient;
}

View file

@ -177,6 +177,16 @@ public class ClientAdapter implements ClientModel {
entity.setSecret(secret);
}
@Override
public String getRegistrationSecret() {
return entity.getRegistrationSecret();
}
@Override
public void setRegistrationSecret(String registrationSecret) {
entity.setRegistrationSecret(registrationSecret);
}
@Override
public boolean validateSecret(String secret) {
return secret.equals(entity.getSecret());

View file

@ -42,6 +42,8 @@ public class ClientEntity {
private boolean enabled;
@Column(name="SECRET")
private String secret;
@Column(name="REGISTRATION_SECRET")
private String registrationSecret;
@Column(name="CLIENT_AUTHENTICATOR_TYPE")
private String clientAuthenticatorType;
@Column(name="NOT_BEFORE")
@ -201,6 +203,14 @@ public class ClientEntity {
this.secret = secret;
}
public String getRegistrationSecret() {
return registrationSecret;
}
public void setRegistrationSecret(String registrationSecret) {
this.registrationSecret = registrationSecret;
}
public int getNotBefore() {
return notBefore;
}

View file

@ -177,6 +177,17 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
updateMongoEntity();
}
@Override
public String getRegistrationSecret() {
return getMongoEntity().getRegistrationSecret();
}
@Override
public void setRegistrationSecret(String registrationSecretsecret) {
getMongoEntity().setRegistrationSecret(registrationSecretsecret);
updateMongoEntity();
}
@Override
public boolean isPublicClient() {
return getMongoEntity().isPublicClient();

View file

@ -33,6 +33,8 @@ import java.util.Map;
*/
public class EntityDescriptorDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
public static final String ID = "saml2-entity-descriptor";
@Override
public boolean isSupported(String description) {
description = description.trim();
@ -161,7 +163,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo
@Override
public String getId() {
return "saml2-entity-descriptor";
return ID;
}
}

View file

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

View file

@ -0,0 +1,38 @@
package org.keycloak.protocol.saml.clientregistration;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
import org.keycloak.services.clientregistration.ClientRegistrationProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class EntityDescriptorClientRegistrationProviderFactory implements ClientRegistrationProviderFactory {
public static final String ID = "saml2-entity-descriptor";
@Override
public ClientRegistrationProvider create(KeycloakSession session) {
return new EntityDescriptorClientRegistrationProvider(session);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1 @@
org.keycloak.protocol.saml.clientregistration.EntityDescriptorClientRegistrationProviderFactory

View file

@ -8,6 +8,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientregistration.oidc.DescriptionConverter;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
@ -17,6 +18,8 @@ import java.io.IOException;
*/
public class OIDCClientDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
public static final String ID = "openid-connect";
@Override
public boolean isSupported(String description) {
description = description.trim();
@ -26,15 +29,8 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte
@Override
public ClientRepresentation convertToInternal(String description) {
try {
OIDCClientRepresentation oidcRep = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
ClientRepresentation client = new ClientRepresentation();
client.setClientId(KeycloakModelUtils.generateId());
client.setName(oidcRep.getClientName());
client.setRedirectUris(oidcRep.getRedirectUris());
client.setBaseUrl(oidcRep.getClientUri());
return client;
OIDCClientRepresentation clientOIDC = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
return DescriptionConverter.toInternal(clientOIDC);
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -59,7 +55,7 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte
@Override
public String getId() {
return "openid-connect";
return ID;
}
}

View file

@ -190,7 +190,7 @@ public class LogoutEndpoint {
}
private ClientModel authorizeClient() {
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient();
if (client.isBearerOnly()) {
throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);

View file

@ -145,7 +145,7 @@ public class TokenEndpoint {
}
private void checkClient() {
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event);
client = clientAuth.getClient();
clientAuthAttributes = clientAuth.getClientAuthAttributes();

View file

@ -12,12 +12,48 @@ public class OIDCClientRepresentation {
@JsonProperty("redirect_uris")
private List<String> redirectUris;
@JsonProperty("token_endpoint_auth_method")
private String tokenEndpointAuthMethod;
@JsonProperty("grant_types")
private String grantTypes;
@JsonProperty("response_types")
private String responseTypes;
@JsonProperty("client_name")
private String clientName;
@JsonProperty("client_uri")
private String clientUri;
@JsonProperty("logo_uri")
private String logoUri;
@JsonProperty("scope")
private String scope;
@JsonProperty("contacts")
private String contacts;
@JsonProperty("tos_uri")
private String tos_uri;
@JsonProperty("policy_uri")
private String policy_uri;
@JsonProperty("jwks_uri")
private String jwks_uri;
@JsonProperty("jwks")
private String jwks;
@JsonProperty("software_id")
private String softwareId;
@JsonProperty("software_version")
private String softwareVersion;
public List<String> getRedirectUris() {
return redirectUris;
}
@ -26,6 +62,30 @@ public class OIDCClientRepresentation {
this.redirectUris = redirectUris;
}
public String getTokenEndpointAuthMethod() {
return tokenEndpointAuthMethod;
}
public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) {
this.tokenEndpointAuthMethod = tokenEndpointAuthMethod;
}
public String getGrantTypes() {
return grantTypes;
}
public void setGrantTypes(String grantTypes) {
this.grantTypes = grantTypes;
}
public String getResponseTypes() {
return responseTypes;
}
public void setResponseTypes(String responseTypes) {
this.responseTypes = responseTypes;
}
public String getClientName() {
return clientName;
}
@ -42,4 +102,76 @@ public class OIDCClientRepresentation {
this.clientUri = clientUri;
}
public String getLogoUri() {
return logoUri;
}
public void setLogoUri(String logoUri) {
this.logoUri = logoUri;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getContacts() {
return contacts;
}
public void setContacts(String contacts) {
this.contacts = contacts;
}
public String getTos_uri() {
return tos_uri;
}
public void setTos_uri(String tos_uri) {
this.tos_uri = tos_uri;
}
public String getPolicy_uri() {
return policy_uri;
}
public void setPolicy_uri(String policy_uri) {
this.policy_uri = policy_uri;
}
public String getJwks_uri() {
return jwks_uri;
}
public void setJwks_uri(String jwks_uri) {
this.jwks_uri = jwks_uri;
}
public String getJwks() {
return jwks;
}
public void setJwks(String jwks) {
this.jwks = jwks;
}
public String getSoftwareId() {
return softwareId;
}
public void setSoftwareId(String softwareId) {
this.softwareId = softwareId;
}
public String getSoftwareVersion() {
return softwareVersion;
}
public void setSoftwareVersion(String softwareVersion) {
this.softwareVersion = softwareVersion;
}
}

View file

@ -19,18 +19,8 @@ import javax.ws.rs.core.Response;
*/
public class AuthorizeClientUtil {
public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event, RealmModel realm) {
AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
String flowId = clientAuthFlow.getId();
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setFlowId(flowId)
.setConnection(session.getContext().getConnection())
.setEventBuilder(event)
.setRealm(realm)
.setSession(session)
.setUriInfo(session.getContext().getUri())
.setRequest(session.getContext().getContextObject(HttpRequest.class));
public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event) {
AuthenticationProcessor processor = getAuthenticationProcessor(session, event);
Response response = processor.authenticateClient();
if (response != null) {
@ -45,6 +35,24 @@ public class AuthorizeClientUtil {
return new ClientAuthResult(client, processor.getClientAuthAttributes());
}
public static AuthenticationProcessor getAuthenticationProcessor(KeycloakSession session, EventBuilder event) {
RealmModel realm = session.getContext().getRealm();
AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
String flowId = clientAuthFlow.getId();
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setFlowId(flowId)
.setConnection(session.getContext().getConnection())
.setEventBuilder(event)
.setRealm(realm)
.setSession(session)
.setUriInfo(session.getContext().getUri())
.setRequest(session.getContext().getContextObject(HttpRequest.class));
return processor;
}
public static class ClientAuthResult {
private final ClientModel client;

View file

@ -10,6 +10,7 @@ import org.keycloak.services.util.LocaleHelper;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.Locale;
/**
@ -29,6 +30,13 @@ public class DefaultKeycloakContext implements KeycloakContext {
this.session = session;
}
@Override
public URI getAuthServerUrl() {
UriInfo uri = getUri();
KeycloakApplication keycloakApplication = getContextObject(KeycloakApplication.class);
return keycloakApplication.getBaseUri(uri);
}
@Override
public String getContextPath() {
KeycloakApplication app = getContextObject(KeycloakApplication.class);

View file

@ -0,0 +1,92 @@
package org.keycloak.services.clientregistration;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AdapterInstallationClientRegistrationProvider implements ClientRegistrationProvider {
private KeycloakSession session;
private EventBuilder event;
private ClientRegAuth auth;
public AdapterInstallationClientRegistrationProvider(KeycloakSession session) {
this.session = session;
}
@GET
@Path("{clientId}")
@Produces(MediaType.APPLICATION_JSON)
public Response get(@PathParam("clientId") String clientId) {
event.event(EventType.CLIENT_INFO);
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
if (auth.isAuthenticated()) {
auth.requireView(client);
} else {
authenticateClient(client);
}
ClientManager clientManager = new ClientManager(new RealmManager(session));
Object rep = clientManager.toInstallationRepresentation(session.getContext().getRealm(), client, session.getContext().getAuthServerUrl());
event.client(client.getClientId()).success();
return Response.ok(rep).build();
}
@Override
public void setAuth(ClientRegAuth auth) {
this.auth = auth;
}
@Override
public void setEvent(EventBuilder event) {
this.event = event;
}
@Override
public void close() {
}
private void authenticateClient(ClientModel client) {
if (client.isPublicClient()) {
return;
}
AuthenticationProcessor processor = AuthorizeClientUtil.getAuthenticationProcessor(session, event);
Response response = processor.authenticateClient();
if (response != null) {
event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
ClientModel authClient = processor.getClient();
if (client == null) {
event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
if (!authClient.getClientId().equals(client.getClientId())) {
event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
}
}

View file

@ -0,0 +1,34 @@
package org.keycloak.services.clientregistration;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AdapterInstallationClientRegistrationProviderFactory implements ClientRegistrationProviderFactory {
@Override
public ClientRegistrationProvider create(KeycloakSession session) {
return new AdapterInstallationClientRegistrationProvider(session);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "install";
}
}

View file

@ -0,0 +1,128 @@
package org.keycloak.services.clientregistration;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.*;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
import javax.ws.rs.core.HttpHeaders;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientRegAuth {
private KeycloakSession session;
private EventBuilder event;
private String token;
private AccessToken.Access bearerRealmAccess;
private boolean authenticated = false;
public ClientRegAuth(KeycloakSession session, EventBuilder event) {
this.session = session;
this.event = event;
init();
}
private void init() {
RealmModel realm = session.getContext().getRealm();
String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authorizationHeader == null) {
return;
}
String[] split = authorizationHeader.split(" ");
if (!split[0].equalsIgnoreCase("bearer")) {
return;
}
if (split[1].indexOf('.') == -1) {
token = split[1];
authenticated = true;
} else {
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm);
bearerRealmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID);
authenticated = true;
}
}
public boolean isAuthenticated() {
return authenticated;
}
public void requireCreate() {
if (!authenticated) {
event.error(Errors.NOT_ALLOWED);
throw new UnauthorizedException();
}
if (bearerRealmAccess != null) {
if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) {
return;
}
}
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
public void requireView(ClientModel client) {
if (!authenticated) {
event.error(Errors.NOT_ALLOWED);
throw new UnauthorizedException();
}
if (client == null) {
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
if (bearerRealmAccess != null) {
if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) {
return;
}
} else if (token != null) {
if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) {
return;
}
}
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
public void requireUpdate(ClientModel client) {
if (!authenticated) {
event.error(Errors.NOT_ALLOWED);
throw new UnauthorizedException();
}
if (client == null) {
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
if (bearerRealmAccess != null) {
if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) {
return;
}
} else if (token != null) {
if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) {
return;
}
}
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
}

View file

@ -9,7 +9,7 @@ import org.keycloak.provider.Provider;
*/
public interface ClientRegistrationProvider extends Provider {
void setRealm(RealmModel realm);
void setAuth(ClientRegAuth auth);
void setEvent(EventBuilder event);

View file

@ -2,36 +2,43 @@ package org.keycloak.services.clientregistration;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.ErrorResponseException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientRegistrationService {
private RealmModel realm;
private EventBuilder event;
@Context
private KeycloakSession session;
public ClientRegistrationService(RealmModel realm, EventBuilder event) {
this.realm = realm;
public ClientRegistrationService(EventBuilder event) {
this.event = event;
}
@Path("{provider}")
public Object getProvider(@PathParam("provider") String providerId) {
checkSsl();
ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
provider.setRealm(realm);
provider.setEvent(event);
provider.setAuth(new ClientRegAuth(session, event));
return provider;
}
private void checkSsl() {
if (!session.getContext().getUri().getBaseUri().getScheme().equals("https")) {
if (session.getContext().getRealm().getSslRequired().isRequired(session.getContext().getConnection())) {
throw new ErrorResponseException("invalid_request", "HTTPS required", Response.Status.FORBIDDEN);
}
}
}
}

View file

@ -1,22 +1,16 @@
package org.keycloak.services.clientregistration;
import org.jboss.logging.Logger;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.*;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
import javax.ws.rs.*;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
@ -26,31 +20,29 @@ import java.net.URI;
*/
public class DefaultClientRegistrationProvider implements ClientRegistrationProvider {
private static final Logger logger = Logger.getLogger(DefaultClientRegistrationProvider.class);
private KeycloakSession session;
private EventBuilder event;
private RealmModel realm;
private ClientRegAuth auth;
public DefaultClientRegistrationProvider(KeycloakSession session) {
this.session = session;
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response create(ClientRepresentation client) {
event.event(EventType.CLIENT_REGISTER);
authenticate(true, null);
auth.requireCreate();
try {
ClientModel clientModel = RepresentationToModel.createClient(session, realm, client, true);
ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
clientModel.setRegistrationSecret(TokenGenerator.createRegistrationAccessToken());
client = ModelToRepresentation.toRepresentation(clientModel);
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
logger.infov("Created client {0}", client.getClientId());
event.client(client.getClientId()).success();
return Response.created(uri).entity(client).build();
} catch (ModelDuplicateException e) {
@ -64,10 +56,10 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
public Response get(@PathParam("clientId") String clientId) {
event.event(EventType.CLIENT_INFO);
ClientModel client = authenticate(false, clientId);
if (client == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
auth.requireView(client);
event.client(client.getClientId()).success();
return Response.ok(ModelToRepresentation.toRepresentation(client)).build();
}
@ -77,12 +69,12 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
public Response update(@PathParam("clientId") String clientId, ClientRepresentation rep) {
event.event(EventType.CLIENT_UPDATE).client(clientId);
ClientModel client = authenticate(false, clientId);
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
auth.requireUpdate(client);
RepresentationToModel.updateClient(rep, client);
logger.infov("Updated client {0}", rep.getClientId());
event.success();
event.client(client.getClientId()).success();
return Response.status(Response.Status.OK).build();
}
@ -91,9 +83,11 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
public Response delete(@PathParam("clientId") String clientId) {
event.event(EventType.CLIENT_DELETE).client(clientId);
ClientModel client = authenticate(false, clientId);
if (realm.removeClient(client.getId())) {
event.success();
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
auth.requireUpdate(client);
if (session.getContext().getRealm().removeClient(client.getId())) {
event.client(client.getClientId()).success();
return Response.ok().build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
@ -101,50 +95,8 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
}
@Override
public void close() {
}
private ClientModel authenticate(boolean create, String clientId) {
String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
boolean bearer = authorizationHeader != null && authorizationHeader.split(" ")[0].equalsIgnoreCase("Bearer");
if (bearer) {
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm);
AccessToken.Access realmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID);
if (realmAccess != null) {
if (realmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS)) {
return create ? null : realm.getClientByClientId(clientId);
}
if (create && realmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) {
return create ? null : realm.getClientByClientId(clientId);
}
}
} else if (!create) {
ClientModel client;
try {
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
client = clientAuth.getClient();
if (client != null && !client.isPublicClient() && client.getClientId().equals(clientId)) {
return client;
}
} catch (Throwable t) {
}
}
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
@Override
public void setRealm(RealmModel realm) {
this.realm = realm;
public void setAuth(ClientRegAuth auth) {
this.auth = auth;
}
@Override
@ -152,4 +104,8 @@ this.realm = realm;
this.event = event;
}
@Override
public void close() {
}
}

View file

@ -1,34 +0,0 @@
package org.keycloak.services.clientregistration;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class OIDCClientRegistrationProvider implements ClientRegistrationProvider {
private KeycloakSession session;
private RealmModel realm;
private EventBuilder event;
public OIDCClientRegistrationProvider(KeycloakSession session) {
this.session = session;
}
@Override
public void close() {
}
@Override
public void setRealm(RealmModel realm) {
this.realm = realm;
}
@Override
public void setEvent(EventBuilder event) {
this.event = event;
}
}

View file

@ -0,0 +1,27 @@
package org.keycloak.services.clientregistration;
import org.keycloak.common.util.Base64Url;
import java.security.SecureRandom;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class TokenGenerator {
private static final int REGISTRATION_ACCESS_TOKEN_BYTES = 32;
private TokenGenerator() {
}
public String createInitialAccessToken() {
return null;
}
public static String createRegistrationAccessToken() {
byte[] buf = new byte[REGISTRATION_ACCESS_TOKEN_BYTES];
new SecureRandom().nextBytes(buf);
return Base64Url.encode(buf);
}
}

View file

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

View file

@ -0,0 +1,100 @@
package org.keycloak.services.clientregistration.oidc;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.protocol.oidc.OIDCClientDescriptionConverter;
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.clientregistration.ClientRegAuth;
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
import org.keycloak.services.clientregistration.TokenGenerator;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class OIDCClientRegistrationProvider implements ClientRegistrationProvider {
private static final Logger logger = Logger.getLogger(OIDCClientRegistrationProvider.class);
private KeycloakSession session;
private EventBuilder event;
private ClientRegAuth auth;
public OIDCClientRegistrationProvider(KeycloakSession session) {
this.session = session;
}
// @POST
// @Consumes(MediaType.APPLICATION_JSON)
// @Produces(MediaType.APPLICATION_JSON)
// public Response create(OIDCClientRepresentation clientOIDC) {
// event.event(EventType.CLIENT_REGISTER);
//
// auth.requireCreate();
//
// ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
//
// try {
// ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
//
// client = ModelToRepresentation.toRepresentation(clientModel);
//
// String registrationAccessToken = TokenGenerator.createRegistrationAccessToken();
//
// clientModel.setRegistrationSecret(registrationAccessToken);
//
// URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
//
// logger.infov("Created client {0}", client.getClientId());
//
// event.client(client.getClientId()).success();
//
// OIDCClientResponseRepresentation response = DescriptionConverter.toExternalResponse(client);
//
// response.setClientName(client.getName());
// response.setClientUri(client.getBaseUrl());
//
// response.setClientSecret(client.getSecret());
// response.setClientSecretExpiresAt(0);
//
// response.setRedirectUris(client.getRedirectUris());
//
// response.setRegistrationAccessToken(registrationAccessToken);
// response.setRegistrationClientUri(uri.toString());
//
// return Response.created(uri).entity(response).build();
// } catch (ModelDuplicateException e) {
// return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
// }
// }
@Override
public void close() {
}
@Override
public void setAuth(ClientRegAuth auth) {
this.auth = auth;
}
@Override
public void setEvent(EventBuilder event) {
this.event = event;
}
}

View file

@ -1,8 +1,10 @@
package org.keycloak.services.clientregistration;
package org.keycloak.services.clientregistration.oidc;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
import org.keycloak.services.clientregistration.ClientRegistrationProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>

View file

@ -0,0 +1,77 @@
package org.keycloak.services.clientregistration.oidc;
import org.codehaus.jackson.annotate.JsonProperty;
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class OIDCClientResponseRepresentation extends OIDCClientRepresentation {
@JsonProperty("client_id")
private String clientId;
@JsonProperty("client_secret")
private String clientSecret;
@JsonProperty("client_id_issued_at")
private int clientIdIssuedAt;
@JsonProperty("client_secret_expires_at")
private int clientSecretExpiresAt;
@JsonProperty("registration_client_uri")
private String registrationClientUri;
@JsonProperty("registration_access_token")
private String registrationAccessToken;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public int getClientIdIssuedAt() {
return clientIdIssuedAt;
}
public void setClientIdIssuedAt(int clientIdIssuedAt) {
this.clientIdIssuedAt = clientIdIssuedAt;
}
public int getClientSecretExpiresAt() {
return clientSecretExpiresAt;
}
public void setClientSecretExpiresAt(int clientSecretExpiresAt) {
this.clientSecretExpiresAt = clientSecretExpiresAt;
}
public String getRegistrationClientUri() {
return registrationClientUri;
}
public void setRegistrationClientUri(String registrationClientUri) {
this.registrationClientUri = registrationClientUri;
}
public String getRegistrationAccessToken() {
return registrationAccessToken;
}
public void setRegistrationAccessToken(String registrationAccessToken) {
this.registrationAccessToken = registrationAccessToken;
}
}

View file

@ -153,7 +153,7 @@ public class ClientsManagementService {
}
protected ClientModel authorizeClient() {
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient();
if (client.isPublicClient()) {
Map<String, String> error = new HashMap<String, String>();

View file

@ -113,11 +113,11 @@ public class RealmsResource {
return service;
}
@Path("{realm}/client-registration")
@Path("{realm}/clients")
public ClientRegistrationService getClientsService(final @PathParam("realm") String name) {
RealmModel realm = init(name);
EventBuilder event = new EventBuilder(realm, session, clientConnection);
ClientRegistrationService service = new ClientRegistrationService(realm, event);
ClientRegistrationService service = new ClientRegistrationService(event);
ResteasyProviderFactory.getInstance().injectProperties(service);
return service;
}

View file

@ -1 +1,3 @@
org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory
org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory
org.keycloak.services.clientregistration.AdapterInstallationClientRegistrationProviderFactory

View file

@ -0,0 +1,96 @@
package org.keycloak.testsuite.client;
import org.junit.After;
import org.junit.Before;
import org.keycloak.client.registration.ClientRegistration;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTest {
static final String REALM_NAME = "test";
ClientRegistration reg;
@Before
public void before() throws Exception {
reg = new ClientRegistration(testContext.getAuthServerContextRoot() + "/auth", "test");
}
@After
public void after() throws Exception {
reg.close();
}
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation rep = new RealmRepresentation();
rep.setEnabled(true);
rep.setRealm(REALM_NAME);
rep.setUsers(new LinkedList<UserRepresentation>());
LinkedList<CredentialRepresentation> credentials = new LinkedList<>();
CredentialRepresentation password = new CredentialRepresentation();
password.setType(CredentialRepresentation.PASSWORD);
password.setValue("password");
credentials.add(password);
UserRepresentation user = new UserRepresentation();
user.setEnabled(true);
user.setUsername("manage-clients");
user.setCredentials(credentials);
user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.MANAGE_CLIENTS)));
rep.getUsers().add(user);
UserRepresentation user2 = new UserRepresentation();
user2.setEnabled(true);
user2.setUsername("create-clients");
user2.setCredentials(credentials);
user2.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.CREATE_CLIENT)));
rep.getUsers().add(user2);
UserRepresentation user3 = new UserRepresentation();
user3.setEnabled(true);
user3.setUsername("no-access");
user3.setCredentials(credentials);
rep.getUsers().add(user3);
testRealms.add(rep);
}
public ClientRepresentation createClient(ClientRepresentation client) {
Response response = adminClient.realm(REALM_NAME).clients().create(client);
String id = response.getLocation().toString();
id = id.substring(id.lastIndexOf('/') + 1);
client.setId(id);
response.close();
return client;
}
public ClientRepresentation getClient(String clientId) {
try {
return adminClient.realm(REALM_NAME).clients().get(clientId).toRepresentation();
} catch (NotFoundException e) {
return null;
}
}
}

View file

@ -0,0 +1,103 @@
package org.keycloak.testsuite.client;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.client.registration.HttpErrorException;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.ClientRepresentation;
import javax.ws.rs.core.Response;
import static org.junit.Assert.*;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AdapterInstallationConfigTest extends AbstractClientRegistrationTest {
private ClientRepresentation client;
private ClientRepresentation client2;
private ClientRepresentation clientPublic;
@Before
public void before() throws Exception {
super.before();
client = new ClientRepresentation();
client.setEnabled(true);
client.setClientId("RegistrationAccessTokenTest");
client.setSecret("RegistrationAccessTokenTestClientSecret");
client.setPublicClient(false);
client.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
client.setRootUrl("http://root");
client = createClient(client);
client2 = new ClientRepresentation();
client2.setEnabled(true);
client2.setClientId("RegistrationAccessTokenTest2");
client2.setSecret("RegistrationAccessTokenTestClientSecret");
client2.setPublicClient(false);
client2.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
client2.setRootUrl("http://root");
client2 = createClient(client2);
clientPublic = new ClientRepresentation();
clientPublic.setEnabled(true);
clientPublic.setClientId("RegistrationAccessTokenTestPublic");
clientPublic.setPublicClient(true);
clientPublic.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessTokenPublic");
clientPublic.setRootUrl("http://root");
clientPublic = createClient(clientPublic);
}
@Test
public void getConfigWithRegistrationAccessToken() throws ClientRegistrationException {
reg.auth(Auth.token(client.getRegistrationAccessToken()));
AdapterConfig config = reg.getAdapterConfig(client.getClientId());
assertNotNull(config);
}
@Test
public void getConfig() throws ClientRegistrationException {
reg.auth(Auth.client(client.getClientId(), client.getSecret()));
AdapterConfig config = reg.getAdapterConfig(client.getClientId());
assertNotNull(config);
}
@Test
public void getConfigMissingSecret() throws ClientRegistrationException {
reg.auth(null);
try {
reg.getAdapterConfig(client.getClientId());
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
@Test
public void getConfigWrongClient() throws ClientRegistrationException {
reg.auth(Auth.client(client.getClientId(), client.getSecret()));
try {
reg.getAdapterConfig(client2.getClientId());
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
@Test
public void getConfigPublicClient() throws ClientRegistrationException {
reg.auth(null);
AdapterConfig config = reg.getAdapterConfig(clientPublic.getClientId());
assertNotNull(config);
}
}

View file

@ -1,302 +1,211 @@
package org.keycloak.testsuite.client;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.client.registration.ClientRegistration;
import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.client.registration.HttpErrorException;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.Constants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import javax.ws.rs.NotFoundException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import static org.junit.Assert.*;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientRegistrationTest extends AbstractKeycloakTest {
public class ClientRegistrationTest extends AbstractClientRegistrationTest {
private static final String REALM_NAME = "test";
private static final String CLIENT_ID = "test-client";
private static final String CLIENT_SECRET = "test-client-secret";
private ClientRegistration clientRegistrationAsAdmin;
private ClientRegistration clientRegistrationAsClient;
@Before
public void before() throws ClientRegistrationException {
clientRegistrationAsAdmin = clientBuilder().auth(getToken("manage-clients", "password")).build();
clientRegistrationAsClient = clientBuilder().auth(CLIENT_ID, CLIENT_SECRET).build();
}
@After
public void after() throws ClientRegistrationException {
clientRegistrationAsAdmin.close();
clientRegistrationAsClient.close();
}
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation rep = new RealmRepresentation();
rep.setEnabled(true);
rep.setRealm(REALM_NAME);
rep.setUsers(new LinkedList<UserRepresentation>());
LinkedList<CredentialRepresentation> credentials = new LinkedList<>();
CredentialRepresentation password = new CredentialRepresentation();
password.setType(CredentialRepresentation.PASSWORD);
password.setValue("password");
credentials.add(password);
UserRepresentation user = new UserRepresentation();
user.setEnabled(true);
user.setUsername("manage-clients");
user.setCredentials(credentials);
user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.MANAGE_CLIENTS)));
rep.getUsers().add(user);
UserRepresentation user2 = new UserRepresentation();
user2.setEnabled(true);
user2.setUsername("create-clients");
user2.setCredentials(credentials);
user2.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.CREATE_CLIENT)));
rep.getUsers().add(user2);
UserRepresentation user3 = new UserRepresentation();
user3.setEnabled(true);
user3.setUsername("no-access");
user3.setCredentials(credentials);
rep.getUsers().add(user3);
testRealms.add(rep);
}
private void registerClient(ClientRegistration clientRegistration) throws ClientRegistrationException {
private ClientRepresentation registerClient() throws ClientRegistrationException {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(CLIENT_ID);
client.setSecret(CLIENT_SECRET);
ClientRepresentation createdClient = clientRegistration.create(client);
ClientRepresentation createdClient = reg.create(client);
assertEquals(CLIENT_ID, createdClient.getClientId());
client = adminClient.realm(REALM_NAME).clients().get(createdClient.getId()).toRepresentation();
assertEquals(CLIENT_ID, client.getClientId());
AccessTokenResponse token2 = oauthClient.getToken(REALM_NAME, CLIENT_ID, CLIENT_SECRET, "manage-clients", "password");
assertNotNull(token2.getToken());
return client;
}
@Test
public void registerClientAsAdmin() throws ClientRegistrationException {
registerClient(clientRegistrationAsAdmin);
authManageClients();
registerClient();
}
@Test
public void registerClientAsAdminWithCreateOnly() throws ClientRegistrationException {
ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
try {
registerClient(clientRegistration);
} finally {
clientRegistration.close();
}
authCreateClients();
registerClient();
}
@Test
public void registerClientAsAdminWithNoAccess() throws ClientRegistrationException {
ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
authNoAccess();
try {
registerClient(clientRegistration);
registerClient();
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
} finally {
clientRegistration.close();
}
}
@Test
public void getClientAsAdmin() throws ClientRegistrationException {
registerClientAsAdmin();
ClientRepresentation rep = reg.get(CLIENT_ID);
assertNotNull(rep);
}
@Test
public void getClientAsAdminWithCreateOnly() throws ClientRegistrationException {
registerClient(clientRegistrationAsAdmin);
ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
registerClientAsAdmin();
authCreateClients();
try {
clientRegistration.get(CLIENT_ID);
reg.get(CLIENT_ID);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
} finally {
clientRegistration.close();
}
}
@Test
public void wrongClient() throws ClientRegistrationException {
registerClient(clientRegistrationAsAdmin);
ClientRepresentation client = new ClientRepresentation();
client.setClientId("test-client-2");
client.setSecret("test-client-2-secret");
clientRegistrationAsAdmin.create(client);
ClientRegistration clientRegistration = clientBuilder().auth("test-client-2", "test-client-2-secret").build();
client = clientRegistration.get("test-client-2");
assertNotNull(client);
assertEquals("test-client-2", client.getClientId());
try {
try {
clientRegistration.get(CLIENT_ID);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
client = clientRegistrationAsAdmin.get(CLIENT_ID);
try {
clientRegistration.update(client);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
try {
clientRegistration.delete(CLIENT_ID);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
finally {
clientRegistration.close();
}
}
@Test
public void getClientAsAdminWithNoAccess() throws ClientRegistrationException {
registerClient(clientRegistrationAsAdmin);
ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
registerClientAsAdmin();
authNoAccess();
try {
clientRegistration.get(CLIENT_ID);
reg.get(CLIENT_ID);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
} finally {
clientRegistration.close();
}
}
private void updateClient(ClientRegistration clientRegistration) throws ClientRegistrationException {
ClientRepresentation client = clientRegistration.get(CLIENT_ID);
@Test
public void getClientNotFound() throws ClientRegistrationException {
authManageClients();
try {
reg.get(CLIENT_ID);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
private void updateClient() throws ClientRegistrationException {
ClientRepresentation client = reg.get(CLIENT_ID);
client.setRedirectUris(Collections.singletonList("http://localhost:8080/app"));
clientRegistration.update(client);
reg.update(client);
ClientRepresentation updatedClient = clientRegistration.get(CLIENT_ID);
ClientRepresentation updatedClient = reg.get(CLIENT_ID);
assertEquals(1, updatedClient.getRedirectUris().size());
assertEquals("http://localhost:8080/app", updatedClient.getRedirectUris().get(0));
}
@Test
public void updateClientAsAdmin() throws ClientRegistrationException {
registerClient(clientRegistrationAsAdmin);
updateClient(clientRegistrationAsAdmin);
registerClientAsAdmin();
authManageClients();
updateClient();
}
@Test
public void updateClientAsAdminWithCreateOnly() throws ClientRegistrationException {
ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
authCreateClients();
try {
updateClient(clientRegistration);
updateClient();
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
} finally {
clientRegistration.close();
}
}
@Test
public void updateClientAsAdminWithNoAccess() throws ClientRegistrationException {
ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
authNoAccess();
try {
updateClient(clientRegistration);
updateClient();
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
} finally {
clientRegistration.close();
}
}
@Test
public void updateClientAsClient() throws ClientRegistrationException {
registerClient(clientRegistrationAsAdmin);
updateClient(clientRegistrationAsClient);
public void updateClientNotFound() throws ClientRegistrationException {
authManageClients();
try {
updateClient();
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
private void deleteClient(ClientRegistration clientRegistration) throws ClientRegistrationException {
clientRegistration.delete(CLIENT_ID);
// Can't authenticate as client after client is deleted
ClientRepresentation client = clientRegistrationAsAdmin.get(CLIENT_ID);
assertNull(client);
private void deleteClient(ClientRepresentation client) throws ClientRegistrationException {
reg.delete(CLIENT_ID);
try {
adminClient.realm("test").clients().get(client.getId()).toRepresentation();
fail("Expected 403");
} catch (NotFoundException e) {
}
}
@Test
public void deleteClientAsAdmin() throws ClientRegistrationException {
registerClient(clientRegistrationAsAdmin);
deleteClient(clientRegistrationAsAdmin);
authCreateClients();
ClientRepresentation client = registerClient();
authManageClients();
deleteClient(client);
}
@Test
public void deleteClientAsAdminWithCreateOnly() throws ClientRegistrationException {
ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
authManageClients();
ClientRepresentation client = registerClient();
try {
deleteClient(clientRegistration);
authCreateClients();
deleteClient(client);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
} finally {
clientRegistration.close();
}
}
@Test
public void deleteClientAsAdminWithNoAccess() throws ClientRegistrationException {
ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
authManageClients();
ClientRepresentation client = registerClient();
try {
deleteClient(clientRegistration);
authNoAccess();
deleteClient(client);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
} finally {
clientRegistration.close();
}
}
@Test
public void deleteClientAsClient() throws ClientRegistrationException {
registerClient(clientRegistrationAsAdmin);
deleteClient(clientRegistrationAsClient);
private void authCreateClients() {
reg.auth(Auth.token(getToken("create-clients", "password")));
}
private ClientRegistration.ClientRegistrationBuilder clientBuilder() {
return ClientRegistration.create().realm("test").authServerUrl(testContext.getAuthServerContextRoot() + "/auth");
private void authManageClients() {
reg.auth(Auth.token(getToken("manage-clients", "password")));
}
private void authNoAccess() {
reg.auth(Auth.token(getToken("no-access", "password")));
}
private String getToken(String username, String password) {

View file

@ -0,0 +1,94 @@
package org.keycloak.testsuite.client;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.client.registration.HttpErrorException;
import org.keycloak.representations.idm.ClientRepresentation;
import javax.ws.rs.core.Response;
import static org.junit.Assert.*;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest {
private ClientRepresentation client;
@Before
public void before() throws Exception {
super.before();
client = new ClientRepresentation();
client.setEnabled(true);
client.setClientId("RegistrationAccessTokenTest");
client.setSecret("RegistrationAccessTokenTestClientSecret");
client.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
client.setRootUrl("http://root");
client = createClient(client);
reg.auth(Auth.token(client.getRegistrationAccessToken()));
}
@Test
public void getClientWithRegistrationToken() throws ClientRegistrationException {
ClientRepresentation rep = reg.get(client.getClientId());
assertNotNull(rep);
}
@Test
public void getClientWithBadRegistrationToken() throws ClientRegistrationException {
reg.auth(Auth.token("invalid"));
try {
reg.get(client.getClientId());
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
@Test
public void updateClientWithRegistrationToken() throws ClientRegistrationException {
client.setRootUrl("http://newroot");
reg.update(client);
assertEquals("http://newroot", getClient(client.getId()).getRootUrl());
}
@Test
public void updateClientWithBadRegistrationToken() throws ClientRegistrationException {
client.setRootUrl("http://newroot");
reg.auth(Auth.token("invalid"));
try {
reg.update(client);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
assertEquals("http://root", getClient(client.getId()).getRootUrl());
}
@Test
public void deleteClientWithRegistrationToken() throws ClientRegistrationException {
reg.delete(client);
assertNull(getClient(client.getId()));
}
@Test
public void deleteClientWithBadRegistrationToken() throws ClientRegistrationException {
reg.auth(Auth.token("invalid"));
try {
reg.delete(client);
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
assertNotNull(getClient(client.getId()));
}
}