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}