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-2015 ForgeRock AS.
015*/
016
017package org.forgerock.jaspi.modules.openid.resolvers;
018
019import static org.forgerock.caf.authentication.framework.AuthenticationFramework.LOG;
020
021import java.io.IOException;
022import java.net.MalformedURLException;
023import java.net.URL;
024import java.util.Map;
025
026import org.forgerock.jaspi.modules.openid.exceptions.FailedToLoadJWKException;
027import org.forgerock.jaspi.modules.openid.helpers.SimpleHTTPClient;
028import org.forgerock.json.JsonValue;
029import org.forgerock.json.jose.utils.Utils;
030
031/**
032 * This class creates JWKOpenIdResolverImpl's from a supplied
033 * well-known open id configuration url.
034 */
035public class WellKnownOpenIdConfigurationFactory {
036
037    private final static String ISSUER = "issuer";
038    private final static String JWKS_URI = "jwks_uri";
039
040    private final SimpleHTTPClient simpleHTTPClient;
041
042    /**
043     * Generates a factory that will use the given timeouts when attempting to
044     * read the data form a remote location.
045     *
046     * @param readTimeout set the read timeout of HTTP operations in this factory
047     * @param connTimeout set the connection timeout of HTTP operations in this factory
048     */
049    public WellKnownOpenIdConfigurationFactory(final int readTimeout, final int connTimeout) {
050        this.simpleHTTPClient = new SimpleHTTPClient(readTimeout, connTimeout);
051    }
052
053    /**
054     * For tests.
055     * @param simpleHTTPClient A passed-in simple client implementation
056     */
057    WellKnownOpenIdConfigurationFactory(SimpleHTTPClient simpleHTTPClient) {
058        this.simpleHTTPClient = simpleHTTPClient;
059    }
060
061    /**
062     * Returns a JWKOpenIdResolverImpl representing the contents of the supplied URL.
063     *
064     * @param configUrl URL from which to read the JWKSet
065     * @return a usable JWKOpenIdResolverIMpl
066     * @throws FailedToLoadJWKException if there are issues reading or parsing the configUrl
067     */
068    public JWKOpenIdResolverImpl build(final URL configUrl) throws FailedToLoadJWKException {
069        final String configurationContents;
070
071        try {
072            configurationContents = simpleHTTPClient.get(configUrl);
073        } catch (IOException e) {
074            LOG.debug("Unable to load the Configuration at  " + configUrl + " over HTTP", e);
075            throw new FailedToLoadJWKException("Unable to load the Configuration over HTTP", e);
076        }
077
078        Map<String, Object> parsedJson =  Utils.parseJson(configurationContents);
079        final JsonValue configuration = new JsonValue(parsedJson);
080
081        final String issuer = configuration.get(ISSUER).asString();
082        final String jwkUri = configuration.get(JWKS_URI).asString();
083
084        if (issuer == null || issuer.isEmpty()) {
085            LOG.debug("Invalid configuration - must include an issuer key");
086            throw new FailedToLoadJWKException("Invalid configuration - must include an issuer key");
087        }
088
089        if (jwkUri == null || jwkUri.isEmpty()) {
090            LOG.debug("No JWK URI in the supplied configuration");
091            throw new FailedToLoadJWKException("No JWK URI in the supplied configuration");
092        }
093
094        URL jwkUrl;
095
096        try {
097            jwkUrl = new URL(jwkUri);
098        } catch (MalformedURLException e) {
099            LOG.debug("Invalid URL supplied to generate JWKs");
100            throw new FailedToLoadJWKException("Invalid URL supplied to generate JWKs", e);
101        }
102
103        return new JWKOpenIdResolverImpl(issuer, jwkUrl, simpleHTTPClient);
104    }
105
106}