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

Commit 9af2142

Browse files
committed
2 parents 913fcb5 + 31fb7e4 commit 9af2142

7 files changed

Lines changed: 141 additions & 43 deletions

File tree

src/restless/main/java/org/comroid/restless/MimeType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
public final class MimeType implements Named, CharSequence {
99
private static final Map<String, MimeType> cache = new ConcurrentHashMap<>();
10+
public static final MimeType ANY = forName("*/*");
1011
public static final MimeType JSON = forName("application/json");
1112
public static final MimeType XML = forName("application/xml");
1213
public static final MimeType JAVASCRIPT = forName("application/javascript");

src/webkit/main/java/org/comroid/restless/server/RestServer.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.comroid.mutatio.model.Ref;
1414
import org.comroid.mutatio.ref.Reference;
1515
import org.comroid.restless.HTTPStatusCodes;
16+
import org.comroid.restless.MimeType;
1617
import org.comroid.restless.REST;
1718
import org.comroid.restless.REST.Response;
1819
import org.comroid.restless.body.URIQueryEditor;
@@ -165,12 +166,13 @@ public void handle(HttpExchange exchange) {
165166
throw new RestEndpointException(UNSUPPORTED_MEDIA_TYPE, "Unsupported Content-Type: " + contentType);
166167

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

172173
// get request body
173174
String body = consumeBody(exchange);
175+
logger.trace("Request body: {}", body);
174176
UniNode requestData = null;
175177
try {
176178
requestData = body.isEmpty() ? serializer.createObjectNode() : serializer.parse(body);
@@ -209,20 +211,26 @@ public void handle(HttpExchange exchange) {
209211
// execute endpoint
210212
logger.debug("Executing Endpoint {}...", endpoint);
211213
response = endpoint.executeMethod(context, requestMethod, requestHeaders, urlParams, requestData);
212-
} catch (RestEndpointException e) {
213-
logger.warn("A REST Endpoint exception was thrown: {}", e.getMessage());
214-
try {
215-
Response alternate = tryRecoverFrom(e, requestURI, INTERNAL_SERVER_ERROR, requestMethod, requestHeaders);
214+
} catch (Throwable t) {
215+
if (t instanceof RestEndpointException && requestHeaders.getHeader(ACCEPTED_CONTENT_TYPE)
216+
.getValues()
217+
.stream()
218+
.anyMatch(str -> str.contains(MimeType.HTML.toString()) || str.contains(MimeType.ANY.toString()))) {
219+
RestEndpointException e = (RestEndpointException) t;
220+
logger.warn("A REST Endpoint exception was thrown: {}", e.getMessage());
221+
try {
222+
Response alternate = tryRecoverFrom(e, requestURI, INTERNAL_SERVER_ERROR, requestMethod, requestHeaders);
216223

217-
if (alternate != null && alternate.getStatusCode() == OK && e.getStatusCode() != OK)
218-
response = alternate;
219-
} catch (Throwable t2) {
220-
logger.debug("An error occurred during recovery", t2);
224+
if (alternate != null && alternate.getStatusCode() == OK && e.getStatusCode() != OK)
225+
response = alternate;
226+
} catch (Throwable t2) {
227+
logger.debug("An error occurred during recovery", t2);
228+
}
229+
} else {
230+
logger.error("An error occurred during request handling", t);
231+
RestEndpointException wrapped = new RestEndpointException(INTERNAL_SERVER_ERROR, t);
232+
response = new Response(wrapped.getStatusCode(), generateErrorNode(this, contentType, wrapped));
221233
}
222-
} catch (Throwable t) {
223-
logger.error("An error occurred during request handling", t);
224-
RestEndpointException wrapped = new RestEndpointException(INTERNAL_SERVER_ERROR, t);
225-
response = new Response(wrapped.getStatusCode(), generateErrorNode(this, contentType, wrapped));
226234
}
227235
// if response is null, send empty OK
228236
if (response == null)

src/webkit/oauth/java/org/comroid/webkit/oauth/client/ClientProvider.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import org.comroid.api.Rewrapper;
44
import org.comroid.restless.CommonHeaderNames;
5-
import org.comroid.webkit.oauth.user.OAuthAuthorization;
65
import org.comroid.restless.REST;
76
import org.comroid.restless.server.RestEndpointException;
7+
import org.comroid.util.Pair;
8+
import org.comroid.webkit.oauth.model.ValidityStage;
9+
import org.comroid.webkit.oauth.user.OAuthAuthorization;
810

911
import java.util.UUID;
1012

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

2022
boolean hasClient(UUID uuid);
2123

24+
Rewrapper<? extends Client> findClient(REST.Header.List headers);
25+
2226
Rewrapper<? extends Client> findClient(UUID uuid);
2327

24-
Client loginClient(String email, String login);
28+
Pair<Client, String> loginClient(String email, String login);
29+
30+
ValidityStage findValidityStage(String token);
2531
}

src/webkit/oauth/java/org/comroid/webkit/oauth/rest/OAuthEndpoint.java

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,6 @@
33
import org.apache.logging.log4j.LogManager;
44
import org.apache.logging.log4j.Logger;
55
import org.comroid.api.StreamSupplier;
6-
import org.comroid.webkit.oauth.OAuth;
7-
import org.comroid.webkit.oauth.client.Client;
8-
import org.comroid.webkit.oauth.client.ClientProvider;
9-
import org.comroid.webkit.oauth.model.OAuthError;
10-
import org.comroid.webkit.oauth.resource.Resource;
11-
import org.comroid.webkit.oauth.resource.ResourceProvider;
12-
import org.comroid.webkit.oauth.rest.request.AuthenticationRequest;
13-
import org.comroid.webkit.oauth.rest.request.TokenRequest;
14-
import org.comroid.webkit.oauth.user.OAuthAuthorization;
156
import org.comroid.restless.CommonHeaderNames;
167
import org.comroid.restless.HTTPStatusCodes;
178
import org.comroid.restless.REST;
@@ -20,8 +11,20 @@
2011
import org.comroid.restless.server.ServerEndpoint;
2112
import org.comroid.uniform.Context;
2213
import org.comroid.uniform.node.UniNode;
14+
import org.comroid.util.Pair;
2315
import org.comroid.webkit.frame.FrameBuilder;
2416
import org.comroid.webkit.model.PagePropertiesProvider;
17+
import org.comroid.webkit.oauth.OAuth;
18+
import org.comroid.webkit.oauth.client.Client;
19+
import org.comroid.webkit.oauth.client.ClientProvider;
20+
import org.comroid.webkit.oauth.model.OAuthError;
21+
import org.comroid.webkit.oauth.model.ValidityStage;
22+
import org.comroid.webkit.oauth.resource.Resource;
23+
import org.comroid.webkit.oauth.resource.ResourceProvider;
24+
import org.comroid.webkit.oauth.rest.request.AuthenticationRequest;
25+
import org.comroid.webkit.oauth.rest.request.TokenRequest;
26+
import org.comroid.webkit.oauth.rest.request.TokenRevocationRequest;
27+
import org.comroid.webkit.oauth.user.OAuthAuthorization;
2528
import org.intellij.lang.annotations.Language;
2629

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

6063
try {
61-
// find session & account
62-
Client account = context.requireFromContext(ClientProvider.class)
63-
.findAccessToken(headers)
64-
.getAuthorization()
65-
.getClient();
64+
// find client
65+
Client client = context.requireFromContext(ClientProvider.class)
66+
.findClient(headers)
67+
// throw with status code OK to send login frame
68+
.orElseThrow(() -> new RestEndpointException(OK));
6669

67-
String authorizationCode = completeAuthorization(account, authenticationRequest, context, service, userAgent);
70+
String authorizationCode = completeAuthorization(client, authenticationRequest, context, service, userAgent);
6871

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

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

128-
String code = OAuthEndpoint.completeAuthorization(client, authenticationRequest, context, service, userAgent);
131+
String code = OAuthEndpoint.completeAuthorization(client.getFirst(), authenticationRequest, context, service, userAgent);
129132

130133
// assemble redirect uri
131134
query.put("code", code);
132135
if (authenticationRequest.state.isNonNull())
133136
query.put("state", authenticationRequest.getState());
134137

135-
return new REST.Response(FOUND, query.toURI());
138+
REST.Header.List response = new REST.Header.List();
139+
response.add("Location", query.toURI().toString());
140+
response.add("Set-Cookie", client.getSecond());
141+
142+
return new REST.Response(FOUND, response);
136143
}
137144
},
138145
TOKEN("/token") {
@@ -146,6 +153,34 @@ public REST.Response executePOST(Context context, REST.Header.List headers, Stri
146153
return new REST.Response(OK, accessToken);
147154
}
148155
},
156+
TOKEN_REVOKE("/token/revoke") {
157+
@Override
158+
public REST.Response executePOST(Context context, REST.Header.List headers, String[] urlParams, UniNode body) throws RestEndpointException {
159+
ClientProvider clientProvider = context.requireFromContext(ClientProvider.class);
160+
TokenRevocationRequest request = new TokenRevocationRequest(context, body);
161+
162+
ValidityStage validity;
163+
if (request.tokenHint.isNull()) {
164+
validity = clientProvider.findValidityStage(request.getToken());
165+
} else switch (request.getTokenHint()) {
166+
case "access_token":
167+
validity = clientProvider.findAccessToken(request.getToken());
168+
break;
169+
case "refresh_token":
170+
// fixme
171+
//validity = clientProvider.findAccessToken(request.getToken());
172+
throw new UnsupportedOperationException("unsupported: refresh token");
173+
default:
174+
throw new AssertionError("invalid token hint: " + request.getTokenHint());
175+
}
176+
177+
if (validity == null)
178+
throw new RestEndpointException(BAD_REQUEST, "Unknown Token");
179+
if (validity.isValid() && !validity.invalidate())
180+
throw new RestEndpointException(INTERNAL_SERVER_ERROR, "Could not invalidate token");
181+
return new REST.Response(OK);
182+
}
183+
},
149184
USER_INFO("/userInfo") {
150185
@Override
151186
public REST.Response executeGET(Context context, REST.Header.List headers, String[] urlParams, UniNode body) throws RestEndpointException {

src/webkit/oauth/java/org/comroid/webkit/oauth/rest/request/AuthenticationRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import java.util.stream.Stream;
2121

2222
public class AuthenticationRequest extends DataContainerBase<AuthenticationRequest> {
23-
public static final String SCOPE_SPLIT_PATTERN = "[\\s+]";
23+
public static final String SCOPE_SPLIT_PATTERN = "[\\s+,]{1,2}";
2424
@RootBind
2525
public static final GroupBind<AuthenticationRequest> Type
2626
= new GroupBind<>(OAuth.CONTEXT, "authentication-request");
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.comroid.webkit.oauth.rest.request;
2+
3+
import org.comroid.mutatio.model.Ref;
4+
import org.comroid.uniform.Context;
5+
import org.comroid.uniform.node.UniNode;
6+
import org.comroid.uniform.node.UniObjectNode;
7+
import org.comroid.util.StandardValueType;
8+
import org.comroid.varbind.annotation.RootBind;
9+
import org.comroid.varbind.bind.GroupBind;
10+
import org.comroid.varbind.bind.VarBind;
11+
import org.comroid.varbind.container.DataContainerBase;
12+
import org.comroid.webkit.oauth.OAuth;
13+
import org.jetbrains.annotations.Nullable;
14+
15+
public class TokenRevocationRequest extends DataContainerBase<TokenRevocationRequest> {
16+
@RootBind
17+
public static final GroupBind<TokenRevocationRequest> Type
18+
= new GroupBind<>(OAuth.CONTEXT, "token-revocation-request");
19+
public static final VarBind<TokenRevocationRequest, String, String, String> TOKEN
20+
= Type.createBind("token")
21+
.extractAs(StandardValueType.STRING)
22+
.asIdentities()
23+
.onceEach()
24+
.setRequired()
25+
.build();
26+
public static final VarBind<TokenRevocationRequest, String, String, String> TOKEN_HINT
27+
= Type.createBind("token_type_hint")
28+
.extractAs(StandardValueType.STRING)
29+
.build();
30+
public final Ref<String> token = getComputedReference(TOKEN);
31+
public final Ref<String> tokenHint = getComputedReference(TOKEN_HINT);
32+
33+
public String getToken() {
34+
return token.assertion("token");
35+
}
36+
37+
public @Nullable String getTokenHint() {
38+
return tokenHint.get();
39+
}
40+
41+
public TokenRevocationRequest(Context context, UniNode body) {
42+
super(context, body.asObjectNode());
43+
}
44+
}

src/webkit/oauth/java/org/comroid/webkit/oauth/user/OAuthAuthorization.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
import org.apache.logging.log4j.LogManager;
44
import org.apache.logging.log4j.Logger;
55
import org.comroid.mutatio.model.Ref;
6+
import org.comroid.uniform.Context;
7+
import org.comroid.uniform.node.UniNode;
8+
import org.comroid.util.StandardValueType;
9+
import org.comroid.varbind.annotation.RootBind;
10+
import org.comroid.varbind.bind.GroupBind;
11+
import org.comroid.varbind.bind.VarBind;
12+
import org.comroid.varbind.container.DataContainerBase;
613
import org.comroid.webkit.oauth.OAuth;
714
import org.comroid.webkit.oauth.client.Client;
815
import org.comroid.webkit.oauth.client.ClientProvider;
@@ -11,13 +18,6 @@
1118
import org.comroid.webkit.oauth.resource.Resource;
1219
import org.comroid.webkit.oauth.resource.ResourceProvider;
1320
import org.comroid.webkit.oauth.rest.request.AuthenticationRequest;
14-
import org.comroid.uniform.Context;
15-
import org.comroid.uniform.node.UniNode;
16-
import org.comroid.util.StandardValueType;
17-
import org.comroid.varbind.annotation.RootBind;
18-
import org.comroid.varbind.bind.GroupBind;
19-
import org.comroid.varbind.bind.VarBind;
20-
import org.comroid.varbind.container.DataContainerBase;
2121
import org.jetbrains.annotations.Nullable;
2222

2323
import java.time.Duration;
@@ -227,8 +227,12 @@ public boolean invalidate() {
227227
return invalidation.complete(null);
228228
}
229229

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

0 commit comments

Comments
 (0)