Browse Source

moved nonce from client to auhtorization

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
sqlite
Stephan Richter 2 months ago
parent
commit
f737c1dc50
  1. 2
      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. 12
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Client.java
  4. 12
      de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/AuthServiceTest.java
  5. 5
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java
  6. 10
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java
  7. 31
      de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java
  8. 15
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java

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

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

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) {
public record Authorization(String clientId, String userId, AuthorizedScopes scopes, String nonce) {
}

12
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Client.java

@ -3,14 +3,12 @@ package de.srsoftware.oidc.api.data; @@ -3,14 +3,12 @@ package de.srsoftware.oidc.api.data;
import static de.srsoftware.oidc.api.Constants.*;
import static de.srsoftware.utils.Optionals.nullable;
import java.util.*;
public final class Client {
private static System.Logger LOG = System.getLogger(Client.class.getSimpleName());
private final String id, name, secret;
private String nonce = null;
private final Set<String> redirectUris;
public Client(String id, String name, String secret, Set<String> redirectUris) {
@ -33,16 +31,6 @@ public final class Client { @@ -33,16 +31,6 @@ public final class Client {
return name;
}
public Client nonce(String newVal) {
nonce = newVal;
;
return this;
}
public Optional nonce() {
return nullable(nonce);
}
public String secret() {
return secret;
}

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

@ -26,9 +26,10 @@ public abstract class AuthServiceTest { @@ -26,9 +26,10 @@ public abstract class AuthServiceTest {
var authorizationService = authorizationService();
var userId1 = uuid();
var expiration = Instant.now();
authorizationService.authorize(userId1, CLIENT1, SCOPES1, expiration);
expiration = Instant.now().plusSeconds(3600).truncatedTo(SECONDS); // test overwrite
authorizationService.authorize(userId1, CLIENT1, SCOPES1, expiration); // test overwrite
var nonce = uuid();
authorizationService.authorize(userId1, CLIENT1, SCOPES1, nonce, expiration);
expiration = Instant.now().plusSeconds(3600).truncatedTo(SECONDS); // test overwrite
authorizationService.authorize(userId1, CLIENT1, SCOPES1, nonce, expiration); // test overwrite
var authorization = authorizationService.getAuthorization(userId1, CLIENT1, Set.of(OPENID));
assertEquals(1, authorization.authorizedScopes().scopes().size());
assertTrue(authorization.authorizedScopes().scopes().contains(OPENID));
@ -52,9 +53,10 @@ public abstract class AuthServiceTest { @@ -52,9 +53,10 @@ 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, expiration);
authorizationService.authorize(userId1, CLIENT1, SCOPES1, nonce, expiration);
var authResult = authorizationService.getAuthorization(userId1, CLIENT1, Set.of(OPENID));
var authCode = authResult.authCode();
assertNotNull(authCode);
@ -72,4 +74,6 @@ public abstract class AuthServiceTest { @@ -72,4 +74,6 @@ public abstract class AuthServiceTest {
optAuth = authorizationService.consumeAuthorization(authCode);
assertTrue(optAuth.isEmpty());
}
// TODO: test nonce passing
}

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

@ -73,13 +73,14 @@ public class ClientController extends Controller { @@ -73,13 +73,14 @@ public class ClientController extends Controller {
var redirect = json.getString(REDIRECT_URI);
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);
if (json.has(AUTHORZED)) { // user did consent
var authorized = json.getJSONObject(AUTHORZED);
var days = authorized.getInt("days");
var list = new ArrayList<String>();
authorized.getJSONArray("scopes").forEach(scope -> list.add(scope.toString()));
authorizations.authorize(user.uuid(), client.id(), list, Instant.now().plus(days, ChronoUnit.DAYS));
var nonce = json.has(NONCE) ? json.getString(NONCE) : null;
authorizations.authorize(user.uuid(), client.id(), list, nonce, Instant.now().plus(days, ChronoUnit.DAYS));
}
var authResult = authorizations.getAuthorization(user.uuid(), client.id(), scopes);

10
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);
String jwToken = createJWT(client, user, accessToken, authorization.nonce());
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) {
private String createJWT(Client client, User user, AccessToken accessToken, String nonce) {
try {
PublicJsonWebKey key = keyManager.getKey();
var algo = key.getAlgorithm();
var atHash = this.atHash(algo, accessToken);
key.setUse("sig");
JwtClaims claims = createIdTokenClaims(user, client, atHash);
JwtClaims claims = createIdTokenClaims(user, client, atHash, nonce);
// 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,7 @@ public class TokenController extends PathHandler { @@ -167,7 +167,7 @@ public class TokenController extends PathHandler {
}
}
private JwtClaims createIdTokenClaims(User user, Client client, String atHash) {
private JwtClaims createIdTokenClaims(User user, Client client, String atHash, String nonce) {
JwtClaims claims = new JwtClaims();
// required claims:
@ -179,7 +179,7 @@ public class TokenController extends PathHandler { @@ -179,7 +179,7 @@ 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
client.nonce().ifPresent(nonce -> claims.setClaim(NONCE, nonce));
if (nonce != null) claims.setClaim(NONCE, nonce);
claims.setGeneratedJwtId(); // a unique identifier for the token
return claims;
}

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

@ -72,15 +72,16 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -72,15 +72,16 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
var clients = authorizations.getJSONObject(userId);
var clientIds = Set.copyOf(clients.keySet());
for (var clientId : clientIds) {
var client = clients.getJSONObject(clientId);
var scopes = Set.copyOf(client.keySet());
var clientData = clients.getJSONObject(clientId);
var scopeMap = clientData.getJSONObject(SCOPE);
var scopes = Set.copyOf(scopeMap.keySet());
for (var scope : scopes) {
var expiration = Instant.ofEpochSecond(client.getLong(scope));
var expiration = Instant.ofEpochSecond(scopeMap.getLong(scope));
if (expiration.isBefore(now)) {
client.remove(scope);
scopeMap.remove(scope);
}
}
if (client.isEmpty()) clients.remove(clientId);
if (scopeMap.isEmpty()) clients.remove(clientId);
}
if (clients.isEmpty()) authorizations.remove(userId);
}
@ -297,7 +298,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -297,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(), Map.of(NAME, client.name(), SECRET, client.secret(), REDIRECT_URIS, client.redirectUris()));
json.getJSONObject(CLIENTS).put(client.id(), client.map());
save();
return this;
}
@ -318,13 +319,20 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -318,13 +319,20 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
}
@Override
public AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, Instant expiration) {
public AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, String nonce, 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 clientScopes = userAuthorizations.getJSONObject(clientId);
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);
for (var scope : scopes) clientScopes.put(scope, expiration.getEpochSecond());
save();
return this;
@ -342,7 +350,9 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -342,7 +350,9 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
var authorizations = json.getJSONObject(AUTHORIZATIONS);
var userAuthorizations = authorizations.has(userId) ? authorizations.getJSONObject(userId) : null;
if (userAuthorizations == null) return unauthorized(scopes);
var clientScopes = userAuthorizations.has(clientId) ? userAuthorizations.getJSONObject(clientId) : null;
var clientData = userAuthorizations.has(clientId) ? userAuthorizations.getJSONObject(clientId) : null;
if (clientData == null) return unauthorized(scopes);
var clientScopes = clientData.has(SCOPE) ? clientData.getJSONObject(SCOPE) : null;
if (clientScopes == null) return unauthorized(scopes);
var now = Instant.now();
var authorizedScopes = new HashSet<String>();
@ -361,7 +371,8 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -361,7 +371,8 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
}
if (authorizedScopes.isEmpty()) return unauthorized(scopes);
var authorization = new Authorization(clientId, userId, new AuthorizedScopes(authorizedScopes, earliestExpiration));
String nonce = clientData.has(NONCE) ? clientData.getString(NONCE) : null;
var authorization = new Authorization(clientId, userId, new AuthorizedScopes(authorizedScopes, earliestExpiration), nonce);
return new AuthResult(authorization.scopes(), unauthorizedScopes, authCode(authorization));
}

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

@ -21,8 +21,8 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi @@ -21,8 +21,8 @@ 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, 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 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 SELECT_AUTH = "SELECT * FROM authorizations WHERE userid = ? AND clientId = ? AND scope IN";
private Map<String, Authorization> authCodes = new HashMap<>();
@ -76,14 +76,16 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi @@ -76,14 +76,16 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi
}
@Override
public AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, Instant expiration) {
public AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, String nonce, Instant expiration) {
try {
conn.setAutoCommit(false);
var stmt = conn.prepareStatement(SAVE_AUTHORIZATION);
stmt.setString(1, userId);
stmt.setString(2, clientId);
stmt.setLong(4, expiration.getEpochSecond());
stmt.setString(4, nonce);
stmt.setLong(5, expiration.getEpochSecond());
stmt.setString(6, nonce);
stmt.setLong(7, expiration.getEpochSecond());
for (var scope : scopes) {
stmt.setString(3, scope);
stmt.execute();
@ -128,8 +130,9 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi @@ -128,8 +130,9 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi
}
rs.close();
if (authorized.isEmpty()) return new AuthResult(null, unauthorized, null);
var authorizedScopes = new AuthorizedScopes(authorized, earliestExp);
var authorization = new Authorization(clientId, userId, authorizedScopes);
var authorizedScopes = new AuthorizedScopes(authorized, earliestExp);
String nonce = null;
var authorization = new Authorization(clientId, userId, authorizedScopes, nonce);
return new AuthResult(authorizedScopes, unauthorized, authCode(authorization));
} catch (SQLException e) {
throw new RuntimeException(e);

Loading…
Cancel
Save