allow dynamic registration of clients
parent
80eb57402c
commit
687e63fe05
|
@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
|
@ -108,12 +109,7 @@ public class TxEndpoint {
|
||||||
// otherwise we build one from the parts
|
// otherwise we build one from the parts
|
||||||
|
|
||||||
tx = new TxEntity();
|
tx = new TxEntity();
|
||||||
// client ID is passed in as the handle for the key object
|
ClientDetailsEntity client = loadOrRegisterClient(json);
|
||||||
// we don't support ephemeral keys (yet)
|
|
||||||
String clientId = json.get("keys").getAsString();
|
|
||||||
|
|
||||||
// first, load the client
|
|
||||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
|
||||||
|
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
m.addAttribute(JsonErrorView.ERROR, "unknown_key");
|
m.addAttribute(JsonErrorView.ERROR, "unknown_key");
|
||||||
|
@ -169,11 +165,14 @@ public class TxEndpoint {
|
||||||
switch (tx.getStatus()) {
|
switch (tx.getStatus()) {
|
||||||
case NEW:
|
case NEW:
|
||||||
// now make sure the client is asking for scopes that it's allowed to
|
// now make sure the client is asking for scopes that it's allowed to
|
||||||
if (!scopeService.scopesMatch(tx.getClient().getScope(), tx.getScope())) {
|
// note: if the client has no scopes registered, it can ask for anything
|
||||||
m.addAttribute(JsonErrorView.ERROR, "resource_not_allowed");
|
if (!tx.getClient().getScope().isEmpty()) {
|
||||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "The client requested resources it does not have access to.");
|
if (!scopeService.scopesMatch(tx.getClient().getScope(), tx.getScope())) {
|
||||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
m.addAttribute(JsonErrorView.ERROR, "resource_not_allowed");
|
||||||
return JsonErrorView.VIEWNAME;
|
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "The client requested resources it does not have access to.");
|
||||||
|
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||||
|
return JsonErrorView.VIEWNAME;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2Request o2r = new OAuth2Request(
|
OAuth2Request o2r = new OAuth2Request(
|
||||||
|
@ -215,22 +214,24 @@ public class TxEndpoint {
|
||||||
String callbackString = callback.get("uri").getAsString();
|
String callbackString = callback.get("uri").getAsString();
|
||||||
Path callbackPath = Paths.get(URI.create(callbackString).getPath());
|
Path callbackPath = Paths.get(URI.create(callbackString).getPath());
|
||||||
|
|
||||||
// we do sub-path matching for the callback
|
if (!tx.getClient().getRedirectUris().isEmpty()) {
|
||||||
// FIXME: this is a really simplistic filter that definitely has holes in it
|
// we do sub-path matching for the callback
|
||||||
boolean callbackMatches = tx.getClient().getRedirectUris().stream()
|
// FIXME: this is a really simplistic filter that definitely has holes in it
|
||||||
.filter(s -> callbackString.startsWith(s))
|
boolean callbackMatches = tx.getClient().getRedirectUris().stream()
|
||||||
.map(URI::create)
|
.filter(s -> callbackString.startsWith(s))
|
||||||
.map(URI::getPath)
|
.map(URI::create)
|
||||||
.map(Paths::get)
|
.map(URI::getPath)
|
||||||
.anyMatch(path ->
|
.map(Paths::get)
|
||||||
callbackPath.startsWith(path)
|
.anyMatch(path ->
|
||||||
);
|
callbackPath.startsWith(path)
|
||||||
|
);
|
||||||
|
|
||||||
if (!callbackMatches) {
|
if (!callbackMatches) {
|
||||||
m.addAttribute(JsonErrorView.ERROR, "invalid_callback_uri");
|
m.addAttribute(JsonErrorView.ERROR, "invalid_callback_uri");
|
||||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "The client presented a callback URI that did not match one registered.");
|
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "The client presented a callback URI that did not match one registered.");
|
||||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||||
return JsonErrorView.VIEWNAME;
|
return JsonErrorView.VIEWNAME;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.setCallbackUri(callbackString);
|
tx.setCallbackUri(callbackString);
|
||||||
|
@ -258,6 +259,14 @@ public class TxEndpoint {
|
||||||
h.put("presentation", "bearer");
|
h.put("presentation", "bearer");
|
||||||
map.put("handle", h);
|
map.put("handle", h);
|
||||||
|
|
||||||
|
if (tx.getClient().isDynamicallyRegistered()) {
|
||||||
|
Map<String, String> kh = ImmutableMap.of(
|
||||||
|
"value", tx.getClient().getClientId(),
|
||||||
|
"presentation", "bearer"
|
||||||
|
);
|
||||||
|
map.put("key_handle", kh);
|
||||||
|
}
|
||||||
|
|
||||||
txService.save(tx);
|
txService.save(tx);
|
||||||
|
|
||||||
m.addAttribute(JsonEntityView.ENTITY, map);
|
m.addAttribute(JsonEntityView.ENTITY, map);
|
||||||
|
@ -320,6 +329,68 @@ public class TxEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ClientDetailsEntity loadOrRegisterClient(JsonObject json) {
|
||||||
|
|
||||||
|
if (json.get("keys").isJsonObject()) {
|
||||||
|
// dynamically register the client
|
||||||
|
|
||||||
|
if (!json.get("keys").getAsJsonObject().has("jwk")) {
|
||||||
|
// we can only do a JWKS-based key proof
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.get("keys").getAsJsonObject().has("proof")
|
||||||
|
&& json.get("keys").getAsJsonObject().get("proof").getAsString().equals("jwsd")) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
String jwkString = json.get("keys").getAsJsonObject().get("jwk").toString();
|
||||||
|
JWK jwk = JWK.parse(jwkString); // we have to round-trip this to get into the native object format for Nimbus
|
||||||
|
|
||||||
|
// TODO: see if we can figure out how to look up the client by its key value
|
||||||
|
//ClientDetailsEntity client = clientService.findClientByPublicKey(jwk);
|
||||||
|
|
||||||
|
// we create a new client and register it with the given key
|
||||||
|
ClientDetailsEntity client = new ClientDetailsEntity();
|
||||||
|
client.setDynamicallyRegistered(true);
|
||||||
|
client.setJwks(new JWKSet(jwk));
|
||||||
|
|
||||||
|
if (json.has("display") && json.get("display").isJsonObject()) {
|
||||||
|
JsonObject display = json.get("display").getAsJsonObject();
|
||||||
|
|
||||||
|
if (display.has("name")) {
|
||||||
|
client.setClientName(display.get("name").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display.has("uri")) {
|
||||||
|
client.setClientUri(display.get("uri").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display.has("logo_uri")) {
|
||||||
|
client.setLogoUri(display.get("logo_uri").getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientDetailsEntity saved = clientService.saveNewClient(client);
|
||||||
|
|
||||||
|
return saved;
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// unsupported proof type
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// client ID is passed in as the handle for the key object
|
||||||
|
String clientId = json.get("keys").getAsString();
|
||||||
|
|
||||||
|
// first, load the client
|
||||||
|
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void checkDetachedJws(String jwsd, String requestBody, JWK clientKey) {
|
private void checkDetachedJws(String jwsd, String requestBody, JWK clientKey) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue