Browse Source

implemented main part of authorization and token delivery

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
sqlite
Stephan Richter 4 months ago
parent
commit
1e8ca6dc3a
  1. 7
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Authorization.java
  2. 8
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthorizationService.java
  3. 7
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Client.java
  4. 3
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/ClientService.java
  5. 6
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java
  6. 4
      de.srsoftware.oidc.app/build.gradle
  7. 34
      de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
  8. 39
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java
  9. 83
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java
  10. 104
      de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java
  11. 5
      de.srsoftware.oidc.web/src/main/resources/en/authorization.html
  12. 4
      de.srsoftware.oidc.web/src/main/resources/en/authorization.js
  13. 10
      de.srsoftware.oidc.web/src/main/resources/en/edit_client.html
  14. 54
      de.srsoftware.oidc.web/src/main/resources/en/edit_client.js
  15. 2
      de.srsoftware.oidc.web/src/main/resources/en/new_client.html
  16. 0
      de.srsoftware.oidc.web/src/main/resources/en/new_client.js
  17. 19
      de.srsoftware.utils/build.gradle
  18. 13
      de.srsoftware.utils/src/main/java/de/srsoftware/utils/Optionals.java
  19. 19
      de.srsoftware.utils/src/main/java/de/srsoftware/utils/Paths.java
  20. 1
      settings.gradle

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

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

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

@ -1,13 +1,17 @@ @@ -1,13 +1,17 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api;
import java.util.Date;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
public interface AuthorizationService {
AuthorizationService authorize(Client client, User user, Date expiration);
AuthorizationService addCode(Client client, User user, String code);
AuthorizationService authorize(Client client, User user, Instant expiration);
boolean isAuthorized(Client client, User user);
List<User> authorizedUsers(Client client);
List<Client> authorizedClients(User user);
AuthorizationService revoke(Client client, User user);
Optional<Authorization> forCode(String code);
}

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

@ -2,20 +2,13 @@ @@ -2,20 +2,13 @@
package de.srsoftware.oidc.api;
import static de.srsoftware.oidc.api.Constants.*;
import static java.lang.System.Logger.Level.WARNING;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public record Client(String id, String name, String secret, Set<String> redirectUris) {
private static System.Logger LOG = System.getLogger(Client.class.getSimpleName());
public Map<String, Object> map() {
return Map.of(CLIENT_ID, id, NAME, name, SECRET, secret, REDIRECT_URIS, redirectUris);
}
public String generateCode() {
LOG.log(WARNING, "{0}.generateCode() not implemented!", getClass().getSimpleName());
return UUID.randomUUID().toString();
}
}

3
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/ClientService.java

@ -6,8 +6,7 @@ import java.util.Optional; @@ -6,8 +6,7 @@ import java.util.Optional;
public interface ClientService {
Optional<Client> getClient(String clientId);
ClientService add(Client client);
List<Client> listClients();
ClientService remove(Client client);
ClientService update(Client client);
ClientService save(Client client);
}

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

