Browse Source

revised TokenController.provideToken

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
sqlite
Stephan Richter 4 months ago
parent
commit
49929adaa3
  1. 7
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthResult.java
  2. 5
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Authorization.java
  3. 7
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthorizedScope.java
  4. 8
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthorizedScopes.java
  5. 8
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/ClaimAuthorizationService.java
  6. 72
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java
  7. 18
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java
  8. 39
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java
  9. 67
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java
  10. 33
      de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java

7
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthResult.java

@ -0,0 +1,7 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api;
import java.util.Set;
public record AuthResult(AuthorizedScopes authorizedScopes, Set<String> unauthorizedScopes, String authCode) {
}

5
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Authorization.java

@ -0,0 +1,5 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api;
public record Authorization(String clientId, String userId, AuthorizedScopes scopes) {
}

7
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthorizedScope.java

@ -1,7 +0,0 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api;
import java.time.Instant;
public record AuthorizedScope(String scope, Instant expiration) {
}

8
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthorizedScopes.java

@ -0,0 +1,8 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api;
import java.time.Instant;
import java.util.Set;
public record AuthorizedScopes(Set<String> scopes, Instant expiration) {
}

8
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/ClaimAuthorizationService.java

@ -3,12 +3,10 @@ package de.srsoftware.oidc.api;
import java.time.Instant; import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.Optional;
import java.util.Set;
public interface ClaimAuthorizationService { public interface ClaimAuthorizationService {
public record AuthResult(List<AuthorizedScope> authorizedScopes, Set<String> unauthorizedScopes, String authCode) {
}
AuthResult getAuthorization(User user, Client client, Collection<String> scopes);
ClaimAuthorizationService authorize(User user, Client client, Collection<String> scopes, Instant expiration); ClaimAuthorizationService authorize(User user, Client client, Collection<String> scopes, Instant expiration);
Optional<Authorization> consumeAuthorization(String authCode);
AuthResult getAuthorization(User user, Client client, Collection<String> scopes);
} }

72
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java

@ -1,40 +1,44 @@
/* © SRSoftware 2024 */ /* © SRSoftware 2024 */
package de.srsoftware.oidc.api; package de.srsoftware.oidc.api;
public class Constants { public class Constants {
public static final String ACCESS_TOKEN = "access_token"; public static final String ACCESS_TOKEN = "access_token";
public static final String APP_NAME = "LightOIDC"; public static final String APP_NAME = "LightOIDC";
public static final String AUTH_CODE = "authorization_code"; public static final String AUTH_CODE = "authorization_code";
public static final String AUTHORIZATION = "Authorization"; public static final String AUTHORIZATION = "Authorization";
public static final String AUTHORZED = "authorized"; public static final String AUTHORZED = "authorized";
public static final String BEARER = "Bearer"; public static final String BEARER = "Bearer";
public static final String CAUSE = "cause"; public static final String CAUSE = "cause";
public static final String CLIENT_ID = "client_id"; public static final String CLIENT_ID = "client_id";
public static final String CLIENT_SECRET = "client_secret"; public static final String CLIENT_SECRET = "client_secret";
public static final String CODE = "code"; public static final String CODE = "code";
public static final String ERROR = "error"; public static final String ERROR = "error";
public static final String CONFIG_PATH = "LIGHTOIDC_CONFIG_PATH"; public static final String CONFIG_PATH = "LIGHTOIDC_CONFIG_PATH";
public static final String CONFIRMED = "confirmed"; public static final String CONFIRMED = "confirmed";
public static final String DAYS = "days"; public static final String DAYS = "days";
public static final String ERROR_DESCRIPTION = "error_description"; public static final String ERROR_DESCRIPTION = "error_description";
public static final String EXPIRATION = "expiration"; public static final String EXPIRATION = "expiration";
public static final String EXPIRES_IN = "expires_in"; public static final String EXPIRES_IN = "expires_in";
public static final String GRANT_TYPE = "grant_type"; public static final String GRANT_TYPE = "grant_type";
public static final String ID_TOKEN = "id_token"; public static final String ID_TOKEN = "id_token";
public static final String INVALID_REDIRECT_URI = "invalid_request_uri"; public static final String INVALID_CLIENT = "invalid_client";
public static final String INVALID_REQUEST = "invalid_request"; public static final String INVALID_GRANT = "invalid_grant";
public static final String INVALID_REDIRECT_URI = "invalid_request_uri";
public static final String INVALID_REQUEST = "invalid_request";
public static final String INVALID_REQUEST_OBJECT = "invalid_request_object"; public static final String INVALID_REQUEST_OBJECT = "invalid_request_object";
public static final String INVALID_SCOPE = "invalid_scope"; public static final String INVALID_SCOPE = "invalid_scope";
public static final String NAME = "name"; public static final String NAME = "name";
public static final String NONCE = "nonce"; public static final String NONCE = "nonce";
public static final String OPENID = "openid"; public static final String OPENID = "openid";
public static final String REDIRECT_URI = "redirect_uri"; public static final String REDIRECT_URI = "redirect_uri";
public static final String REDIRECT_URIS = "redirect_uris"; public static final String REDIRECT_URIS = "redirect_uris";
public static final String REQUEST_NOT_SUPPORTED = "request_not_supported"; public static final String REQUEST_NOT_SUPPORTED = "request_not_supported";
public static final String RESPONSE_TYPE = "response_type"; public static final String RESPONSE_TYPE = "response_type";
public static final String SCOPE = "scope"; public static final String SCOPE = "scope";
public static final String SECRET = "secret"; public static final String SECRET = "secret";
public static final String STATE = "state"; public static final String STATE = "state";
public static final String TOKEN = "token"; public static final String TOKEN = "token";
public static final String TOKEN_TYPE = "token_type"; public static final String TOKEN_TYPE = "token_type";
public static final String UNAUTHORIZED_CLIENT = "unauthorized_client";
} }

