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