Browse Source

tied nonce to AuthorizationService by dedicated methods

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
sqlite
Stephan Richter 2 months ago
parent
commit
8bfaf22084
  1. 6
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthorizationService.java
  2. 2
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Authorization.java
  3. 10
      de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/AuthServiceTest.java
  4. 5
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java
  5. 13
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java
  6. 49
      de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java
  7. 29
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java

6
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthorizationService.java

@ -8,7 +8,11 @@ import java.util.Collection; @@ -8,7 +8,11 @@ import java.util.Collection;
import java.util.Optional;
public interface AuthorizationService {
AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, String nonce, Instant expiration);
AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, Instant expiration);
Optional<Authorization> consumeAuthorization(String authCode);
AuthResult getAuthorization(String userId, String clientId, Collection<String> scopes);
Optional<String> consumeNonce(String uuid, String id);
void nonce(String uuid, String id, String string);
}

2
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Authorization.java

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

10
de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/AuthServiceTest.java

@ -26,10 +26,9 @@ public abstract class AuthServiceTest { @@ -26,10 +26,9 @@ public abstract class AuthServiceTest {
var authorizationService = authorizationService();
var userId1 = uuid();
var expiration = Instant.now();
var nonce = uuid();
authorizationService.authorize(userId1, CLIENT1, SCOPES1, nonce, expiration);
authorizationService.authorize(userId1, CLIENT1, SCOPES1, expiration);
expiration = Instant.now().plusSeconds(3600).truncatedTo(SECONDS); // test overwrite
authorizationService.authorize(userId1, CLIENT1, SCOPES1, nonce, expiration); // test overwrite
authorizationService.authorize(userId1, CLIENT1, SCOPES1, expiration); // test overwrite
var authorization = authorizationService.getAuthorization(userId1, CLIENT1, Set.of(OPENID));
assertEquals(1, authorization.authorizedScopes().scopes().size());
assertTrue(authorization.authorizedScopes().scopes().contains(OPENID));
@ -53,10 +52,9 @@ public abstract class AuthServiceTest { @@ -53,10 +52,9 @@ public abstract class AuthServiceTest {
public void testConsume() {
var authorizationService = authorizationService();
var nonce = uuid();
var userId1 = uuid();
var expiration = Instant.now().plusSeconds(3600).truncatedTo(SECONDS);
authorizationService.authorize(userId1, CLIENT1, SCOPES1, nonce, expiration);
authorizationService.authorize(userId1, CLIENT1, SCOPES1, expiration);
var authResult = authorizationService.getAuthorization(userId1, CLIENT1, Set.of(OPENID));
var authCode = authResult.authCode();
assertNotNull(authCode);
@ -75,5 +73,5 @@ public abstract class AuthServiceTest { @@ -75,5 +73,5 @@ public abstract class AuthServiceTest {
assertTrue(optAuth.isEmpty());
}
// TODO: test nonce passing
// TODO: test nonce methods
}

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

@ -79,9 +79,10 @@ public class ClientController extends Controller { @@ -79,9 +79,10 @@ public class ClientController extends Controller {
var days = authorized.getInt("days");
var list = new ArrayList<String>();
authorized.getJSONArray("scopes").forEach(scope -> list.add(scope.toString()));
var nonce = json.has(NONCE) ? json.getString(NONCE) : null;
authorizations.authorize(user.uuid(), client.id(), list, nonce, Instant.now().plus(days, ChronoUnit.DAYS));
authorizations.authorize(user.uuid(), client.id(), list, Instant.now().plus(days, ChronoUnit.DAYS));
}
if (json.has(NONCE)) authorizations.nonce(user.uuid(), client.id(), json.getString(NONCE));
var authResult = authorizations.getAuthorization(user.uuid(), client.id(), scopes);
if (!authResult.unauthorizedScopes().isEmpty()) {

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

@ -115,7 +115,7 @@ public class TokenController extends PathHandler { @@ -115,7 +115,7 @@ public class TokenController extends PathHandler {
var user = optUser.get();
var accessToken = users.accessToken(user);
String jwToken = createJWT(client, user, accessToken, authorization.nonce());
String jwToken = createJWT(client, user, accessToken);
ex.getResponseHeaders().add("Cache-Control", "no-store");
JSONObject response = new JSONObject();
response.put(ACCESS_TOKEN, accessToken.id());
@ -126,13 +126,13 @@ public class TokenController extends PathHandler { @@ -126,13 +126,13 @@ public class TokenController extends PathHandler {
return sendContent(ex, response);
}
private String createJWT(Client client, User user, AccessToken accessToken, String nonce) {
private String createJWT(Client client, User user, AccessToken accessToken) {
try {
PublicJsonWebKey key = keyManager.getKey();
var algo = key.getAlgorithm();
var atHash = this.atHash(algo, accessToken);
key.setUse("sig");
JwtClaims claims = createIdTokenClaims(user, client, atHash, nonce);
JwtClaims claims = createIdTokenClaims(user, client, atHash);
// 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.
@ -167,7 +167,9 @@ public class TokenController extends PathHandler { @@ -167,7 +167,9 @@ public class TokenController extends PathHandler {
}
}
private JwtClaims createIdTokenClaims(User user, Client client, String atHash, String nonce) {
private JwtClaims createIdTokenClaims(User user, Client client, String atHash) {
var optNonce = authorizations.consumeNonce(user.uuid(), client.id());
optNonce.ifPresent(nonce -> LOG.log(System.Logger.Level.ERROR, "consumed nonce: %s", nonce));
JwtClaims claims = new JwtClaims();
// required claims:
@ -179,7 +181,8 @@ public class TokenController extends PathHandler { @@ -179,7 +181,8 @@ public class TokenController extends PathHandler {
claims.setClaim(AT_HASH, atHash);
claims.setClaim(CLIENT_ID, client.id());
claims.setClaim(EMAIL, user.email()); // additional claims/attributes about the subject can be added
if (nonce != null) claims.setClaim(NONCE, nonce);
optNonce.ifPresent(nonce -> claims.setClaim(NONCE, nonce));
claims.setGeneratedJwtId(); // a unique identifier for the token
return claims;
}

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

@ -37,6 +37,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -37,6 +37,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
private final PasswordHasher<String> passwordHasher;
private Map<String, AccessToken> accessTokens = new HashMap<>();
private Map<String, Authorization> authCodes = new HashMap<>();
private Map<String, String> nonceMap = new HashMap<>();
private Authenticator auth;
public FileStore(File storage, PasswordHasher<String> passwordHasher) throws IOException {
@ -72,16 +73,15 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -72,16 +73,15 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
var clients = authorizations.getJSONObject(userId);
var clientIds = Set.copyOf(clients.keySet());
for (var clientId : clientIds) {
var clientData = clients.getJSONObject(clientId);
var scopeMap = clientData.getJSONObject(SCOPE);
var scopes = Set.copyOf(scopeMap.keySet());
var client = clients.getJSONObject(clientId);
var scopes = Set.copyOf(client.keySet());
for (var scope : scopes) {
var expiration = Instant.ofEpochSecond(scopeMap.getLong(scope));
var expiration = Instant.ofEpochSecond(client.getLong(scope));
if (expiration.isBefore(now)) {
scopeMap.remove(scope);
client.remove(scope);
}
}
if (scopeMap.isEmpty()) clients.remove(clientId);
if (client.isEmpty()) clients.remove(clientId);
}
if (clients.isEmpty()) authorizations.remove(userId);
}
@ -298,7 +298,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -298,7 +298,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
@Override
public ClientService save(Client client) {
if (!json.has(CLIENTS)) json.put(CLIENTS, new JSONObject());
json.getJSONObject(CLIENTS).put(client.id(), client.map());
json.getJSONObject(CLIENTS).put(client.id(), Map.of(NAME, client.name(), SECRET, client.secret(), REDIRECT_URIS, client.redirectUris()));
save();
return this;
}
@ -311,7 +311,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -311,7 +311,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
return new Client(clientId, clientData.getString(NAME), clientData.getString(SECRET), redirectUris);
}
/*** ClaimAuthorizationService methods ***/
/*** AuthorizationService methods ***/
private String authCode(Authorization authorization) {
var code = uuid();
authCodes.put(code, authorization);
@ -319,20 +319,13 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -319,20 +319,13 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
}
@Override
public AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, String nonce, Instant expiration) {
public AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, Instant expiration) {
if (!json.has(AUTHORIZATIONS)) json.put(AUTHORIZATIONS, new JSONObject());
var authorizations = json.getJSONObject(AUTHORIZATIONS);
if (!authorizations.has(userId)) authorizations.put(userId, new JSONObject());
var userAuthorizations = authorizations.getJSONObject(userId);
if (!userAuthorizations.has(clientId)) userAuthorizations.put(clientId, new JSONObject());
var clientData = userAuthorizations.getJSONObject(clientId);
if (nonce != null) {
clientData.put(NONCE, nonce);
} else {
if (clientData.has(NONCE)) clientData.remove(NONCE);
}
if (!clientData.has(SCOPE)) clientData.put(SCOPE, new JSONObject());
var clientScopes = clientData.getJSONObject(SCOPE);
var clientScopes = userAuthorizations.getJSONObject(clientId);
for (var scope : scopes) clientScopes.put(scope, expiration.getEpochSecond());
save();
return this;
@ -344,15 +337,19 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -344,15 +337,19 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
return nullable(authCodes.remove(authCode));
}
@Override
public Optional<String> consumeNonce(String userId, String clientId) {
var nonceKey = String.join("@", userId, clientId);
return nullable(nonceMap.get(nonceKey));
}
@Override
public AuthResult getAuthorization(String userId, String clientId, Collection<String> scopes) {
if (!json.has(AUTHORIZATIONS)) return unauthorized(scopes);
var authorizations = json.getJSONObject(AUTHORIZATIONS);
var userAuthorizations = authorizations.has(userId) ? authorizations.getJSONObject(userId) : null;
if (userAuthorizations == null) return unauthorized(scopes);
var clientData = userAuthorizations.has(clientId) ? userAuthorizations.getJSONObject(clientId) : null;
if (clientData == null) return unauthorized(scopes);
var clientScopes = clientData.has(SCOPE) ? clientData.getJSONObject(SCOPE) : null;
var clientScopes = userAuthorizations.has(clientId) ? userAuthorizations.getJSONObject(clientId) : null;
if (clientScopes == null) return unauthorized(scopes);
var now = Instant.now();
var authorizedScopes = new HashSet<String>();
@ -371,11 +368,19 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -371,11 +368,19 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
}
if (authorizedScopes.isEmpty()) return unauthorized(scopes);
String nonce = clientData.has(NONCE) ? clientData.getString(NONCE) : null;
var authorization = new Authorization(clientId, userId, new AuthorizedScopes(authorizedScopes, earliestExpiration), nonce);
var authorization = new Authorization(clientId, userId, new AuthorizedScopes(authorizedScopes, earliestExpiration));
return new AuthResult(authorization.scopes(), unauthorizedScopes, authCode(authorization));
}
@Override
public void nonce(String userId, String clientId, String nonce) {
var nonceKey = String.join("@", userId, clientId);
if (nonce != null) {
nonceMap.put(nonceKey, nonce);
} else
nonceMap.remove(nonceKey);
}
private AuthResult unauthorized(Collection<String> scopes) {
return new AuthResult(null, new HashSet<>(scopes), null);
}

29
de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java

@ -21,11 +21,12 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi @@ -21,11 +21,12 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi
private static final String SELECT_STORE_VERSION = "SELECT * FROM metainfo WHERE key = '" + STORE_VERSION + "'";
private static final String SET_STORE_VERSION = "UPDATE metainfo SET value = ? WHERE key = '" + STORE_VERSION + "'";
private static final String CREATE_AUTHSTORE_TABLE = "CREATE TABLE IF NOT EXISTS authorizations(userId VARCHAR(255), clientId VARCHAR(255), scope VARCHAR(255), expiration LONG, nonce VARCHAR(255), PRIMARY KEY(userId, clientId, scope));";
private static final String SAVE_AUTHORIZATION = "INSERT INTO authorizations(userId, clientId, scope, nonce, expiration) VALUES (?,?,?,?,?) ON CONFLICT DO UPDATE SET nonce = ?, expiration = ?";
private static final String CREATE_AUTHSTORE_TABLE = "CREATE TABLE IF NOT EXISTS authorizations(userId VARCHAR(255), clientId VARCHAR(255), scope VARCHAR(255), expiration LONG, PRIMARY KEY(userId, clientId, scope));";
private static final String SAVE_AUTHORIZATION = "INSERT INTO authorizations(userId, clientId, scope, expiration) VALUES (?,?,?,?) ON CONFLICT DO UPDATE SET expiration = ?";
private static final String SELECT_AUTH = "SELECT * FROM authorizations WHERE userid = ? AND clientId = ? AND scope IN";
private Map<String, Authorization> authCodes = new HashMap<>();
private Map<String, String> nonceMap = new HashMap<>();
public SqliteAuthService(Connection connection) throws SQLException {
super(connection);
@ -76,16 +77,14 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi @@ -76,16 +77,14 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi
}
@Override
public AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, String nonce, Instant expiration) {
public AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, Instant expiration) {
try {
conn.setAutoCommit(false);
var stmt = conn.prepareStatement(SAVE_AUTHORIZATION);
stmt.setString(1, userId);
stmt.setString(2, clientId);
stmt.setString(4, nonce);
stmt.setLong(4, expiration.getEpochSecond());
stmt.setLong(5, expiration.getEpochSecond());
stmt.setString(6, nonce);
stmt.setLong(7, expiration.getEpochSecond());
for (var scope : scopes) {
stmt.setString(3, scope);
stmt.execute();
@ -103,6 +102,12 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi @@ -103,6 +102,12 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi
return nullable(authCodes.remove(authCode));
}
@Override
public Optional<String> consumeNonce(String userId, String clientId) {
var nonceKey = String.join("@", userId, clientId);
return nullable(nonceMap.get(nonceKey));
}
@Override
public AuthResult getAuthorization(String userId, String clientId, Collection<String> scopes) {
try {
@ -131,11 +136,19 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi @@ -131,11 +136,19 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi
rs.close();
if (authorized.isEmpty()) return new AuthResult(null, unauthorized, null);
var authorizedScopes = new AuthorizedScopes(authorized, earliestExp);
String nonce = null;
var authorization = new Authorization(clientId, userId, authorizedScopes, nonce);
var authorization = new Authorization(clientId, userId, authorizedScopes);
return new AuthResult(authorizedScopes, unauthorized, authCode(authorization));
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public void nonce(String userId, String clientId, String nonce) {
var nonceKey = String.join("@", userId, clientId);
if (nonce != null) {
nonceMap.put(nonceKey, nonce);
} else
nonceMap.remove(nonceKey);
}
}

Loading…
Cancel
Save