Merge pull request #1825 from stianst/client-reg
KEYCLOAK-1749 Client registration service
This commit is contained in:
commit
83ff02ea53
44 changed files with 1542 additions and 543 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.protocol.saml.clientregistration.EntityDescriptorClientRegistrationProviderFactory
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory
|
||||
org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory
|
||||
org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory
|
||||
org.keycloak.services.clientregistration.AdapterInstallationClientRegistrationProviderFactory
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue