re-implemented authorization
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -1,17 +0,0 @@
|
|||||||
/* © SRSoftware 2024 */
|
|
||||||
package de.srsoftware.oidc.api;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface AuthorizationService {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@@ -3,5 +3,5 @@ package de.srsoftware.oidc.api;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
public record Authorization(String clientId, String userId, Instant expiration) {
|
public record AuthorizedScope(String scope, Instant expiration) {
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/* © SRSoftware 2024 */
|
||||||
|
package de.srsoftware.oidc.api;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface ClaimAuthorizationService {
|
||||||
|
public record AuthResult(List<AuthorizedScope> authorizedScopes, Set<String> unauthorizedScopes, String authCode) {
|
||||||
|
}
|
||||||
|
AuthResult getAuthorization(User user, Client client, Collection<String> scopes);
|
||||||
|
ClaimAuthorizationService authorize(User user, Client client, Collection<String> scopes, Instant expiration);
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ public class Constants {
|
|||||||
public static final String APP_NAME = "LightOIDC";
|
public static final String APP_NAME = "LightOIDC";
|
||||||
public static final String AUTH_CODE = "authorization_code";
|
public static final String AUTH_CODE = "authorization_code";
|
||||||
public static final String AUTHORIZATION = "Authorization";
|
public static final String AUTHORIZATION = "Authorization";
|
||||||
|
public static final String AUTHORZED = "authorized";
|
||||||
public static final String BEARER = "Bearer";
|
public static final String BEARER = "Bearer";
|
||||||
public static final String CAUSE = "cause";
|
public static final String CAUSE = "cause";
|
||||||
public static final String CLIENT_ID = "client_id";
|
public static final String CLIENT_ID = "client_id";
|
||||||
@@ -14,6 +15,7 @@ public class Constants {
|
|||||||
public static final String CONFIG_PATH = "LIGHTOIDC_CONFIG_PATH";
|
public static final String CONFIG_PATH = "LIGHTOIDC_CONFIG_PATH";
|
||||||
public static final String CONFIRMED = "confirmed";
|
public static final String CONFIRMED = "confirmed";
|
||||||
public static final String DAYS = "days";
|
public static final String DAYS = "days";
|
||||||
|
public static final String EXPIRATION = "expiration";
|
||||||
public static final String EXPIRES_IN = "expires_in";
|
public static final String EXPIRES_IN = "expires_in";
|
||||||
public static final String GRANT_TYPE = "grant_type";
|
public static final String GRANT_TYPE = "grant_type";
|
||||||
public static final String ID_TOKEN = "id_token";
|
public static final String ID_TOKEN = "id_token";
|
||||||
|
|||||||
@@ -3,26 +3,23 @@ package de.srsoftware.oidc.backend;
|
|||||||
|
|
||||||
import static de.srsoftware.oidc.api.Constants.*;
|
import static de.srsoftware.oidc.api.Constants.*;
|
||||||
import static de.srsoftware.oidc.api.Permission.MANAGE_CLIENTS;
|
import static de.srsoftware.oidc.api.Permission.MANAGE_CLIENTS;
|
||||||
import static de.srsoftware.utils.Strings.uuid;
|
|
||||||
import static java.lang.System.Logger.Level.ERROR;
|
|
||||||
import static java.net.HttpURLConnection.*;
|
import static java.net.HttpURLConnection.*;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import de.srsoftware.oidc.api.*;
|
import de.srsoftware.oidc.api.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.HashSet;
|
import java.util.*;
|
||||||
import java.util.Map;
|
import java.util.stream.Collectors;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
public class ClientController extends Controller {
|
public class ClientController extends Controller {
|
||||||
private static final System.Logger LOG = System.getLogger(ClientController.class.getSimpleName());
|
private static final System.Logger LOG = System.getLogger(ClientController.class.getSimpleName());
|
||||||
private final AuthorizationService authorizations;
|
private final ClaimAuthorizationService authorizations;
|
||||||
private final ClientService clients;
|
private final ClientService clients;
|
||||||
|
|
||||||
public ClientController(AuthorizationService authorizationService, ClientService clientService, SessionService sessionService) {
|
public ClientController(ClaimAuthorizationService authorizationService, ClientService clientService, SessionService sessionService) {
|
||||||
super(sessionService);
|
super(sessionService);
|
||||||
authorizations = authorizationService;
|
authorizations = authorizationService;
|
||||||
clients = clientService;
|
clients = clientService;
|
||||||
@@ -32,31 +29,56 @@ public class ClientController extends Controller {
|
|||||||
private boolean authorize(HttpExchange ex, Session session) throws IOException {
|
private boolean authorize(HttpExchange ex, Session session) throws IOException {
|
||||||
var user = session.user();
|
var user = session.user();
|
||||||
var json = json(ex);
|
var json = json(ex);
|
||||||
var scope = json.getString(SCOPE);
|
for (String param : List.of(SCOPE, RESPONSE_TYPE, CLIENT_ID, REDIRECT_URI)) {
|
||||||
if (!Arrays.asList(scope.split(" ")).contains(OPENID)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "openid scope missing in request"));
|
if (!json.has(param)) return badRequest(ex, "Missing required parameter \"%s\"!".formatted(param));
|
||||||
|
}
|
||||||
|
var scopes = toList(json, SCOPE);
|
||||||
|
if (!scopes.contains(OPENID)) return badRequest(ex, "This is an OpenID Provider. You should request \"openid\" scope!");
|
||||||
|
var responseTypes = toList(json, RESPONSE_TYPE);
|
||||||
|
for (var responseType : responseTypes) {
|
||||||
|
switch (responseType) {
|
||||||
|
case ID_TOKEN:
|
||||||
|
case TOKEN:
|
||||||
|
return sendContent(ex, HTTP_NOT_IMPLEMENTED, "Response type \"%s\" currently not supported".formatted(responseType));
|
||||||
|
case CODE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return badRequest(ex, "Unknown response type \"%s\"".formatted(responseType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!responseTypes.contains(CODE)) return badRequest(ex, "Sorry, at the moment I can only handle \"%s\" response type".formatted(CODE));
|
||||||
|
|
||||||
var clientId = json.getString(CLIENT_ID);
|
var clientId = json.getString(CLIENT_ID);
|
||||||
var redirect = json.getString(REDIRECT_URI);
|
|
||||||
var optClient = clients.getClient(clientId);
|
var optClient = clients.getClient(clientId);
|
||||||
if (optClient.isEmpty()) return badRequest(ex, Map.of(CAUSE, "unknown client", CLIENT_ID, clientId));
|
if (optClient.isEmpty()) return badRequest(ex, Map.of(CAUSE, "unknown client", CLIENT_ID, clientId));
|
||||||
var client = optClient.get();
|
var client = optClient.get();
|
||||||
|
var redirect = json.getString(REDIRECT_URI);
|
||||||
if (!client.redirectUris().contains(redirect)) return badRequest(ex, Map.of(CAUSE, "unknown redirect uri", REDIRECT_URI, redirect));
|
if (!client.redirectUris().contains(redirect)) return badRequest(ex, Map.of(CAUSE, "unknown redirect uri", REDIRECT_URI, redirect));
|
||||||
|
var state = json.has(STATE) ? json.getString(STATE) : null;
|
||||||
|
|
||||||
client.nonce(json.has(NONCE) ? json.getString(NONCE) : null);
|
client.nonce(json.has(NONCE) ? json.getString(NONCE) : null);
|
||||||
|
if (json.has(AUTHORZED)) {
|
||||||
|
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, client, list, Instant.now().plus(days, ChronoUnit.DAYS));
|
||||||
|
}
|
||||||
|
|
||||||
if (!authorizations.isAuthorized(client, session.user())) {
|
var authResult = authorizations.getAuthorization(user, client, scopes);
|
||||||
if (json.has(DAYS)) {
|
if (!authResult.unauthorizedScopes().isEmpty()) {
|
||||||
var days = json.getInt(DAYS);
|
return sendContent(ex, Map.of("unauthorized_scopes", authResult.unauthorizedScopes(), "rp", client.name()));
|
||||||
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 authoriedScopes = authResult.authorizedScopes().stream().map(AuthorizedScope::scope).collect(Collectors.joining(" "));
|
||||||
|
var result = new HashMap<String, String>();
|
||||||
|
result.put(SCOPE, authoriedScopes);
|
||||||
|
result.put(CODE, authResult.authCode());
|
||||||
|
if (state != null) result.put(STATE, state);
|
||||||
|
return sendContent(ex, result);
|
||||||
}
|
}
|
||||||
var state = json.getString(STATE);
|
|
||||||
var code = uuid();
|
private List<String> toList(JSONObject json, String key) {
|
||||||
authorizations.addCode(client, session.user(), code);
|
return Arrays.asList(json.getString(key).split(" "));
|
||||||
return sendContent(ex, Map.of(CONFIRMED, true, CODE, code, REDIRECT_URI, redirect, STATE, state));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean deleteClient(HttpExchange ex, Session session) throws IOException {
|
private boolean deleteClient(HttpExchange ex, Session session) throws IOException {
|
||||||
@@ -86,7 +108,7 @@ public class ClientController extends Controller {
|
|||||||
@Override
|
@Override
|
||||||
public boolean doPost(String path, HttpExchange ex) throws IOException {
|
public boolean doPost(String path, HttpExchange ex) throws IOException {
|
||||||
var optSession = getSession(ex);
|
var optSession = getSession(ex);
|
||||||
if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
|
if (optSession.isEmpty()) return sendContent(ex, HTTP_UNAUTHORIZED, "No authorized!");
|
||||||
|
|
||||||
// post-login paths
|
// post-login paths
|
||||||
var session = optSession.get();
|
var session = optSession.get();
|
||||||
|
|||||||
@@ -2,33 +2,29 @@
|
|||||||
package de.srsoftware.oidc.backend;
|
package de.srsoftware.oidc.backend;
|
||||||
|
|
||||||
import static de.srsoftware.oidc.api.Constants.*;
|
import static de.srsoftware.oidc.api.Constants.*;
|
||||||
import static de.srsoftware.utils.Optionals.nullable;
|
|
||||||
import static java.lang.System.Logger.Level.*;
|
import static java.lang.System.Logger.Level.*;
|
||||||
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import de.srsoftware.oidc.api.*;
|
import de.srsoftware.oidc.api.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.jose4j.jwk.PublicJsonWebKey;
|
import org.jose4j.jwk.PublicJsonWebKey;
|
||||||
import org.jose4j.jws.JsonWebSignature;
|
import org.jose4j.jws.JsonWebSignature;
|
||||||
import org.jose4j.jwt.JwtClaims;
|
import org.jose4j.jwt.JwtClaims;
|
||||||
import org.jose4j.lang.JoseException;
|
import org.jose4j.lang.JoseException;
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class TokenController extends PathHandler {
|
public class TokenController extends PathHandler {
|
||||||
public record Configuration(String issuer, int tokenExpirationMinutes) {
|
public record Configuration(String issuer, int tokenExpirationMinutes) {
|
||||||
}
|
}
|
||||||
private final ClientService clients;
|
private final ClientService clients;
|
||||||
private final AuthorizationService authorizations;
|
private final ClaimAuthorizationService authorizations;
|
||||||
private final UserService users;
|
private final UserService users;
|
||||||
private final KeyManager keyManager;
|
private final KeyManager keyManager;
|
||||||
private Configuration config;
|
private Configuration config;
|
||||||
|
|
||||||
public TokenController(AuthorizationService authorizationService, ClientService clientService, KeyManager keyManager, UserService userService, Configuration configuration) {
|
public TokenController(ClaimAuthorizationService authorizationService, ClientService clientService, KeyManager keyManager, UserService userService, Configuration configuration) {
|
||||||
authorizations = authorizationService;
|
authorizations = authorizationService;
|
||||||
clients = clientService;
|
clients = clientService;
|
||||||
this.keyManager = keyManager;
|
this.keyManager = keyManager;
|
||||||
@@ -53,7 +49,8 @@ public class TokenController extends PathHandler {
|
|||||||
private boolean provideToken(HttpExchange ex) throws IOException {
|
private boolean provideToken(HttpExchange ex) throws IOException {
|
||||||
var map = deserialize(body(ex));
|
var map = deserialize(body(ex));
|
||||||
// TODO: check data, → https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
|
// TODO: check data, → https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
|
||||||
|
return sendEmptyResponse(HTTP_NOT_IMPLEMENTED, ex);
|
||||||
|
/*
|
||||||
var grantType = map.get(GRANT_TYPE);
|
var grantType = map.get(GRANT_TYPE);
|
||||||
if (!AUTH_CODE.equals(grantType)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown grant type", GRANT_TYPE, grantType));
|
if (!AUTH_CODE.equals(grantType)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown grant type", GRANT_TYPE, grantType));
|
||||||
|
|
||||||
@@ -87,7 +84,7 @@ public class TokenController extends PathHandler {
|
|||||||
response.put(EXPIRES_IN, 3600);
|
response.put(EXPIRES_IN, 3600);
|
||||||
response.put(ID_TOKEN, jwToken);
|
response.put(ID_TOKEN, jwToken);
|
||||||
LOG.log(DEBUG, jwToken);
|
LOG.log(DEBUG, jwToken);
|
||||||
return sendContent(ex, response);
|
return sendContent(ex, response);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createJWT(Client client, User user) {
|
private String createJWT(Client client, User user) {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
/* © SRSoftware 2024 */
|
/* © SRSoftware 2024 */
|
||||||
package de.srsoftware.oidc.datastore.file; /* © SRSoftware 2024 */
|
package de.srsoftware.oidc.datastore.file; /* © SRSoftware 2024 */
|
||||||
|
import static de.srsoftware.oidc.api.Constants.EXPIRATION;
|
||||||
import static de.srsoftware.oidc.api.User.*;
|
import static de.srsoftware.oidc.api.User.*;
|
||||||
import static de.srsoftware.utils.Optionals.nullable;
|
import static de.srsoftware.utils.Optionals.nullable;
|
||||||
import static de.srsoftware.utils.Strings.uuid;
|
import static de.srsoftware.utils.Strings.uuid;
|
||||||
|
import static java.lang.System.Logger.Level.WARNING;
|
||||||
import static java.util.Optional.empty;
|
import static java.util.Optional.empty;
|
||||||
|
|
||||||
import de.srsoftware.oidc.api.*;
|
import de.srsoftware.oidc.api.*;
|
||||||
@@ -17,11 +19,11 @@ import java.time.temporal.ChronoUnit;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
public class FileStore implements AuthorizationService, ClientService, SessionService, UserService {
|
public class FileStore implements ClaimAuthorizationService, ClientService, SessionService, UserService {
|
||||||
private static final String AUTHORIZATIONS = "authorizations";
|
private static final String AUTHORIZATIONS = "authorizations";
|
||||||
private static final String CLIENTS = "clients";
|
private static final String CLIENTS = "clients";
|
||||||
private static final String CODES = "codes";
|
private static final String CODES = "codes";
|
||||||
private static final String EXPIRATION = "expiration";
|
private static final System.Logger LOG = System.getLogger(FileStore.class.getSimpleName());
|
||||||
private static final String NAME = "name";
|
private static final String NAME = "name";
|
||||||
private static final String REDIRECT_URIS = "redirect_uris";
|
private static final String REDIRECT_URIS = "redirect_uris";
|
||||||
private static final String SECRET = "secret";
|
private static final String SECRET = "secret";
|
||||||
@@ -35,6 +37,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
|
|||||||
private Duration sessionDuration = Duration.of(10, ChronoUnit.MINUTES);
|
private Duration sessionDuration = Duration.of(10, ChronoUnit.MINUTES);
|
||||||
private Map<String, Client> clients = new HashMap<>();
|
private Map<String, Client> clients = new HashMap<>();
|
||||||
private Map<String, User> accessTokens = new HashMap<>();
|
private Map<String, User> accessTokens = new HashMap<>();
|
||||||
|
private Map<String, String> authCodes = new HashMap<>();
|
||||||
|
|
||||||
public FileStore(File storage, PasswordHasher<String> passwordHasher) throws IOException {
|
public FileStore(File storage, PasswordHasher<String> passwordHasher) throws IOException {
|
||||||
this.storageFile = storage.toPath();
|
this.storageFile = storage.toPath();
|
||||||
@@ -262,80 +265,51 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
|
|||||||
return new Client(clientId, clientData.getString(NAME), clientData.getString(SECRET), redirectUris);
|
return new Client(clientId, clientData.getString(NAME), clientData.getString(SECRET), redirectUris);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** ClaimAuthorizationService methods ***/
|
||||||
/*** Authorization service methods ***/
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Authorization> forCode(String code) {
|
public ClaimAuthorizationService authorize(User user, Client client, Collection<String> scopes, Instant expiration) {
|
||||||
|
LOG.log(WARNING, "{0}.authorize({1}, {2}, {3}, {4}) not implemented", getClass().getSimpleName(), user.realName(), client.name(), scopes, expiration);
|
||||||
var authorizations = json.getJSONObject(AUTHORIZATIONS);
|
var authorizations = json.getJSONObject(AUTHORIZATIONS);
|
||||||
if (!authorizations.has(code)) return empty();
|
if (!authorizations.has(user.uuid())) authorizations.put(user.uuid(), new JSONObject());
|
||||||
String authId = authorizations.getString(code);
|
var userAuthorizations = authorizations.getJSONObject(user.uuid());
|
||||||
if (!authorizations.has(authId)) {
|
if (!userAuthorizations.has(client.id())) userAuthorizations.put(client.id(), new JSONObject());
|
||||||
authorizations.remove(code);
|
var clientScopes = userAuthorizations.getJSONObject(client.id());
|
||||||
return empty();
|
for (var scope : scopes) clientScopes.put(scope, expiration.getEpochSecond());
|
||||||
}
|
|
||||||
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 empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationService addCode(Client client, User user, String code) {
|
|
||||||
var authorizations = json.getJSONObject(AUTHORIZATIONS);
|
|
||||||
authorizations.put(code, authorizationId(user, client));
|
|
||||||
save();
|
save();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AuthResult unauthorized(Collection<String> scopes) {
|
||||||
|
return new AuthResult(List.of(), new HashSet<>(scopes), null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthorizationService authorize(Client client, User user, Instant expiration) {
|
public AuthResult getAuthorization(User user, Client client, Collection<String> scopes) {
|
||||||
var authorizations = json.getJSONObject(AUTHORIZATIONS);
|
var authorizations = json.getJSONObject(AUTHORIZATIONS);
|
||||||
authorizations.put(authorizationId(user, client), expiration.getEpochSecond());
|
var userAuthorizations = authorizations.has(user.uuid()) ? authorizations.getJSONObject(user.uuid()) : null;
|
||||||
return this;
|
if (userAuthorizations == null) return unauthorized(scopes);
|
||||||
|
var clientScopes = userAuthorizations.has(client.id()) ? userAuthorizations.getJSONObject(client.id()) : null;
|
||||||
|
if (clientScopes == null) return unauthorized(scopes);
|
||||||
|
var now = Instant.now();
|
||||||
|
var authorizedScopes = new ArrayList<AuthorizedScope>();
|
||||||
|
var unauthorizedScopes = new HashSet<String>();
|
||||||
|
for (var scope : scopes) {
|
||||||
|
if (clientScopes.has(scope)) {
|
||||||
|
var expiration = Instant.ofEpochSecond(clientScopes.getLong(scope));
|
||||||
|
if (expiration.isAfter(now)) {
|
||||||
|
authorizedScopes.add(new AuthorizedScope(scope, expiration));
|
||||||
|
} else {
|
||||||
|
unauthorizedScopes.add(scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new AuthResult(authorizedScopes, unauthorizedScopes, authCode(user, client));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String authorizationId(User user, Client client) {
|
private String authCode(User user, Client client) {
|
||||||
return String.join("@", user.uuid(), client.id());
|
var code = uuid();
|
||||||
}
|
authCodes.put(user.uuid() + "@" + client.id(), code);
|
||||||
|
return code;
|
||||||
@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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<User> authorizedUsers(Client client) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Client> authorizedClients(User user) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationService revoke(Client client, User user) {
|
|
||||||
var authorizations = json.getJSONObject(AUTHORIZATIONS);
|
|
||||||
var authId = authorizationId(user, client);
|
|
||||||
if (!authorizations.has(authId)) return this;
|
|
||||||
authorizations.remove(authId);
|
|
||||||
return save();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,18 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav></nav>
|
<nav></nav>
|
||||||
<div id="content" style="display: none">
|
<div id="content" style="display: none">
|
||||||
<h1>Authorization</h1>
|
A relying party, <span id="rp">unknown</span>, requested access to the following information:
|
||||||
Confirmation required: are you shure you want to grant access to <span id="name">some client</span>?
|
<ul id="scopes">
|
||||||
<button type="button" onclick="grantAutorization(1)">Yes - 1 day</button>
|
|
||||||
<button type="button" onclick="grantAutorization(7)">Yes - 1 week</button>
|
</ul>
|
||||||
<button type="button" onclick="grantAutorization(30)">Yes - 1 month</button>
|
Do you consent to share this information with <span id="rp2">unknown</span>?
|
||||||
<button type="button" onclick="grantAutorization(365)">Yes - 1 year</button>
|
<button type="button" onclick="grantAutorization(1)">Yes - for 1 day</button>
|
||||||
|
<button type="button" onclick="grantAutorization(7)">Yes - for 1 week</button>
|
||||||
|
<button type="button" onclick="grantAutorization(30)">Yes - for 1 month</button>
|
||||||
|
<button type="button" onclick="grantAutorization(365)">Yes - for 1 year</button>
|
||||||
<button type="button" onclick="denyAutorization()">No</button>
|
<button type="button" onclick="denyAutorization()">No</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="error" class="error" style="display: none"></div>
|
<div id="error" class="error" style="display: none"></div>
|
||||||
|
<div id="missing_scopes" class="error" style="display: none">Authorization response contained neither list of <em>unauthorized scopes</em> nor list of <em>authorized scopes</em>! This is a server problem.</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,32 +1,57 @@
|
|||||||
var params = new URLSearchParams(window.location.search)
|
var params = new URLSearchParams(window.location.search)
|
||||||
var json = Object.fromEntries(params);
|
var json = Object.fromEntries(params);
|
||||||
|
var scopes = {};
|
||||||
|
|
||||||
function showConfirmationDialog(name){
|
function showConfirmationDialog(name){
|
||||||
get('name').innerHTML = name;
|
get('name').innerHTML = name;
|
||||||
show('content');
|
show('content');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function showScope(response,scope){
|
||||||
|
if (response.ok){
|
||||||
|
var content = await response.text();
|
||||||
|
get('scopes').innerHTML += content;
|
||||||
|
} else {
|
||||||
|
get('scopes').innerHTML += '<li>'+scope+' (???)</li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleResponse(response){
|
async function handleResponse(response){
|
||||||
if (response.ok){
|
if (response.ok){
|
||||||
var json = await response.json();
|
var json = await response.json();
|
||||||
console.log("handleResponse(ok) ←",json);
|
if (json.rp) {
|
||||||
if (!json.confirmed){
|
setText("rp",json.rp);
|
||||||
showConfirmationDialog(json.name);
|
setText("rp2",json.rp);
|
||||||
} else {
|
|
||||||
console.log('redirecting to '+json.redirect_uri+'?code='+json.code+'&state='+json.state+'&scope=openid');
|
|
||||||
redirect(json.redirect_uri+'?code='+json.code+'&state='+json.state+'&scope=openid');
|
|
||||||
}
|
}
|
||||||
|
get('scopes').innerHTML = '';
|
||||||
|
if (json.unauthorized_scopes){
|
||||||
|
scopes = json.unauthorized_scopes;
|
||||||
|
for (var scope of json.unauthorized_scopes){
|
||||||
|
fetch(web+"scopes/"+scope+".html").then(response => showScope(response,scope))
|
||||||
|
}
|
||||||
|
show("content");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
if (json.scope){
|
||||||
|
var url = params.get('redirect_uri') + '?' + new URLSearchParams(json).toString();
|
||||||
|
redirect(url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
show('missing_scopes');
|
||||||
} else {
|
} else {
|
||||||
var json = await response.json();
|
console.log(response);
|
||||||
console.log("handleResponse(error) ←",json);
|
if (response.status == 401){
|
||||||
get('error').innerHTML = "Error: <br/>"+JSON.stringify(json);
|
login();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var text = await response.text();
|
||||||
|
setText('error',"Error: <br/>"+text);
|
||||||
show('error');
|
show('error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function grantAutorization(days){
|
function grantAutorization(days){
|
||||||
json.days = days;
|
json['authorized'] = { days : days, scopes : scopes};
|
||||||
backendAutorization();
|
backendAutorization();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ function hide(id){
|
|||||||
get(id).style.display = 'none';
|
get(id).style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function login(){
|
||||||
|
redirect('login.html?return_to='+encodeURIComponent(window.location.href));
|
||||||
|
}
|
||||||
|
|
||||||
function redirect(page){
|
function redirect(page){
|
||||||
window.location.href = page;
|
window.location.href = page;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<li>Your email address</li>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<li>Your OpenID – let the relying party know who you are</li>
|
||||||
@@ -33,3 +33,7 @@ form th{
|
|||||||
.warning{
|
.warning{
|
||||||
color: yellow;
|
color: yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error{
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
var user = null;
|
var user = null;
|
||||||
async function handleUser(response){
|
async function handleUser(response){
|
||||||
if (response.status == UNAUTHORIZED) {
|
if (response.status == UNAUTHORIZED) {
|
||||||
redirect('login.html?return_to='+encodeURIComponent(window.location.href));
|
login();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (response.ok){
|
if (response.ok){
|
||||||
|
|||||||
Reference in New Issue
Block a user