/*
* Licensed to DuraSpace under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* DuraSpace licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fcrepo.auth.common;
import java.security.Principal;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.jcr.Credentials;
import javax.servlet.http.HttpServletRequest;
import org.modeshape.jcr.ExecutionContext;
import org.modeshape.jcr.api.ServletCredentials;
import org.modeshape.jcr.security.AuthenticationProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Authenticates ModeShape logins where JAX-RS credentials are supplied. Capable
* of authenticating whether or not container has performed user authentication.
* This is a singleton with an injected policy enforcement point. The singleton
* pattern allows ModeShape to obtain this instance via classname configuration.
*
* @author Gregory Jansen
*/
public final class ServletContainerAuthenticationProvider implements
AuthenticationProvider {
private static ServletContainerAuthenticationProvider instance = null;
private ServletContainerAuthenticationProvider() {
instance = this;
}
/**
* User role for Fedora's admin users
*/
public static final String FEDORA_ADMIN_ROLE = "fedoraAdmin";
/**
* User role for Fedora's ordinary users
*/
public static final String FEDORA_USER_ROLE = "fedoraUser";
private static final Logger LOGGER = LoggerFactory
.getLogger(ServletContainerAuthenticationProvider.class);
private Set<PrincipalProvider> principalProviders = Collections.emptySet();
private FedoraAuthorizationDelegate fad;
/**
* Provides the singleton bean to ModeShape via reflection based on class
* name.
*
* @return a AuthenticationProvider
*/
public static synchronized AuthenticationProvider getInstance() {
if (instance != null) {
return instance;
}
instance = new ServletContainerAuthenticationProvider();
LOGGER.warn("Security is MINIMAL, no Policy Enforcement Point configured.");
return instance;
}
/**
* @return the principalProviders
*/
public Set<PrincipalProvider> getPrincipalProviders() {
return principalProviders;
}
/**
* @param principalProviders the principalProviders to set
*/
public void setPrincipalProviders(
final Set<PrincipalProvider> principalProviders) {
this.principalProviders = principalProviders;
}
/**
* Authenticate the user that is using the supplied credentials.
* <p>
* If the credentials given establish that the authenticated user has the fedoraAdmin role, construct an
* ExecutionContext with FedoraAdminSecurityContext as the SecurityContext. Otherwise, construct an
* ExecutionContext with FedoraUserSecurityContext as the SecurityContext.
* </p>
* <p>
* If the authenticated user does not have the fedoraAdmin role, session attributes will be assigned in the
* sessionAttributes map:
* </p>
* <ul>
* <li>FEDORA_SERVLET_REQUEST will be assigned the ServletRequest instance associated with credentials.</li>
* <li>FEDORA_ALL_PRINCIPALS will be assigned the union of all principals obtained from configured
* PrincipalProvider instances plus the authenticated user's principal; FEDORA_ALL_PRINCIPALS will be assigned the
* singleton set containing the fad.getEveryonePrincipal() principal otherwise.</li>
* </ul>
*/
@Override
public ExecutionContext authenticate(final Credentials credentials,
final String repositoryName, final String workspaceName,
final ExecutionContext repositoryContext,
final Map<String, Object> sessionAttributes) {
LOGGER.debug("Trying to authenticate: {}; FAD: {}", credentials, fad);
if (!(credentials instanceof ServletCredentials)) {
return null;
}
final HttpServletRequest servletRequest =
((ServletCredentials) credentials).getRequest();
Principal userPrincipal = servletRequest.getUserPrincipal();
if (userPrincipal != null && servletRequest.isUserInRole(FEDORA_ADMIN_ROLE)) {
// check if delegation is configured
final Principal delegatedPrincipal = getDelegatedPrincipal(credentials);
if (delegatedPrincipal != null) {
// replace the userPrincipal with the delegated principal
// then fall through to the normal user processing
userPrincipal = delegatedPrincipal;
LOGGER.info("Admin user is delegating to {}", userPrincipal);
} else {
// delegation is configured, but there is no delegated user set in the header of this request
LOGGER.debug("Returning admin user");
return repositoryContext.with(new FedoraAdminSecurityContext(userPrincipal.getName()));
}
}
if (userPrincipal != null) {
LOGGER.debug("Found user-principal: {}.", userPrincipal.getName());
sessionAttributes.put(
FedoraAuthorizationDelegate.FEDORA_SERVLET_REQUEST,
servletRequest);
sessionAttributes.put(
FedoraAuthorizationDelegate.FEDORA_USER_PRINCIPAL,
userPrincipal);
final Set<Principal> principals = collectPrincipals(credentials);
principals.add(userPrincipal);
principals.add(fad.getEveryonePrincipal());
sessionAttributes.put(
FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS,
principals);
LOGGER.debug("All principals: {}", principals);
} else {
LOGGER.debug("No user-principal found.");
sessionAttributes.put(FedoraAuthorizationDelegate.FEDORA_USER_PRINCIPAL,
fad.getEveryonePrincipal());
sessionAttributes.put(
FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS,
Collections.singleton(fad.getEveryonePrincipal()));
}
return repositoryContext.with(new FedoraUserSecurityContext(
userPrincipal, fad));
}
private Principal getDelegatedPrincipal(final Credentials credentials) {
for (final PrincipalProvider provider : this.getPrincipalProviders()) {
if (provider instanceof DelegateHeaderPrincipalProvider) {
return ((DelegateHeaderPrincipalProvider) provider).getDelegate(credentials);
}
}
return null;
}
/**
* @return the authorization delegate
*/
public FedoraAuthorizationDelegate getFad() {
return fad;
}
/**
* @param fad the authorization delegate to set
*/
public void setFad(final FedoraAuthorizationDelegate fad) {
this.fad = fad;
}
private Set<Principal> collectPrincipals(final Credentials credentials) {
final Set<Principal> principals = new HashSet<>();
// TODO add exception handling for principal providers
for (final PrincipalProvider p : this.getPrincipalProviders()) {
// if the provider is DelegateHeader, it is either already processed (if logged user has fedora admin role)
// or should be ignored completely (the user was not in admin role, so on-behalf-of header must be ignored)
if (!(p instanceof DelegateHeaderPrincipalProvider)) {
final Set<Principal> ps = p.getPrincipals(credentials);
if (ps != null) {
principals.addAll(p.getPrincipals(credentials));
}
}
}
return principals;
}
}