diff --git a/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java new file mode 100644 index 0000000000..26ee4e46a1 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java @@ -0,0 +1,203 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; + +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.subject.SimplePrincipalCollection; + +import sonia.scm.group.Group; +import sonia.scm.group.GroupException; +import sonia.scm.group.GroupManager; +import sonia.scm.group.GroupNames; +import sonia.scm.user.User; +import sonia.scm.user.UserException; +import sonia.scm.user.UserManager; +import sonia.scm.web.security.AdministrationContext; +import sonia.scm.web.security.PrivilegedAction; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +import java.util.Collection; + +/** + * Helper class for syncing realms. The class should simplify the creation of + * realms, which are syncing authenticated users with the local database. + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +public final class SyncingRealmHelper +{ + + /** + * Constructs a new SyncingRealmHelper. + * + * + * @param ctx administration context + * @param userManager user manager + * @param groupManager group manager + */ + @Inject + public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, + GroupManager groupManager) + { + this.ctx = ctx; + this.userManager = userManager; + this.groupManager = groupManager; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Create {@link AuthenticationInfo} from user and groups. + * + * + * @param realm name of the realm + * @param user authenticated user + * @param groups groups of the authenticated user + * + * @return authentication info + */ + public AuthenticationInfo createAuthenticationInfo(String realm, User user, + String... groups) + { + return createAuthenticationInfo(realm, user, ImmutableList.copyOf(groups)); + } + + /** + * Create {@link AuthenticationInfo} from user and groups. + * + * + * @param realm name of the realm + * @param user authenticated user + * @param groups groups of the authenticated user + * + * @return authentication info + */ + public AuthenticationInfo createAuthenticationInfo(String realm, User user, + Collection groups) + { + SimplePrincipalCollection collection = new SimplePrincipalCollection(); + + collection.add(user.getId(), realm); + collection.add(user, realm); + collection.add(new GroupNames(groups), realm); + + return new SimpleAuthenticationInfo(collection, user.getPassword()); + } + + /** + * Stores the group in local database of scm-manager. + * + * + * @param group group to store + */ + public void store(final Group group) + { + ctx.runAsAdmin(new PrivilegedAction() + { + + @Override + public void run() + { + try + { + if (groupManager.get(group.getId()) != null) + { + groupManager.modify(group); + } + else + { + groupManager.create(group); + } + } + catch (GroupException | IOException ex) + { + throw new AuthenticationException("could not store group", ex); + } + } + }); + } + + /** + * Stores the user in local database of scm-manager. + * + * + * @param user user to store + */ + public void store(final User user) + { + ctx.runAsAdmin(new PrivilegedAction() + { + + @Override + public void run() + { + try + { + if (userManager.contains(user.getName())) + { + userManager.modify(user); + } + else + { + userManager.create(user); + } + } + catch (UserException | IOException ex) + { + throw new AuthenticationException("could not store user", ex); + } + } + }); + } + + //~--- fields --------------------------------------------------------------- + + /** administration context */ + private final AdministrationContext ctx; + + /** group manager */ + private final GroupManager groupManager; + + /** user manager */ + private final UserManager userManager; +} diff --git a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java new file mode 100644 index 0000000000..05a8ce009a --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java @@ -0,0 +1,242 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Throwables; + +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import sonia.scm.group.Group; +import sonia.scm.group.GroupException; +import sonia.scm.group.GroupManager; +import sonia.scm.group.GroupNames; +import sonia.scm.user.User; +import sonia.scm.user.UserException; +import sonia.scm.user.UserManager; +import sonia.scm.web.security.AdministrationContext; +import sonia.scm.web.security.PrivilegedAction; + +import static org.hamcrest.Matchers.*; + +import static org.junit.Assert.*; + +import static org.mockito.Mockito.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class SyncingRealmHelperTest +{ + + /** + * Method description + * + */ + @Test + public void testCreateAuthenticationInfo() + { + User user = new User("tricia"); + AuthenticationInfo authInfo = helper.createAuthenticationInfo("unit-test", + user, "heartOfGold"); + + assertNotNull(authInfo); + assertEquals("tricia", authInfo.getPrincipals().getPrimaryPrincipal()); + assertThat(authInfo.getPrincipals().getRealmNames(), hasItem("unit-test")); + assertEquals(user, authInfo.getPrincipals().oneByType(User.class)); + + GroupNames groups = authInfo.getPrincipals().oneByType(GroupNames.class); + + assertThat(groups, hasItem("heartOfGold")); + } + + /** + * Method description + * + * @throws GroupException + * @throws IOException + */ + @Test + public void testStoreGroupCreate() throws GroupException, IOException + { + Group group = new Group("unit-test", "heartOfGold"); + + helper.store(group); + verify(groupManager, times(1)).create(group); + } + + /** + * Method description + * + * + * @throws GroupException + * @throws IOException + */ + @Test(expected = AuthenticationException.class) + public void testStoreGroupFailure() throws GroupException, IOException + { + Group group = new Group("unit-test", "heartOfGold"); + + doThrow(GroupException.class).when(groupManager).create(group); + helper.store(group); + } + + /** + * Method description + * + * @throws GroupException + * @throws IOException + */ + @Test + public void testStoreGroupModify() throws GroupException, IOException + { + Group group = new Group("unit-test", "heartOfGold"); + + when(groupManager.get("heartOfGold")).thenReturn(group); + + helper.store(group); + verify(groupManager, times(1)).modify(group); + } + + /** + * Method description + * + * @throws UserException + * @throws IOException + */ + @Test + public void testStoreUserCreate() throws UserException, IOException + { + User user = new User("tricia"); + + helper.store(user); + verify(userManager, times(1)).create(user); + } + + /** + * Method description + * + * + * @throws IOException + * @throws UserException + */ + @Test(expected = AuthenticationException.class) + public void testStoreUserFailure() throws UserException, IOException + { + User user = new User("tricia"); + + doThrow(UserException.class).when(userManager).create(user); + helper.store(user); + } + + /** + * Method description + * + * @throws UserException + * @throws IOException + */ + @Test + public void testStoreUserModify() throws UserException, IOException + { + when(userManager.contains("tricia")).thenReturn(Boolean.TRUE); + + User user = new User("tricia"); + + helper.store(user); + verify(userManager, times(1)).modify(user); + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + */ + @Before + public void setUp() + { + AdministrationContext ctx = new AdministrationContext() + { + + @Override + public void runAsAdmin(PrivilegedAction action) + { + action.run(); + } + + @Override + public void runAsAdmin(Class actionClass) + { + try + { + runAsAdmin(actionClass.newInstance()); + } + catch (IllegalAccessException | InstantiationException ex) + { + throw Throwables.propagate(ex); + } + } + }; + + helper = new SyncingRealmHelper(ctx, userManager, groupManager); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Mock + private GroupManager groupManager; + + /** Field description */ + private SyncingRealmHelper helper; + + /** Field description */ + @Mock + private UserManager userManager; +}