implemented main part of authorization and token delivery
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -9,8 +9,11 @@ import static java.net.HttpURLConnection.*;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import de.srsoftware.oidc.api.*;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class ClientController extends Controller {
|
||||
@@ -24,17 +27,6 @@ public class ClientController extends Controller {
|
||||
clients = clientService;
|
||||
}
|
||||
|
||||
private boolean add(HttpExchange ex, Session session) throws IOException {
|
||||
if (!session.user().hasPermission(MANAGE_CLIENTS)) return badRequest(ex, "NOT ALLOWED");
|
||||
var json = json(ex);
|
||||
var redirects = new HashSet<String>();
|
||||
for (Object o : json.getJSONArray(REDIRECT_URIS)) {
|
||||
if (o instanceof String s) redirects.add(s);
|
||||
}
|
||||
var client = new Client(json.getString(CLIENT_ID), json.getString(NAME), json.getString(SECRET), redirects);
|
||||
clients.add(client);
|
||||
return sendContent(ex, client);
|
||||
}
|
||||
|
||||
private boolean authorize(HttpExchange ex, Session session) throws IOException {
|
||||
var user = session.user();
|
||||
@@ -48,14 +40,17 @@ public class ClientController extends Controller {
|
||||
if (!client.redirectUris().contains(redirect)) return badRequest(ex, Map.of(CAUSE, "unknown redirect uri", REDIRECT_URI, redirect));
|
||||
|
||||
if (!authorizations.isAuthorized(client, session.user())) {
|
||||
if (json.has(CONFIRMED) && json.getBoolean(CONFIRMED)) {
|
||||
authorizations.authorize(client, user, null);
|
||||
if (json.has(DAYS)) {
|
||||
var days = json.getInt(DAYS);
|
||||
var expiration = Instant.now().plus(Duration.ofDays(days));
|
||||
authorizations.authorize(client, user, expiration);
|
||||
} else {
|
||||
return sendContent(ex, Map.of(CONFIRMED, false, NAME, client.name()));
|
||||
}
|
||||
}
|
||||
var state = json.getString(STATE);
|
||||
var code = client.generateCode();
|
||||
var code = UUID.randomUUID().toString();
|
||||
authorizations.addCode(client, session.user(), code);
|
||||
return sendContent(ex, Map.of(CONFIRMED, true, CODE, code, REDIRECT_URI, redirect, STATE, state));
|
||||
}
|
||||
|
||||
@@ -94,8 +89,8 @@ public class ClientController extends Controller {
|
||||
switch (path) {
|
||||
case "/":
|
||||
return load(ex, session);
|
||||
case "/add":
|
||||
return add(ex, session);
|
||||
case "/add", "/update":
|
||||
return save(ex, session);
|
||||
case "/authorize":
|
||||
return authorize(ex, session);
|
||||
case "/list":
|
||||
@@ -123,4 +118,16 @@ public class ClientController extends Controller {
|
||||
}
|
||||
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
|
||||
}
|
||||
|
||||
private boolean save(HttpExchange ex, Session session) throws IOException {
|
||||
if (!session.user().hasPermission(MANAGE_CLIENTS)) return badRequest(ex, "NOT ALLOWED");
|
||||
var json = json(ex);
|
||||
var redirects = new HashSet<String>();
|
||||
for (Object o : json.getJSONArray(REDIRECT_URIS)) {
|
||||
if (o instanceof String s) redirects.add(s);
|
||||
}
|
||||
var client = new Client(json.getString(CLIENT_ID), json.getString(NAME), json.getString(SECRET), redirects);
|
||||
clients.save(client);
|
||||
return sendContent(ex, client);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,9 @@ import static java.lang.System.Logger.Level.*;
|
||||
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import de.srsoftware.oidc.api.Client;
|
||||
import de.srsoftware.oidc.api.ClientService;
|
||||
import de.srsoftware.oidc.api.PathHandler;
|
||||
import de.srsoftware.oidc.api.*;
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -21,10 +20,14 @@ import org.jose4j.lang.JoseException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class TokenController extends PathHandler {
|
||||
private final ClientService clients;
|
||||
private final ClientService clients;
|
||||
private final AuthorizationService authorizations;
|
||||
private final UserService users;
|
||||
|
||||
public TokenController(ClientService clientService) {
|
||||
clients = clientService;
|
||||
public TokenController(AuthorizationService authorizationService, ClientService clientService, UserService userService) {
|
||||
authorizations = authorizationService;
|
||||
clients = clientService;
|
||||
users = userService;
|
||||
}
|
||||
|
||||
private Map<String, String> deserialize(String body) {
|
||||
@@ -42,27 +45,31 @@ public class TokenController extends PathHandler {
|
||||
}
|
||||
|
||||
private boolean provideToken(HttpExchange ex) throws IOException {
|
||||
var map = deserialize(body(ex));
|
||||
// TODO: check Authorization Code, → https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
|
||||
// TODO: check Redirect URL
|
||||
LOG.log(DEBUG, "post data: {0}", map);
|
||||
LOG.log(WARNING, "{0}.provideToken(ex) not implemented!", getClass().getSimpleName());
|
||||
var map = deserialize(body(ex));
|
||||
var grantType = map.get(GRANT_TYPE);
|
||||
if (!ATUH_CODE.equals(grantType)) sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown grant type", GRANT_TYPE, grantType));
|
||||
var optClient = Optional.ofNullable(map.get(CLIENT_ID)).flatMap(clients::getClient);
|
||||
if (optClient.isEmpty()) {
|
||||
LOG.log(ERROR, "client not found");
|
||||
return sendEmptyResponse(HTTP_BAD_REQUEST, ex);
|
||||
// TODO: send correct response
|
||||
}
|
||||
if (!AUTH_CODE.equals(grantType)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown grant type", GRANT_TYPE, grantType));
|
||||
|
||||
var code = map.get(CODE);
|
||||
var optAuthorization = authorizations.forCode(code);
|
||||
if (optAuthorization.isEmpty()) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "invalid auth code", CODE, code));
|
||||
var authorization = optAuthorization.get();
|
||||
|
||||
var clientId = map.get(CLIENT_ID);
|
||||
if (!authorization.clientId().equals(clientId)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "invalid client id", CLIENT_ID, clientId));
|
||||
var optClient = clients.getClient(clientId);
|
||||
if (optClient.isEmpty()) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown client", CLIENT_ID, clientId));
|
||||
var client = optClient.get();
|
||||
|
||||
var user = users.load(authorization.userId());
|
||||
if (user.isEmpty()) return sendContent(ex, 500, Map.of(ERROR, "User not found"));
|
||||
|
||||
var uri = URLDecoder.decode(map.get(REDIRECT_URI), StandardCharsets.UTF_8);
|
||||
if (!client.redirectUris().contains(uri)) sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown redirect uri", REDIRECT_URI, uri));
|
||||
|
||||
var secretFromClient = map.get(CLIENT_SECRET);
|
||||
var client = optClient.get();
|
||||
if (!client.secret().equals(secretFromClient)) {
|
||||
LOG.log(ERROR, "client secret mismatch");
|
||||
return sendEmptyResponse(HTTP_BAD_REQUEST, ex);
|
||||
// TODO: send correct response
|
||||
}
|
||||
String jwToken = createJWT(client);
|
||||
if (!client.secret().equals(secretFromClient)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "client secret mismatch"));
|
||||
|
||||
String jwToken = createJWT(client, user.get());
|
||||
ex.getResponseHeaders().add("Cache-Control", "no-store");
|
||||
JSONObject response = new JSONObject();
|
||||
response.put(ACCESS_TOKEN, UUID.randomUUID().toString()); // TODO: wofür genau wird der verwendet, was gilt es hier zu beachten
|
||||
@@ -72,22 +79,12 @@ public class TokenController extends PathHandler {
|
||||
return sendContent(ex, response);
|
||||
}
|
||||
|
||||
private String createJWT(Client client) {
|
||||
private String createJWT(Client client, User user) {
|
||||
try {
|
||||
byte[] secretBytes = client.secret().getBytes(StandardCharsets.UTF_8);
|
||||
HmacKey hmacKey = new HmacKey(secretBytes);
|
||||
|
||||
JwtClaims claims = new JwtClaims();
|
||||
claims.setIssuer("Issuer"); // who creates the token and signs it
|
||||
claims.setAudience("Audience"); // to whom the token is intended to be sent
|
||||
claims.setExpirationTimeMinutesInTheFuture(10); // time when the token will expire (10 minutes from now)
|
||||
claims.setGeneratedJwtId(); // a unique identifier for the token
|
||||
claims.setIssuedAtToNow(); // when the token was issued/created (now)
|
||||
claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago)
|
||||
claims.setSubject("subject"); // the subject/principal is whom the token is about
|
||||
claims.setClaim("email", "mail@example.com"); // additional claims/attributes about the subject can be added
|
||||
List<String> groups = Arrays.asList("group-one", "other-group", "group-three");
|
||||
claims.setStringListClaim("groups", groups); // multi-valued claims work too and will end up as a JSON array
|
||||
JwtClaims claims = getJwtClaims(user);
|
||||
|
||||
// A JWT is a JWS and/or a JWE with JSON claims as the payload.
|
||||
// In this example it is a JWS so we create a JsonWebSignature object.
|
||||
@@ -105,4 +102,16 @@ public class TokenController extends PathHandler {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static JwtClaims getJwtClaims(User user) {
|
||||
JwtClaims claims = new JwtClaims();
|
||||
claims.setIssuer(APP_NAME); // who creates the token and signs it
|
||||
claims.setExpirationTimeMinutesInTheFuture(10); // time when the token will expire (10 minutes from now)
|
||||
claims.setGeneratedJwtId(); // a unique identifier for the token
|
||||
claims.setIssuedAtToNow(); // when the token was issued/created (now)
|
||||
claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago)
|
||||
claims.setSubject(user.uuid()); // the subject/principal is whom the token is about
|
||||
claims.setClaim("email", user.email()); // additional claims/attributes about the subject can be added
|
||||
return claims;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user