| | |
| | | |
| | | 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; |
| | |
| | | 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 |
| | |
| | | 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. |
| | | } |
| | | } |