mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Gaetan Boismal
18.27.2016 c8585baebc9fc35ed12a3321acf47730c967b5d3
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Authorizations.java
@@ -17,12 +17,27 @@
import static org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.asConditionalFilter;
import static org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.newConditionalFilter;
import static org.forgerock.util.promise.Promises.newResultPromise;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.forgerock.authz.modules.oauth2.AccessTokenInfo;
import org.forgerock.authz.modules.oauth2.AccessTokenResolver;
import org.forgerock.authz.modules.oauth2.OAuth2Context;
import org.forgerock.authz.modules.oauth2.ResourceAccess;
import org.forgerock.authz.modules.oauth2.ResourceServerFilter;
import org.forgerock.http.Filter;
import org.forgerock.http.Handler;
import org.forgerock.http.filter.Filters;
import org.forgerock.http.protocol.Headers;
import org.forgerock.http.protocol.Request;
import org.forgerock.http.protocol.Response;
import org.forgerock.http.protocol.ResponseException;
import org.forgerock.http.protocol.Status;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
@@ -33,15 +48,15 @@
import org.forgerock.services.context.SecurityContext;
import org.forgerock.util.Function;
import org.forgerock.util.Pair;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.NeverThrowsException;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.time.TimeService;
/**
 * Factory methods to create {@link Filter} performing authentication and authorizations.
 */
