Skip to content
This repository has been archived by the owner on Feb 25, 2024. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
burdoto committed Apr 30, 2021
2 parents 913fcb5 + 31fb7e4 commit 9af2142
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 43 deletions.
1 change: 1 addition & 0 deletions src/restless/main/java/org/comroid/restless/MimeType.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

public final class MimeType implements Named, CharSequence {
private static final Map<String, MimeType> cache = new ConcurrentHashMap<>();
public static final MimeType ANY = forName("*/*");
public static final MimeType JSON = forName("application/json");
public static final MimeType XML = forName("application/xml");
public static final MimeType JAVASCRIPT = forName("application/javascript");
Expand Down
34 changes: 21 additions & 13 deletions src/webkit/main/java/org/comroid/restless/server/RestServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.comroid.mutatio.model.Ref;
import org.comroid.mutatio.ref.Reference;
import org.comroid.restless.HTTPStatusCodes;
import org.comroid.restless.MimeType;
import org.comroid.restless.REST;
import org.comroid.restless.REST.Response;
import org.comroid.restless.body.URIQueryEditor;
Expand Down Expand Up @@ -165,12 +166,13 @@ public void handle(HttpExchange exchange) {
throw new RestEndpointException(UNSUPPORTED_MEDIA_TYPE, "Unsupported Content-Type: " + contentType);

logger.debug("Receiving {} {}-Request to {} with {} headers", serializer.getMimeType(), requestMethod, requestURI, requestHeaders.size());
logger.trace("Response has headers:\n{}", requestHeaders.stream()
logger.trace("Request has headers:\n{}", requestHeaders.stream()
.map(REST.Header::toString)
.collect(Collectors.joining("\n")));

// get request body
String body = consumeBody(exchange);
logger.trace("Request body: {}", body);
UniNode requestData = null;
try {
requestData = body.isEmpty() ? serializer.createObjectNode() : serializer.parse(body);
Expand Down Expand Up @@ -209,20 +211,26 @@ public void handle(HttpExchange exchange) {
// execute endpoint
logger.debug("Executing Endpoint {}...", endpoint);
response = endpoint.executeMethod(context, requestMethod, requestHeaders, urlParams, requestData);
} catch (RestEndpointException e) {
logger.warn("A REST Endpoint exception was thrown: {}", e.getMessage());
try {
Response alternate = tryRecoverFrom(e, requestURI, INTERNAL_SERVER_ERROR, requestMethod, requestHeaders);
} catch (Throwable t) {
if (t instanceof RestEndpointException && requestHeaders.getHeader(ACCEPTED_CONTENT_TYPE)
.getValues()
.stream()
.anyMatch(str -> str.contains(MimeType.HTML.toString()) || str.contains(MimeType.ANY.toString()))) {
RestEndpointException e = (RestEndpointException) t;
logger.warn("A REST Endpoint exception was thrown: {}", e.getMessage());
try {
Response alternate = tryRecoverFrom(e, requestURI, INTERNAL_SERVER_ERROR, requestMethod, requestHeaders);

if (alternate != null && alternate.getStatusCode() == OK && e.getStatusCode() != OK)
response = alternate;
} catch (Throwable t2) {
logger.debug("An error occurred during recovery", t2);
if (alternate != null && alternate.getStatusCode() == OK && e.getStatusCode() != OK)
response = alternate;
} catch (Throwable t2) {
logger.debug("An error occurred during recovery", t2);
}
} else {
logger.error("An error occurred during request handling", t);
RestEndpointException wrapped = new RestEndpointException(INTERNAL_SERVER_ERROR, t);
response = new Response(wrapped.getStatusCode(), generateErrorNode(this, contentType, wrapped));
}
} catch (Throwable t) {
logger.error("An error occurred during request handling", t);
RestEndpointException wrapped = new RestEndpointException(INTERNAL_SERVER_ERROR, t);
response = new Response(wrapped.getStatusCode(), generateErrorNode(this, contentType, wrapped));
}
// if response is null, send empty OK
if (response == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import org.comroid.api.Rewrapper;
import org.comroid.restless.CommonHeaderNames;
import org.comroid.webkit.oauth.user.OAuthAuthorization;
import org.comroid.restless.REST;
import org.comroid.restless.server.RestEndpointException;
import org.comroid.util.Pair;
import org.comroid.webkit.oauth.model.ValidityStage;
import org.comroid.webkit.oauth.user.OAuthAuthorization;

import java.util.UUID;

Expand All @@ -19,7 +21,11 @@ default OAuthAuthorization.AccessToken findAccessToken(REST.Header.List headers)

boolean hasClient(UUID uuid);

Rewrapper<? extends Client> findClient(REST.Header.List headers);

Rewrapper<? extends Client> findClient(UUID uuid);

Client loginClient(String email, String login);
Pair<Client, String> loginClient(String email, String login);

ValidityStage findValidityStage(String token);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,6 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.comroid.api.StreamSupplier;
import org.comroid.webkit.oauth.OAuth;
import org.comroid.webkit.oauth.client.Client;
import org.comroid.webkit.oauth.client.ClientProvider;
import org.comroid.webkit.oauth.model.OAuthError;
import org.comroid.webkit.oauth.resource.Resource;
import org.comroid.webkit.oauth.resource.ResourceProvider;
import org.comroid.webkit.oauth.rest.request.AuthenticationRequest;
import org.comroid.webkit.oauth.rest.request.TokenRequest;
import org.comroid.webkit.oauth.user.OAuthAuthorization;
import org.comroid.restless.CommonHeaderNames;
import org.comroid.restless.HTTPStatusCodes;
import org.comroid.restless.REST;
Expand All @@ -20,8 +11,20 @@
import org.comroid.restless.server.ServerEndpoint;
import org.comroid.uniform.Context;
import org.comroid.uniform.node.UniNode;
import org.comroid.util.Pair;
import org.comroid.webkit.frame.FrameBuilder;
import org.comroid.webkit.model.PagePropertiesProvider;
import org.comroid.webkit.oauth.OAuth;
import org.comroid.webkit.oauth.client.Client;
import org.comroid.webkit.oauth.client.ClientProvider;
import org.comroid.webkit.oauth.model.OAuthError;
import org.comroid.webkit.oauth.model.ValidityStage;
import org.comroid.webkit.oauth.resource.Resource;
import org.comroid.webkit.oauth.resource.ResourceProvider;
import org.comroid.webkit.oauth.rest.request.AuthenticationRequest;
import org.comroid.webkit.oauth.rest.request.TokenRequest;
import org.comroid.webkit.oauth.rest.request.TokenRevocationRequest;
import org.comroid.webkit.oauth.user.OAuthAuthorization;
import org.intellij.lang.annotations.Language;

import java.net.URI;
Expand Down Expand Up @@ -58,13 +61,13 @@ public REST.Response executeGET(Context context, REST.Header.List headers, Strin
final String userAgent = headers.getFirst(CommonHeaderNames.USER_AGENT);

try {
// find session & account
Client account = context.requireFromContext(ClientProvider.class)
.findAccessToken(headers)
.getAuthorization()
.getClient();
// find client
Client client = context.requireFromContext(ClientProvider.class)
.findClient(headers)
// throw with status code OK to send login frame
.orElseThrow(() -> new RestEndpointException(OK));

String authorizationCode = completeAuthorization(account, authenticationRequest, context, service, userAgent);
String authorizationCode = completeAuthorization(client, authenticationRequest, context, service, userAgent);

// assemble redirect uri
query.put("code", authorizationCode);
Expand Down Expand Up @@ -110,7 +113,7 @@ public REST.Response executePOST(Context context, REST.Header.List headers, Stri
AuthenticationRequest authenticationRequest = loginRequests.getOrDefault(requestId, null);
URIQueryEditor query = new URIQueryEditor(authenticationRequest.getRedirectURI());

Client client;
Pair<Client, String> client;
try {
client = context.requireFromContext(ClientProvider.class)
.loginClient(email, login);
Expand All @@ -125,14 +128,18 @@ public REST.Response executePOST(Context context, REST.Header.List headers, Stri
.orElseThrow(() -> new RestEndpointException(UNAUTHORIZED, "Service with ID " + clientID + " not found"));
String userAgent = headers.getFirst(CommonHeaderNames.USER_AGENT);

String code = OAuthEndpoint.completeAuthorization(client, authenticationRequest, context, service, userAgent);
String code = OAuthEndpoint.completeAuthorization(client.getFirst(), authenticationRequest, context, service, userAgent);

// assemble redirect uri
query.put("code", code);
if (authenticationRequest.state.isNonNull())
query.put("state", authenticationRequest.getState());

return new REST.Response(FOUND, query.toURI());
REST.Header.List response = new REST.Header.List();
response.add("Location", query.toURI().toString());
response.add("Set-Cookie", client.getSecond());

return new REST.Response(FOUND, response);
}
},
TOKEN("/token") {
Expand All @@ -146,6 +153,34 @@ public REST.Response executePOST(Context context, REST.Header.List headers, Stri
return new REST.Response(OK, accessToken);
}
},
TOKEN_REVOKE("/token/revoke") {
@Override
public REST.Response executePOST(Context context, REST.Header.List headers, String[] urlParams, UniNode body) throws RestEndpointException {
ClientProvider clientProvider = context.requireFromContext(ClientProvider.class);
TokenRevocationRequest request = new TokenRevocationRequest(context, body);

ValidityStage validity;
if (request.tokenHint.isNull()) {
validity = clientProvider.findValidityStage(request.getToken());
} else switch (request.getTokenHint()) {
case "access_token":
validity = clientProvider.findAccessToken(request.getToken());
break;
case "refresh_token":
// fixme
//validity = clientProvider.findAccessToken(request.getToken());
throw new UnsupportedOperationException("unsupported: refresh token");
default:
throw new AssertionError("invalid token hint: " + request.getTokenHint());
}

if (validity == null)
throw new RestEndpointException(BAD_REQUEST, "Unknown Token");
if (validity.isValid() && !validity.invalidate())
throw new RestEndpointException(INTERNAL_SERVER_ERROR, "Could not invalidate token");
return new REST.Response(OK);
}
},
USER_INFO("/userInfo") {
@Override
public REST.Response executeGET(Context context, REST.Header.List headers, String[] urlParams, UniNode body) throws RestEndpointException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.stream.Stream;

public class AuthenticationRequest extends DataContainerBase<AuthenticationRequest> {
public static final String SCOPE_SPLIT_PATTERN = "[\\s+]";
public static final String SCOPE_SPLIT_PATTERN = "[\\s+,]{1,2}";
@RootBind
public static final GroupBind<AuthenticationRequest> Type
= new GroupBind<>(OAuth.CONTEXT, "authentication-request");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.comroid.webkit.oauth.rest.request;

import org.comroid.mutatio.model.Ref;
import org.comroid.uniform.Context;
import org.comroid.uniform.node.UniNode;
import org.comroid.uniform.node.UniObjectNode;
import org.comroid.util.StandardValueType;
import org.comroid.varbind.annotation.RootBind;
import org.comroid.varbind.bind.GroupBind;
import org.comroid.varbind.bind.VarBind;
import org.comroid.varbind.container.DataContainerBase;
import org.comroid.webkit.oauth.OAuth;
import org.jetbrains.annotations.Nullable;

public class TokenRevocationRequest extends DataContainerBase<TokenRevocationRequest> {
@RootBind
public static final GroupBind<TokenRevocationRequest> Type
= new GroupBind<>(OAuth.CONTEXT, "token-revocation-request");
public static final VarBind<TokenRevocationRequest, String, String, String> TOKEN
= Type.createBind("token")
.extractAs(StandardValueType.STRING)
.asIdentities()
.onceEach()
.setRequired()
.build();
public static final VarBind<TokenRevocationRequest, String, String, String> TOKEN_HINT
= Type.createBind("token_type_hint")
.extractAs(StandardValueType.STRING)
.build();
public final Ref<String> token = getComputedReference(TOKEN);
public final Ref<String> tokenHint = getComputedReference(TOKEN_HINT);

public String getToken() {
return token.assertion("token");
}

public @Nullable String getTokenHint() {
return tokenHint.get();
}

public TokenRevocationRequest(Context context, UniNode body) {
super(context, body.asObjectNode());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.comroid.mutatio.model.Ref;
import org.comroid.uniform.Context;
import org.comroid.uniform.node.UniNode;
import org.comroid.util.StandardValueType;
import org.comroid.varbind.annotation.RootBind;
import org.comroid.varbind.bind.GroupBind;
import org.comroid.varbind.bind.VarBind;
import org.comroid.varbind.container.DataContainerBase;
import org.comroid.webkit.oauth.OAuth;
import org.comroid.webkit.oauth.client.Client;
import org.comroid.webkit.oauth.client.ClientProvider;
Expand All @@ -11,13 +18,6 @@
import org.comroid.webkit.oauth.resource.Resource;
import org.comroid.webkit.oauth.resource.ResourceProvider;
import org.comroid.webkit.oauth.rest.request.AuthenticationRequest;
import org.comroid.uniform.Context;
import org.comroid.uniform.node.UniNode;
import org.comroid.util.StandardValueType;
import org.comroid.varbind.annotation.RootBind;
import org.comroid.varbind.bind.GroupBind;
import org.comroid.varbind.bind.VarBind;
import org.comroid.varbind.container.DataContainerBase;
import org.jetbrains.annotations.Nullable;

import java.time.Duration;
Expand Down Expand Up @@ -227,8 +227,12 @@ public boolean invalidate() {
return invalidation.complete(null);
}

public boolean checkToken(String token) {
return this.token.contentEquals(token);
public boolean checkToken(String otherTk) {
// strip prefix if present
int indexOfSpace = otherTk.indexOf(' ');
otherTk = indexOfSpace < 8 ? otherTk.substring(indexOfSpace + 1) : otherTk;
//logger.trace("checking token [{}] vs other token [{}]", this.token.get(), otherTk);
return this.token.contentEquals(otherTk);
}
}
}

0 comments on commit 9af2142

Please sign in to comment.