/*
* 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 static org.fcrepo.auth.common.FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS;
import static org.fcrepo.auth.common.ServletContainerAuthenticationProvider.FEDORA_ADMIN_ROLE;
import static org.fcrepo.auth.common.ServletContainerAuthenticationProvider.FEDORA_USER_ROLE;
import static org.fcrepo.auth.common.ServletContainerAuthenticationProvider.getInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import java.security.Principal;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.jcr.Credentials;
import javax.jcr.Session;
import javax.servlet.http.HttpServletRequest;
import com.google.common.collect.Sets;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.modeshape.jcr.ExecutionContext;
import org.modeshape.jcr.api.ServletCredentials;
import org.modeshape.jcr.security.AdvancedAuthorizationProvider;
import org.modeshape.jcr.security.AuthenticationProvider;
import org.modeshape.jcr.value.Path;
/**
* @author bbpennel
* @since Feb 12, 2014
*/
public class ServletContainerAuthenticationProviderTest {
@Mock
private ServletCredentials creds;
@Mock
private FedoraAuthorizationDelegate fad;
@Mock
private Principal principal;
@Mock
private Principal everyone;
@Mock
private HttpServletRequest request;
@Mock
private DelegateHeaderPrincipalProvider delegateProvider;
@Mock
private Principal delegatePrincipal;
private Map<String, Object> sessionAttributes;
@Captor
private ArgumentCaptor<Set<Principal>> principalCaptor;
private ExecutionContext context;
@Before
public void setUp() {
initMocks(this);
when(request.getUserPrincipal()).thenReturn(principal);
when(fad.getEveryonePrincipal()).thenReturn(everyone);
when(everyone.getName()).thenReturn("EVERYONE");
when(creds.getRequest()).thenReturn(request);
context = new ExecutionContext();
sessionAttributes = new HashMap<>();
}
@Test
public void testGetInstance() {
final AuthenticationProvider provider = getInstance();
assertNotNull(provider);
final AuthenticationProvider secondProvider = getInstance();
assertTrue(
"Provider instance retrieved on second call should be the same object",
provider == secondProvider);
}
@Test
public void testInvalidCredentialsObject() {
final AuthenticationProvider provider = getInstance();
ExecutionContext result =
provider.authenticate(null, "repo", "workspace", context,
sessionAttributes);
assertNull(result);
result =
provider.authenticate(mock(Credentials.class), "repo",
"workspace", context, null);
assertNull(result);
}
@Test
public void testAuthenticateFedoraAdmin() {
final AuthenticationProvider provider =
ServletContainerAuthenticationProvider.getInstance();
when(request.isUserInRole(FEDORA_ADMIN_ROLE)).thenReturn(true);
when(principal.getName()).thenReturn("adminName");
final ExecutionContext result =
provider.authenticate(creds, "repo", "workspace", context,
sessionAttributes);
assertEquals(
"Resulting security context must exist and belong to adminName",
"adminName", result.getSecurityContext().getUserName());
}
@Test
public void testDelegatedAuthenticationForAdmins() {
final ServletContainerAuthenticationProvider provider = (ServletContainerAuthenticationProvider) getInstance();
provider.setPrincipalProviders(Collections.singleton(delegateProvider));
when(request.isUserInRole(FEDORA_ADMIN_ROLE)).thenReturn(true);
when(principal.getName()).thenReturn("adminName");
when(delegateProvider.getDelegate(creds)).thenReturn(delegatePrincipal);
when(delegatePrincipal.getName()).thenReturn("delegatedUserName");
final ExecutionContext result =
provider.authenticate(creds, "repo", "workspace", context,
sessionAttributes);
assertEquals(
"Resulting security context must exist and belong to delegatedUserName",
"delegatedUserName", result.getSecurityContext().getUserName());
}
@Test
public void testNoDelegatedAuthenticationForUsers() {
final ServletContainerAuthenticationProvider provider = (ServletContainerAuthenticationProvider) getInstance();
provider.setPrincipalProviders(Collections.singleton(delegateProvider));
when(request.isUserInRole(FEDORA_ADMIN_ROLE)).thenReturn(false);
when(principal.getName()).thenReturn("userName");
when(delegateProvider.getDelegate(creds)).thenReturn(delegatePrincipal);
when(delegatePrincipal.getName()).thenReturn("delegatedUserName");
// delegateProvider being HeaderProvider returns the header content in getPrincipals regardless of logged user
when(delegateProvider.getPrincipals(creds)).thenReturn(Sets.newHashSet(delegatePrincipal));
final ExecutionContext result =
provider.authenticate(creds, "repo", "workspace", context,
sessionAttributes);
assertEquals(
"Resulting security context must exist and belong to userName (delegated user is ignored)",
"userName", result.getSecurityContext().getUserName());
// check that the delegated header has not leaked into sessionAttributes.FEDORA_ALL_PRINCIPALS
assertTrue("There must be a set of principals in sessionAttributes",
sessionAttributes.containsKey(FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS));
@SuppressWarnings("unchecked")
final Set<Principal> principals =
(Set<Principal>) sessionAttributes.get(FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS);
assertFalse(
"The sessionAttributes must not contain delegatedUserName " +
"(delegated user must not leak to FEDORA_ALL_PRINCIPALS)",
principals.stream().map(Principal::getName).anyMatch("delegatedUserName"::equals));
}
@Test
public void testAuthenticateUserRole() {
final ServletContainerAuthenticationProvider provider =
(ServletContainerAuthenticationProvider) ServletContainerAuthenticationProvider
.getInstance();
provider.setFad(fad);
when(request.isUserInRole(FEDORA_USER_ROLE)).thenReturn(true);
when(principal.getName()).thenReturn("userName");
final ExecutionContext result =
provider.authenticate(creds, "repo", "workspace", context,
sessionAttributes);
assertNotNull(result);
final AdvancedAuthorizationProvider authProvider =
(AdvancedAuthorizationProvider) result.getSecurityContext();
final AdvancedAuthorizationProvider.Context authContext =
mock(AdvancedAuthorizationProvider.Context.class);
// Perform hasPermission on auth provider to capture result principals
// from authenticate
authProvider.hasPermission(authContext, mock(Path.class),
new String[] {"read"});
verify(fad).hasPermission(any(Session.class), any(Path.class), any(String[].class));
@SuppressWarnings("unchecked")
final Set<Principal> resultPrincipals = (Set<Principal>) sessionAttributes.get(FEDORA_ALL_PRINCIPALS);
assertEquals(2, resultPrincipals.size());
assertTrue("EVERYONE principal must be present", resultPrincipals
.contains(fad.getEveryonePrincipal()));
assertTrue("User principal must be present", resultPrincipals
.contains(principal));
assertEquals(
"Resulting security context must exist and belong to userName",
"userName", result.getSecurityContext().getUserName());
assertEquals(fad, provider.getFad());
}
@Test
public void testAuthenticateWithPrincipalFactory() {
final ServletContainerAuthenticationProvider provider =
(ServletContainerAuthenticationProvider) ServletContainerAuthenticationProvider.getInstance();
provider.setFad(fad);
when(request.isUserInRole(FEDORA_USER_ROLE)).thenReturn(true);
final Set<Principal> groupPrincipals = new HashSet<>();
final Principal groupPrincipal = mock(Principal.class);
groupPrincipals.add(groupPrincipal);
final HttpHeaderPrincipalProvider principalProvider = mock(HttpHeaderPrincipalProvider.class);
when(principalProvider.getPrincipals(any(Credentials.class))).thenReturn(groupPrincipals);
final Set<PrincipalProvider> providers = new HashSet<>();
providers.add(principalProvider);
provider.setPrincipalProviders(providers);
final ExecutionContext result = provider.authenticate(creds, "repo", "workspace", context, sessionAttributes);
final AdvancedAuthorizationProvider authProvider = (AdvancedAuthorizationProvider) result.getSecurityContext();
final AdvancedAuthorizationProvider.Context authContext = mock(AdvancedAuthorizationProvider.Context.class);
// Perform hasPermission on auth provider to capture result principals
// from authenticate
authProvider.hasPermission(authContext, mock(Path.class), new String[] {"read"});
verify(fad).hasPermission(any(Session.class), any(Path.class), any(String[].class));
@SuppressWarnings("unchecked")
final Set<Principal> resultPrincipals = (Set<Principal>) sessionAttributes.get(FEDORA_ALL_PRINCIPALS);
assertEquals(3, resultPrincipals.size());
assertTrue("EVERYONE principal must be present", resultPrincipals
.contains(fad.getEveryonePrincipal()));
assertTrue("User principal must be present", resultPrincipals.contains(principal));
assertTrue("Group Principal from factory must be present", resultPrincipals.contains(groupPrincipal));
// getInstance returns a static provider so zero out internal set
provider.setPrincipalProviders(new HashSet<PrincipalProvider>());
}
@Test
public void testAuthenticateNoUserPrincipal() {
final ServletContainerAuthenticationProvider provider =
(ServletContainerAuthenticationProvider) ServletContainerAuthenticationProvider.getInstance();
provider.setFad(fad);
when(request.getUserPrincipal()).thenReturn(null);
evaluateDefaultAuthenticateCase(provider, 1);
}
@Test
public void testAuthenticateUnrecognizedRole() {
final ServletContainerAuthenticationProvider provider =
(ServletContainerAuthenticationProvider) ServletContainerAuthenticationProvider.getInstance();
provider.setFad(fad);
when(request.isUserInRole("unknownRole")).thenReturn(true);
evaluateDefaultAuthenticateCase(provider, 2);
}
private void evaluateDefaultAuthenticateCase(final ServletContainerAuthenticationProvider provider,
final int expected) {
final ExecutionContext result = provider.authenticate(creds, "repo", "workspace", context, sessionAttributes);
assertNotNull(result);
final AdvancedAuthorizationProvider authProvider = (AdvancedAuthorizationProvider) result.getSecurityContext();
final AdvancedAuthorizationProvider.Context authContext = mock(AdvancedAuthorizationProvider.Context.class);
authProvider.hasPermission(authContext, mock(Path.class), new String[] {"read"});
verify(fad).hasPermission(any(Session.class), any(Path.class), any(String[].class));
@SuppressWarnings("unchecked")
final Set<Principal> resultPrincipals = (Set<Principal>) sessionAttributes.get(FEDORA_ALL_PRINCIPALS);
assertEquals(expected, resultPrincipals.size());
assertTrue("EVERYONE principal must be present", resultPrincipals.contains(fad.getEveryonePrincipal()));
final Iterator<Principal> iterator = resultPrincipals.iterator();
boolean succeeds = false;
while (iterator.hasNext()) {
final String name = iterator.next().getName();
if (name != null && name.equals(fad.getEveryonePrincipal().getName())) {
succeeds = true;
}
}
assertTrue("Expected to find: " + fad.getEveryonePrincipal().getName(), succeeds);
}
}