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 static org.fcrepo.auth.common.FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS; 021import static org.fcrepo.auth.common.ServletContainerAuthenticationProvider.FEDORA_ADMIN_ROLE; 022import static org.fcrepo.auth.common.ServletContainerAuthenticationProvider.FEDORA_USER_ROLE; 023import static org.fcrepo.auth.common.ServletContainerAuthenticationProvider.getInstance; 024import static org.junit.Assert.assertEquals; 025import static org.junit.Assert.assertFalse; 026import static org.junit.Assert.assertNotNull; 027import static org.junit.Assert.assertNull; 028import static org.junit.Assert.assertTrue; 029import static org.mockito.Matchers.any; 030import static org.mockito.Mockito.mock; 031import static org.mockito.Mockito.verify; 032import static org.mockito.Mockito.when; 033import static org.mockito.MockitoAnnotations.initMocks; 034 035import java.security.Principal; 036import java.util.Collections; 037import java.util.HashMap; 038import java.util.HashSet; 039import java.util.Iterator; 040import java.util.Map; 041import java.util.Set; 042 043import javax.jcr.Credentials; 044import javax.jcr.Session; 045import javax.servlet.http.HttpServletRequest; 046 047import com.google.common.collect.Sets; 048import org.junit.Before; 049import org.junit.Test; 050import org.mockito.ArgumentCaptor; 051import org.mockito.Captor; 052import org.mockito.Mock; 053import org.modeshape.jcr.ExecutionContext; 054import org.modeshape.jcr.api.ServletCredentials; 055import org.modeshape.jcr.security.AdvancedAuthorizationProvider; 056import org.modeshape.jcr.security.AuthenticationProvider; 057import org.modeshape.jcr.value.Path; 058 059/** 060 * @author bbpennel 061 * @since Feb 12, 2014 062 */ 063public class ServletContainerAuthenticationProviderTest { 064 065 @Mock 066 private ServletCredentials creds; 067 068 @Mock 069 private FedoraAuthorizationDelegate fad; 070 071 @Mock 072 private Principal principal; 073 074 @Mock 075 private Principal everyone; 076 077 @Mock 078 private HttpServletRequest request; 079 080 @Mock 081 private DelegateHeaderPrincipalProvider delegateProvider; 082 083 @Mock 084 private Principal delegatePrincipal; 085 086 private Map<String, Object> sessionAttributes; 087 088 @Captor 089 private ArgumentCaptor<Set<Principal>> principalCaptor; 090 091 private ExecutionContext context; 092 093 @Before 094 public void setUp() { 095 initMocks(this); 096 when(request.getUserPrincipal()).thenReturn(principal); 097 when(fad.getEveryonePrincipal()).thenReturn(everyone); 098 when(everyone.getName()).thenReturn("EVERYONE"); 099 when(creds.getRequest()).thenReturn(request); 100 context = new ExecutionContext(); 101 sessionAttributes = new HashMap<>(); 102 } 103 104 @Test 105 public void testGetInstance() { 106 final AuthenticationProvider provider = getInstance(); 107 108 assertNotNull(provider); 109 110 final AuthenticationProvider secondProvider = getInstance(); 111 112 assertTrue( 113 "Provider instance retrieved on second call should be the same object", 114 provider == secondProvider); 115 } 116 117 @Test 118 public void testInvalidCredentialsObject() { 119 final AuthenticationProvider provider = getInstance(); 120 121 ExecutionContext result = 122 provider.authenticate(null, "repo", "workspace", context, 123 sessionAttributes); 124 assertNull(result); 125 126 result = 127 provider.authenticate(mock(Credentials.class), "repo", 128 "workspace", context, null); 129 assertNull(result); 130 } 131 132 @Test 133 public void testAuthenticateFedoraAdmin() { 134 final AuthenticationProvider provider = 135 ServletContainerAuthenticationProvider.getInstance(); 136 137 when(request.isUserInRole(FEDORA_ADMIN_ROLE)).thenReturn(true); 138 139 when(principal.getName()).thenReturn("adminName"); 140 141 final ExecutionContext result = 142 provider.authenticate(creds, "repo", "workspace", context, 143 sessionAttributes); 144 145 assertEquals( 146 "Resulting security context must exist and belong to adminName", 147 "adminName", result.getSecurityContext().getUserName()); 148 } 149 150 @Test 151 public void testDelegatedAuthenticationForAdmins() { 152 final ServletContainerAuthenticationProvider provider = (ServletContainerAuthenticationProvider) getInstance(); 153 provider.setPrincipalProviders(Collections.singleton(delegateProvider)); 154 155 when(request.isUserInRole(FEDORA_ADMIN_ROLE)).thenReturn(true); 156 157 when(principal.getName()).thenReturn("adminName"); 158 159 when(delegateProvider.getDelegate(creds)).thenReturn(delegatePrincipal); 160 when(delegatePrincipal.getName()).thenReturn("delegatedUserName"); 161 162 final ExecutionContext result = 163 provider.authenticate(creds, "repo", "workspace", context, 164 sessionAttributes); 165 166 assertEquals( 167 "Resulting security context must exist and belong to delegatedUserName", 168 "delegatedUserName", result.getSecurityContext().getUserName()); 169 } 170 171 @Test 172 public void testNoDelegatedAuthenticationForUsers() { 173 final ServletContainerAuthenticationProvider provider = (ServletContainerAuthenticationProvider) getInstance(); 174 provider.setPrincipalProviders(Collections.singleton(delegateProvider)); 175 176 when(request.isUserInRole(FEDORA_ADMIN_ROLE)).thenReturn(false); 177 178 when(principal.getName()).thenReturn("userName"); 179 180 when(delegateProvider.getDelegate(creds)).thenReturn(delegatePrincipal); 181 when(delegatePrincipal.getName()).thenReturn("delegatedUserName"); 182 183 // delegateProvider being HeaderProvider returns the header content in getPrincipals regardless of logged user 184 when(delegateProvider.getPrincipals(creds)).thenReturn(Sets.newHashSet(delegatePrincipal)); 185 186 187 final ExecutionContext result = 188 provider.authenticate(creds, "repo", "workspace", context, 189 sessionAttributes); 190 191 assertEquals( 192 "Resulting security context must exist and belong to userName (delegated user is ignored)", 193 "userName", result.getSecurityContext().getUserName()); 194 195 // check that the delegated header has not leaked into sessionAttributes.FEDORA_ALL_PRINCIPALS 196 assertTrue("There must be a set of principals in sessionAttributes", 197 sessionAttributes.containsKey(FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS)); 198 199 @SuppressWarnings("unchecked") 200 final Set<Principal> principals = 201 (Set<Principal>) sessionAttributes.get(FedoraAuthorizationDelegate.FEDORA_ALL_PRINCIPALS); 202 203 assertFalse( 204 "The sessionAttributes must not contain delegatedUserName " + 205 "(delegated user must not leak to FEDORA_ALL_PRINCIPALS)", 206 principals.stream().map(Principal::getName).anyMatch("delegatedUserName"::equals)); 207 } 208 209 @Test 210 public void testAuthenticateUserRole() { 211 final ServletContainerAuthenticationProvider provider = 212 (ServletContainerAuthenticationProvider) ServletContainerAuthenticationProvider 213 .getInstance(); 214 215 provider.setFad(fad); 216 217 when(request.isUserInRole(FEDORA_USER_ROLE)).thenReturn(true); 218 219 when(principal.getName()).thenReturn("userName"); 220 221 final ExecutionContext result = 222 provider.authenticate(creds, "repo", "workspace", context, 223 sessionAttributes); 224 225 assertNotNull(result); 226 227 final AdvancedAuthorizationProvider authProvider = 228 (AdvancedAuthorizationProvider) result.getSecurityContext(); 229 final AdvancedAuthorizationProvider.Context authContext = 230 mock(AdvancedAuthorizationProvider.Context.class); 231 232 // Perform hasPermission on auth provider to capture result principals 233 // from authenticate 234 authProvider.hasPermission(authContext, mock(Path.class), 235 new String[] {"read"}); 236 237 verify(fad).hasPermission(any(Session.class), any(Path.class), any(String[].class)); 238 239 @SuppressWarnings("unchecked") 240 final Set<Principal> resultPrincipals = (Set<Principal>) sessionAttributes.get(FEDORA_ALL_PRINCIPALS); 241 242 assertEquals(2, resultPrincipals.size()); 243 assertTrue("EVERYONE principal must be present", resultPrincipals 244 .contains(fad.getEveryonePrincipal())); 245 assertTrue("User principal must be present", resultPrincipals 246 .contains(principal)); 247 248 assertEquals( 249 "Resulting security context must exist and belong to userName", 250 "userName", result.getSecurityContext().getUserName()); 251 252 assertEquals(fad, provider.getFad()); 253 } 254 255 @Test 256 public void testAuthenticateWithPrincipalFactory() { 257 final ServletContainerAuthenticationProvider provider = 258 (ServletContainerAuthenticationProvider) ServletContainerAuthenticationProvider.getInstance(); 259 260 provider.setFad(fad); 261 262 when(request.isUserInRole(FEDORA_USER_ROLE)).thenReturn(true); 263 264 final Set<Principal> groupPrincipals = new HashSet<>(); 265 final Principal groupPrincipal = mock(Principal.class); 266 groupPrincipals.add(groupPrincipal); 267 final HttpHeaderPrincipalProvider principalProvider = mock(HttpHeaderPrincipalProvider.class); 268 when(principalProvider.getPrincipals(any(Credentials.class))).thenReturn(groupPrincipals); 269 270 final Set<PrincipalProvider> providers = new HashSet<>(); 271 providers.add(principalProvider); 272 273 provider.setPrincipalProviders(providers); 274 275 final ExecutionContext result = provider.authenticate(creds, "repo", "workspace", context, sessionAttributes); 276 277 final AdvancedAuthorizationProvider authProvider = (AdvancedAuthorizationProvider) result.getSecurityContext(); 278 final AdvancedAuthorizationProvider.Context authContext = mock(AdvancedAuthorizationProvider.Context.class); 279 280 // Perform hasPermission on auth provider to capture result principals 281 // from authenticate 282 authProvider.hasPermission(authContext, mock(Path.class), new String[] {"read"}); 283 284 verify(fad).hasPermission(any(Session.class), any(Path.class), any(String[].class)); 285 286 @SuppressWarnings("unchecked") 287 final Set<Principal> resultPrincipals = (Set<Principal>) sessionAttributes.get(FEDORA_ALL_PRINCIPALS); 288 289 assertEquals(3, resultPrincipals.size()); 290 assertTrue("EVERYONE principal must be present", resultPrincipals 291 .contains(fad.getEveryonePrincipal())); 292 assertTrue("User principal must be present", resultPrincipals.contains(principal)); 293 assertTrue("Group Principal from factory must be present", resultPrincipals.contains(groupPrincipal)); 294 295 // getInstance returns a static provider so zero out internal set 296 provider.setPrincipalProviders(new HashSet<PrincipalProvider>()); 297 } 298 299 @Test 300 public void testAuthenticateNoUserPrincipal() { 301 302 final ServletContainerAuthenticationProvider provider = 303 (ServletContainerAuthenticationProvider) ServletContainerAuthenticationProvider.getInstance(); 304 305 provider.setFad(fad); 306 307 when(request.getUserPrincipal()).thenReturn(null); 308 309 evaluateDefaultAuthenticateCase(provider, 1); 310 } 311 312 @Test 313 public void testAuthenticateUnrecognizedRole() { 314 315 final ServletContainerAuthenticationProvider provider = 316 (ServletContainerAuthenticationProvider) ServletContainerAuthenticationProvider.getInstance(); 317 318 provider.setFad(fad); 319 320 when(request.isUserInRole("unknownRole")).thenReturn(true); 321 322 evaluateDefaultAuthenticateCase(provider, 2); 323 } 324 325 private void evaluateDefaultAuthenticateCase(final ServletContainerAuthenticationProvider provider, 326 final int expected) { 327 final ExecutionContext result = provider.authenticate(creds, "repo", "workspace", context, sessionAttributes); 328 329 assertNotNull(result); 330 331 final AdvancedAuthorizationProvider authProvider = (AdvancedAuthorizationProvider) result.getSecurityContext(); 332 final AdvancedAuthorizationProvider.Context authContext = mock(AdvancedAuthorizationProvider.Context.class); 333 334 authProvider.hasPermission(authContext, mock(Path.class), new String[] {"read"}); 335 336 verify(fad).hasPermission(any(Session.class), any(Path.class), any(String[].class)); 337 338 @SuppressWarnings("unchecked") 339 final Set<Principal> resultPrincipals = (Set<Principal>) sessionAttributes.get(FEDORA_ALL_PRINCIPALS); 340 341 assertEquals(expected, resultPrincipals.size()); 342 assertTrue("EVERYONE principal must be present", resultPrincipals.contains(fad.getEveryonePrincipal())); 343 344 final Iterator<Principal> iterator = resultPrincipals.iterator(); 345 boolean succeeds = false; 346 347 while (iterator.hasNext()) { 348 final String name = iterator.next().getName(); 349 350 if (name != null && name.equals(fad.getEveryonePrincipal().getName())) { 351 succeeds = true; 352 } 353 } 354 355 assertTrue("Expected to find: " + fad.getEveryonePrincipal().getName(), succeeds); 356 } 357}