added dashboard
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -15,6 +15,7 @@ import java.io.IOException;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
public abstract class PathHandler implements HttpHandler {
|
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 {
|
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);
|
if (o instanceof JSONObject) ex.getResponseHeaders().add(CONTENT_TYPE, JSON);
|
||||||
return sendContent(ex, status, o.toString().getBytes(UTF_8));
|
return sendContent(ex, status, o.toString().getBytes(UTF_8));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ import de.srsoftware.oidc.api.data.AuthResult;
|
|||||||
import de.srsoftware.oidc.api.data.Authorization;
|
import de.srsoftware.oidc.api.data.Authorization;
|
||||||
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.Optional;
|
||||||
|
|
||||||
public interface AuthorizationService {
|
public interface AuthorizationService {
|
||||||
AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, Instant expiration);
|
AuthorizationService authorize(String userId, String clientId, Collection<String> scopes, Instant expiration);
|
||||||
Optional<Authorization> consumeAuthorization(String authCode);
|
Optional<Authorization> consumeAuthorization(String authCode);
|
||||||
AuthResult getAuthorization(String userId, String clientId, Collection<String> scopes);
|
AuthResult getAuthorization(String userId, String clientId, Collection<String> scopes);
|
||||||
|
List<String> authorizedClients(String userId);
|
||||||
Optional<String> consumeNonce(String uuid, String id);
|
Optional<String> consumeNonce(String uuid, String id);
|
||||||
|
|
||||||
void nonce(String uuid, String id, String string);
|
void nonce(String uuid, String id, String string);
|
||||||
|
|||||||
@@ -37,12 +37,8 @@ public final class Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> map() {
|
public Map<String, Object> map() {
|
||||||
var map = new HashMap<String, Object>();
|
var map = safeMap();
|
||||||
map.put(CLIENT_ID, id);
|
|
||||||
map.put(NAME, name);
|
|
||||||
map.put(SECRET, secret);
|
map.put(SECRET, secret);
|
||||||
nullable(redirectUris).ifPresent(uris -> map.put(REDIRECT_URIS, uris));
|
|
||||||
nullable(landingPage).ifPresent(lp -> map.put(LANDING_PAGE, lp));
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +47,15 @@ public final class Client {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> safeMap() {
|
||||||
|
var map = new HashMap<String, Object>();
|
||||||
|
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() {
|
public String secret() {
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,4 +74,5 @@ public abstract class AuthServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test nonce methods
|
// TODO: test nonce methods
|
||||||
|
// TODO: test authorizedClients method
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import de.srsoftware.oidc.api.*;
|
|||||||
import de.srsoftware.oidc.api.data.AuthorizedScopes;
|
import de.srsoftware.oidc.api.data.AuthorizedScopes;
|
||||||
import de.srsoftware.oidc.api.data.Client;
|
import de.srsoftware.oidc.api.data.Client;
|
||||||
import de.srsoftware.oidc.api.data.Session;
|
import de.srsoftware.oidc.api.data.Session;
|
||||||
|
import de.srsoftware.oidc.api.data.User;
|
||||||
import de.srsoftware.utils.Optionals;
|
import de.srsoftware.utils.Optionals;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -130,6 +131,39 @@ public class ClientController extends Controller {
|
|||||||
return notFound(ex);
|
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
|
@Override
|
||||||
public boolean doPost(String path, HttpExchange ex) throws IOException {
|
public boolean doPost(String path, HttpExchange ex) throws IOException {
|
||||||
@@ -150,8 +184,6 @@ public class ClientController extends Controller {
|
|||||||
return save(ex, session);
|
return save(ex, session);
|
||||||
case "/authorize":
|
case "/authorize":
|
||||||
return authorize(ex, session);
|
return authorize(ex, session);
|
||||||
case "/list":
|
|
||||||
return list(ex, session);
|
|
||||||
}
|
}
|
||||||
return notFound(ex);
|
return notFound(ex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,6 +334,15 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> 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
|
@Override
|
||||||
public Optional<Authorization> consumeAuthorization(String authCode) {
|
public Optional<Authorization> consumeAuthorization(String authCode) {
|
||||||
|
|||||||
@@ -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 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 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<String, Authorization> authCodes = new HashMap<>();
|
private Map<String, Authorization> authCodes = new HashMap<>();
|
||||||
|
|
||||||
private Map<String, String> nonceMap = new HashMap<>();
|
private Map<String, String> nonceMap = new HashMap<>();
|
||||||
@@ -97,6 +98,21 @@ public class SqliteAuthService extends SqliteStore implements AuthorizationServi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> authorizedClients(String userId) {
|
||||||
|
try {
|
||||||
|
var stmt = conn.prepareStatement(SELECT_USER_CLIENTS);
|
||||||
|
stmt.setString(1, userId);
|
||||||
|
var rs = stmt.executeQuery();
|
||||||
|
var result = new ArrayList<String>();
|
||||||
|
while (rs.next()) result.add(rs.getString(1));
|
||||||
|
rs.close();
|
||||||
|
return result;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Authorization> consumeAuthorization(String authCode) {
|
public Optional<Authorization> consumeAuthorization(String authCode) {
|
||||||
return nullable(authCodes.remove(authCode));
|
return nullable(authCodes.remove(authCode));
|
||||||
|
|||||||
@@ -4,13 +4,15 @@
|
|||||||
<title>Light OIDC</title>
|
<title>Light OIDC</title>
|
||||||
<script src="scripts/common.js"></script>
|
<script src="scripts/common.js"></script>
|
||||||
<script src="scripts/user.js"></script>
|
<script src="scripts/user.js"></script>
|
||||||
|
<script src="scripts/index.js"></script>
|
||||||
<link rel="stylesheet" href="style.css" />
|
<link rel="stylesheet" href="style.css" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav></nav>
|
<nav></nav>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<h1>Welcome!</h1>
|
<h1 id="welcome">Welcome, {}!</h1>
|
||||||
|
<h3 id="client_hint" style="display: none">These are your authorized clients:</h3>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -44,4 +44,4 @@ function remove(clientId){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(client_controller+"/list",{method:'POST',credentials:'include'}).then(handleClients);
|
fetch(client_controller+"/list").then(handleClients);
|
||||||
@@ -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 = `<button onclick="window.location.href='${client.landing_page}';">${client.name}</button>`;
|
||||||
|
content.append(div);
|
||||||
|
any = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (any) show('client_hint');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(client_controller+"/dash").then(handleDash)
|
||||||
Reference in New Issue
Block a user