/** Factory methods to create {@link Filter} performing authentication and authorizations. */
public final class Authorizations {
    private Authorizations() {
    }
    private static final String OAUTH2_AUTHORIZATION_HEADER = "Authorization";
    /**
     * Creates a new {@link Filter} in charge of injecting an {@link AuthenticatedConnectionContext}. This
@@ -113,4 +128,155 @@
    public static Filter newProxyAuthorizationFilter(ConnectionFactory connectionFactory) {
        return new ProxiedAuthV2Filter(connectionFactory);
    }
    /**
     * Creates a new {@link AccessTokenResolver} as defined in the RFC-7662.
     * <p>
     * @see <a href="https://tools.ietf.org/html/rfc7662">RFC-7662</a>
     *
     * @param httpClient
     *          Http client handler used to perform the request
     * @param introspectionEndPointURL
     *          Introspect endpoint URL to use to resolve the access token.
     * @param clientAppId
     *          Client application id to use in HTTP Basic authentication header.
     * @param clientAppSecret
     *          Client application secret to use in HTTP Basic authentication header.
     * @return A new {@link AccessTokenResolver} instance.
     */
    public static AccessTokenResolver newRfc7662AccessTokenResolver(final Handler httpClient,
                                                                    final URI introspectionEndPointURL,
                                                                    final String clientAppId,
                                                                    final String clientAppSecret) {
        return new Rfc7662AccessTokenResolver(httpClient, introspectionEndPointURL, clientAppId, clientAppSecret);
    }
    /**
     * Creates a new CTS access token resolver.
     *
     * @param connectionFactory
     *          The {@link ConnectionFactory} to use to perform search against the CTS.
     * @param ctsBaseDNTemplate
     *          The base DN template to use to resolve the access token DN.
     * @return A new CTS access token resolver.
     */
    public static AccessTokenResolver newCtsAccessTokenResolver(final ConnectionFactory connectionFactory,
                                                                final String ctsBaseDNTemplate) {
        return new CtsAccessTokenResolver(connectionFactory, ctsBaseDNTemplate);
    }
    /**
     * Creates a new file access token resolver which should only be used for test purpose.
     *
     * @param tokenFolder
     *          The folder where the access token to resolve must be stored.
     * @return A new file access token resolver which should only be used for test purpose.
     */
    public static AccessTokenResolver newFileAccessTokenResolver(final String tokenFolder) {
        return new FileAccessTokenResolver(tokenFolder);
    }
    /**
     * Creates a new OAuth2 authorization filter configured with provided parameters.
     *
     * @param realm
     *          The realm to displays in error responses.
     * @param scopes
     *          Scopes that an access token must have to be access a resource.
     * @param resolver
     *          The {@link AccessTokenResolver} to use to resolve an access token.
     * @param authzIdTemplate
     *          Authorization ID template.
     * @return A new OAuth2 authorization filter configured with provided parameters.
     */
    public static Filter newOAuth2ResourceServerFilter(final String realm,
                                                                  final Set<String> scopes,
                                                                  final AccessTokenResolver resolver,
                                                                  final String authzIdTemplate) {
        return createResourceServerFilter(realm, scopes, resolver, authzIdTemplate);
    }
    /**
     * Creates a new optional OAuth2 authorization filter configured with provided parameters.
     * <p>
     * This filter will be used only if an OAuth2 Authorization header is present in the incoming request.
     *
     * @param realm
     *          The realm to displays in error responses.
     * @param scopes
     *          Scopes that an access token must have to be access a resource.
     * @param resolver
     *          The {@link AccessTokenResolver} to use to resolve an access token.
     * @param authzIdTemplate
     *          Authorization ID template.
     * @return A new OAuth2 authorization filter configured with provided parameters.
     */
    public static ConditionalFilter newConditionalOAuth2ResourceServerFilter(final String realm,
                                                                             final Set<String> scopes,
                                                                             final AccessTokenResolver resolver,
                                                                             final String authzIdTemplate) {
        return new ConditionalFilter() {
            @Override
            public Filter getFilter() {
                return createResourceServerFilter(realm, scopes, resolver, authzIdTemplate);
            }
            @Override
            public Condition getCondition() {
                return new Condition() {
                    @Override
                    public boolean canApplyFilter(final Context context, final Request request) {
                        return request.getHeaders().containsKey(OAUTH2_AUTHORIZATION_HEADER);
                    }
                };
            }
        };
    }
    private static Filter createResourceServerFilter(final String realm,
                                                     final Set<String> scopes,
                                                     final AccessTokenResolver resolver,
                                                     final String authzIdTemplate) {
        Reject.ifTrue(realm == null || realm.isEmpty(), "realm must not be empty");
        Reject.ifNull(resolver, "Access token resolver must not be null");
        Reject.ifTrue(scopes == null || scopes.isEmpty(), "scopes set can not be empty");
        Reject.ifTrue(authzIdTemplate == null || authzIdTemplate.isEmpty(), "Authz id template must not be empty");
        final ResourceAccess scopesProvider = new ResourceAccess() {
            @Override
            public Set<String> getRequiredScopes(final Context context, final Request request)
                    throws ResponseException {
                return scopes;
            }
        };
        return Filters.chainOf(new ResourceServerFilter(resolver, TimeService.SYSTEM, scopesProvider, realm),
                               createSecurityContextInjectionFilter(authzIdTemplate));
    }
    private static Filter createSecurityContextInjectionFilter(final String authzIdTemplate) {
        final AuthzIdTemplate template = new AuthzIdTemplate(authzIdTemplate);
        return new Filter() {
            @Override
            public Promise<Response, NeverThrowsException> filter(final Context context,
                                                                  final Request request,
                                                                  final Handler next) {
                final AccessTokenInfo token = context.asContext(OAuth2Context.class).getAccessToken();
                final Map<String, Object> authz = new HashMap<>(1);
                try {
                    authz.put(template.getSecurityContextID(), template.formatAsAuthzId(token.asJsonValue()));
                } catch (final IllegalArgumentException e) {
                    return newResultPromise(
                            new Response().setStatus(Status.FORBIDDEN).setCause(e).setEntity("Invalid configuration"));
                }
                final Context securityContext = new SecurityContext(context, token.getToken(), authz);
                return next.handle(securityContext, request);
            }
        };
    }
    private Authorizations() {
        // Prevent instantiation.
    }
}