001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.auth.common;
019
020import java.security.Principal;
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.Set;
025
026import javax.jcr.Credentials;
027import javax.servlet.http.HttpServletRequest;
028
029import org.modeshape.jcr.ExecutionContext;
030import org.modeshape.jcr.api.ServletCredentials;
031import org.modeshape.jcr.security.AuthenticationProvider;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * Authenticates ModeShape logins where JAX-RS credentials are supplied. Capable
037 * of authenticating whether or not container has performed user authentication.
038 * This is a singleton with an injected policy enforcement point. The singleton
039 * pattern allows ModeShape to obtain this instance via classname configuration.
040 *
041 * @author Gregory Jansen
042 */
043public final class ServletContainerAuthenticationProvider implements
044        AuthenticationProvider {
045
046    private static ServletContainerAuthenticationProvider instance = null;
047
048    private ServletContainerAuthenticationProvider() {
049        instance = this;
050    }
051
052    /**
053     * User role for Fedora's admin users
054     */
055    public static final String FEDORA_ADMIN_ROLE = "fedoraAdmin";
056
057    /**
058     * User role for Fedora's ordinary users
059     */
060    public static final String FEDORA_USER_ROLE = "fedoraUser";
061
062    private static final Logger LOGGER = LoggerFactory
063            .getLogger(ServletContainerAuthenticationProvider.class);
064
065    private Set<PrincipalProvider> principalProviders = Collections.emptySet();
066
067    private FedoraAuthorizationDelegate fad;
068
069    /**
070     * Provides the singleton bean to ModeShape via reflection based on class
071     * name.
072     *
073     * @return a AuthenticationProvider
074     */
075    public static synchronized AuthenticationProvider getInstance() {
076        if (instance != null) {
077            return instance;
078        }
079        instance = new ServletContainerAuthenticationProvider();
080        LOGGER.warn("Security is MINIMAL, no Policy Enforcement Point configured.");
081        return instance;
082    }
083
084    /**
085     * @return the principalProviders
086     */
087    public Set<PrincipalProvider> getPrincipalProviders() {
088        return principalProviders;
089    }
090
091    /**
092     * @param principalProviders the principalProviders to set
093     */
094    public void setPrincipalProviders(
095            final Set<PrincipalProvider> principalProviders) {
096        this.principalProviders = principalProviders;
097    }
098
099    /**
100     * Authenticate the user that is using the supplied credentials.
101     * <p>
102     * If the credentials given establish that the authenticated user has the fedoraAdmin role, construct an
103     * ExecutionContext with FedoraAdminSecurityContext as the SecurityContext. Otherwise, construct an
104     * ExecutionContext with FedoraUserSecurityContext as the SecurityContext.
105     * </p>
106     * <p>
107     * If the authenticated user does not have the fedoraAdmin role, session attributes will be assigned in the
108     * sessionAttributes map:
109     * </p>
110     * <ul>
111     * <li>FEDORA_SERVLET_REQUEST will be assigned the ServletRequest instance associated with credentials.</li>
112     * <li>FEDORA_ALL_PRINCIPALS will be assigned the union of all principals obtained from configured
113     * PrincipalProvider instances plus the authenticated user's principal; FEDORA_ALL_PRINCIPALS will be assigned the
114     * singleton set containing the fad.getEveryonePrincipal() principal otherwise.</li>
115     * </ul>
116     */
117    @Override
118    public ExecutionContext authenticate(final Credentials credentials,
119            final String repositoryName, final String workspaceName,
120            final ExecutionContext repositoryContext,
121            final Map<String, Object> sessionAttributes) {
122        LOGGER.debug("Trying to authenticate: {}; FAD: {}", credentials, fad);
123
124        if (!(credentials instanceof ServletCredentials)) {
125            return null;
126        }
127
128        final HttpServletRequest servletRequest =
129                ((ServletCredentials) credentials).getRequest();
130        Principal userPrincipal = servletRequest.getUserPrincipal();
131
132        if (userPrincipal != null && servletRequest.isUserInRole(FEDORA_ADMIN_ROLE)) {
133            // check if delegation is configured
134            final Principal delegatedPrincipal = getDelegatedPrincipal(credentials);
135            if (delegatedPrincipal != null) {
136                // replace the userPrincipal with the delegated principal
137                // then fall through to the normal user processing
138                userPrincipal = delegatedPrincipal;
139                LOGGER.info("Admin user is delegating to {}", userPrincipal);
140
141            } else {
142                // delegation is configured, but there is no delegated user set in the header of this request
143                LOGGER.debug("Returning admin user");
144                return repositoryContext.with(new FedoraAdminSecurityContext(userPrincipal.getName()));
145            }
146        }
147
148        if (userPrincipal != null) {
149            LOGGER.debug("Found user-principal: {}.", userPrincipal.getName());
150
151            sessionAttributes.put(
152                    FedoraAuthorizationDelegate.FEDORA_SERVLET_REQUEST,
153                    servletRequest);
154
155            sessionAttributes.put(
156                    FedoraAuthorizationDelegate.FEDORA_USER_PRINCIPAL,
157                    userPrincipal);
158
159            final Set<Principal> principals = collectPrincipals(credentials);
160            principals.add(userPrincipal);
161            principals.add(fad.getEveryonePrincipal());
162
163            sessionAttributes.put(
164                    FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS,
165                    principals);
166
167            LOGGER.debug("All principals: {}", principals);
168
169        } else {
170            LOGGER.debug("No user-principal found.");
171
172            sessionAttributes.put(FedoraAuthorizationDelegate.FEDORA_USER_PRINCIPAL,
173                    fad.getEveryonePrincipal());
174
175            sessionAttributes.put(
176                    FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS,
177                    Collections.singleton(fad.getEveryonePrincipal()));
178
179        }
180
181        return repositoryContext.with(new FedoraUserSecurityContext(
182                userPrincipal, fad));
183    }
184
185    private Principal getDelegatedPrincipal(final Credentials credentials) {
186        for (final PrincipalProvider provider : this.getPrincipalProviders()) {
187            if (provider instanceof DelegateHeaderPrincipalProvider) {
188                return ((DelegateHeaderPrincipalProvider) provider).getDelegate(credentials);
189            }
190        }
191        return null;
192    }
193
194    /**
195     * @return the authorization delegate
196     */
197    public FedoraAuthorizationDelegate getFad() {
198        return fad;
199    }
200
201    /**
202     * @param fad the authorization delegate to set
203     */
204    public void setFad(final FedoraAuthorizationDelegate fad) {
205        this.fad = fad;
206    }
207
208    private Set<Principal> collectPrincipals(final Credentials credentials) {
209        final Set<Principal> principals = new HashSet<>();
210
211        // TODO add exception handling for principal providers
212        for (final PrincipalProvider p : this.getPrincipalProviders()) {
213            // if the provider is DelegateHeader, it is either already processed (if logged user has fedora admin role)
214            // or should be ignored completely (the user was not in admin role, so on-behalf-of header must be ignored)
215            if (!(p instanceof DelegateHeaderPrincipalProvider)) {
216                final Set<Principal> ps = p.getPrincipals(credentials);
217
218                if (ps != null) {
219                    principals.addAll(p.getPrincipals(credentials));
220                }
221            }
222        }
223
224        return principals;
225    }
226}