001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2014 ForgeRock AS.
015 */
016package org.forgerock.openig.filter.oauth2.client;
017
018import static org.forgerock.openig.filter.oauth2.client.OAuth2Utils.*;
019import static org.forgerock.openig.util.Json.*;
020
021import java.net.URI;
022import java.nio.charset.Charset;
023import java.util.Collections;
024import java.util.List;
025
026import org.forgerock.json.fluent.JsonValue;
027import org.forgerock.openig.el.Expression;
028import org.forgerock.openig.handler.HandlerException;
029import org.forgerock.openig.http.Exchange;
030import org.forgerock.openig.http.Form;
031import org.forgerock.openig.http.Request;
032import org.forgerock.util.encode.Base64;
033
034/**
035 * An OAuth 2.0 authorization server or OpenID Connect Provider.
036 */
037public class OAuth2Provider {
038    private final String name;
039    private Expression authorizeEndpoint;
040    private Expression clientId;
041    private Expression clientSecret;
042    private List<Expression> scopes;
043    private Expression tokenEndpoint;
044    private Expression userInfoEndpoint;
045    private final boolean tokenEndpointUseBasicAuth = false; // Do we want to make this configurable?
046
047    /**
048     * Creates a new provider having the specified name. The returned provider
049     * should be configured using the setters before being used.
050     *
051     * @param name
052     *            The provider name.
053     */
054    public OAuth2Provider(final String name) {
055        this.name = name;
056    }
057
058    /**
059     * Sets the expression which will be used for obtaining the authorization
060     * server's authorize end-point. This configuration parameter is required.
061     *
062     * @param endpoint
063     *            The expression which will be used for obtaining the
064     *            authorization server's authorize end-point.
065     * @return This provider.
066     */
067    public OAuth2Provider setAuthorizeEndpoint(final Expression endpoint) {
068        this.authorizeEndpoint = endpoint;
069        return this;
070    }
071
072    /**
073     * Sets the expression which will be used for obtaining the OAuth 2 client
074     * ID. This configuration parameter is required.
075     *
076     * @param clientId
077     *            The expression which will be used for obtaining the OAuth 2
078     *            client ID.
079     * @return This provider.
080     */
081    public OAuth2Provider setClientId(final Expression clientId) {
082        this.clientId = clientId;
083        return this;
084    }
085
086    /**
087     * Sets the expression which will be used for obtaining the OAuth 2 client
088     * secret. This configuration parameter is required.
089     *
090     * @param clientSecret
091     *            The expression which will be used for obtaining the OAuth 2
092     *            client secret.
093     * @return This provider.
094     */
095    public OAuth2Provider setClientSecret(final Expression clientSecret) {
096        this.clientSecret = clientSecret;
097        return this;
098    }
099
100    /**
101     * Sets the expressions which will be used for obtaining the OAuth 2 scopes
102     * for this provider. This configuration parameter is optional and defaults
103     * to the set of scopes defined for the client filter.
104     *
105     * @param scopes
106     *            The expressions which will be used for obtaining the OAuth 2
107     *            scopes.
108     * @return This provider.
109     */
110    public OAuth2Provider setScopes(final List<Expression> scopes) {
111        this.scopes = scopes != null ? scopes : Collections.<Expression> emptyList();
112        return this;
113    }
114
115    /**
116     * Sets the expression which will be used for obtaining the authorization
117     * server's access token end-point. This configuration parameter is
118     * required.
119     *
120     * @param endpoint
121     *            The expression which will be used for obtaining the
122     *            authorization server's access token end-point.
123     * @return This provider.
124     */
125    public OAuth2Provider setTokenEndpoint(final Expression endpoint) {
126        this.tokenEndpoint = endpoint;
127        return this;
128    }
129
130    /**
131     * Sets the expression which will be used for obtaining the authorization
132     * server's OpenID Connect user info end-point. This configuration parameter
133     * is optional.
134     *
135     * @param endpoint
136     *            The expression which will be used for obtaining the
137     *            authorization server's OpenID Connect user info end-point.
138     * @return This provider.
139     */
140    public OAuth2Provider setUserInfoEndpoint(final Expression endpoint) {
141        this.userInfoEndpoint = endpoint;
142        return this;
143    }
144
145    /**
146     * Configures this provider using the specified OpenID Connect Well Known
147     * configuration.
148     *
149     * @param wellKnown
150     *            The OpenID Connect provider's Well Known configuration.
151     * @return This provider.
152     */
153    public OAuth2Provider setWellKnownConfiguration(final JsonValue wellKnown) {
154        setAuthorizeEndpoint(asExpression(wellKnown.get("authorization_endpoint").required()));
155        setTokenEndpoint(asExpression(wellKnown.get("token_endpoint").required()));
156        setUserInfoEndpoint(asExpression(wellKnown.get("userinfo_endpoint")));
157        return this;
158    }
159
160    Request createRequestForAccessToken(final Exchange exchange, final String code,
161            final String callbackUri) throws HandlerException {
162        final Request request = new Request();
163        request.setMethod("POST");
164        request.setUri(buildUri(exchange, tokenEndpoint));
165        final Form form = new Form();
166        form.add("grant_type", "authorization_code");
167        form.add("redirect_uri", callbackUri);
168        form.add("code", code);
169        addClientIdAndSecret(exchange, request, form);
170        form.toRequestEntity(request);
171        return request;
172    }
173
174    Request createRequestForTokenRefresh(final Exchange exchange, final OAuth2Session session)
175            throws HandlerException {
176        final Request request = new Request();
177        request.setMethod("POST");
178        request.setUri(buildUri(exchange, tokenEndpoint));
179        final Form form = new Form();
180        form.add("grant_type", "refresh_token");
181        form.add("refresh_token", session.getRefreshToken());
182        addClientIdAndSecret(exchange, request, form);
183        form.toRequestEntity(request);
184        return request;
185    }
186
187    Request createRequestForUserInfo(final Exchange exchange, final String accessToken)
188            throws HandlerException {
189        final Request request = new Request();
190        request.setMethod("GET");
191        request.setUri(buildUri(exchange, userInfoEndpoint));
192        request.getHeaders().add("Authorization", "Bearer " + accessToken);
193        return request;
194    }
195
196    URI getAuthorizeEndpoint(final Exchange exchange) throws HandlerException {
197        return buildUri(exchange, authorizeEndpoint);
198    }
199
200    String getClientId(final Exchange exchange) throws HandlerException {
201        final String result = clientId.eval(exchange, String.class);
202        if (result == null) {
203            throw new HandlerException("Unable to determine the clientId");
204        }
205        return result;
206    }
207
208    String getName() {
209        return name;
210    }
211
212    List<String> getScopes(final Exchange exchange) throws HandlerException {
213        return OAuth2Utils.getScopes(exchange, scopes);
214    }
215
216    boolean hasUserInfoEndpoint() {
217        return userInfoEndpoint != null;
218    }
219
220    private void addClientIdAndSecret(final Exchange exchange, final Request request,
221            final Form form) throws HandlerException {
222        final String user = getClientId(exchange);
223        final String pass = getClientSecret(exchange);
224        if (!tokenEndpointUseBasicAuth) {
225            form.add("client_id", user);
226            form.add("client_secret", pass);
227        } else {
228            final String userpass =
229                    Base64.encode((user + ":" + pass).getBytes(Charset.defaultCharset()));
230            request.getHeaders().add("Authorization", "Basic " + userpass);
231        }
232    }
233
234    private String getClientSecret(final Exchange exchange) throws HandlerException {
235        final String result = clientSecret.eval(exchange, String.class);
236        if (result == null) {
237            throw new HandlerException("Unable to determine the clientSecret");
238        }
239        return result;
240    }
241}