From 30f2e115ea45f0482dfe2989cbf62a737657a9d4 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Tue, 17 Sep 2024 12:40:05 +0200 Subject: [PATCH] added dashboard Signed-off-by: Stephan Richter --- .../java/de/srsoftware/http/PathHandler.java | 4 +- .../oidc/api/AuthorizationService.java | 5 ++- .../de/srsoftware/oidc/api/data/Client.java | 15 +++++--- .../srsoftware/oidc/api/AuthServiceTest.java | 1 + .../oidc/backend/ClientController.java | 38 +++++++++++++++++-- .../oidc/datastore/file/FileStore.java | 9 +++++ .../datastore/sqlite/SqliteAuthService.java | 18 ++++++++- .../src/main/resources/en/index.html | 4 +- .../src/main/resources/en/scripts/clients.js | 2 +- .../src/main/resources/en/scripts/index.js | 27 +++++++++++++ 10 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 de.srsoftware.oidc.web/src/main/resources/en/scripts/index.js diff --git a/de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java b/de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java index 7b95947..796fc65 100644 --- a/de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java +++ b/de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.json.JSONArray; import org.json.JSONObject; public abstract class PathHandler implements HttpHandler { @@ -173,7 +174,8 @@ public abstract class PathHandler implements HttpHandler { } public static boolean sendContent(HttpExchange ex, int status, Object o) throws IOException { - if (o instanceof Map map) o = new JSONObject(map); + if (o instanceof List list) o = new JSONArray(list); + if (o instanceof Map map) o = new JSONObject(map); if (o instanceof JSONObject) ex.getResponseHeaders().add(CONTENT_TYPE, JSON); return sendContent(ex, status, o.toString().getBytes(UTF_8)); } diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthorizationService.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthorizationService.java index a783c98..115b079 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthorizationService.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/AuthorizationService.java @@ -5,14 +5,15 @@ import de.srsoftware.oidc.api.data.AuthResult; import de.srsoftware.oidc.api.data.Authorization; import java.time.Instant; import java.util.Collection; +import java.util.List; import java.util.Optional; public interface AuthorizationService { AuthorizationService authorize(String userId, String clientId, Collection scopes, Instant expiration); Optional consumeAuthorization(String authCode); AuthResult getAuthorization(String userId, String clientId, Collection scopes); - - Optional consumeNonce(String uuid, String id); + List authorizedClients(String userId); + Optional consumeNonce(String uuid, String id); void nonce(String uuid, String id, String string); } diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Client.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Client.java index ff87307..7b240de 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Client.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Client.java @@ -37,12 +37,8 @@ public final class Client { } public Map map() { - var map = new HashMap(); - map.put(CLIENT_ID, id); - map.put(NAME, name); + var map = safeMap(); map.put(SECRET, secret); - nullable(redirectUris).ifPresent(uris -> map.put(REDIRECT_URIS, uris)); - nullable(landingPage).ifPresent(lp -> map.put(LANDING_PAGE, lp)); return map; } @@ -51,6 +47,15 @@ public final class Client { return name; } + public Map safeMap() { + var map = new HashMap(); + map.put(CLIENT_ID, id); + map.put(NAME, name); + nullable(redirectUris).ifPresent(uris -> map.put(REDIRECT_URIS, uris)); + nullable(landingPage).ifPresent(lp -> map.put(LANDING_PAGE, lp)); + return map; + } + public String secret() { return secret; } diff --git a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/AuthServiceTest.java b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/AuthServiceTest.java index 27cf08f..d70ab47 100644 --- a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/AuthServiceTest.java +++ b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/AuthServiceTest.java @@ -74,4 +74,5 @@ public abstract class AuthServiceTest { } // TODO: test nonce methods + // TODO: test authorizedClients method } diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java index 40103aa..7770695 100644 --- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java @@ -11,6 +11,7 @@ import de.srsoftware.oidc.api.*; import de.srsoftware.oidc.api.data.AuthorizedScopes; import de.srsoftware.oidc.api.data.Client; import de.srsoftware.oidc.api.data.Session; +import de.srsoftware.oidc.api.data.User; import de.srsoftware.utils.Optionals; import java.io.IOException; import java.time.Instant; @@ -130,6 +131,39 @@ public class ClientController extends Controller { return notFound(ex); } + @Override + public boolean doGet(String path, HttpExchange ex) throws IOException { + var optSession = getSession(ex); + if (optSession.isEmpty()) return sendContent(ex, HTTP_UNAUTHORIZED, "No authorized!"); + + // post-login paths + var session = optSession.get(); + var optUser = users.load(session.userId()); + if (optUser.isEmpty()) return invalidSessionUser(ex); + var user = optUser.get(); + sessions.extend(session, user); + + switch (path) { + case "/dash": + return dashboard(ex, user); + case "/list": + return list(ex, session); + } + return notFound(ex); + } + + private boolean dashboard(HttpExchange ex, User user) throws IOException { + var authorizedClients = authorizations // + .authorizedClients(user.uuid()) + .stream() + .map(clients::getClient) + .flatMap(Optional::stream) + .sorted(Comparator.comparing(Client::name)) + .map(Client::safeMap) + .toList(); + return sendContent(ex, Map.of(AUTHORZED, authorizedClients, NAME, user.realName())); + } + @Override public boolean doPost(String path, HttpExchange ex) throws IOException { @@ -150,8 +184,6 @@ public class ClientController extends Controller { return save(ex, session); case "/authorize": return authorize(ex, session); - case "/list": - return list(ex, session); } return notFound(ex); } @@ -189,7 +221,7 @@ public class ClientController extends Controller { if (o instanceof String s) redirects.add(s); } var landingPage = json.has(LANDING_PAGE) ? json.getString(LANDING_PAGE) : null; - var client = new Client(json.getString(CLIENT_ID), json.getString(NAME), json.getString(SECRET), redirects).landingPage(landingPage); + var client = new Client(json.getString(CLIENT_ID), json.getString(NAME), json.getString(SECRET), redirects).landingPage(landingPage); clients.save(client); return sendContent(ex, client); } diff --git a/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java b/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java index df1cdb3..6aa966a 100644 --- a/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java +++ b/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java @@ -334,6 +334,15 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe return this; } + @Override + public List authorizedClients(String userId) { + if (!json.has(AUTHORIZATIONS)) return List.of(); + var authorizations = json.getJSONObject(AUTHORIZATIONS); + if (!authorizations.has(userId)) return List.of(); + var clients = authorizations.getJSONObject(userId); + return new ArrayList<>(clients.keySet()); + } + @Override public Optional consumeAuthorization(String authCode) { diff --git a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java index 3702807..aea83cd 100644 --- a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java +++ b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java @@ -23,7 +23,8 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi 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 static final String SELECT_AUTH = "SELECT * FROM authorizations WHERE userId = ? AND clientId = ? AND scope IN"; + private static final String SELECT_USER_CLIENTS = "SELECT DISTINCT clientId FROM authorizations WHERE userId = ?"; private Map authCodes = new HashMap<>(); private Map nonceMap = new HashMap<>(); @@ -97,6 +98,21 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi } } + @Override + public List authorizedClients(String userId) { + try { + var stmt = conn.prepareStatement(SELECT_USER_CLIENTS); + stmt.setString(1, userId); + var rs = stmt.executeQuery(); + var result = new ArrayList(); + while (rs.next()) result.add(rs.getString(1)); + rs.close(); + return result; + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + @Override public Optional consumeAuthorization(String authCode) { return nullable(authCodes.remove(authCode)); diff --git a/de.srsoftware.oidc.web/src/main/resources/en/index.html b/de.srsoftware.oidc.web/src/main/resources/en/index.html index ed2c3f4..44b54a5 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/index.html +++ b/de.srsoftware.oidc.web/src/main/resources/en/index.html @@ -4,13 +4,15 @@ Light OIDC +
-

Welcome!

+

Welcome, {}!

+
\ No newline at end of file diff --git a/de.srsoftware.oidc.web/src/main/resources/en/scripts/clients.js b/de.srsoftware.oidc.web/src/main/resources/en/scripts/clients.js index 7fe0331..5699352 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/scripts/clients.js +++ b/de.srsoftware.oidc.web/src/main/resources/en/scripts/clients.js @@ -44,4 +44,4 @@ function remove(clientId){ } } -fetch(client_controller+"/list",{method:'POST',credentials:'include'}).then(handleClients); \ No newline at end of file +fetch(client_controller+"/list").then(handleClients); \ No newline at end of file diff --git a/de.srsoftware.oidc.web/src/main/resources/en/scripts/index.js b/de.srsoftware.oidc.web/src/main/resources/en/scripts/index.js new file mode 100644 index 0000000..1818fd7 --- /dev/null +++ b/de.srsoftware.oidc.web/src/main/resources/en/scripts/index.js @@ -0,0 +1,27 @@ +function handleDash(response){ + if (response.status == UNAUTHORIZED) { + redirect('login.html?return_to='+encodeURI(window.location.href)) + return; + } + var clients = response.json().then(data => { + var name = data.name; + var welcome = get('welcome'); + welcome.innerHTML = welcome.innerHTML.replace('{}',name); + + var clients = data.authorized; + var content = document.getElementById('content'); + var any = false; + for (let id in clients){ + var client = clients[id]; + if (client.landing_page){ + var div = document.createElement("div"); + div.innerHTML = ``; + content.append(div); + any = true; + } + } + if (any) show('client_hint'); + }); +} + +fetch(client_controller+"/dash").then(handleDash) \ No newline at end of file