From 98d917f3b9b52f026cb87c6b24cf090ae100c1f2 Mon Sep 17 00:00:00 2001 From: Justin Richer Date: Wed, 26 Jun 2013 13:21:35 -0400 Subject: [PATCH] added extension mechanism for saving client information in between runs --- .../service/RegisteredClientService.java | 31 ++++ ...egistrationClientConfigurationService.java | 80 ++++++++-- .../impl/InMemoryRegisteredClientService.java | 36 +++++ .../impl/JsonFileRegisteredClientService.java | 142 ++++++++++++++++++ 4 files changed, 273 insertions(+), 16 deletions(-) create mode 100644 openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/RegisteredClientService.java create mode 100644 openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/InMemoryRegisteredClientService.java create mode 100644 openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/JsonFileRegisteredClientService.java diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/RegisteredClientService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/RegisteredClientService.java new file mode 100644 index 000000000..d558cd05d --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/RegisteredClientService.java @@ -0,0 +1,31 @@ +/** + * + */ +package org.mitre.openid.connect.client.service; + +import org.mitre.oauth2.model.RegisteredClient; + +/** + * @author jricher + * + */ +public interface RegisteredClientService { + + /** + * Get a remembered client (if one exists) to talk to the given issuer. This + * client likely doesn't have its full configuration information but contains + * the information needed to fetch it. + * @param issuer + * @return + */ + RegisteredClient getByIssuer(String issuer); + + /** + * Save this client's information for talking to the given issuer. This will + * save only enough information to fetch the client's full configuration from + * the server. + * @param client + */ + void save(String issuer, RegisteredClient client); + +} diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicRegistrationClientConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicRegistrationClientConfigurationService.java index 4a30e0917..bf249a6a2 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicRegistrationClientConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicRegistrationClientConfigurationService.java @@ -27,6 +27,7 @@ import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.RegisteredClient; import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor; import org.mitre.openid.connect.client.service.ClientConfigurationService; +import org.mitre.openid.connect.client.service.RegisteredClientService; import org.mitre.openid.connect.config.ServerConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,6 +35,7 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.web.client.RestTemplate; @@ -53,7 +55,10 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf private static Logger logger = LoggerFactory.getLogger(DynamicServerConfigurationService.class); private LoadingCache clients; + + private RegisteredClientService registeredClientService = new InMemoryRegisteredClientService(); + // TODO: make sure the template doesn't have "client_id", "client_secret", or "registration_access_token" set on it already private RegisteredClient template; public DynamicRegistrationClientConfigurationService() { @@ -84,6 +89,30 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf this.template = template; } + /** + * @return the registeredClientService + */ + public RegisteredClientService getRegisteredClientService() { + return registeredClientService; + } + + /** + * @param registeredClientService the registeredClientService to set + */ + public void setRegisteredClientService(RegisteredClientService registeredClientService) { + this.registeredClientService = registeredClientService; + } + + + /** + * Loader class that fetches the client information. + * + * If a client has been registered (ie, it's known to the RegisteredClientService), then this + * will fetch the client's configuration from the server. + * + * @author jricher + * + */ public class DynamicClientRegistrationLoader extends CacheLoader { private HttpClient httpClient = new DefaultHttpClient(); private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); @@ -93,22 +122,41 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf public RegisteredClient load(ServerConfiguration serverConfig) throws Exception { RestTemplate restTemplate = new RestTemplate(httpFactory); - // dynamically register this client - JsonObject jsonRequest = ClientDetailsEntityJsonProcessor.serialize(template); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); - - HttpEntity entity = new HttpEntity(jsonRequest.toString(), headers); - - String registered = restTemplate.postForObject(serverConfig.getRegistrationEndpointUri(), entity, String.class); - // TODO: handle HTTP errors - - // TODO: save registration token and other important bits - RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered); - - return client; + + RegisteredClient knownClient = registeredClientService.getByIssuer(serverConfig.getIssuer()); + if (knownClient == null) { + + // dynamically register this client + JsonObject jsonRequest = ClientDetailsEntityJsonProcessor.serialize(template); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); + + HttpEntity entity = new HttpEntity(jsonRequest.toString(), headers); + + String registered = restTemplate.postForObject(serverConfig.getRegistrationEndpointUri(), entity, String.class); + // TODO: handle HTTP errors + + RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered); + + // save this client for later + registeredClientService.save(serverConfig.getIssuer(), client); + + return client; + } else { + + // load this client's information from the server + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, knownClient.getRegistrationAccessToken())); + headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); + + String registered = restTemplate.getForObject(knownClient.getRegistrationClientUri(), String.class); + + RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered); + + return client; + } } } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/InMemoryRegisteredClientService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/InMemoryRegisteredClientService.java new file mode 100644 index 000000000..aeb4dbde6 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/InMemoryRegisteredClientService.java @@ -0,0 +1,36 @@ +/** + * + */ +package org.mitre.openid.connect.client.service.impl; + +import java.util.HashMap; +import java.util.Map; + +import org.mitre.oauth2.model.RegisteredClient; +import org.mitre.openid.connect.client.service.RegisteredClientService; + +/** + * @author jricher + * + */ +public class InMemoryRegisteredClientService implements RegisteredClientService { + + private Map clients = new HashMap(); + + /* (non-Javadoc) + * @see org.mitre.openid.connect.client.service.RegisteredClientService#getByIssuer(java.lang.String) + */ + @Override + public RegisteredClient getByIssuer(String issuer) { + return clients.get(issuer); + } + + /* (non-Javadoc) + * @see org.mitre.openid.connect.client.service.RegisteredClientService#save(org.mitre.oauth2.model.RegisteredClient) + */ + @Override + public void save(String issuer, RegisteredClient client) { + clients.put(issuer, client); + } + +} diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/JsonFileRegisteredClientService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/JsonFileRegisteredClientService.java new file mode 100644 index 000000000..648b0648e --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/JsonFileRegisteredClientService.java @@ -0,0 +1,142 @@ +/** + * + */ +package org.mitre.openid.connect.client.service.impl; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.mitre.oauth2.model.RegisteredClient; +import org.mitre.openid.connect.client.service.RegisteredClientService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +/** + * @author jricher + * + */ +public class JsonFileRegisteredClientService implements RegisteredClientService { + + private static Logger logger = LoggerFactory.getLogger(JsonFileRegisteredClientService.class); + + private Gson gson = new GsonBuilder() + .registerTypeAdapter(RegisteredClient.class, new JsonSerializer() { + @Override + public JsonElement serialize(RegisteredClient src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject obj = new JsonObject(); + obj.addProperty("token", src.getRegistrationAccessToken()); + obj.addProperty("uri", src.getRegistrationClientUri()); + if (src.getClientIdIssuedAt() != null) { + obj.addProperty("issued", src.getClientIdIssuedAt().getTime()); + } + if (src.getClientSecretExpiresAt() != null) { + obj.addProperty("expires", src.getClientSecretExpiresAt().getTime()); + } + return obj; + } + }) + .registerTypeAdapter(RegisteredClient.class, new JsonDeserializer() { + @Override + public RegisteredClient deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (json.isJsonObject()) { + JsonObject src = json.getAsJsonObject(); + RegisteredClient rc = new RegisteredClient(); + rc.setRegistrationAccessToken(src.get("token").getAsString()); + rc.setRegistrationClientUri(src.get("uri").getAsString()); + if (src.has("issued") && !src.get("issued").isJsonNull()) { + rc.setClientIdIssuedAt(new Date(src.get("issued").getAsLong())); + } + if (src.has("expires") && !src.get("expires").isJsonNull()) { + rc.setClientSecretExpiresAt(new Date(src.get("expires").getAsLong())); + } + return rc; + } else { + return null; + } + } + }) + .create(); + + private File file; + + private Map clients = new HashMap(); + + public JsonFileRegisteredClientService(String filename) { + this.file = new File(filename); + load(); + } + + /* (non-Javadoc) + * @see org.mitre.openid.connect.client.service.RegisteredClientService#getByIssuer(java.lang.String) + */ + @Override + public RegisteredClient getByIssuer(String issuer) { + return clients.get(issuer); + } + + /* (non-Javadoc) + * @see org.mitre.openid.connect.client.service.RegisteredClientService#save(java.lang.String, org.mitre.oauth2.model.RegisteredClient) + */ + @Override + public void save(String issuer, RegisteredClient client) { + clients.put(issuer, client); + write(); + } + + /** + * Sync this file out to disk. + */ + private void write() { + try { + FileWriter out = new FileWriter(file); + + gson.toJson(clients, new TypeToken>(){}.getType(), out); + + out.close(); + + } catch (FileNotFoundException e) { + logger.error("Could not write to output file", e); + } catch (IOException e) { + logger.error("Could not write to output file", e); + } + } + + /** + * Load the map in from disk. + */ + private void load() { + try { + FileReader in = new FileReader(file); + + clients = gson.fromJson(in, new TypeToken>(){}.getType()); + + in.close(); + + } catch (FileNotFoundException e) { + logger.error("Could not read from input file", e); + } catch (IOException e) { + logger.error("Could not read from input file", e); + } + } + +}