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    public static final String EVERYONE_NAME = "EVERYONE";
051
052    /**
053     * The security principal for every request.
054     */
055    public static final Principal EVERYONE = new Principal() {
056
057        @Override
058        public String getName() {
059            return ServletContainerAuthenticationProvider.EVERYONE_NAME;
060        }
061
062        @Override
063        public String toString() {
064            return getName();
065        }
066
067    };
068
069    /**
070     * User role for Fedora's admin users
071     */
072    public static final String FEDORA_ADMIN_ROLE = "fedoraAdmin";
073
074    /**
075     * User role for Fedora's ordinary users
076     */
077    public static final String FEDORA_USER_ROLE = "fedoraUser";
078
079    private static final Logger LOGGER = LoggerFactory
080            .getLogger(ServletContainerAuthenticationProvider.class);
081
082    private Set<PrincipalProvider> principalProviders = Collections.emptySet();
083
084    private FedoraAuthorizationDelegate fad;
085
086    /**
087     * Provides the singleton bean to ModeShape via reflection based on class
088     * name.
089     *
090     * @return a AuthenticationProvider
091     */
092    public static synchronized AuthenticationProvider getInstance() {
093        if (instance != null) {
094            return instance;
095        }
096        instance = new ServletContainerAuthenticationProvider();
097        LOGGER.warn("Security is MINIMAL, no Policy Enforcement Point configured.");
098        return instance;
099    }
100
101    /**
102     * @return the principalProviders
103     */
104    public Set<PrincipalProvider> getPrincipalProviders() {
105        return principalProviders;
106    }
107
108    /**
109     * @param principalProviders the principalProviders to set
110     */
111    public void setPrincipalProviders(
112            final Set<PrincipalProvider> principalProviders) {
113        this.principalProviders = principalProviders;
114    }
115
116    /**
117     * Authenticate the user that is using the supplied credentials.
118     * <p>
119     * If the credentials given establish that the authenticated user has the
120     * fedoraAdmin role, construct an ExecutionContext with
121     * FedoraAdminSecurityContext as the SecurityContext. Otherwise, construct
122     * an ExecutionContext with FedoraUserSecurityContext as the
123     * SecurityContext.
124     * </p>
125     * <p>
126     * If the authenticated user does not have the fedoraAdmin role, session
127     * attributes will be assigned in the sessionAttributes map:
128     * </p>
129     * <ul>
130     * <li>FEDORA_SERVLET_REQUEST will be assigned the ServletRequest instance
131     * associated with credentials.</li>
132     * <li>FEDORA_ALL_PRINCIPALS will be assigned the union of all principals
133     * obtained from configured PrincipalProvider instances plus the
134     * authenticated user's principal; FEDORA_ALL_PRINCIPALS will be assigned
135     * the singleton set containing the EVERYONE principal otherwise.</li>
136     * </ul>
137     */
138    @Override
139    public ExecutionContext authenticate(final Credentials credentials,
140            final String repositoryName, final String workspaceName,
141            final ExecutionContext repositoryContext,
142            final Map<String, Object> sessionAttributes) {
143        LOGGER.debug("Trying to authenticate: {}; FAD: {}", credentials, fad);
144
145        if (!(credentials instanceof ServletCredentials)) {
146            return null;
147        }
148
149        final HttpServletRequest servletRequest =
150                ((ServletCredentials) credentials).getRequest();
151        final Principal userPrincipal = servletRequest.getUserPrincipal();
152
153        if (userPrincipal != null &&
154                servletRequest.isUserInRole(FEDORA_ADMIN_ROLE)) {
155            return repositoryContext.with(new FedoraAdminSecurityContext(
156                    userPrincipal.getName()));
157        }
158
159        if (userPrincipal != null) {
160
161            sessionAttributes.put(
162                    FedoraAuthorizationDelegate.FEDORA_SERVLET_REQUEST,
163                    servletRequest);
164
165            sessionAttributes.put(
166                    FedoraAuthorizationDelegate.FEDORA_USER_PRINCIPAL,
167                    userPrincipal);
168
169            final Set<Principal> principals = collectPrincipals(credentials);
170            principals.add(userPrincipal);
171            principals.add(EVERYONE);
172
173            sessionAttributes.put(
174                    FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS,
175                    principals);
176
177        } else {
178
179            sessionAttributes
180                    .put(FedoraAuthorizationDelegate.FEDORA_USER_PRINCIPAL,
181                            EVERYONE);
182
183            sessionAttributes.put(
184                    FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS,
185                    Collections.singleton(EVERYONE));
186
187        }
188
189        return repositoryContext.with(new FedoraUserSecurityContext(
190                userPrincipal, fad));
191    }
192
193    /**
194     * @return the authorization delegate
195     */
196    public FedoraAuthorizationDelegate getFad() {
197        return fad;
198    }
199
200    /**
201     * @param fad the authorization delegate to set
202     */
203    public void setFad(final FedoraAuthorizationDelegate fad) {
204        this.fad = fad;
205    }
206
207    private Set<Principal> collectPrincipals(final Credentials credentials) {
208        final Set<Principal> principals = new HashSet<>();
209
210        // TODO add exception handling for principal providers
211        for (final PrincipalProvider p : this.getPrincipalProviders()) {
212            final Set<Principal> ps = p.getPrincipals(credentials);
213
214            if (ps != null) {
215                principals.addAll(p.getPrincipals(credentials));
216            }
217        }
218
219        return principals;
220    }
221}