Skip to content

Commit

Permalink
feat(oauth2): Add OAuth2 attributes in execution context
Browse files Browse the repository at this point in the history
  • Loading branch information
brasseld committed Nov 4, 2016
1 parent ab8976b commit eb1f604
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 97 deletions.
82 changes: 68 additions & 14 deletions README.adoc
@@ -1,40 +1,74 @@
= Oauth2 Policy
= OAuth2 Policy

ifdef::env-github[]
image:https://ci.gravitee.io/buildStatus/icon?job=gravitee-io/gravitee-policy-oauth2/master["Build status", link="https://ci.gravitee.io/job/gravitee-io/job/gravitee-policy-oauth2/"]
image:https://badges.gitter.im/Join Chat.svg["Gitter", link="https://gitter.im/gravitee-io/gravitee-io?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
endif::[]

== Scope
== Phase

[cols="2*", options="header"]
|===
|onRequest |onResponse
^|onRequest
^|onResponse

| X
|
^.^| X
^.^|

|===

== Description

Gravitee.io OAuth 2 policy authentication. Check if an OAuth 2 access token is valid during the request process.
The OAuth2 policy checks if an access token is valid during request processing.

If the access token is valid, the request is allowed to proceed, if not the process stops and reject the request.
If the access token is valid, the request is allowed to proceed, if not the process stops and rejects the request.

The access token must be supply in the Authorization HTTP request header :
The access token must be supply in the ```Authorization``` HTTP request header :

[source]
[source, shell]
----
$ curl -H "Authorization: Bearer |accessToken|" \
http://gravitee.io-gateway/yourApi/yourRestrictedData
http://gateway/api/resource
----

== Attributes

|===
|Name |Description

.^|oauth.access_token
|Access token extracted from ```Authorization``` HTTP header.

.^|oauth.payload
|Payload from token endpoint / authorization server. Useful when you want to parse and extract data from it. Only if `extractPayload` is enabled from policy configuration.

|===

== Configuration

The policy use the following Gravitee.io OAuth 2.0 resource properties :
This policy use a Gravitee.io resource to access the OAuth2 Authorization Server.

|===
|Property |Required |Description |Type| Default

.^|oauthResource
^.^|X
|The OAuth2 resource used to validate access_token. This must reference a valid Gravitee.io OAuth2 resource.
^.^|string
|

.^|extractPayload
^.^|-
|When access token is validated, the token endpoint payload is saved under the ```oauth.payload``` context attribute.
^.^|boolean
^.^|false

|===

The OAuth2 resource must be defined with these properties:

