Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
KEYCLOAK-1749 Client registration service and client java api
- Loading branch information
Showing
21 changed files
with
910 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?xml version="1.0"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
<parent> | ||
<artifactId>keycloak-parent</artifactId> | ||
<groupId>org.keycloak</groupId> | ||
<version>1.6.0.Final-SNAPSHOT</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>keycloak-client-api</artifactId> | ||
<name>Keycloak Client API</name> | ||
<description/> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.keycloak</groupId> | ||
<artifactId>keycloak-core</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.httpcomponents</groupId> | ||
<artifactId>httpclient</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
275 changes: 275 additions & 0 deletions
275
client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,275 @@ | ||
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.idm.ClientRepresentation; | ||
import org.keycloak.util.Base64; | ||
import org.keycloak.util.JsonSerialization; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
|
||
/** | ||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> | ||
*/ | ||
public class ClientRegistration { | ||
|
||
private String clientRegistrationUrl; | ||
private HttpClient httpClient; | ||
private Auth auth; | ||
|
||
public static ClientRegistrationBuilder create() { | ||
return new ClientRegistrationBuilder(); | ||
} | ||
|
||
private ClientRegistration() { | ||
} | ||
|
||
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException { | ||
String content = serialize(client); | ||
InputStream resultStream = doPost(content); | ||
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 = doGet(clientId); | ||
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null; | ||
} | ||
|
||
public void update(ClientRepresentation client) throws ClientRegistrationException { | ||
String content = serialize(client); | ||
doPut(content, 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(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); | ||
} | ||
} | ||
|
||
private String serialize(ClientRepresentation client) throws ClientRegistrationException { | ||
try { | ||
return JsonSerialization.writeValueAsString(client); | ||
} catch (IOException e) { | ||
throw new ClientRegistrationException("Failed to write json object", e); | ||
} | ||
} | ||
|
||
private <T> T deserialize(InputStream inputStream, Class<T> clazz) throws ClientRegistrationException { | ||
try { | ||
return JsonSerialization.readValue(inputStream, clazz); | ||
} catch (IOException e) { | ||
throw new ClientRegistrationException("Failed to read json object", e); | ||
} | ||
} | ||
|
||
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"; | ||
|
||
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; | ||
} | ||
} | ||
|
||
} |
16 changes: 16 additions & 0 deletions
16
client-api/src/main/java/org/keycloak/client/registration/ClientRegistrationException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.keycloak.client.registration; | ||
|
||
/** | ||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> | ||
*/ | ||
public class ClientRegistrationException extends Exception { | ||
|
||
public ClientRegistrationException(String s, Throwable throwable) { | ||
super(s, throwable); | ||
} | ||
|
||
public ClientRegistrationException(String s) { | ||
super(s); | ||
} | ||
|
||
} |
22 changes: 22 additions & 0 deletions
22
client-api/src/main/java/org/keycloak/client/registration/HttpErrorException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package org.keycloak.client.registration; | ||
|
||
import org.apache.http.StatusLine; | ||
|
||
import java.io.IOException; | ||
|
||
/** | ||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> | ||
*/ | ||
public class HttpErrorException extends IOException { | ||
|
||
private StatusLine statusLine; | ||
|
||
public HttpErrorException(StatusLine statusLine) { | ||
this.statusLine = statusLine; | ||
} | ||
|
||
public StatusLine getStatusLine() { | ||
return statusLine; | ||
} | ||
|
||
} |
6 changes: 2 additions & 4 deletions
6
core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.