added extension mechanism for saving client information in between runs
parent
70958376cb
commit
98d917f3b9
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
import org.mitre.oauth2.model.RegisteredClient;
|
||||||
import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor;
|
import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor;
|
||||||
import org.mitre.openid.connect.client.service.ClientConfigurationService;
|
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.mitre.openid.connect.config.ServerConfiguration;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -34,6 +35,7 @@ import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||||
|
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@ -53,7 +55,10 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf
|
||||||
private static Logger logger = LoggerFactory.getLogger(DynamicServerConfigurationService.class);
|
private static Logger logger = LoggerFactory.getLogger(DynamicServerConfigurationService.class);
|
||||||
|
|
||||||
private LoadingCache<ServerConfiguration, RegisteredClient> clients;
|
private LoadingCache<ServerConfiguration, RegisteredClient> 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;
|
private RegisteredClient template;
|
||||||
|
|
||||||
public DynamicRegistrationClientConfigurationService() {
|
public DynamicRegistrationClientConfigurationService() {
|
||||||
|
@ -84,6 +89,30 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf
|
||||||
this.template = template;
|
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<ServerConfiguration, RegisteredClient> {
|
public class DynamicClientRegistrationLoader extends CacheLoader<ServerConfiguration, RegisteredClient> {
|
||||||
private HttpClient httpClient = new DefaultHttpClient();
|
private HttpClient httpClient = new DefaultHttpClient();
|
||||||
private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||||
|
@ -93,22 +122,41 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf
|
||||||
public RegisteredClient load(ServerConfiguration serverConfig) throws Exception {
|
public RegisteredClient load(ServerConfiguration serverConfig) throws Exception {
|
||||||
RestTemplate restTemplate = new RestTemplate(httpFactory);
|
RestTemplate restTemplate = new RestTemplate(httpFactory);
|
||||||
|
|
||||||
// dynamically register this client
|
|
||||||
JsonObject jsonRequest = ClientDetailsEntityJsonProcessor.serialize(template);
|
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<String> entity = new HttpEntity<String>(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));
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
String registered = restTemplate.getForObject(knownClient.getRegistrationClientUri(), String.class);
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
||||||
headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON));
|
RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered);
|
||||||
|
|
||||||
HttpEntity<String> entity = new HttpEntity<String>(jsonRequest.toString(), headers);
|
return client;
|
||||||
|
}
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<String, RegisteredClient> clients = new HashMap<String, RegisteredClient>();
|
||||||
|
|
||||||
|
/* (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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<RegisteredClient>() {
|
||||||
|
@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<RegisteredClient>() {
|
||||||
|
@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<String, RegisteredClient> clients = new HashMap<String, RegisteredClient>();
|
||||||
|
|
||||||
|
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<Map<String, RegisteredClient>>(){}.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<Map<String, RegisteredClient>>(){}.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue