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.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));
|
||||
}
|
||||
|
||||
@@ -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<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);
|
||||
List<String> authorizedClients(String userId);
|
||||
Optional<String> consumeNonce(String uuid, String id);
|
||||
|
||||
void nonce(String uuid, String id, String string);
|
||||
}
|
||||
|
||||
@@ -37,12 +37,8 @@ public final class Client {
|
||||
}
|
||||
|
||||
public Map<String, Object> map() {
|
||||
var map = new HashMap<String, Object>();
|
||||
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<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() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
@@ -74,4 +74,5 @@ public abstract class AuthServiceTest {
|
||||
}
|
||||
|
||||
// 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.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);
|
||||
}
|
||||
|
||||
@@ -334,6 +334,15 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
|
||||
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
|
||||
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 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, 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
|
||||
public Optional<Authorization> consumeAuthorization(String authCode) {
|
||||
return nullable(authCodes.remove(authCode));
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
<title>Light OIDC</title>
|
||||
<script src="scripts/common.js"></script>
|
||||
<script src="scripts/user.js"></script>
|
||||
<script src="scripts/index.js"></script>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
<nav></nav>
|
||||
<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>
|
||||
</body>
|
||||
</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