@ -3,14 +3,16 @@ package de.srsoftware.oidc.api; @@ -3,14 +3,16 @@ package de.srsoftware.oidc.api;
public class Constants {
public static final String ACCESS_TOKEN = "access_token";
public static final String ATUH_CODE = "authorization_code";
public static final String APP_NAME = "LightOIDC";
public static final String AUTH_CODE = "authorization_code";
public static final String BEARER = "Bearer";
public static final String CAUSE = "cause";
public static final String CLIENT_ID = "client_id";
public static final String CLIENT_SECRET = "client_secret";
public static final String CODE = "code";
public static final String CONFIG_PATH = "LIGHTOIDC_CONFIG_PATH";
public static final String CONFIRMED = "confirmed";
public static final String DEFAULT_KEY = "default_key";
public static final String DAYS = "days";
public static final String EXPIRES_IN = "expires_in";
public static final String GRANT_TYPE = "grant_type";
public static final String ID_TOKEN = "id_token";

4
de.srsoftware.oidc.app/build.gradle

@ -16,8 +16,8 @@ dependencies { @@ -16,8 +16,8 @@ dependencies {
implementation project(':de.srsoftware.oidc.api')
implementation project(':de.srsoftware.oidc.backend')
implementation project(':de.srsoftware.oidc.web')
implementation project(':de.srsoftware.oidc.datastore.file')
}
implementation project(':de.srsoftware.utils')
implementation project(':de.srsoftware.oidc.datastore.file')}
test {
useJUnitPlatform()

34
de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java

@ -2,9 +2,13 @@ @@ -2,9 +2,13 @@
package de.srsoftware.oidc.app;
import static de.srsoftware.oidc.api.Constants.*;
import static de.srsoftware.oidc.api.Permission.MANAGE_CLIENTS;
import static de.srsoftware.utils.Optionals.nonEmpty;
import static de.srsoftware.utils.Paths.configDir;
import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.ERROR;
import static java.lang.System.getenv;
import com.sun.net.httpserver.HttpServer;
import de.srsoftware.logging.ColorLogger;
@ -17,7 +21,6 @@ import de.srsoftware.oidc.datastore.file.FileStore; @@ -17,7 +21,6 @@ import de.srsoftware.oidc.datastore.file.FileStore;
import de.srsoftware.oidc.datastore.file.UuidHasher;
import de.srsoftware.oidc.web.Forward;
import de.srsoftware.oidc.web.StaticPages;
import java.io.File;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.*;
@ -40,19 +43,19 @@ public class Application { @@ -40,19 +43,19 @@ public class Application {
private static System.Logger LOG = new ColorLogger("Application").setLogLevel(DEBUG);
public static void main(String[] args) throws Exception {
var argMap = map(args);
Optional<Path> basePath = argMap.get(BASE_PATH) instanceof Path p ? Optional.of(p) : Optional.empty();
var storageFile = new File("/tmp/lightoidc.json");
var passwordHasher = new UuidHasher();
var firstHash = passwordHasher.hash(FIRST_USER_PASS, FIRST_UUID);
var firstUser = new User(FIRST_USER, firstHash, FIRST_USER, "%s@internal".formatted(FIRST_USER), FIRST_UUID).add(MANAGE_CLIENTS);
FileStore fileStore = new FileStore(storageFile, passwordHasher).init(firstUser);
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
var argMap = map(args);
Optional<Path> basePath = argMap.get(BASE_PATH) instanceof Path p ? Optional.of(p) : Optional.empty();
var storageFile = (argMap.get(CONFIG_PATH) instanceof Path p ? p : configDir(APP_NAME).resolve("config.json")).toFile();
var passwordHasher = new UuidHasher();
var firstHash = passwordHasher.hash(FIRST_USER_PASS, FIRST_UUID);
var firstUser = new User(FIRST_USER, firstHash, FIRST_USER, "%s@internal".formatted(FIRST_USER), FIRST_UUID).add(MANAGE_CLIENTS);
FileStore fileStore = new FileStore(storageFile, passwordHasher).init(firstUser);
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
new StaticPages(basePath).bindPath(STATIC_PATH, FAVICON).on(server);
new Forward(INDEX).bindPath(ROOT).on(server);
new WellKnownController().bindPath(WELL_KNOWN).on(server);
new UserController(fileStore, fileStore).bindPath(API_USER).on(server);
new TokenController(fileStore).bindPath(API_TOKEN).on(server);
new TokenController(fileStore, fileStore, fileStore).bindPath(API_TOKEN).on(server);
new ClientController(fileStore, fileStore, fileStore).bindPath(API_CLIENT).on(server);
// server.setExecutor(Executors.newCachedThreadPool());
server.setExecutor(Executors.newSingleThreadExecutor());
@ -62,13 +65,22 @@ public class Application { @@ -62,13 +65,22 @@ public class Application {
private static Map<String, Object> map(String[] args) {
var tokens = new ArrayList<>(List.of(args));
var map = new HashMap<String, Object>();
nonEmpty(getenv(BASE_PATH)).map(Path::of).ifPresent(path -> map.put(BASE_PATH, path));
nonEmpty(getenv(CONFIG_PATH)).map(Path::of).ifPresent(path -> map.put(CONFIG_PATH, path));
// Command line arguments override environment
while (!tokens.isEmpty()) {
var token = tokens.remove(0);
switch (token) {
case "--base":
if (tokens.isEmpty()) throw new IllegalArgumentException("--path option requires second argument!");
if (tokens.isEmpty()) throw new IllegalArgumentException("--base option requires second argument!");
map.put(BASE_PATH, Path.of(tokens.remove(0)));
break;
case "--config":
if (tokens.isEmpty()) throw new IllegalArgumentException("--config option requires second argument!");
map.put(CONFIG_PATH, Path.of(tokens.remove(0)));
break;
default:
LOG.log(ERROR, "Unknown option: {0}", token);
}

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

@ -9,8 +9,11 @@ import static java.net.HttpURLConnection.*; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);
}
}

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

@ -6,10 +6,9 @@ import static java.lang.System.Logger.Level.*; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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;
}
}

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

@ -15,14 +15,16 @@ import java.util.*; @@ -15,14 +15,16 @@ import java.util.*;
import org.json.JSONObject;
public class FileStore implements AuthorizationService, ClientService, SessionService, UserService {
private static final String CLIENTS = "clients";
private static final String EXPIRATION = "expiration";
private static final String NAME = "name";
private static final String REDIRECT_URIS = "redirect_uris";
private static final String SECRET = "secret";
private static final String SESSIONS = "sessions";
private static final String USERS = "users";
private static final String USER = "user";
private static final String AUTHORIZATIONS = "authorizations";
private static final String CLIENTS = "clients";
private static final String CODES = "codes";
private static final String EXPIRATION = "expiration";
private static final String NAME = "name";
private static final String REDIRECT_URIS = "redirect_uris";
private static final String SECRET = "secret";
private static final String SESSIONS = "sessions";
private static final String USERS = "users";
private static final String USER = "user";
private final Path storageFile;
private final JSONObject json;
@ -61,6 +63,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -61,6 +63,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
@Override
public FileStore init(User defaultUser) {
if (!json.has(AUTHORIZATIONS)) json.put(AUTHORIZATIONS, new JSONObject());
if (!json.has(CLIENTS)) json.put(CLIENTS, new JSONObject());
if (!json.has(SESSIONS)) json.put(SESSIONS, new JSONObject());
if (!json.has(USERS)) save(defaultUser);
@ -179,6 +182,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -179,6 +182,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
if (expiration.isAfter(Instant.now())) {
return load(userId).map(user -> new Session(user, expiration, sessionId));
}
dropSession(sessionId);
} catch (Exception ignored) {
}
return Optional.empty();
@ -197,13 +201,6 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -197,13 +201,6 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
/** client service methods **/
@Override
public ClientService add(Client client) {
json.getJSONObject(CLIENTS).put(client.id(), Map.of(NAME, client.name(), SECRET, client.secret(), REDIRECT_URIS, client.redirectUris()));
save();
return this;
}
@Override
public Optional<Client> getClient(String clientId) {
var clients = json.getJSONObject(CLIENTS);
@ -211,13 +208,6 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -211,13 +208,6 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
return Optional.empty();
}
private Client toClient(String clientId, JSONObject clientData) {
var redirectUris = new HashSet<String>();
for (var o : clientData.getJSONArray(REDIRECT_URIS)) {
if (o instanceof String s) redirectUris.add(s);
}
return new Client(clientId, clientData.getString(NAME), clientData.getString(SECRET), redirectUris);
}
@Override
public List<Client> listClients() {
@ -235,19 +225,75 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -235,19 +225,75 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
}
@Override
public ClientService update(Client client) {
return null;
public ClientService save(Client client) {
json.getJSONObject(CLIENTS).put(client.id(), Map.of(NAME, client.name(), SECRET, client.secret(), REDIRECT_URIS, client.redirectUris()));
save();
return this;
}
private Client toClient(String clientId, JSONObject clientData) {
var redirectUris = new HashSet<String>();
for (var o : clientData.getJSONArray(REDIRECT_URIS)) {
if (o instanceof String s) redirectUris.add(s);
}
return new Client(clientId, clientData.getString(NAME), clientData.getString(SECRET), redirectUris);
}
/*** Authorization service methods ***/
@Override
public AuthorizationService authorize(Client client, User user, Date expiration) {
return null;
public Optional<Authorization> forCode(String code) {
var authorizations = json.getJSONObject(AUTHORIZATIONS);
if (!authorizations.has(code)) return Optional.empty();
String authId = authorizations.getString(code);
if (!authorizations.has(authId)) {
authorizations.remove(code);
return Optional.empty();
}
try {
var expiration = Instant.ofEpochSecond(authorizations.getLong(authId));
if (expiration.isAfter(Instant.now())) {
String[] parts = authId.split("@");
return Optional.of(new Authorization(parts[1], parts[0], expiration));
}
authorizations.remove(authId);
} catch (Exception ignored) {
}
return Optional.empty();
}
@Override
public AuthorizationService addCode(Client client, User user, String code) {
var authorizations = json.getJSONObject(AUTHORIZATIONS);
authorizations.put(code, authorizationId(user, client));
save();
return this;
}
@Override
public AuthorizationService authorize(Client client, User user, Instant expiration) {
var authorizations = json.getJSONObject(AUTHORIZATIONS);
authorizations.put(authorizationId(user, client), expiration.getEpochSecond());
return this;
}
private String authorizationId(User user, Client client) {
return String.join("@", user.uuid(), client.id());
}
@Override
public boolean isAuthorized(Client client, User user) {
var authorizations = json.getJSONObject(AUTHORIZATIONS);
var authId = authorizationId(user, client);
if (!authorizations.has(authId)) return false;
try {
if (Instant.ofEpochSecond(authorizations.getLong(authId)).isAfter(Instant.now())) return true;
} catch (Exception ignored) {
}
revoke(client, user);
return false;
}
@ -263,6 +309,10 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -263,6 +309,10 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
@Override
public AuthorizationService revoke(Client client, User user) {
return null;
var authorizations = json.getJSONObject(AUTHORIZATIONS);
var authId = authorizationId(user, client);
if (!authorizations.has(authId)) return this;
authorizations.remove(authId);
return save();
}
}

5
de.srsoftware.oidc.web/src/main/resources/en/authorization.html

@ -12,7 +12,10 @@ @@ -12,7 +12,10 @@
<div id="content" style="display: none">
<h1>Authorization</h1>
Confirmation required: are you shure you want to grant access to <span id="name">some client</span>?
<button type="button" onclick="grantAutorization()">Yes</button>
<button type="button" onclick="grantAutorization(1)">Yes - 1 day</button>
<button type="button" onclick="grantAutorization(7)">Yes - 1 week</button>
<button type="button" onclick="grantAutorization(30)">Yes - 1 month</button>
<button type="button" onclick="grantAutorization(365)">Yes - 1 year</button>
<button type="button" onclick="denyAutorization()">No</button>
</div>
<div id="error" class="error" style="display: none"></div>

4
de.srsoftware.oidc.web/src/main/resources/en/authorization.js

@ -24,8 +24,8 @@ async function handleResponse(response){ @@ -24,8 +24,8 @@ async function handleResponse(response){
}
}
function grantAutorization(){
json.confirmed = true;
function grantAutorization(days){
json.days = days;
backendAutorization();
}

10
de.srsoftware.oidc.web/src/main/resources/en/edit_client.html

@ -15,25 +15,25 @@ @@ -15,25 +15,25 @@
<table>
<tr>
<th>ID</th>
<td><input type="text" disabled="true" id="client_id" /></td>
<td><input type="text" disabled="true" id="client-id" /></td>
</tr>
<tr>
<th>Name</th>
<td><input type="text" id="name" /></td>
<td><input type="text" id="client-name" /></td>
</tr>
<tr>
<th>Secret</th>
<td><input type="text" id="secret" /></td>
<td><input type="text" id="client-secret" /></td>
</tr>
<tr>
<th>Redirect URIs</th>
<td>
<textarea id="redirects"></textarea>
<textarea id="redirect-urls"></textarea>
</td>
</tr>
<tr>
<td></td>
<td><button type="button" id="button">Update</button></td>
<td><button type="button" id="button" onclick="updateClient();">Update</button></td>
</tr>
</table>
</fieldset>

54
de.srsoftware.oidc.web/src/main/resources/en/edit_client.js

@ -1,20 +1,52 @@ @@ -1,20 +1,52 @@
var params = new URLSearchParams(window.location.search);
var id = params.get('id');
async function handleLoadResponse(response){
if (response.ok){
var json = await response.json();
get('client-id').value = json.client_id;
get('client-name').value = json.name;
get('client-secret').value = json.secret;
get('redirect-urls').value = json.redirect_uris.join("\n");
}
}
async function handleUpdateResponse(response){
if (response.ok) {
enable('button');
setText('button','saved.');
}
}
function resetButton(){
enable('button');
setText('button','Update')
}
function updateClient(){
disable('button');
setText('button','sent data…')
var data = {
client_id : getValue('client-id'),
name : getValue('client-name'),
secret : getValue('client-secret'),
redirect_uris : getValue('redirect-urls').split("\n")
};
fetch(client_controller+'/update',{
method : 'POST',
headers : {
'Content-Type': 'application/json'
},
body : JSON.stringify(data)
}).then(handleUpdateResponse);
setTimeout(resetButton,4000);
}
fetch(api+'/client',
{
method: 'POST',
body: JSON.stringify({
client_id : id
})
}).then(handleResponse);
async function handleResponse(response){
if (response.ok){
var json = await response.json();
get('client_id').value = json.client_id;
get('name').value = json.name;
get('secret').value = json.secret;
get('redirects').value = json.redirect_uris.join("\n");
}
}
}).then(handleLoadResponse);

2
de.srsoftware.oidc.web/src/main/resources/en/newclient.html → de.srsoftware.oidc.web/src/main/resources/en/new_client.html

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
<title>Light OIDC</title>
<script src="common.js"></script>
<script src="user.js"></script>
<script src="newclient.js"></script>
<script src="new_client.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>

0
de.srsoftware.oidc.web/src/main/resources/en/newclient.js → de.srsoftware.oidc.web/src/main/resources/en/new_client.js

19
de.srsoftware.utils/build.gradle

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
plugins {
id 'java'
}
group = 'de.srsoftware'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
}
test {
useJUnitPlatform()
}

13
de.srsoftware.utils/src/main/java/de/srsoftware/utils/Optionals.java

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
/* © SRSoftware 2024 */
package de.srsoftware.utils;
import java.util.Optional;
public class Optionals {
public static <T> Optional<T> optional(T val) {
return Optional.ofNullable(val);
}
public static Optional<String> nonEmpty(String text) {
return text == null || text.isBlank() ? Optional.empty() : optional(text.trim());
}
}

19
de.srsoftware.utils/src/main/java/de/srsoftware/utils/Paths.java

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
/* © SRSoftware 2024 */
package de.srsoftware.utils;
import java.nio.file.Path;
public class Paths {
public static Path configDir(String applicationName) {
String home = System.getProperty("user.home");
return Path.of(home, ".config", applicationName);
}
public static Path configDir(Class clazz) {
return configDir(clazz.getSimpleName());
}
public static Path configDir(Object clazz) {
return configDir(clazz.getClass());
}
}

1
settings.gradle

@ -6,4 +6,5 @@ include 'de.srsoftware.oidc.backend' @@ -6,4 +6,5 @@ include 'de.srsoftware.oidc.backend'
include 'de.srsoftware.oidc.datastore.file'
include 'de.srsoftware.cookies'
include 'de.srsoftware.logging'
include 'de.srsoftware.utils'

Loading…
Cancel
Save