[source, json]
.Gravitee.io OAuth2 Resource
OAuth2 Resource example:
----
"properties" : {
"serverURL" : {
Expand Down Expand Up @@ -107,6 +141,26 @@ The policy use the following Gravitee.io OAuth 2.0 resource properties :
.Sample
----
"oauth2": {
"oauthResource": {}
"oauthResource": "oauth2-resource-name",
"extractPayload": true
}
----
----

== Http Status Code

|===
|Code |Message

.^| ```401```
| In case of:

* No OAuth authorization server has been configured
* No OAuth authorization header was supplied
* No OAuth access_token was supplied
* Access token can not be validated by authorization server

.^| ```403```
| Access token can not be validated because of a technical error with
authorization server.

|===
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -38,7 +38,7 @@
<gravitee-policy-api.version>1.0.0</gravitee-policy-api.version>
<gravitee-resource-api.version>1.0.0</gravitee-resource-api.version>
<gravitee-common.version>1.0.0</gravitee-common.version>
<gravitee-resource-oauth2.version>1.0.0</gravitee-resource-oauth2.version>
<gravitee-resource-oauth2.version>1.1.0-SNAPSHOT</gravitee-resource-oauth2.version>

<maven-assembly-plugin.version>2.5.5</maven-assembly-plugin.version>
<async-http-client.version>2.0.2</async-http-client.version>
Expand Down
108 changes: 40 additions & 68 deletions src/main/java/io/gravitee/policy/oauth2/Oauth2Policy.java
Expand Up @@ -20,120 +20,92 @@
import io.gravitee.gateway.api.ExecutionContext;
import io.gravitee.gateway.api.Request;
import io.gravitee.gateway.api.Response;
import io.gravitee.gateway.api.handler.Handler;
import io.gravitee.policy.api.PolicyChain;
import io.gravitee.policy.api.PolicyResult;
import io.gravitee.policy.api.annotations.OnRequest;
import io.gravitee.policy.oauth2.configuration.OAuth2PolicyConfiguration;
import io.gravitee.resource.api.ResourceManager;
import io.gravitee.resource.oauth2.OAuth2Request;
import io.gravitee.resource.oauth2.OAuth2Resource;
import io.gravitee.resource.oauth2.configuration.OAuth2ResourceConfiguration;
import org.asynchttpclient.AsyncCompletionHandler;
import org.asynchttpclient.AsyncHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.gravitee.resource.oauth2.OAuth2Response;

import javax.inject.Inject;
import java.util.*;
import java.util.Optional;

/**
* @author David BRASSELY (david at gravitee.io)
* @author David BRASSELY (david.brassely at graviteesource.com)
* @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
* @author GraviteeSource Team
*/
public class Oauth2Policy {

private static final Logger LOGGER = LoggerFactory.getLogger(Oauth2Policy.class);

private static final String BEARER_TYPE = "Bearer";
private static final String OAUTH2_ACCESS_TOKEN = "OAUTH2_ACCESS_TOKEN";
static final String CONTEXT_ATTRIBUTE_OAUTH_PAYLOAD = "oauth.payload";
static final String CONTEXT_ATTRIBUTE_OAUTH_ACCESS_TOKEN = "oauth.access_token";

@Inject
private OAuth2PolicyConfiguration oAuth2PolicyConfiguration;

public Oauth2Policy (OAuth2PolicyConfiguration oAuth2PolicyConfiguration) {
this.oAuth2PolicyConfiguration = oAuth2PolicyConfiguration;
}

@OnRequest
public void onRequest(Request request, Response response, ExecutionContext executionContext, PolicyChain policyChain) {
OAuth2Resource oauth2 = executionContext.getComponent(ResourceManager.class).getResource(
oAuth2PolicyConfiguration.getOauthResource(), OAuth2Resource.class);

if (oauth2 == null) {
policyChain.failWith(PolicyResult.failure(HttpStatusCode.UNAUTHORIZED_401,
"No OAuth authorization server has been configured"));
return;
}

if (request.headers() == null || request.headers().get(HttpHeaders.AUTHORIZATION) == null || request.headers().get(HttpHeaders.AUTHORIZATION).isEmpty()) {
response.headers().add(HttpHeaders.WWW_AUTHENTICATE, BEARER_TYPE+" realm=gravitee.io - No OAuth authorization header was supplied");
policyChain.failWith(PolicyResult.failure(HttpStatusCode.UNAUTHORIZED_401,
"No OAuth authorization header was supplied"));
return;
}
Optional<String> optionalHeaderAccessToken = request.headers().get(HttpHeaders.AUTHORIZATION).stream().filter(h -> h.startsWith("Bearer")).findFirst();

Optional<String> optionalHeaderAccessToken = request.headers().get(HttpHeaders.AUTHORIZATION).stream().filter(h -> h.startsWith("Bearer")).findFirst();
if (!optionalHeaderAccessToken.isPresent()) {
response.headers().add(HttpHeaders.WWW_AUTHENTICATE, BEARER_TYPE+" realm=gravitee.io - No OAuth authorization header was supplied");
policyChain.failWith(PolicyResult.failure(HttpStatusCode.UNAUTHORIZED_401,
"No OAuth authorization header was supplied"));
return;
}

String accessToken = extractHeaderToken(optionalHeaderAccessToken.get());

String accessToken = optionalHeaderAccessToken.get().substring(BEARER_TYPE.length()).trim();
if (accessToken.isEmpty()) {
response.headers().add(HttpHeaders.WWW_AUTHENTICATE, BEARER_TYPE+" realm=gravitee.io - No OAuth access token was supplied");
policyChain.failWith(PolicyResult.failure(HttpStatusCode.UNAUTHORIZED_401,
"No OAuth access token was supplied"));
return;
}

OAuth2Resource oauth2 = executionContext.getComponent(ResourceManager.class).getResource(
oAuth2PolicyConfiguration.getOauthResource(), OAuth2Resource.class);

oauth2.validateToken(buildOAuthRequest(oauth2.configuration(), accessToken), responseHandler(policyChain, request, response, executionContext));
}

private String extractHeaderToken(String headerAccessToken) {
return headerAccessToken.substring(BEARER_TYPE.length()).trim();
}

private OAuth2Request buildOAuthRequest(OAuth2ResourceConfiguration configuration, String accessToken) {
Map<String, Collection<String>> headers = new HashMap<>();
Map<String, List<String>> queryParams = new HashMap<>();

OAuth2Request oAuth2Request = new OAuth2Request();

oAuth2Request.setUrl(configuration.getServerURL());
oAuth2Request.setMethod(configuration.getHttpMethod());

if (configuration.isSecure()) {
headers.put(configuration.getAuthorizationHeaderName(),
Collections.singletonList(configuration.getAuthorizationScheme().trim() + " " + configuration.getAuthorizationValue()));
}

if (configuration.isTokenIsSuppliedByQueryParam()) {
queryParams.put(configuration.getTokenQueryParamName(), Collections.singletonList(accessToken));
} else if (configuration.isTokenIsSuppliedByHttpHeader()) {
headers.put(configuration.getTokenHeaderName(), Collections.singletonList(accessToken));
}

oAuth2Request.setHeaders(headers);
oAuth2Request.setQueryParams(queryParams);
// Set access_token in context
executionContext.setAttribute(CONTEXT_ATTRIBUTE_OAUTH_ACCESS_TOKEN, accessToken);

return oAuth2Request;
// Validate access token
oauth2.validate(accessToken, handleResponse(policyChain, request, response, executionContext));
}

private AsyncHandler responseHandler(PolicyChain policyChain, Request request, Response response, ExecutionContext executionContext) {
return new AsyncCompletionHandler<Void>() {
private Handler<OAuth2Response> handleResponse(PolicyChain policyChain, Request request, Response response, ExecutionContext executionContext) {
return oauth2response -> {
if (oauth2response.isSuccess()) {
if (oAuth2PolicyConfiguration.isExtractPayload()) {
executionContext.setAttribute(CONTEXT_ATTRIBUTE_OAUTH_PAYLOAD, oauth2response.getPayload());
}
policyChain.doNext(request, response);
} else {
response.headers().add(HttpHeaders.WWW_AUTHENTICATE, BEARER_TYPE+" realm=gravitee.io " + oauth2response.getPayload());

@Override
public Void onCompleted(org.asynchttpclient.Response clientResponse) throws Exception {
if (clientResponse.getStatusCode() == HttpStatusCode.OK_200) {
executionContext.setAttribute(OAUTH2_ACCESS_TOKEN, clientResponse.getResponseBody());
policyChain.doNext(request, response);
} else {
response.headers().add(HttpHeaders.WWW_AUTHENTICATE, BEARER_TYPE+" realm=gravitee.io " + clientResponse.getResponseBody());
if (oauth2response.getThrowable() == null) {
policyChain.failWith(PolicyResult.failure(HttpStatusCode.UNAUTHORIZED_401,
clientResponse.getResponseBody()));
oauth2response.getPayload()));
} else {
policyChain.failWith(PolicyResult.failure(HttpStatusCode.SERVICE_UNAVAILABLE_503,
"Service Unavailable"));
}
return null;
}

@Override
public void onThrowable(Throwable t) {
LOGGER.warn("Unexpected error while invoking remote OAuth2 server", t);
response.headers().add(HttpHeaders.WWW_AUTHENTICATE, BEARER_TYPE + " realm=gravitee.io " + t.getMessage());
policyChain.failWith(PolicyResult.failure(HttpStatusCode.SERVICE_UNAVAILABLE_503,
"Service Unavailable"));
}
};
}
Expand Down
Expand Up @@ -18,18 +18,28 @@
import io.gravitee.policy.api.PolicyConfiguration;

/**
* @author David BRASSELY (david at gravitee.io)
* @author David BRASSELY (david.brassely at graviteesource.com)
* @author GraviteeSource Team
*/
public class OAuth2PolicyConfiguration implements PolicyConfiguration {

private String oauthResource;

private boolean extractPayload = false;

public String getOauthResource() {
return oauthResource;
}

public void setOauthResource(String oauthResource) {
this.oauthResource = oauthResource;
}

public boolean isExtractPayload() {
return extractPayload;
}

public void setExtractPayload(boolean extractPayload) {
this.extractPayload = extractPayload;
}
}
Expand Up @@ -6,6 +6,12 @@
"title": "OAuth2 resource",
"description": "OAuth2 resource used to validate token.",
"type" : "string"
},
"extractPayload" : {
"title": "Extract OAuth2 payload",
"description": "Push the token endpoint payload into the 'oauth.payload' context attribute.",
"type" : "boolean",
"default": false
}
},
"required": [
Expand Down

0 comments on commit eb1f604

Please sign in to comment.