From d6dcb9ddf21599d3b45a2de6abf4590a865f5e5e Mon Sep 17 00:00:00 2001 From: Paul Millar Date: Wed, 31 Jul 2024 23:39:59 +0200 Subject: [PATCH] Add initial set of tests for AlisePlugin --- .../org/dcache/util/PrincipalSetMaker.java | 27 +++ .../dcache/gplazma/alise/AlisePluginTest.java | 210 +++++++++++++++++- 2 files changed, 234 insertions(+), 3 deletions(-) diff --git a/modules/common/src/main/java/org/dcache/util/PrincipalSetMaker.java b/modules/common/src/main/java/org/dcache/util/PrincipalSetMaker.java index 80d4f7bb07e..0282911a65c 100644 --- a/modules/common/src/main/java/org/dcache/util/PrincipalSetMaker.java +++ b/modules/common/src/main/java/org/dcache/util/PrincipalSetMaker.java @@ -1,6 +1,7 @@ package org.dcache.util; import com.google.common.collect.Sets; +import java.net.URI; import java.security.Principal; import java.util.Collections; import java.util.Set; @@ -9,8 +10,10 @@ import org.dcache.auth.EmailAddressPrincipal; import org.dcache.auth.ExemptFromNamespaceChecks; import org.dcache.auth.FQANPrincipal; +import org.dcache.auth.FullNamePrincipal; import org.dcache.auth.GidPrincipal; import org.dcache.auth.GroupNamePrincipal; +import org.dcache.auth.OAuthProviderPrincipal; import org.dcache.auth.OidcSubjectPrincipal; import org.dcache.auth.UidPrincipal; import org.dcache.auth.UserNamePrincipal; @@ -52,12 +55,25 @@ public PrincipalSetMaker withUid(int uid) { * Add a username Principal to the set. * * @param name the username to add + * */ public PrincipalSetMaker withUsername(String username) { _principals.add(new UserNamePrincipal(username)); return this; } + /** + * Add a Full Name Principal to the set. + * + * @param name the full name of the user. + * + */ + public PrincipalSetMaker withFullname(String name) { + _principals.add(new FullNamePrincipal(name)); + return this; + } + + /** * Add a primary groupname Principal to the set. * @@ -152,6 +168,17 @@ public PrincipalSetMaker withOidc(String sub, String op) { return this; } + /** + * Add an OAuth2 Provider (OP) to the set. + * + * @param alias the name/alias of this OAuth2 Provider. + * @param uri the URI identity of the OAuth2 Provider. + */ + public PrincipalSetMaker withOauth2Provider(String alias, URI uri) { + _principals.add(new OAuthProviderPrincipal(alias, uri)); + return this; + } + /** * Add an Email principal to the set. * diff --git a/modules/gplazma2-alise/src/test/java/org/dcache/gplazma/alise/AlisePluginTest.java b/modules/gplazma2-alise/src/test/java/org/dcache/gplazma/alise/AlisePluginTest.java index 43b86243ddd..6a6b1238ea0 100644 --- a/modules/gplazma2-alise/src/test/java/org/dcache/gplazma/alise/AlisePluginTest.java +++ b/modules/gplazma2-alise/src/test/java/org/dcache/gplazma/alise/AlisePluginTest.java @@ -18,16 +18,220 @@ */ package org.dcache.gplazma.alise; +import java.net.URI; +import java.security.Principal; +import java.util.Collection; +import java.util.Properties; +import java.util.Set; +import org.dcache.auth.UserNamePrincipal; +import org.dcache.gplazma.AuthenticationException; +import org.dcache.util.PrincipalSetMaker; +import org.dcache.util.Result; import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.Before; +import org.mockito.ArgumentCaptor; +import java.util.HashSet; +import java.util.List; +import org.dcache.auth.FullNamePrincipal; + +import static com.google.common.base.Preconditions.checkState; +import static java.util.Objects.requireNonNull; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class AlisePluginTest { - public AlisePluginTest() { + private AlisePlugin plugin; + private Set principals; + private LookupAgent agent; + private List lookupCalls; + + @Before + public void setup() { + plugin = null; + principals = null; + agent = null; + lookupCalls = null; + } + + @Test(expected=AuthenticationException.class) + public void shouldFailIfNoSubClaim() throws Exception { + given(anAlisePlugin() + .withProprety("gplazma.alise.issuers", "") + .withAgent(aLookupAgent().thatFailsTestIfCalled())); + + whenPluginMapWith(principals().withDn("/O=ACME/CN=Wile E Coyote")); + } + + @Test(expected=AuthenticationException.class) + public void shouldFailIfMissingOpPrincipal() throws Exception { + given(anAlisePlugin() + .withProprety("gplazma.alise.issuers", "") + .withAgent(aLookupAgent().thatFailsTestIfCalled())); + + whenPluginMapWith(principals().withOidc("paul", "EXAMPLE-OP")); + } + + @Test(expected=AuthenticationException.class) + public void shouldFailIfOpNotListed() throws Exception { + given(anAlisePlugin() + .withProprety("gplazma.alise.issuers", "EXAMPLE-OP") + .withAgent(aLookupAgent().thatFailsTestIfCalled())); + + whenPluginMapWith(principals().withOidc("paul", "SOME-OTHER-OP")); } @Test - public void testSomeMethod() { + public void shouldAcceptSuccessfulLookupWithUsername() throws Exception { + given(anAlisePlugin() + .withProprety("gplazma.alise.issuers", "") + .withAgent(aLookupAgent().thatReturnsSuccess(principals().withUsername("paul")))); + + whenPluginMapWith(principals() + .withOidc("paul", "EXAMPLE-OP") + .withOauth2Provider("EXAMPLE-OP", URI.create("https://example.org/"))); + + assertThat(lookupCalls, contains(new Identity(URI.create("https://example.org/"), "paul"))); + + assertThat(principals, hasItem(new UserNamePrincipal("paul"))); + } + + @Test(expected=AuthenticationException.class) + public void shouldFailIfLookupFails() throws Exception { + given(anAlisePlugin() + .withProprety("gplazma.alise.issuers", "") + .withAgent(aLookupAgent().thatReturnsFailure("fnord"))); + + try { + whenPluginMapWith(principals() + .withOidc("paul", "EXAMPLE-OP") + .withOauth2Provider("EXAMPLE-OP", URI.create("https://example.org/"))); + } catch (AuthenticationException e) { + assertThat(e.getMessage(), containsString("fnord")); + throw e; + } + } + + @Test + public void shouldAcceptSuccessfulLookupWithUsernameAndFullname() throws Exception { + given(anAlisePlugin() + .withProprety("gplazma.alise.issuers", "") + .withAgent(aLookupAgent().thatReturnsSuccess(principals() + .withUsername("paul") + .withFullname("Paul Millar")))); + + whenPluginMapWith(principals() + .withOidc("paul", "EXAMPLE-OP") + .withOauth2Provider("EXAMPLE-OP", URI.create("https://example.org/"))); + + assertThat(lookupCalls, contains(new Identity(URI.create("https://example.org/"), "paul"))); + assertThat(principals, hasItems(new UserNamePrincipal("paul"), + new FullNamePrincipal("Paul Millar"))); } + @Test + public void shouldFilterSuppressedOP() throws Exception { + given(anAlisePlugin() + .withProprety("gplazma.alise.issuers", "EXAMPLE-OP") + .withAgent(aLookupAgent().thatReturnsSuccess(principals() + .withUsername("paul")))); + + whenPluginMapWith(principals() + .withOidc("paul", "EXAMPLE-OP") + .withOauth2Provider("EXAMPLE-OP", URI.create("https://example.org/")) + .withOidc("paul", "SOME-OTHER-OP") + .withOauth2Provider("SOME-OTHER-OP", URI.create("https://some-other.example.com/"))); + + assertThat(lookupCalls, contains(new Identity(URI.create("https://example.org/"), "paul"))); + assertThat(principals, hasItems(new UserNamePrincipal("paul"))); + } + + private AlisePluginBuilder anAlisePlugin() { + return new AlisePluginBuilder(); + } + + private LookupAgentBuilder aLookupAgent() { + return new LookupAgentBuilder(); + } + + private PrincipalSetMaker principals() { + return new PrincipalSetMaker(); + } + + private void given(AlisePluginBuilder builder) { + plugin = builder.build(); + } + + private void whenPluginMapWith(PrincipalSetMaker maker) throws AuthenticationException { + // PrincipalSetMaker creates an unmodifiable Set; but, for gPlazma, we need a Set that can be modified. + principals = new HashSet<>(maker.build()); + + plugin.map(principals); + + ArgumentCaptor identityCaptor = ArgumentCaptor.forClass(Identity.class); + verify(agent).lookup(identityCaptor.capture()); + lookupCalls = identityCaptor.getAllValues(); + } + + private class AlisePluginBuilder { + private final Properties config = new Properties(); + + private AlisePluginBuilder withProprety(String key, String value) { + config.setProperty(key, value); + return this; + } + + private AlisePluginBuilder withAgent(LookupAgentBuilder builder) { + agent = builder.build(); + return this; + } + + private AlisePlugin build() { + checkState(agent != null); + return new AlisePlugin(config, agent); + } + } + + private static class LookupAgentBuilder { + private final LookupAgent agent = mock(LookupAgent.class); + private Result,String> result; + private boolean failTest; + + private LookupAgentBuilder thatReturnsFailure(String failure) { + checkState(result == null); + checkState(!failTest); + result = Result.failure(requireNonNull(failure)); + return this; + } + + private LookupAgentBuilder thatReturnsSuccess(PrincipalSetMaker maker) { + checkState(result == null); + checkState(!failTest); + result = Result.success(maker.build()); + return this; + } + + private LookupAgentBuilder thatFailsTestIfCalled() { + checkState(result == null); + failTest = true; + return this; + } + + private LookupAgent build() { + checkState(result != null || failTest); + if (failTest) { + when(agent.lookup(any())).thenThrow(new AssertionError("Unexpected call to LookupAgent#lookup")); + } else { + when(agent.lookup(any())).thenReturn(result); + } + return agent; + } + } }