18
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java

@ -11,10 +11,7 @@ import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpServer;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.json.JSONObject; import org.json.JSONObject;
@ -30,6 +27,9 @@ public abstract class PathHandler implements HttpHandler {
private String[] paths; private String[] paths;
public record BasicAuth(String userId, String pass) {
}
public class Bond { public class Bond {
Bond(String[] paths) { Bond(String[] paths) {
PathHandler.this.paths = paths; PathHandler.this.paths = paths;
@ -102,6 +102,16 @@ public abstract class PathHandler implements HttpHandler {
return getHeader(ex, AUTHORIZATION); return getHeader(ex, AUTHORIZATION);
} }
public static Optional<BasicAuth> getBasicAuth(HttpExchange ex) {
return getAuthToken(ex)
.filter(token -> token.startsWith("Basic ")) //
.map(token -> token.substring(6))
.map(Base64.getDecoder()::decode)
.map(bytes -> new String(bytes, UTF_8))
.map(token -> token.split(":", 2))
.map(arr -> new BasicAuth(arr[0], arr[1]));
}
public static Optional<String> getBearer(HttpExchange ex) { public static Optional<String> getBearer(HttpExchange ex) {
return getAuthToken(ex).filter(token -> token.startsWith("Bearer ")).map(token -> token.substring(7)); return getAuthToken(ex).filter(token -> token.startsWith("Bearer ")).map(token -> token.substring(7));
} }

39
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java

@ -3,15 +3,16 @@ package de.srsoftware.oidc.backend;
import static de.srsoftware.oidc.api.Constants.*; import static de.srsoftware.oidc.api.Constants.*;
import static de.srsoftware.oidc.api.Permission.MANAGE_CLIENTS; import static de.srsoftware.oidc.api.Permission.MANAGE_CLIENTS;
import static de.srsoftware.utils.Optionals.emptyIfBlank;
import static java.net.HttpURLConnection.*; import static java.net.HttpURLConnection.*;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.oidc.api.*; import de.srsoftware.oidc.api.*;
import de.srsoftware.utils.Optionals;
import java.io.IOException; import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import org.json.JSONObject; import org.json.JSONObject;
public class ClientController extends Controller { public class ClientController extends Controller {
@ -27,46 +28,46 @@ public class ClientController extends Controller {
private boolean authorizationError(HttpExchange ex, String errorCode, String description, String state) throws IOException { private boolean authorizationError(HttpExchange ex, String errorCode, String description, String state) throws IOException {
var map = new HashMap<String, String>(); var map = new HashMap<String, String>();
map.put(ERROR,errorCode); map.put(ERROR, errorCode);
if (description != null) map.put(ERROR_DESCRIPTION,description); emptyIfBlank(description).ifPresent(d -> map.put(ERROR_DESCRIPTION, d));
if (state != null) map.put(STATE,state); emptyIfBlank(state).ifPresent(s -> map.put(STATE, s));
return badRequest(ex,map); return badRequest(ex, map);
} }
private boolean authorize(HttpExchange ex, Session session) throws IOException { private boolean authorize(HttpExchange ex, Session session) throws IOException {
var user = session.user(); var user = session.user();
var json = json(ex); var json = json(ex);
var state = json.has(STATE) ? json.getString(STATE) : null; var state = json.has(STATE) ? json.getString(STATE) : null;
if (!json.has(CLIENT_ID)) return authorizationError(ex, INVALID_REQUEST,"Missing required parameter \"%s\"!".formatted(CLIENT_ID),state); if (!json.has(CLIENT_ID)) return authorizationError(ex, INVALID_REQUEST, "Missing required parameter \"%s\"!".formatted(CLIENT_ID), state);
var clientId = json.getString(CLIENT_ID); var clientId = json.getString(CLIENT_ID);
var optClient = clients.getClient(clientId); var optClient = clients.getClient(clientId);
if (optClient.isEmpty()) return authorizationError(ex,INVALID_REQUEST_OBJECT,"unknown client: %s".formatted(clientId),state); if (optClient.isEmpty()) return authorizationError(ex, INVALID_REQUEST_OBJECT, "unknown client: %s".formatted(clientId), state);
for (String param : List.of(SCOPE, RESPONSE_TYPE, REDIRECT_URI)) { for (String param : List.of(SCOPE, RESPONSE_TYPE, REDIRECT_URI)) {
if (!json.has(param)) return authorizationError(ex,INVALID_REQUEST,"Missing required parameter \"%s\"!".formatted(param),state); if (!json.has(param)) return authorizationError(ex, INVALID_REQUEST, "Missing required parameter \"%s\"!".formatted(param), state);
} }
var scopes = toList(json, SCOPE); var scopes = toList(json, SCOPE);
if (!scopes.contains(OPENID)) return authorizationError(ex,INVALID_SCOPE,"This is an OpenID Provider. You should request \"openid\" scope!",state); if (!scopes.contains(OPENID)) return authorizationError(ex, INVALID_SCOPE, "This is an OpenID Provider. You should request \"openid\" scope!", state);
var responseTypes = toList(json, RESPONSE_TYPE); var responseTypes = toList(json, RESPONSE_TYPE);
for (var responseType : responseTypes) { for (var responseType : responseTypes) {
switch (responseType) { switch (responseType) {
case ID_TOKEN: case ID_TOKEN:
case TOKEN: case TOKEN:
return authorizationError(ex, REQUEST_NOT_SUPPORTED, "Response type \"%s\" currently not supported".formatted(responseType),state); return authorizationError(ex, REQUEST_NOT_SUPPORTED, "Response type \"%s\" currently not supported".formatted(responseType), state);
case CODE: case CODE:
break; break;
default: default:
return authorizationError(ex,INVALID_REQUEST_OBJECT,"Unknown response type \"%s\"".formatted(responseType),state); return authorizationError(ex, INVALID_REQUEST_OBJECT, "Unknown response type \"%s\"".formatted(responseType), state);
} }
} }
if ( !responseTypes.contains(CODE)) return authorizationError(ex, REQUEST_NOT_SUPPORTED, "Sorry, at the moment I can only handle \"%s\" response type".formatted(CODE),state); if (!responseTypes.contains(CODE)) return authorizationError(ex, REQUEST_NOT_SUPPORTED, "Sorry, at the moment I can only handle \"%s\" response type".formatted(CODE), state);
var client = optClient.get(); var client = optClient.get();
var redirect = json.getString(REDIRECT_URI); var redirect = json.getString(REDIRECT_URI);
if (!client.redirectUris().contains(redirect)) authorizationError(ex, INVALID_REDIRECT_URI, "unknown redirect uri: %s".formatted(redirect),state); if (!client.redirectUris().contains(redirect)) authorizationError(ex, INVALID_REDIRECT_URI, "unknown redirect uri: %s".formatted(redirect), state);
client.nonce(json.has(NONCE) ? json.getString(NONCE) : null); client.nonce(json.has(NONCE) ? json.getString(NONCE) : null);
if (json.has(AUTHORZED)) { if (json.has(AUTHORZED)) { // user did consent
var authorized = json.getJSONObject(AUTHORZED); var authorized = json.getJSONObject(AUTHORZED);
var days = authorized.getInt("days"); var days = authorized.getInt("days");
var list = new ArrayList<String>(); var list = new ArrayList<String>();
@ -78,9 +79,9 @@ public class ClientController extends Controller {
if (!authResult.unauthorizedScopes().isEmpty()) { if (!authResult.unauthorizedScopes().isEmpty()) {
return sendContent(ex, Map.of("unauthorized_scopes", authResult.unauthorizedScopes(), "rp", client.name())); return sendContent(ex, Map.of("unauthorized_scopes", authResult.unauthorizedScopes(), "rp", client.name()));
} }
var authorizedScopes = authResult.authorizedScopes().stream().map(AuthorizedScope::scope).collect(Collectors.joining(" ")); var joinedAuthorizedScopes = Optionals.nullable(authResult.authorizedScopes()).map(AuthorizedScopes::scopes).map(list -> String.join(" ", list));
var result = new HashMap<String, String>(); var result = new HashMap<String, String>();
result.put(SCOPE, authorizedScopes); joinedAuthorizedScopes.ifPresent(authorizedScopes -> result.put(SCOPE, authorizedScopes));
result.put(CODE, authResult.authCode()); result.put(CODE, authResult.authCode());
if (state != null) result.put(STATE, state); if (state != null) result.put(STATE, state);
return sendContent(ex, result); return sendContent(ex, result);

67
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java

@ -2,18 +2,23 @@
package de.srsoftware.oidc.backend; package de.srsoftware.oidc.backend;
import static de.srsoftware.oidc.api.Constants.*; import static de.srsoftware.oidc.api.Constants.*;
import static de.srsoftware.oidc.api.Constants.ERROR;
import static de.srsoftware.utils.Optionals.emptyIfBlank;
import static java.lang.System.Logger.Level.*; import static java.lang.System.Logger.Level.*;
import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.oidc.api.*; import de.srsoftware.oidc.api.*;
import java.io.IOException; import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jose4j.jwk.PublicJsonWebKey; import org.jose4j.jwk.PublicJsonWebKey;
import org.jose4j.jws.JsonWebSignature; import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.JwtClaims;
import org.jose4j.lang.JoseException; import org.jose4j.lang.JoseException;
import org.json.JSONObject;
public class TokenController extends PathHandler { public class TokenController extends PathHandler {
public record Configuration(String issuer, int tokenExpirationMinutes) { public record Configuration(String issuer, int tokenExpirationMinutes) {
@ -46,36 +51,54 @@ public class TokenController extends PathHandler {
return notFound(ex); return notFound(ex);
} }
private HashMap<String, String> tokenResponse(String errorCode, String description) throws IOException {
var map = new HashMap<String, String>();
map.put(ERROR, errorCode);
emptyIfBlank(description).ifPresent(d -> map.put(ERROR_DESCRIPTION, d));
return map;
}
private boolean provideToken(HttpExchange ex) throws IOException { private boolean provideToken(HttpExchange ex) throws IOException {
var map = deserialize(body(ex)); var map = deserialize(body(ex));
// TODO: check data, → https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
return sendEmptyResponse(HTTP_NOT_IMPLEMENTED, ex);
/*
var grantType = map.get(GRANT_TYPE); var grantType = map.get(GRANT_TYPE);
if (!AUTH_CODE.equals(grantType)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown grant type", GRANT_TYPE, grantType)); // verify grant type
if (!AUTH_CODE.equals(grantType)) return badRequest(ex, tokenResponse(INVALID_GRANT, "unknown grant type \"%s\"".formatted(grantType)));
var code = map.get(CODE); var basicAuth = getBasicAuth(ex).orElse(null);
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); var clientId = basicAuth != null ? basicAuth.userId() : 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); var optClient = clients.getClient(clientId);
if (optClient.isEmpty()) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown client", CLIENT_ID, clientId)); if (optClient.isEmpty()) return badRequest(ex, tokenResponse(INVALID_CLIENT, "unknown client \"%s\"".formatted(clientId)));
var client = optClient.get(); var client = optClient.get();
if (client.secret() != null) { // for confidential clients:
// authenticate client by matching secret
String clientSecret = basicAuth != null ? basicAuth.pass() : map.get(CLIENT_SECRET);
if (clientSecret == null) return sendContent(ex, HTTP_UNAUTHORIZED, tokenResponse(INVALID_CLIENT, "client not authenticated"));
if (!client.secret().equals(clientSecret)) return sendContent(ex, HTTP_UNAUTHORIZED, tokenResponse(INVALID_CLIENT, "client not authenticated"));
}
var user = users.load(authorization.userId()); var authCode = map.get(CODE);
if (user.isEmpty()) return sendContent(ex, 500, Map.of(ERROR, "User not found"));
// verify that code is not re-used
var optAuthorization = authorizations.consumeAuthorization(authCode);
if (optAuthorization.isEmpty()) return badRequest(ex, tokenResponse(INVALID_GRANT, "invalid auth code: \"%s\"".formatted(authCode)));
var authorization = optAuthorization.get();
// verify authorization code was issued to the authenticated client
if (!authorization.clientId().equals(clientId)) return badRequest(ex, tokenResponse(UNAUTHORIZED_CLIENT, null));
// verify redirect URI
var uri = URLDecoder.decode(map.get(REDIRECT_URI), StandardCharsets.UTF_8); 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)); if (!client.redirectUris().contains(uri)) return badRequest(ex, tokenResponse(INVALID_REQUEST, "unknown redirect uri: \"%s\"".formatted(uri)));
// verify user is valid
var user = users.load(authorization.userId());
if (user.isEmpty()) return badRequest(ex, tokenResponse(INVALID_REQUEST, "unknown user"));
if (!authorization.scopes().scopes().contains(OPENID)) return badRequest(ex, tokenResponse(INVALID_REQUEST, "Token invalid for OpenID scope"));
if (client.secret() != null) {
String clientSecret = nullable(ex.getRequestHeaders().get(AUTHORIZATION)).map(list -> list.get(0)).filter(s -> s.startsWith("Basic ")).map(s -> s.substring(6)).map(s -> Base64.getDecoder().decode(s)).map(bytes -> new String(bytes, StandardCharsets.UTF_8)).filter(s -> s.startsWith("%s:".formatted(client.id()))).map(s -> s.substring(client.id().length() + 1).trim()).orElseGet(() -> map.get(CLIENT_SECRET));
if (clientSecret == null) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "client secret missing"));
if (!client.secret().equals(clientSecret)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "client secret mismatch"));
}
String jwToken = createJWT(client, user.get()); String jwToken = createJWT(client, user.get());
ex.getResponseHeaders().add("Cache-Control", "no-store"); ex.getResponseHeaders().add("Cache-Control", "no-store");
JSONObject response = new JSONObject(); JSONObject response = new JSONObject();
@ -83,8 +106,8 @@ public class TokenController extends PathHandler {
response.put(TOKEN_TYPE, BEARER); response.put(TOKEN_TYPE, BEARER);
response.put(EXPIRES_IN, 3600); response.put(EXPIRES_IN, 3600);
response.put(ID_TOKEN, jwToken); response.put(ID_TOKEN, jwToken);
LOG.log(DEBUG, jwToken);
return sendContent(ex, response);*/ return sendContent(ex, response);
} }
private String createJWT(Client client, User user) { private String createJWT(Client client, User user) {

33
de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java

@ -37,7 +37,7 @@ public class FileStore implements ClaimAuthorizationService, ClientService, Sess
private Duration sessionDuration = Duration.of(10, ChronoUnit.MINUTES); private Duration sessionDuration = Duration.of(10, ChronoUnit.MINUTES);
private Map<String, Client> clients = new HashMap<>(); private Map<String, Client> clients = new HashMap<>();
private Map<String, User> accessTokens = new HashMap<>(); private Map<String, User> accessTokens = new HashMap<>();
private Map<String, String> authCodes = new HashMap<>(); private Map<String, Authorization> authCodes = new HashMap<>();
public FileStore(File storage, PasswordHasher<String> passwordHasher) throws IOException { public FileStore(File storage, PasswordHasher<String> passwordHasher) throws IOException {
this.storageFile = storage.toPath(); this.storageFile = storage.toPath();
@ -266,6 +266,11 @@ public class FileStore implements ClaimAuthorizationService, ClientService, Sess
} }
/*** ClaimAuthorizationService methods ***/ /*** ClaimAuthorizationService methods ***/
private String authCode(User user, Client client, Authorization authorization) {
var code = uuid();
authCodes.put(code, authorization);
return code;
}
@Override @Override
public ClaimAuthorizationService authorize(User user, Client client, Collection<String> scopes, Instant expiration) { public ClaimAuthorizationService authorize(User user, Client client, Collection<String> scopes, Instant expiration) {
@ -280,8 +285,10 @@ public class FileStore implements ClaimAuthorizationService, ClientService, Sess
return this; return this;
} }
private AuthResult unauthorized(Collection<String> scopes) {
return new AuthResult(List.of(), new HashSet<>(scopes), null); @Override
public Optional<Authorization> consumeAuthorization(String authCode) {
return nullable(authCodes.remove(authCode));
} }
@Override @Override
@ -291,25 +298,27 @@ public class FileStore implements ClaimAuthorizationService, ClientService, Sess
if (userAuthorizations == null) return unauthorized(scopes); if (userAuthorizations == null) return unauthorized(scopes);
var clientScopes = userAuthorizations.has(client.id()) ? userAuthorizations.getJSONObject(client.id()) : null; var clientScopes = userAuthorizations.has(client.id()) ? userAuthorizations.getJSONObject(client.id()) : null;
if (clientScopes == null) return unauthorized(scopes); if (clientScopes == null) return unauthorized(scopes);
var now = Instant.now(); var now = Instant.now();
var authorizedScopes = new ArrayList<AuthorizedScope>(); var authorizedScopes = new HashSet<String>();
var unauthorizedScopes = new HashSet<String>(); var unauthorizedScopes = new HashSet<String>();
Instant earliestExpiration = null;
for (var scope : scopes) { for (var scope : scopes) {
if (clientScopes.has(scope)) { if (clientScopes.has(scope)) {
var expiration = Instant.ofEpochSecond(clientScopes.getLong(scope)); var expiration = Instant.ofEpochSecond(clientScopes.getLong(scope));
if (expiration.isAfter(now)) { if (expiration.isAfter(now)) {
authorizedScopes.add(new AuthorizedScope(scope, expiration)); authorizedScopes.add(scope);
if (earliestExpiration == null || expiration.isBefore(earliestExpiration)) earliestExpiration = expiration;
} else { } else {
unauthorizedScopes.add(scope); unauthorizedScopes.add(scope);
} }
} }
} }
return new AuthResult(authorizedScopes, unauthorizedScopes, authCode(user, client));
var authorization = new Authorization(client.id(), user.uuid(), new AuthorizedScopes(authorizedScopes, earliestExpiration));
return new AuthResult(authorization.scopes(), unauthorizedScopes, authCode(user, client, authorization));
} }
private String authCode(User user, Client client) { private AuthResult unauthorized(Collection<String> scopes) {
var code = uuid(); return new AuthResult(null, new HashSet<>(scopes), null);
authCodes.put(user.uuid() + "@" + client.id(), code);
return code;
} }
} }

Loading…
Cancel
Save