From a3751853f538c10ec29c3aec8c83b7ab9b306e93 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 14 Apr 2013 15:11:07 +0200 Subject: [PATCH 01/25] use subject.isPermitted for repository permission check --- .../sonia/scm/repository/PermissionUtil.java | 52 ++----------------- 1 file changed, 3 insertions(+), 49 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java b/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java index afd1d1e681..95bd89ada7 100644 --- a/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java +++ b/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java @@ -44,16 +44,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.config.ScmConfiguration; -import sonia.scm.group.GroupNames; +import sonia.scm.security.RepositoryPermission; import sonia.scm.security.Role; import sonia.scm.security.ScmSecurityException; import sonia.scm.util.AssertUtil; import sonia.scm.web.security.WebSecurityContext; -//~--- JDK imports ------------------------------------------------------------ - -import java.util.List; - /** * * @author Sebastian Sdorra @@ -179,7 +175,7 @@ public final class PermissionUtil public static boolean hasPermission(ScmConfiguration configuration, Repository repository, PermissionType pt) { - boolean result = false; + boolean result; Subject subject = SecurityUtils.getSubject(); @@ -196,16 +192,7 @@ public final class PermissionUtil } else { - List permissions = repository.getPermissions(); - - if (permissions != null) - { - GroupNames groupNames = - subject.getPrincipals().oneByType(GroupNames.class); - - result = hasPermission(permissions, username, groupNames, pt); - - } + result = subject.isPermitted(new RepositoryPermission(repository, pt)); } } else @@ -270,37 +257,4 @@ public final class PermissionUtil return permitted; } - - /** - * Method description - * - * - * @param permissions - * @param username - * @param groups - * @param pt - * - * @return - */ - private static boolean hasPermission(List permissions, - String username, GroupNames groups, PermissionType pt) - { - boolean result = false; - - for (Permission p : permissions) - { - String name = p.getName(); - - if (((name != null) && (p.getType().getValue() >= pt.getValue())) - && (name.equals(username) - || (p.isGroupPermission() && groups.contains(p.getName())))) - { - result = true; - - break; - } - } - - return result; - } } From 24ee48356207d04579fa517c09d9a3cd58139723 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 14 Apr 2013 15:13:27 +0200 Subject: [PATCH 02/25] start implementation of a new security system to allow global permissions for repositories --- .../java/sonia/scm/repository/Permission.java | 8 +- .../sonia/scm/security/GlobalPermission.java | 207 ++++++++++++++++++ .../sonia/scm/security/PermissionObject.java | 57 +++++ .../scm/security/SecurityConfiguration.java | 80 +++++++ .../SecurityConfigurationChangedEvent.java | 88 ++++++++ .../sonia/scm/security/SecuritySystem.java | 71 ++++++ .../main/java/sonia/scm/ScmServletModule.java | 3 + .../scm/security/DefaultSecuritySystem.java | 132 +++++++++++ .../java/sonia/scm/security/ScmRealm.java | 80 ++++++- .../java/sonia/scm/security/ScmRealmTest.java | 7 + 10 files changed, 725 insertions(+), 8 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/security/GlobalPermission.java create mode 100644 scm-core/src/main/java/sonia/scm/security/PermissionObject.java create mode 100644 scm-core/src/main/java/sonia/scm/security/SecurityConfiguration.java create mode 100644 scm-core/src/main/java/sonia/scm/security/SecurityConfigurationChangedEvent.java create mode 100644 scm-core/src/main/java/sonia/scm/security/SecuritySystem.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java diff --git a/scm-core/src/main/java/sonia/scm/repository/Permission.java b/scm-core/src/main/java/sonia/scm/repository/Permission.java index fe1e442f98..60c6628d61 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Permission.java +++ b/scm-core/src/main/java/sonia/scm/repository/Permission.java @@ -37,6 +37,8 @@ package sonia.scm.repository; import com.google.common.base.Objects; +import sonia.scm.security.PermissionObject; + //~--- JDK imports ------------------------------------------------------------ import java.io.Serializable; @@ -52,7 +54,7 @@ import javax.xml.bind.annotation.XmlRootElement; */ @XmlRootElement(name = "permissions") @XmlAccessorType(XmlAccessType.FIELD) -public class Permission implements Serializable +public class Permission implements PermissionObject, Serializable { /** Field description */ @@ -135,7 +137,7 @@ public class Permission implements Serializable final Permission other = (Permission) obj; return Objects.equal(name, other.name) && Objects.equal(type, other.type) - && Objects.equal(groupPermission, groupPermission); + && Objects.equal(groupPermission, groupPermission); } /** @@ -176,6 +178,7 @@ public class Permission implements Serializable * * @return name of the user or group */ + @Override public String getName() { return name; @@ -198,6 +201,7 @@ public class Permission implements Serializable * * @return true if the permision is a group permission */ + @Override public boolean isGroupPermission() { return groupPermission; diff --git a/scm-core/src/main/java/sonia/scm/security/GlobalPermission.java b/scm-core/src/main/java/sonia/scm/security/GlobalPermission.java new file mode 100644 index 0000000000..252cffacad --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/GlobalPermission.java @@ -0,0 +1,207 @@ +/** + * Copyright (c) 2010, 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.Objects; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.Serializable; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @author Sebastian Sdorra + * @since 1.31 + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "global-permission") +public final class GlobalPermission implements PermissionObject, Serializable +{ + + /** Field description */ + private static final long serialVersionUID = 4794267414178121515L; + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + */ + public GlobalPermission() {} + + /** + * Constructs ... + * + * + * @param id + * @param name + * @param permission + */ + public GlobalPermission(String name, String permission) + { + this(name, false, permission); + } + + /** + * Constructs ... + * + * + * @param id + * @param name + * @param group + * @param permission + */ + public GlobalPermission(String name, boolean groupPermission, String permission) + { + this.name = name; + this.groupPermission = groupPermission; + this.permission = permission; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param obj + * + * @return + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final GlobalPermission other = (GlobalPermission) obj; + + //J- + return Objects.equal(name, other.name) + && Objects.equal(groupPermission, other.groupPermission) + && Objects.equal(permission, other.permission); + //J+ + } + + /** + * Method description + * + * + * @return + */ + @Override + public int hashCode() + { + return Objects.hashCode(name, groupPermission, permission); + } + + /** + * Method description + * + * + * @return + */ + @Override + public String toString() + { + //J- + return Objects.toStringHelper(this) + .add("name", name) + .add("groupPermission", groupPermission) + .add("permisison", permission) + .toString(); + //J+ + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @Override + public String getName() + { + return name; + } + + /** + * Method description + * + * + * @return + */ + public String getPermission() + { + return permission; + } + + /** + * Method description + * + * + * @return + */ + @Override + public boolean isGroupPermission() + { + return groupPermission; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @XmlElement(name = "group") + private boolean groupPermission; + + /** Field description */ + private String name; + + /** Field description */ + private String permission; +} diff --git a/scm-core/src/main/java/sonia/scm/security/PermissionObject.java b/scm-core/src/main/java/sonia/scm/security/PermissionObject.java new file mode 100644 index 0000000000..58b4f065ae --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/PermissionObject.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010, 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; + +/** + * + * @author Sebastian Sdorra + * @since 1.31 + */ +public interface PermissionObject +{ + + /** + * Method description + * + * + * @return + */ + public String getName(); + + /** + * Method description + * + * + * @return + */ + public boolean isGroupPermission(); +} diff --git a/scm-core/src/main/java/sonia/scm/security/SecurityConfiguration.java b/scm-core/src/main/java/sonia/scm/security/SecurityConfiguration.java new file mode 100644 index 0000000000..aeae5ddbc7 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/SecurityConfiguration.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010, 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.Lists; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @author Sebastian Sdorra + * @since 1.31 + */ +@XmlRootElement(name = "security") +@XmlAccessorType(XmlAccessType.FIELD) +public class SecurityConfiguration +{ + + /** + * Method description + * + * + * @return + */ + public List getGlobalPermissions() + { + if (globalPermissions == null) + { + globalPermissions = Lists.newArrayList(); + } + + return globalPermissions; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @XmlElement(name = "permission") + @XmlElementWrapper(name = "global-permissions") + private List globalPermissions; +} diff --git a/scm-core/src/main/java/sonia/scm/security/SecurityConfigurationChangedEvent.java b/scm-core/src/main/java/sonia/scm/security/SecurityConfigurationChangedEvent.java new file mode 100644 index 0000000000..aa541bfaea --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/SecurityConfigurationChangedEvent.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2010, 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; + +/** + * + * @author Sebastian Sdorra + * @since 1.31 + */ +public class SecurityConfigurationChangedEvent +{ + + /** + * Constructs ... + * + * + * @param oldConfiguration + * @param newConfiguration + */ + public SecurityConfigurationChangedEvent( + SecurityConfiguration oldConfiguration, + SecurityConfiguration newConfiguration) + { + this.oldConfiguration = oldConfiguration; + this.newConfiguration = newConfiguration; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public SecurityConfiguration getNewConfiguration() + { + return newConfiguration; + } + + /** + * Method description + * + * + * @return + */ + public SecurityConfiguration getOldConfiguration() + { + return oldConfiguration; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private SecurityConfiguration newConfiguration; + + /** Field description */ + private SecurityConfiguration oldConfiguration; +} diff --git a/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java b/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java new file mode 100644 index 0000000000..cc21dca8ad --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010, 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 org.apache.shiro.subject.PrincipalCollection; + +/** + * + * @author Sebastian Sdorra + * @since 1.31 + */ +public interface SecuritySystem +{ + + /** + * Method description + * + * + * @return + */ + public SecurityConfiguration getConfiguration(); + + /** + * Method description + * + * + * @return + */ + public PrincipalCollection getSystemAccount(); + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param configuration + */ + public void setConfiguration(SecurityConfiguration configuration); +} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 35cb5192e6..dc1255eee2 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -143,6 +143,8 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; import sonia.scm.cache.GuavaCacheManager; +import sonia.scm.security.DefaultSecuritySystem; +import sonia.scm.security.SecuritySystem; /** * @@ -271,6 +273,7 @@ public class ScmServletModule extends ServletModule bind(AuthenticationManager.class, ChainAuthenticatonManager.class); bind(SecurityContext.class).to(BasicSecurityContext.class); bind(WebSecurityContext.class).to(BasicSecurityContext.class); + bind(SecuritySystem.class).to(DefaultSecuritySystem.class); bind(AdministrationContext.class, DefaultAdministrationContext.class); // bind cache diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java new file mode 100644 index 0000000000..36e8be4899 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2010, 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.inject.Inject; +import com.google.inject.Singleton; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.PrincipalCollection; + +import sonia.scm.event.ScmEventBus; +import sonia.scm.store.Store; +import sonia.scm.store.StoreFactory; + +/** + * + * @author Sebastian Sdorra + * @since 1.31 + */ +@Singleton +public class DefaultSecuritySystem implements SecuritySystem +{ + + /** Field description */ + private static final String NAME = "security"; + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param storeFactory + */ + @Inject + public DefaultSecuritySystem(StoreFactory storeFactory) + { + store = storeFactory.getStore(SecurityConfiguration.class, NAME); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @Override + public SecurityConfiguration getConfiguration() + { + SecurityConfiguration configuration = store.get(); + + if (configuration == null) + { + configuration = new SecurityConfiguration(); + } + + return configuration; + } + + /** + * Method description + * + * + * @return + */ + @Override + public PrincipalCollection getSystemAccount() + { + throw new UnsupportedOperationException("Not supported yet."); // To change body of generated methods, choose Tools | Templates. + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param newConfiguration + */ + @Override + public void setConfiguration(SecurityConfiguration newConfiguration) + { + SecurityUtils.getSubject().checkRole(Role.ADMIN); + + SecurityConfiguration oldConfiguration = store.get(); + + store.set(newConfiguration); + //J- + ScmEventBus.getInstance().post( + new SecurityConfigurationChangedEvent(oldConfiguration, newConfiguration) + ); + //J+ + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private Store store; +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java index 8863ffc926..8c2b443b94 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java @@ -74,6 +74,7 @@ import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryDAO; import sonia.scm.repository.RepositoryEvent; +import sonia.scm.repository.RepositoryManager; import sonia.scm.user.User; import sonia.scm.user.UserDAO; import sonia.scm.user.UserEvent; @@ -95,7 +96,6 @@ import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import sonia.scm.repository.RepositoryManager; /** * @@ -128,25 +128,28 @@ public class ScmRealm extends AuthorizingRealm * * * @param configuration + * @param securitySystem * @param cacheManager * @param userManager * @param groupManager * @param repositoryDAO * @param userDAO * @param authenticator + * @param manager * @param requestProvider * @param responseProvider */ @Inject - public ScmRealm(ScmConfiguration configuration, CacheManager cacheManager, + public ScmRealm(ScmConfiguration configuration, + SecuritySystem securitySystem, CacheManager cacheManager, UserManager userManager, GroupManager groupManager, RepositoryDAO repositoryDAO, UserDAO userDAO, - AuthenticationManager authenticator, - RepositoryManager manager, + AuthenticationManager authenticator, RepositoryManager manager, Provider requestProvider, Provider responseProvider) { this.configuration = configuration; + this.securitySystem = securitySystem; this.userManager = userManager; this.groupManager = groupManager; this.repositoryDAO = repositoryDAO; @@ -194,6 +197,23 @@ public class ScmRealm extends AuthorizingRealm } } + /** + * Method description + * + * + * @param event + */ + @Subscribe + public void onEvent(SecurityConfigurationChangedEvent event) + { + if (logger.isDebugEnabled()) + { + logger.debug("clear cache, because security configuration has changed"); + } + + cache.clear(); + } + /** * Method description * @@ -474,6 +494,44 @@ public class ScmRealm extends AuthorizingRealm } } + /** + * Method description + * + * + * @param user + * @param groups + * + * @return + */ + private List collectGlobalPermissions(User user, GroupNames groups) + { + if (logger.isTraceEnabled()) + { + logger.trace("collect global permissions for user {}", user.getName()); + } + + List permissions = Lists.newArrayList(); + + List globalPermissions = + securitySystem.getConfiguration().getGlobalPermissions(); + + for (GlobalPermission gp : globalPermissions) + { + if (isUserPermission(user, groups, gp)) + { + if (logger.isTraceEnabled()) + { + logger.trace("add permission {} for user {}", gp.getPermission(), + user.getName()); + } + + permissions.add(gp.getPermission()); + } + } + + return permissions; + } + /** * Method description * @@ -585,7 +643,8 @@ public class ScmRealm extends AuthorizingRealm GroupNames groups) { Set roles = Sets.newHashSet(); - List permissions = null; + List permissions; + List globalPermissions = null; roles.add(Role.USER); @@ -604,12 +663,18 @@ public class ScmRealm extends AuthorizingRealm else { permissions = collectRepositoryPermissions(user, groups); + globalPermissions = collectGlobalPermissions(user, groups); } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles); info.addObjectPermissions(permissions); + if (globalPermissions != null) + { + info.addStringPermissions(globalPermissions); + } + return info; } @@ -734,7 +799,7 @@ public class ScmRealm extends AuthorizingRealm * @return */ private boolean isUserPermission(User user, GroupNames groups, - Permission perm) + PermissionObject perm) { //J- return (perm.isGroupPermission() && groups.contains(perm.getName())) @@ -765,6 +830,9 @@ public class ScmRealm extends AuthorizingRealm /** Field description */ private Provider responseProvider; + /** Field description */ + private SecuritySystem securitySystem; + /** Field description */ private UserDAO userDAO; diff --git a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java index aac6c6305a..def745f625 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java @@ -459,9 +459,16 @@ public class ScmRealmTest AuthenticationResult.NOT_FOUND ); + SecuritySystem securitySystem = mock(SecuritySystem.class); + when( + securitySystem.getConfiguration() + ).thenReturn( + new SecurityConfiguration() + ); return new ScmRealm( new ScmConfiguration(), + securitySystem, new MapCacheManager(), userManager, groupManager, From 77acba82e122dd76d41e932334b9e66a5d00cde5 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 14 Apr 2013 15:53:23 +0200 Subject: [PATCH 03/25] start implementation of a ui for the security system configuration --- .../SecurityConfigurationResource.java | 136 ++++++++++++++++++ scm-webapp/src/main/webapp/index.mustache | 4 + .../resources/js/security/sonia.security.js | 33 +++++ .../sonia.security.permissionspanel.js | 51 +++++++ .../src/main/webapp/resources/js/sonia.scm.js | 17 ++- 5 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecurityConfigurationResource.java create mode 100644 scm-webapp/src/main/webapp/resources/js/security/sonia.security.js create mode 100644 scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecurityConfigurationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecurityConfigurationResource.java new file mode 100644 index 0000000000..332f1c1bf5 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecurityConfigurationResource.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2010, 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.api.rest.resources; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.Inject; + +import org.apache.shiro.SecurityUtils; + +import org.codehaus.enunciate.jaxrs.TypeHint; +import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; + +import sonia.scm.security.Role; +import sonia.scm.security.SecurityConfiguration; +import sonia.scm.security.SecuritySystem; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +/** + * + * @author Sebastian Sdorra + */ +@Path("security") +@ExternallyManagedLifecycle +public class SecurityConfigurationResource +{ + + /** + * Constructs ... + * + * + * @param system + */ + @Inject + public SecurityConfigurationResource(SecuritySystem system) + { + this.system = system; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @GET + @TypeHint(SecurityConfiguration.class) + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + public Response getConfiguration() + { + Response response = null; + + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) + { + response = Response.ok(system.getConfiguration()).build(); + } + else + { + response = Response.status(Response.Status.FORBIDDEN).build(); + } + + return response; + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param uriInfo + * @param newConfig + * + * @return + */ + @POST + @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + public Response setConfig(@Context UriInfo uriInfo, + SecurityConfiguration newConfig) + { + + // TODO replace by checkRole + SecurityUtils.getSubject().checkRole(Role.ADMIN); + + system.setConfiguration(newConfig); + + return Response.created(uriInfo.getRequestUri()).build(); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private SecuritySystem system; +} diff --git a/scm-webapp/src/main/webapp/index.mustache b/scm-webapp/src/main/webapp/index.mustache index 869112096e..32929592a7 100644 --- a/scm-webapp/src/main/webapp/index.mustache +++ b/scm-webapp/src/main/webapp/index.mustache @@ -143,6 +143,10 @@ + + + + diff --git a/scm-webapp/src/main/webapp/resources/js/security/sonia.security.js b/scm-webapp/src/main/webapp/resources/js/security/sonia.security.js new file mode 100644 index 0000000000..9625f214d2 --- /dev/null +++ b/scm-webapp/src/main/webapp/resources/js/security/sonia.security.js @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2010, 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 + * + */ + +// register namespace +Ext.ns('Sonia.security'); diff --git a/scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js b/scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js new file mode 100644 index 0000000000..4d9940f3bb --- /dev/null +++ b/scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2010, 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 + * + */ + +Sonia.security.PermissionsPanel = Ext.extend(Ext.Panel, { + + //TODO i18n + titleText: 'Permissions', + + initComponent: function(){ + + var config = { + title: this.titleText, + bodyCssClass: 'x-panel-mc' + }; + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + Sonia.security.PermissionsPanel.superclass.initComponent.apply(this, arguments); + } + +}); + +// register xtype +Ext.reg('permissionsPanel', Sonia.security.PermissionsPanel); \ No newline at end of file diff --git a/scm-webapp/src/main/webapp/resources/js/sonia.scm.js b/scm-webapp/src/main/webapp/resources/js/sonia.scm.js index 547465a758..4771e61e0b 100644 --- a/scm-webapp/src/main/webapp/resources/js/sonia.scm.js +++ b/scm-webapp/src/main/webapp/resources/js/sonia.scm.js @@ -50,6 +50,10 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, { tabUsersText: 'Users', navGroupsText: 'Groups', tabGroupsText: 'Groups', + + // TODO i18n + navPermissionsText: 'Permissions', + tabPermissionsText: 'Permissions', sectionLoginText: 'Login', navLoginText: 'Login', @@ -161,6 +165,12 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, { this.addTabPanel('groups', 'groupPanel', this.tabGroupsText); } }, + + addPermissionsTabPanel: function(){ + if (admin){ + this.addTabPanel('permissions', 'permissionsPanel', this.tabPermissionsText); + } + }, createMainMenu: function(){ if ( debug ){ @@ -236,11 +246,14 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, { label: this.navUsersText, fn: this.addUsersTabPanel, scope: this - }); - securitySection.links.push({ + },{ label: this.navGroupsText, fn: this.addGroupsTabPanel, scope: this + },{ + label: this.navPermissionsText, + fn: this.addPermissionsTabPanel, + scope: this }); } From fd7b1f61f09d951d78391bdaaa99d64da4d7b701 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 27 Apr 2013 09:22:46 +0200 Subject: [PATCH 04/25] added store api for multiple configuration entries --- .../scm/store/ConfigurationEntryStore.java | 64 +++++++++++++++++++ .../store/ConfigurationEntryStoreFactory.java | 61 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStore.java create mode 100644 scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java diff --git a/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStore.java b/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStore.java new file mode 100644 index 0000000000..116e781717 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStore.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010, 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.store; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Predicate; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Collection; + +/** + * A ConfigurationEntryStore can be used to store multiple entries of structured + * configuration data. Note: the default implementation use JAXB to + * marshall the items. + * + * @author Sebastian Sdorra + * + * @param store value type + * @since 1.31 + */ +public interface ConfigurationEntryStore extends DataStore +{ + + /** + * Return all values matching the given {@link Predicate}. + * + * + * @param predicate predicate to match values + * + * @return filtered collection of values + */ + public Collection getMatchingValues(Predicate predicate); +} diff --git a/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java b/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java new file mode 100644 index 0000000000..f83e2e164c --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010, 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.store; + +/** + * The ConfigurationEntryStoreFactory can be used to create new or get existing + * {@link ConfigurationEntryStore}s. Note: the default implementation + * uses the same location as the {@link StoreFactory}, so be sure that the + * store names are unique for all {@link ConfigurationEntryStore}s and + * {@link Store}s. + * + * @author Sebastian Sdorra + * @since 1.31 + * + * @apiviz.landmark + * @apiviz.uses sonia.scm.store.ConfigurationEntryStore + */ +public interface ConfigurationEntryStoreFactory +{ + + /** + * Get an existing {@link ConfigurationEntryStore} or create a new one. + * + * + * @param type type of the store objects + * @param name name of the store + * @param type of the store objects + * + * @return {@link ConfigurationEntryStore} with given name and type + */ + public ConfigurationEntryStore getStore(Class type, String name); +} From 2da419063f2fc838fba04fb17bdf0f584b73956c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 27 Apr 2013 09:23:20 +0200 Subject: [PATCH 05/25] added jaxb implementation for new configuration entry store api --- .../store/JAXBConfigurationEntryStore.java | 407 ++++++++++++++++++ .../JAXBConfigurationEntryStoreFactory.java | 113 +++++ .../java/sonia/scm/store/JAXBDataStore.java | 5 +- .../sonia/scm/store/JAXBStoreFactory.java | 11 +- .../java/sonia/scm/store/StoreConstants.java | 46 ++ .../JAXBConfigurationEntryStoreTest.java | 57 +++ 6 files changed, 627 insertions(+), 12 deletions(-) create mode 100644 scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java create mode 100644 scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java create mode 100644 scm-dao-xml/src/main/java/sonia/scm/store/StoreConstants.java create mode 100644 scm-dao-xml/src/test/java/sonia/scm/store/JAXBConfigurationEntryStoreTest.java diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java new file mode 100644 index 0000000000..b8a02b68ea --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java @@ -0,0 +1,407 @@ +/** + * Copyright (c) 2010, 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.store; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.Maps; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.security.KeyGenerator; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; +import java.io.FileOutputStream; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.stream.StreamSource; + +/** + * + * @author Sebastian Sdorra + * + * @param + */ +public class JAXBConfigurationEntryStore + implements ConfigurationEntryStore +{ + + /** Field description */ + private static final Object LOCK = new Object(); + + /** Field description */ + private static final String TAG_CONFIGURATION = "configuration"; + + /** Field description */ + private static final String TAG_ENTRY = "entry"; + + /** Field description */ + private static final String TAG_KEY = "key"; + + /** Field description */ + private static final String TAG_VALUE = "value"; + + /** + * the logger for JAXBConfigurationEntryStore + */ + private static final Logger logger = + LoggerFactory.getLogger(JAXBConfigurationEntryStore.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * + * @param keyGenerator + * @param file + * @param type + */ + JAXBConfigurationEntryStore(KeyGenerator keyGenerator, File file, + Class type) + { + this.keyGenerator = keyGenerator; + this.file = file; + this.type = type; + + try + { + this.context = JAXBContext.newInstance(type); + + if (file.exists()) + { + load(); + } + } + catch (JAXBException ex) + { + throw new StoreException("could not create jaxb context", ex); + } + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + @Override + public void clear() + { + logger.debug("clear configuration store"); + + synchronized (LOCK) + { + entries.clear(); + store(); + } + } + + /** + * Method description + * + * + * @param item + * + * @return + */ + @Override + public String put(V item) + { + String id = keyGenerator.createKey(); + + put(id, item); + + return id; + } + + /** + * Method description + * + * + * @param id + * @param item + */ + @Override + public void put(String id, V item) + { + logger.debug("put item {} to configuration store", id); + + synchronized (LOCK) + { + entries.put(id, item); + store(); + } + } + + /** + * Method description + * + * + * @param id + */ + @Override + public void remove(String id) + { + logger.debug("remove item {} from configuration store", id); + + synchronized (LOCK) + { + entries.remove(id); + store(); + } + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param id + * + * @return + */ + @Override + public V get(String id) + { + logger.trace("get item {} from configuration store"); + + return entries.get(id); + } + + /** + * Method description + * + * + * @return + */ + @Override + public Map getAll() + { + logger.trace("get all items from configuration store"); + + return Collections.unmodifiableMap(entries); + } + + /** + * Method description + * + * + * @param predicate + * + * @return + */ + @Override + public Collection getMatchingValues(Predicate predicate) + { + return Collections2.filter(entries.values(), predicate); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param writer + */ + private void close(XMLStreamWriter writer) + { + if (writer != null) + { + try + { + writer.close(); + } + catch (XMLStreamException ex) + { + logger.error("could not close writer", ex); + } + } + } + + /** + * Method description + * + * + * @param reader + */ + private void close(XMLStreamReader reader) + { + if (reader != null) + { + try + { + reader.close(); + } + catch (XMLStreamException ex) + { + logger.error("could not close reader", ex); + } + } + } + + /** + * Method description + * + * + * @return + */ + private void load() + { + logger.debug("load configuration from {}", file); + + XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + + XMLStreamReader reader = null; + + try + { + Unmarshaller u = context.createUnmarshaller(); + + reader = xmlInputFactory.createXMLStreamReader(new StreamSource(file)); + reader.nextTag(); + reader.nextTag(); + + while (reader.isStartElement() && reader.getLocalName().equals(TAG_ENTRY)) + { + + // read key + reader.nextTag(); + + String key = reader.getElementText(); + + // read entry + reader.nextTag(); + + V v = (V) u.unmarshal(reader, type).getValue(); + + entries.put(key, v); + } + } + catch (Exception ex) + { + throw new StoreException("could not load configuration", ex); + } + finally + { + close(reader); + } + } + + /** + * Method description + * + */ + private void store() + { + logger.debug("store configuration to {}", file); + + XMLStreamWriter writer = null; + + try + { + writer = XMLOutputFactory.newFactory().createXMLStreamWriter( + new FileOutputStream(file)); + writer.writeStartDocument(); + writer.writeStartElement(TAG_CONFIGURATION); + + Marshaller m = context.createMarshaller(); + + m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); + + for (Entry e : entries.entrySet()) + { + writer.writeStartElement(TAG_ENTRY); + writer.writeStartElement(TAG_KEY); + writer.writeCharacters(e.getKey()); + writer.writeEndElement(); + + JAXBElement je = new JAXBElement(QName.valueOf(TAG_VALUE), type, + e.getValue()); + + m.marshal(je, writer); + } + + writer.writeEndElement(); + writer.writeEndDocument(); + } + catch (Exception ex) + { + throw new StoreException("could not store configuration", ex); + } + finally + { + close(writer); + } + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private JAXBContext context; + + /** Field description */ + private Map entries = Maps.newHashMap(); + + /** Field description */ + private File file; + + /** Field description */ + private KeyGenerator keyGenerator; + + /** Field description */ + private Class type; +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java new file mode 100644 index 0000000000..296d03cd70 --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2010, 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.store; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.SCMContextProvider; +import sonia.scm.security.KeyGenerator; +import sonia.scm.util.IOUtil; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; + +/** + * + * @author Sebastian Sdorra + */ +@Singleton +public class JAXBConfigurationEntryStoreFactory + implements ConfigurationEntryStoreFactory +{ + + /** + * the logger for JAXBConfigurationEntryStoreFactory + */ + private static final Logger logger = + LoggerFactory.getLogger(JAXBConfigurationEntryStoreFactory.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param keyGenerator + * @param context + */ + @Inject + public JAXBConfigurationEntryStoreFactory(KeyGenerator keyGenerator, + SCMContextProvider context) + { + this.keyGenerator = keyGenerator; + directory = new File(context.getBaseDirectory(), + StoreConstants.CONFIGDIRECTORY_NAME); + IOUtil.mkdirs(directory); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param type + * @param name + * @param + * + * @return + */ + @Override + public ConfigurationEntryStore getStore(Class type, String name) + { + logger.debug("create new configuration store for type {} with name {}", + type, name); + + return new JAXBConfigurationEntryStore(keyGenerator, + new File(directory, name.concat(StoreConstants.FILE_EXTENSION)), type); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private File directory; + + /** Field description */ + private KeyGenerator keyGenerator; +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStore.java index 40b22a182b..40ba0a2053 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStore.java @@ -61,9 +61,6 @@ import javax.xml.bind.Marshaller; public class JAXBDataStore extends FileBasedStore implements DataStore { - /** Field description */ - private static final String SUFFIX = ".xml"; - /** * the logger for JAXBDataStore */ @@ -82,7 +79,7 @@ public class JAXBDataStore extends FileBasedStore implements DataStore */ public JAXBDataStore(KeyGenerator keyGenerator, Class type, File directory) { - super(directory, SUFFIX); + super(directory, StoreConstants.FILE_EXTENSION); this.keyGenerator = keyGenerator; try diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBStoreFactory.java index 3291be4806..34f8fb3c98 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBStoreFactory.java @@ -56,12 +56,6 @@ import java.io.IOException; public class JAXBStoreFactory implements ListenableStoreFactory { - /** Field description */ - public static final String CONFIGDIRECTORY_NAME = "config"; - - /** Field description */ - public static final String FILE_EXTENSION = ".xml"; - /** the logger for JAXBStoreFactory */ private static final Logger logger = LoggerFactory.getLogger(JAXBStoreFactory.class); @@ -91,7 +85,7 @@ public class JAXBStoreFactory implements ListenableStoreFactory public void init(SCMContextProvider context) { configDirectory = new File(context.getBaseDirectory(), - CONFIGDIRECTORY_NAME); + StoreConstants.CONFIGDIRECTORY_NAME); IOUtil.mkdirs(configDirectory); } @@ -115,7 +109,8 @@ public class JAXBStoreFactory implements ListenableStoreFactory throw new IllegalStateException("store factory is not initialized"); } - File configFile = new File(configDirectory, name.concat(FILE_EXTENSION)); + File configFile = new File(configDirectory, + name.concat(StoreConstants.FILE_EXTENSION)); if (logger.isDebugEnabled()) { diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/StoreConstants.java b/scm-dao-xml/src/main/java/sonia/scm/store/StoreConstants.java new file mode 100644 index 0000000000..a44fda417d --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/store/StoreConstants.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010, 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.store; + +/** + * + * @author Sebastian Sdorra + */ +public interface StoreConstants +{ + + /** Field description */ + public static final String CONFIGDIRECTORY_NAME = "config"; + + /** Field description */ + public static final String FILE_EXTENSION = ".xml"; +} diff --git a/scm-dao-xml/src/test/java/sonia/scm/store/JAXBConfigurationEntryStoreTest.java b/scm-dao-xml/src/test/java/sonia/scm/store/JAXBConfigurationEntryStoreTest.java new file mode 100644 index 0000000000..7e393454aa --- /dev/null +++ b/scm-dao-xml/src/test/java/sonia/scm/store/JAXBConfigurationEntryStoreTest.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010, 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.store; + +//~--- non-JDK imports -------------------------------------------------------- + +import sonia.scm.security.UUIDKeyGenerator; + +/** + * + * @author Sebastian Sdorra + */ +public class JAXBConfigurationEntryStoreTest extends ConfigurationEntryStoreTestBase +{ + + /** + * Method description + * + * + * @return + */ + @Override + protected ConfigurationEntryStoreFactory createConfigurationStoreFactory() + { + return new JAXBConfigurationEntryStoreFactory(new UUIDKeyGenerator(), + contextProvider); + } +} From 76f02b169f444542e8fbc454ad5bb492cf8e3440 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 27 Apr 2013 09:23:41 +0200 Subject: [PATCH 06/25] bind new configuration entry store api --- .../src/main/java/sonia/scm/ScmServletModule.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index dc1255eee2..84b27d8277 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -47,7 +47,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.api.rest.UriExtensionsConfig; import sonia.scm.cache.CacheManager; -import sonia.scm.cache.EhCacheManager; +import sonia.scm.cache.GuavaCacheManager; import sonia.scm.config.ScmConfiguration; import sonia.scm.event.ScmEventBus; import sonia.scm.filter.AdminSecurityFilter; @@ -86,13 +86,17 @@ import sonia.scm.resources.ScriptResourceServlet; import sonia.scm.security.CipherHandler; import sonia.scm.security.CipherUtil; import sonia.scm.security.DefaultKeyGenerator; +import sonia.scm.security.DefaultSecuritySystem; import sonia.scm.security.EncryptionHandler; import sonia.scm.security.KeyGenerator; import sonia.scm.security.MessageDigestEncryptionHandler; import sonia.scm.security.SecurityContext; +import sonia.scm.security.SecuritySystem; import sonia.scm.store.BlobStoreFactory; +import sonia.scm.store.ConfigurationEntryStoreFactory; import sonia.scm.store.DataStoreFactory; import sonia.scm.store.FileBlobStoreFactory; +import sonia.scm.store.JAXBConfigurationEntryStoreFactory; import sonia.scm.store.JAXBDataStoreFactory; import sonia.scm.store.JAXBStoreFactory; import sonia.scm.store.ListenableStoreFactory; @@ -142,9 +146,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; -import sonia.scm.cache.GuavaCacheManager; -import sonia.scm.security.DefaultSecuritySystem; -import sonia.scm.security.SecuritySystem; /** * @@ -254,6 +255,8 @@ public class ScmServletModule extends ServletModule // bind core bind(StoreFactory.class, JAXBStoreFactory.class); bind(ListenableStoreFactory.class, JAXBStoreFactory.class); + bind(ConfigurationEntryStoreFactory.class, + JAXBConfigurationEntryStoreFactory.class); bind(DataStoreFactory.class, JAXBDataStoreFactory.class); bind(BlobStoreFactory.class, FileBlobStoreFactory.class); bind(ScmConfiguration.class).toInstance(config); From 57b77130dbd890daa10378c3f82bbdb7816b12f6 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 27 Apr 2013 09:24:08 +0200 Subject: [PATCH 07/25] added missing unit test base classes --- .../ConfigurationEntryStoreTestBase.java | 63 +++++ .../sonia/scm/store/DataStoreTestBase.java | 173 +------------- .../scm/store/KeyValueStoreTestBase.java | 221 ++++++++++++++++++ 3 files changed, 288 insertions(+), 169 deletions(-) create mode 100644 scm-test/src/main/java/sonia/scm/store/ConfigurationEntryStoreTestBase.java create mode 100644 scm-test/src/main/java/sonia/scm/store/KeyValueStoreTestBase.java diff --git a/scm-test/src/main/java/sonia/scm/store/ConfigurationEntryStoreTestBase.java b/scm-test/src/main/java/sonia/scm/store/ConfigurationEntryStoreTestBase.java new file mode 100644 index 0000000000..144c00e2d6 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/store/ConfigurationEntryStoreTestBase.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010, 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.store; + +/** + * + * @author Sebastian Sdorra + */ +public abstract class ConfigurationEntryStoreTestBase extends KeyValueStoreTestBase +{ + + /** + * Method description + * + * + * @return + */ + protected abstract ConfigurationEntryStoreFactory createConfigurationStoreFactory(); + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @Override + protected ConfigurationEntryStore getDataStore() + { + return createConfigurationStoreFactory().getStore(StoreObject.class, + "test"); + } +} diff --git a/scm-test/src/main/java/sonia/scm/store/DataStoreTestBase.java b/scm-test/src/main/java/sonia/scm/store/DataStoreTestBase.java index ad4e290213..3129d3a339 100644 --- a/scm-test/src/main/java/sonia/scm/store/DataStoreTestBase.java +++ b/scm-test/src/main/java/sonia/scm/store/DataStoreTestBase.java @@ -30,26 +30,14 @@ */ + package sonia.scm.store; -//~--- non-JDK imports -------------------------------------------------------- - -import org.junit.Before; -import org.junit.Test; - -import sonia.scm.AbstractTestBase; - -import static org.junit.Assert.*; - -//~--- JDK imports ------------------------------------------------------------ - -import java.util.Map; - /** * * @author Sebastian Sdorra */ -public abstract class DataStoreTestBase extends AbstractTestBase +public abstract class DataStoreTestBase extends KeyValueStoreTestBase { /** @@ -60,155 +48,6 @@ public abstract class DataStoreTestBase extends AbstractTestBase */ protected abstract DataStoreFactory createDataStoreFactory(); - /** - * Method description - * - */ - @Before - public void before() - { - store = getDataStore(); - store.clear(); - } - - /** - * Method description - * - */ - @Test - public void testClear() - { - testPutWithId(); - - store.clear(); - assertNull(store.get("1")); - assertNull(store.get("2")); - - assertTrue(store.getAll().isEmpty()); - } - - /** - * Method description - * - */ - @Test - public void testGet() - { - StoreObject other = store.get("1"); - - assertNull(other); - - StoreObject obj = new StoreObject("test-1"); - - store.put("1", obj); - other = store.get("1"); - assertNotNull(other); - assertEquals(obj, other); - } - - /** - * Method description - * - */ - @Test - public void testGetAll() - { - StoreObject obj1 = new StoreObject("test-1"); - - store.put("1", obj1); - - StoreObject obj2 = new StoreObject("test-2"); - - store.put("2", obj2); - - Map map = store.getAll(); - - assertNotNull(map); - - assertFalse(map.isEmpty()); - assertEquals(2, map.size()); - - assertEquals(obj1, map.get("1")); - assertEquals(obj2, map.get("2")); - - assertNull(map.get("3")); - } - - /** - * Method description - * - */ - @Test - public void testGetAllFromEmpty() - { - Map map = store.getAll(); - - assertNotNull(map); - assertTrue(map.isEmpty()); - } - - /** - * Method description - * - */ - @Test - public void testGetFromEmpty() - { - StoreObject obj = store.get("test"); - - assertNull(obj); - } - - /** - * Method description - * - */ - @Test - public void testPutWithId() - { - StoreObject obj1 = new StoreObject("test-1"); - - store.put("1", obj1); - - StoreObject obj2 = new StoreObject("test-2"); - - store.put("2", obj2); - - assertEquals(obj1, store.get("1")); - assertEquals(obj2, store.get("2")); - } - - /** - * Method description - * - */ - @Test - public void testPutWithoutId() - { - StoreObject obj = new StoreObject("test-1"); - String id = store.put(obj); - - assertNotNull(id); - - assertEquals(obj, store.get(id)); - } - - /** - * Method description - * - */ - @Test - public void testRemove() - { - testPutWithId(); - - store.remove("1"); - assertNull(store.get("1")); - assertNotNull(store.get("2")); - store.remove("2"); - assertNull(store.get("2")); - } - //~--- get methods ---------------------------------------------------------- /** @@ -217,13 +56,9 @@ public abstract class DataStoreTestBase extends AbstractTestBase * * @return */ - private DataStore getDataStore() + @Override + protected DataStore getDataStore() { return createDataStoreFactory().getStore(StoreObject.class, "test"); } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private DataStore store; } diff --git a/scm-test/src/main/java/sonia/scm/store/KeyValueStoreTestBase.java b/scm-test/src/main/java/sonia/scm/store/KeyValueStoreTestBase.java new file mode 100644 index 0000000000..16c6824812 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/store/KeyValueStoreTestBase.java @@ -0,0 +1,221 @@ +/** + * Copyright (c) 2010, 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.store; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.junit.Before; +import org.junit.Test; + +import sonia.scm.AbstractTestBase; + +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; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Map; + +/** + * + * @author Sebastian Sdorra + */ +public abstract class KeyValueStoreTestBase extends AbstractTestBase +{ + + /** + * Method description + * + * + * @return + */ + protected abstract DataStore getDataStore(); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + @Before + public void before() + { + store = getDataStore(); + store.clear(); + } + + /** + * Method description + * + */ + @Test + public void testClear() + { + testPutWithId(); + + store.clear(); + assertNull(store.get("1")); + assertNull(store.get("2")); + + assertTrue(store.getAll().isEmpty()); + } + + /** + * Method description + * + */ + @Test + public void testGet() + { + StoreObject other = store.get("1"); + + assertNull(other); + + StoreObject obj = new StoreObject("test-1"); + + store.put("1", obj); + other = store.get("1"); + assertNotNull(other); + assertEquals(obj, other); + } + + /** + * Method description + * + */ + @Test + public void testGetAll() + { + StoreObject obj1 = new StoreObject("test-1"); + + store.put("1", obj1); + + StoreObject obj2 = new StoreObject("test-2"); + + store.put("2", obj2); + + Map map = store.getAll(); + + assertNotNull(map); + + assertFalse(map.isEmpty()); + assertEquals(2, map.size()); + + assertEquals(obj1, map.get("1")); + assertEquals(obj2, map.get("2")); + + assertNull(map.get("3")); + } + + /** + * Method description + * + */ + @Test + public void testGetAllFromEmpty() + { + Map map = store.getAll(); + + assertNotNull(map); + assertTrue(map.isEmpty()); + } + + /** + * Method description + * + */ + @Test + public void testGetFromEmpty() + { + StoreObject obj = store.get("test"); + + assertNull(obj); + } + + /** + * Method description + * + */ + @Test + public void testPutWithId() + { + StoreObject obj1 = new StoreObject("test-1"); + + store.put("1", obj1); + + StoreObject obj2 = new StoreObject("test-2"); + + store.put("2", obj2); + + assertEquals(obj1, store.get("1")); + assertEquals(obj2, store.get("2")); + } + + /** + * Method description + * + */ + @Test + public void testPutWithoutId() + { + StoreObject obj = new StoreObject("test-1"); + String id = store.put(obj); + + assertNotNull(id); + + assertEquals(obj, store.get(id)); + } + + /** + * Method description + * + */ + @Test + public void testRemove() + { + testPutWithId(); + + store.remove("1"); + assertNull(store.get("1")); + assertNotNull(store.get("2")); + store.remove("2"); + assertNull(store.get("2")); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private DataStore store; +} From 210c400456b65e2b541985020092ba6ece4566ae Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 30 Apr 2013 15:36:45 +0200 Subject: [PATCH 08/25] improve api for global permissions --- ...ngedEvent.java => AssignedPermission.java} | 87 ++++++++++++++++--- ...mission.java => PermissionDescriptor.java} | 81 +++++++---------- .../sonia/scm/security/SecuritySystem.java | 74 +++++++++++++--- ...ion.java => StoredAssignedPermission.java} | 47 +++++----- 4 files changed, 196 insertions(+), 93 deletions(-) rename scm-core/src/main/java/sonia/scm/security/{SecurityConfigurationChangedEvent.java => AssignedPermission.java} (55%) rename scm-core/src/main/java/sonia/scm/security/{GlobalPermission.java => PermissionDescriptor.java} (70%) rename scm-core/src/main/java/sonia/scm/security/{SecurityConfiguration.java => StoredAssignedPermission.java} (77%) diff --git a/scm-core/src/main/java/sonia/scm/security/SecurityConfigurationChangedEvent.java b/scm-core/src/main/java/sonia/scm/security/AssignedPermission.java similarity index 55% rename from scm-core/src/main/java/sonia/scm/security/SecurityConfigurationChangedEvent.java rename to scm-core/src/main/java/sonia/scm/security/AssignedPermission.java index aa541bfaea..15ff1b7622 100644 --- a/scm-core/src/main/java/sonia/scm/security/SecurityConfigurationChangedEvent.java +++ b/scm-core/src/main/java/sonia/scm/security/AssignedPermission.java @@ -31,27 +31,69 @@ package sonia.scm.security; +//~--- JDK imports ------------------------------------------------------------ + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + /** * * @author Sebastian Sdorra * @since 1.31 */ -public class SecurityConfigurationChangedEvent +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "assigned-permission") +public class AssignedPermission implements PermissionObject { + /** + * Constructor is only visible for JAXB. + * + */ + public AssignedPermission() {} + /** * Constructs ... * * - * @param oldConfiguration - * @param newConfiguration + * @param permission */ - public SecurityConfigurationChangedEvent( - SecurityConfiguration oldConfiguration, - SecurityConfiguration newConfiguration) + public AssignedPermission(AssignedPermission permission) { - this.oldConfiguration = oldConfiguration; - this.newConfiguration = newConfiguration; + this.name = permission.name; + this.groupPermission = permission.groupPermission; + this.permission = permission.permission; + } + + /** + * Constructs ... + * + * + * @param name + * @param permission + */ + public AssignedPermission(String name, String permission) + { + this.name = name; + this.permission = permission; + } + + /** + * Constructs ... + * + * + * @param name + * @param groupPermission + * @param permission + */ + public AssignedPermission(String name, boolean groupPermission, + String permission) + { + this.name = name; + this.groupPermission = groupPermission; + this.permission = permission; } //~--- get methods ---------------------------------------------------------- @@ -62,9 +104,10 @@ public class SecurityConfigurationChangedEvent * * @return */ - public SecurityConfiguration getNewConfiguration() + @Override + public String getName() { - return newConfiguration; + return name; } /** @@ -73,16 +116,32 @@ public class SecurityConfigurationChangedEvent * * @return */ - public SecurityConfiguration getOldConfiguration() + public String getPermission() { - return oldConfiguration; + return permission; + } + + /** + * Method description + * + * + * @return + */ + @Override + public boolean isGroupPermission() + { + return groupPermission; } //~--- fields --------------------------------------------------------------- /** Field description */ - private SecurityConfiguration newConfiguration; + @XmlElement(name = "group-permission") + private boolean groupPermission; /** Field description */ - private SecurityConfiguration oldConfiguration; + private String name; + + /** Field description */ + private String permission; } diff --git a/scm-core/src/main/java/sonia/scm/security/GlobalPermission.java b/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java similarity index 70% rename from scm-core/src/main/java/sonia/scm/security/GlobalPermission.java rename to scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java index 252cffacad..0e6d710ad5 100644 --- a/scm-core/src/main/java/sonia/scm/security/GlobalPermission.java +++ b/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java @@ -49,49 +49,36 @@ import javax.xml.bind.annotation.XmlRootElement; * @author Sebastian Sdorra * @since 1.31 */ +@XmlRootElement(name = "permissions") @XmlAccessorType(XmlAccessType.FIELD) -@XmlRootElement(name = "global-permission") -public final class GlobalPermission implements PermissionObject, Serializable +public class PermissionDescriptor implements Serializable { /** Field description */ - private static final long serialVersionUID = 4794267414178121515L; + private static final long serialVersionUID = -9141065458354047154L; //~--- constructors --------------------------------------------------------- /** - * Constructs ... + * Constructor is only visible for JAXB. * */ - public GlobalPermission() {} + public PermissionDescriptor() {} /** * Constructs ... * * - * @param id - * @param name - * @param permission + * @param displayName + * @param description + * @param value */ - public GlobalPermission(String name, String permission) + public PermissionDescriptor(String displayName, String description, + String value) { - this(name, false, permission); - } - - /** - * Constructs ... - * - * - * @param id - * @param name - * @param group - * @param permission - */ - public GlobalPermission(String name, boolean groupPermission, String permission) - { - this.name = name; - this.groupPermission = groupPermission; - this.permission = permission; + this.displayName = displayName; + this.description = description; + this.value = value; } //~--- methods -------------------------------------------------------------- @@ -117,13 +104,11 @@ public final class GlobalPermission implements PermissionObject, Serializable return false; } - final GlobalPermission other = (GlobalPermission) obj; + final PermissionDescriptor other = (PermissionDescriptor) obj; - //J- - return Objects.equal(name, other.name) - && Objects.equal(groupPermission, other.groupPermission) - && Objects.equal(permission, other.permission); - //J+ + return Objects.equal(displayName, other.displayName) + && Objects.equal(description, other.description) + && Objects.equal(value, other.value); } /** @@ -135,7 +120,7 @@ public final class GlobalPermission implements PermissionObject, Serializable @Override public int hashCode() { - return Objects.hashCode(name, groupPermission, permission); + return Objects.hashCode(displayName, description, value); } /** @@ -147,12 +132,14 @@ public final class GlobalPermission implements PermissionObject, Serializable @Override public String toString() { + //J- return Objects.toStringHelper(this) - .add("name", name) - .add("groupPermission", groupPermission) - .add("permisison", permission) + .add("displayName", displayName) + .add("description", description) + .add("value", value) .toString(); + //J+ } @@ -164,10 +151,9 @@ public final class GlobalPermission implements PermissionObject, Serializable * * @return */ - @Override - public String getName() + public String getDescription() { - return name; + return description; } /** @@ -176,9 +162,9 @@ public final class GlobalPermission implements PermissionObject, Serializable * * @return */ - public String getPermission() + public String getDisplayName() { - return permission; + return displayName; } /** @@ -187,21 +173,20 @@ public final class GlobalPermission implements PermissionObject, Serializable * * @return */ - @Override - public boolean isGroupPermission() + public String getValue() { - return groupPermission; + return value; } //~--- fields --------------------------------------------------------------- /** Field description */ - @XmlElement(name = "group") - private boolean groupPermission; + private String description; /** Field description */ - private String name; + @XmlElement(name = "display-name") + private String displayName; /** Field description */ - private String permission; + private String value; } diff --git a/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java b/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java index cc21dca8ad..deaee886a8 100644 --- a/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java +++ b/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java @@ -33,8 +33,14 @@ package sonia.scm.security; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Predicate; + import org.apache.shiro.subject.PrincipalCollection; +//~--- JDK imports ------------------------------------------------------------ + +import java.util.List; + /** * * @author Sebastian Sdorra @@ -47,9 +53,65 @@ public interface SecuritySystem * Method description * * + * @param permission + * * @return */ - public SecurityConfiguration getConfiguration(); + public StoredAssignedPermission addPermission(AssignedPermission permission); + + /** + * Method description + * + * + * @param permission + */ + public void deletePermission(StoredAssignedPermission permission); + + /** + * Method description + * + * + * @param id + */ + public void deletePermission(String id); + + /** + * Method description + * + * + * @param id + * @param permission + */ + public void modifyPermission(StoredAssignedPermission permission); + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public List getAllPermissions(); + + /** + * Method description + * + * + * @return + */ + public List getAvailablePermissions(); + + /** + * Method description + * + * + * @param predicate + * + * @return + */ + public List getPermissions( + Predicate predicate); /** * Method description @@ -58,14 +120,4 @@ public interface SecuritySystem * @return */ public PrincipalCollection getSystemAccount(); - - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param configuration - */ - public void setConfiguration(SecurityConfiguration configuration); } diff --git a/scm-core/src/main/java/sonia/scm/security/SecurityConfiguration.java b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java similarity index 77% rename from scm-core/src/main/java/sonia/scm/security/SecurityConfiguration.java rename to scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java index aeae5ddbc7..e740c39187 100644 --- a/scm-core/src/main/java/sonia/scm/security/SecurityConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java @@ -31,18 +31,10 @@ package sonia.scm.security; -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.collect.Lists; - //~--- JDK imports ------------------------------------------------------------ -import java.util.List; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; /** @@ -50,31 +42,46 @@ import javax.xml.bind.annotation.XmlRootElement; * @author Sebastian Sdorra * @since 1.31 */ -@XmlRootElement(name = "security") @XmlAccessorType(XmlAccessType.FIELD) -public class SecurityConfiguration +@XmlRootElement(name = "assigned-permission") +public class StoredAssignedPermission extends AssignedPermission { + /** + * Constructor is only visible for JAXB. + * + */ + public StoredAssignedPermission() {} + + /** + * Constructs ... + * + * + * @param id + * @param permission + */ + public StoredAssignedPermission(String id, AssignedPermission permission) + { + super(permission); + this.id = id; + + } + + //~--- get methods ---------------------------------------------------------- + /** * Method description * * * @return */ - public List getGlobalPermissions() + public String getId() { - if (globalPermissions == null) - { - globalPermissions = Lists.newArrayList(); - } - - return globalPermissions; + return id; } //~--- fields --------------------------------------------------------------- /** Field description */ - @XmlElement(name = "permission") - @XmlElementWrapper(name = "global-permissions") - private List globalPermissions; + private String id; } From c7cc0fabb04ddd31375b5678522fd68ebc50ccfc Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 30 Apr 2013 16:23:40 +0200 Subject: [PATCH 09/25] pass available permissions to the state object, if the current user has the administrator role --- .../src/main/java/sonia/scm/ScmState.java | 56 +++++++++++++++++++ .../resources/AuthenticationResource.java | 29 ++++++++-- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/ScmState.java b/scm-core/src/main/java/sonia/scm/ScmState.java index 8508d90c67..a04413f199 100644 --- a/scm-core/src/main/java/sonia/scm/ScmState.java +++ b/scm-core/src/main/java/sonia/scm/ScmState.java @@ -35,12 +35,14 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- +import sonia.scm.security.PermissionDescriptor; import sonia.scm.user.User; import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ import java.util.Collection; +import java.util.List; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -122,6 +124,29 @@ public class ScmState public ScmState(SCMContextProvider provider, User user, Collection groups, Collection repositoryTypes, String defaultUserType, ScmClientConfig clientConfig) + { + this(provider, user, groups, repositoryTypes, defaultUserType, + clientConfig, null); + } + + /** + * Constructs {@link ScmState} object. + * + * + * @param provider context provider + * @param user current user + * @param groups groups of the current user + * @param repositoryTypes available repository types + * @param defaultUserType default user type + * @param clientConfig client configuration + * @param availablePermissions list of available permissions + * + * @since 1.31 + */ + public ScmState(SCMContextProvider provider, User user, + Collection groups, Collection repositoryTypes, + String defaultUserType, ScmClientConfig clientConfig, + List availablePermissions) { this.version = provider.getVersion(); this.user = user; @@ -129,10 +154,23 @@ public class ScmState this.repositoryTypes = repositoryTypes; this.clientConfig = clientConfig; this.defaultUserType = defaultUserType; + this.availablePermissions = availablePermissions; } //~--- get methods ---------------------------------------------------------- + /** + * Returns a list of available global permissions. + * + * + * @return available global permissions + * @since 1.31 + */ + public List getAvailablePermissions() + { + return availablePermissions; + } + /** * Returns configuration for SCM-Manager clients. * @@ -215,6 +253,18 @@ public class ScmState //~--- set methods ---------------------------------------------------------- + /** + * Sets a list of available global permissions. + * + * + * @param permissions list of available global permisisons + * @since 1.31 + */ + public void setAvailablePermissions(List permissions) + { + this.availablePermissions = permissions; + } + /** * Setter for the client configuration * @@ -299,6 +349,12 @@ public class ScmState //~--- fields --------------------------------------------------------------- + /** + * Avaliable global permission + * @since 1.31 + */ + private List availablePermissions; + /** Field description */ private ScmClientConfig clientConfig; diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java index 150d352219..cc5b60e821 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java @@ -56,6 +56,9 @@ import sonia.scm.ScmState; import sonia.scm.config.ScmConfiguration; import sonia.scm.group.GroupNames; import sonia.scm.repository.RepositoryManager; +import sonia.scm.security.PermissionDescriptor; +import sonia.scm.security.Role; +import sonia.scm.security.SecuritySystem; import sonia.scm.security.Tokens; import sonia.scm.user.User; import sonia.scm.user.UserManager; @@ -64,6 +67,7 @@ import sonia.scm.user.UserManager; import java.util.Collection; import java.util.Collections; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -104,16 +108,18 @@ public class AuthenticationResource * @param repositoryManger * @param userManager * @param securityContextProvider + * @param securitySystem */ @Inject public AuthenticationResource(SCMContextProvider contextProvider, ScmConfiguration configuration, RepositoryManager repositoryManger, - UserManager userManager) + UserManager userManager, SecuritySystem securitySystem) { this.contextProvider = contextProvider; this.configuration = configuration; this.repositoryManger = repositoryManger; this.userManager = userManager; + this.securitySystem = securitySystem; } //~--- methods -------------------------------------------------------------- @@ -287,7 +293,8 @@ public class AuthenticationResource */ private ScmState createAnonymousState() { - return createState(SCMContext.ANONYMOUS, Collections.EMPTY_LIST); + return createState(SCMContext.ANONYMOUS, Collections.EMPTY_LIST, + Collections.EMPTY_LIST); } /** @@ -306,7 +313,14 @@ public class AuthenticationResource User user = collection.oneByType(User.class); GroupNames groups = collection.oneByType(GroupNames.class); - return createState(user, groups.getCollection()); + List ap = Collections.EMPTY_LIST; + + if (subject.hasRole(Role.ADMIN)) + { + ap = securitySystem.getAvailablePermissions(); + } + + return createState(user, groups.getCollection(), ap); } /** @@ -315,14 +329,16 @@ public class AuthenticationResource * * @param user * @param groups + * @param availablePermissions * * @return */ - private ScmState createState(User user, Collection groups) + private ScmState createState(User user, Collection groups, + List availablePermissions) { return new ScmState(contextProvider, user, groups, repositoryManger.getConfiguredTypes(), userManager.getDefaultType(), - new ScmClientConfig(configuration)); + new ScmClientConfig(configuration), availablePermissions); } //~--- fields --------------------------------------------------------------- @@ -336,6 +352,9 @@ public class AuthenticationResource /** Field description */ private RepositoryManager repositoryManger; + /** Field description */ + private SecuritySystem securitySystem; + /** Field description */ private UserManager userManager; } From 7b00a4e52d1511834fceb048f8a8866c5af554db Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 30 Apr 2013 16:27:08 +0200 Subject: [PATCH 10/25] start implementation of new security system --- .../scm/security/DefaultSecuritySystem.java | 147 ++++++++++++++---- .../java/sonia/scm/security/ScmRealm.java | 51 +++--- 2 files changed, 142 insertions(+), 56 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java index 36e8be4899..787ded5d94 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java @@ -33,17 +33,26 @@ package sonia.scm.security; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; import com.google.inject.Inject; import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.PrincipalCollection; -import sonia.scm.event.ScmEventBus; -import sonia.scm.store.Store; -import sonia.scm.store.StoreFactory; +import sonia.scm.store.ConfigurationEntryStore; +import sonia.scm.store.ConfigurationEntryStoreFactory; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Collections; +import java.util.List; +import java.util.Map.Entry; /** + * TODO add events * * @author Sebastian Sdorra * @since 1.31 @@ -64,9 +73,73 @@ public class DefaultSecuritySystem implements SecuritySystem * @param storeFactory */ @Inject - public DefaultSecuritySystem(StoreFactory storeFactory) + public DefaultSecuritySystem(ConfigurationEntryStoreFactory storeFactory) { - store = storeFactory.getStore(SecurityConfiguration.class, NAME); + store = storeFactory.getStore(AssignedPermission.class, NAME); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param permission + * + * @return + */ + @Override + public StoredAssignedPermission addPermission(AssignedPermission permission) + { + assertIsAdmin(); + + String id = store.put(permission); + + return new StoredAssignedPermission(id, permission); + } + + /** + * Method description + * + * + * @param permission + */ + @Override + public void deletePermission(StoredAssignedPermission permission) + { + assertIsAdmin(); + deletePermission(permission.getId()); + } + + /** + * Method description + * + * + * @param id + */ + @Override + public void deletePermission(String id) + { + assertIsAdmin(); + store.remove(id); + } + + /** + * Method description + * + * + * @param permission + */ + @Override + public void modifyPermission(StoredAssignedPermission permission) + { + assertIsAdmin(); + + synchronized (store) + { + store.remove(permission.getId()); + store.put(permission.getId(), new AssignedPermission(permission)); + } } //~--- get methods ---------------------------------------------------------- @@ -78,16 +151,48 @@ public class DefaultSecuritySystem implements SecuritySystem * @return */ @Override - public SecurityConfiguration getConfiguration() + public List getAllPermissions() { - SecurityConfiguration configuration = store.get(); + return getPermissions(null); + } - if (configuration == null) + /** + * Method description + * + * + * @return + */ + @Override + public List getAvailablePermissions() + { + + // TODO + return Collections.EMPTY_LIST; + } + + /** + * Method description + * + * + * @param predicate + * + * @return + */ + @Override + public List getPermissions( + Predicate predicate) + { + Builder permissions = ImmutableList.builder(); + + for (Entry e : store.getAll().entrySet()) { - configuration = new SecurityConfiguration(); + if ((predicate == null) || predicate.apply(e.getValue())) + { + permissions.add(new StoredAssignedPermission(e.getKey(), e.getValue())); + } } - return configuration; + return permissions.build(); } /** @@ -99,34 +204,24 @@ public class DefaultSecuritySystem implements SecuritySystem @Override public PrincipalCollection getSystemAccount() { - throw new UnsupportedOperationException("Not supported yet."); // To change body of generated methods, choose Tools | Templates. + + // TODO + throw new UnsupportedOperationException("Not supported yet."); } - //~--- set methods ---------------------------------------------------------- + //~--- methods -------------------------------------------------------------- /** * Method description * - * - * @param newConfiguration */ - @Override - public void setConfiguration(SecurityConfiguration newConfiguration) + private void assertIsAdmin() { SecurityUtils.getSubject().checkRole(Role.ADMIN); - - SecurityConfiguration oldConfiguration = store.get(); - - store.set(newConfiguration); - //J- - ScmEventBus.getInstance().post( - new SecurityConfigurationChangedEvent(oldConfiguration, newConfiguration) - ); - //J+ } //~--- fields --------------------------------------------------------------- /** Field description */ - private Store store; + private final ConfigurationEntryStore store; } diff --git a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java index 8c2b443b94..8bd9286fb9 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java @@ -36,6 +36,7 @@ package sonia.scm.security; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Joiner; +import com.google.common.base.Predicate; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.eventbus.Subscribe; @@ -197,23 +198,6 @@ public class ScmRealm extends AuthorizingRealm } } - /** - * Method description - * - * - * @param event - */ - @Subscribe - public void onEvent(SecurityConfigurationChangedEvent event) - { - if (logger.isDebugEnabled()) - { - logger.debug("clear cache, because security configuration has changed"); - } - - cache.clear(); - } - /** * Method description * @@ -503,7 +487,8 @@ public class ScmRealm extends AuthorizingRealm * * @return */ - private List collectGlobalPermissions(User user, GroupNames groups) + private List collectGlobalPermissions(final User user, + final GroupNames groups) { if (logger.isTraceEnabled()) { @@ -512,21 +497,27 @@ public class ScmRealm extends AuthorizingRealm List permissions = Lists.newArrayList(); - List globalPermissions = - securitySystem.getConfiguration().getGlobalPermissions(); - - for (GlobalPermission gp : globalPermissions) + List globalPermissions = + securitySystem.getPermissions(new Predicate() { - if (isUserPermission(user, groups, gp)) - { - if (logger.isTraceEnabled()) - { - logger.trace("add permission {} for user {}", gp.getPermission(), - user.getName()); - } - permissions.add(gp.getPermission()); + @Override + public boolean apply(AssignedPermission input) + { + return isUserPermission(user, groups, input); } + }); + + for (StoredAssignedPermission gp : globalPermissions) + { + if (logger.isTraceEnabled()) + { + logger.trace("add permission {} for user {}", gp.getPermission(), + user.getName()); + } + + permissions.add(gp.getPermission()); + } return permissions; From 1d1a92708a64fbac194e9cd6da2f8eed00f276c4 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 30 Apr 2013 16:55:48 +0200 Subject: [PATCH 11/25] read permission descriptor from classpath --- .../scm/security/PermissionDescriptor.java | 2 +- .../scm/security/DefaultSecuritySystem.java | 173 +++++++++++++++++- .../resources/META-INF/scm/permissions.xml | 54 ++++++ .../java/sonia/scm/security/ScmRealmTest.java | 9 +- 4 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 scm-webapp/src/main/resources/META-INF/scm/permissions.xml diff --git a/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java b/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java index 0e6d710ad5..bbb02eae02 100644 --- a/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java +++ b/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java @@ -49,7 +49,7 @@ import javax.xml.bind.annotation.XmlRootElement; * @author Sebastian Sdorra * @since 1.31 */ -@XmlRootElement(name = "permissions") +@XmlRootElement(name = "permission") @XmlAccessorType(XmlAccessType.FIELD) public class PermissionDescriptor implements Serializable { diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java index 787ded5d94..47f8612665 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java @@ -42,15 +42,30 @@ import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.PrincipalCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import sonia.scm.store.ConfigurationEntryStore; import sonia.scm.store.ConfigurationEntryStoreFactory; //~--- JDK imports ------------------------------------------------------------ +import java.io.IOException; + +import java.net.URL; + import java.util.Collections; +import java.util.Enumeration; import java.util.List; import java.util.Map.Entry; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + /** * TODO add events * @@ -64,6 +79,16 @@ public class DefaultSecuritySystem implements SecuritySystem /** Field description */ private static final String NAME = "security"; + /** Field description */ + private static final String PERMISSION_DESCRIPTOR = + "META-INF/scm/permissions.xml"; + + /** + * the logger for DefaultSecuritySystem + */ + private static final Logger logger = + LoggerFactory.getLogger(DefaultSecuritySystem.class); + //~--- constructors --------------------------------------------------------- /** @@ -76,6 +101,7 @@ public class DefaultSecuritySystem implements SecuritySystem public DefaultSecuritySystem(ConfigurationEntryStoreFactory storeFactory) { store = storeFactory.getStore(AssignedPermission.class, NAME); + readAvailablePermissions(); } //~--- methods -------------------------------------------------------------- @@ -165,9 +191,9 @@ public class DefaultSecuritySystem implements SecuritySystem @Override public List getAvailablePermissions() { + assertIsAdmin(); - // TODO - return Collections.EMPTY_LIST; + return availablePermissions; } /** @@ -220,8 +246,151 @@ public class DefaultSecuritySystem implements SecuritySystem SecurityUtils.getSubject().checkRole(Role.ADMIN); } + /** + * Method description + * + * + * @param context + * @param descriptorUrl + * + * @return + */ + private List parsePermissionDescriptor( + JAXBContext context, URL descriptorUrl) + { + List descriptors = Collections.EMPTY_LIST; + + try + { + PermissionDescriptors descriptorWrapper = + (PermissionDescriptors) context.createUnmarshaller().unmarshal( + descriptorUrl); + + descriptors = descriptorWrapper.getPermissions(); + + logger.debug("found {} permissions at {}", descriptors.size(), + descriptorUrl); + logger.trace("permissions from {}: {}", descriptorUrl, descriptors); + } + catch (JAXBException ex) + { + logger.error("could not parse permission descriptor", ex); + } + + return descriptors; + } + + /** + * Method description + * + */ + private void readAvailablePermissions() + { + Builder builder = ImmutableList.builder(); + + try + { + JAXBContext context = + JAXBContext.newInstance(PermissionDescriptors.class); + + Enumeration descirptorEnum = + getClassLoader().getResources(PERMISSION_DESCRIPTOR); + + while (descirptorEnum.hasMoreElements()) + { + URL descriptorUrl = descirptorEnum.nextElement(); + + logger.debug("read permission descriptor from {}", descriptorUrl); + + builder.addAll(parsePermissionDescriptor(context, descriptorUrl)); + } + } + catch (IOException ex) + { + logger.error("could not read permission descriptors", ex); + } + catch (JAXBException ex) + { + logger.error( + "could not create jaxb context to read permission descriptors", ex); + } + + availablePermissions = builder.build(); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + private ClassLoader getClassLoader() + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + if (classLoader == null) + { + logger.warn("could not find context classloader, use default"); + + classLoader = DefaultSecuritySystem.class.getClassLoader(); + } + + return classLoader; + } + + //~--- inner classes -------------------------------------------------------- + + /** + * Class description + * + * + * @version Enter version here..., 13/04/30 + * @author Enter your name here... + */ + @XmlRootElement(name = "permissions") + @XmlAccessorType(XmlAccessType.FIELD) + private static class PermissionDescriptors + { + + /** + * Constructs ... + * + */ + public PermissionDescriptors() {} + + //~--- get methods -------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public List getPermissions() + { + if (permissions == null) + { + permissions = Collections.EMPTY_LIST; + } + + return permissions; + } + + //~--- fields ------------------------------------------------------------- + + /** Field description */ + @XmlElement(name = "permission") + private List permissions; + } + + //~--- fields --------------------------------------------------------------- /** Field description */ private final ConfigurationEntryStore store; + + /** Field description */ + private List availablePermissions; } diff --git a/scm-webapp/src/main/resources/META-INF/scm/permissions.xml b/scm-webapp/src/main/resources/META-INF/scm/permissions.xml new file mode 100644 index 0000000000..4371b17c5a --- /dev/null +++ b/scm-webapp/src/main/resources/META-INF/scm/permissions.xml @@ -0,0 +1,54 @@ + + + + + + + + All Repository (read) + Read access to all repositories + repository:*:READ + + + + All Repository (write) + Write access to all repositories + repository:*:WRITE + + + + All Repository (owner) + Owner access to all repositories + repository:*:OWNER + + + diff --git a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java index def745f625..80d994c808 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java @@ -30,10 +30,12 @@ */ + package sonia.scm.security; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.inject.Provider; @@ -49,6 +51,8 @@ import org.apache.shiro.subject.PrincipalCollection; import org.junit.Test; +import org.mockito.Mockito; + import sonia.scm.cache.MapCacheManager; import sonia.scm.config.ScmConfiguration; import sonia.scm.group.Group; @@ -75,6 +79,7 @@ import static org.mockito.Mockito.*; //~--- JDK imports ------------------------------------------------------------ import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; @@ -461,9 +466,9 @@ public class ScmRealmTest SecuritySystem securitySystem = mock(SecuritySystem.class); when( - securitySystem.getConfiguration() + securitySystem.getPermissions(Mockito.any()) ).thenReturn( - new SecurityConfiguration() + Collections.EMPTY_LIST ); return new ScmRealm( From 3a398af09b0cc0b2fb73e332ad9f75a261bc349d Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 1 May 2013 13:52:04 +0200 Subject: [PATCH 12/25] added missing getPermission method --- .../sonia/scm/security/SecuritySystem.java | 10 ++++++++ .../scm/security/DefaultSecuritySystem.java | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java b/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java index deaee886a8..66ce48b061 100644 --- a/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java +++ b/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java @@ -102,6 +102,16 @@ public interface SecuritySystem */ public List getAvailablePermissions(); + /** + * Method description + * + * + * @param id + * + * @return + */ + public StoredAssignedPermission getPermission(String id); + /** * Method description * diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java index 47f8612665..cdf9ce26e5 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java @@ -196,6 +196,30 @@ public class DefaultSecuritySystem implements SecuritySystem return availablePermissions; } + /** + * Method description + * + * + * @param id + * + * @return + */ + @Override + public StoredAssignedPermission getPermission(String id) + { + assertIsAdmin(); + + StoredAssignedPermission sap = null; + AssignedPermission ap = store.get(id); + + if (ap != null) + { + sap = new StoredAssignedPermission(id, ap); + } + + return sap; + } + /** * Method description * From e2450d114ae66ddee6f61abfdf84d0205808f913 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 1 May 2013 14:11:45 +0200 Subject: [PATCH 13/25] added rest api for new security system --- .../java/sonia/scm/api/rest/Permission.java | 167 +++++++++++ .../resources/AbstractPermissionResource.java | 265 ++++++++++++++++++ .../resources/GroupPermissionResource.java | 135 +++++++++ ...ource.java => SecuritySystemResource.java} | 65 ++--- .../resources/UserPermissionResource.java | 135 +++++++++ 5 files changed, 720 insertions(+), 47 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/Permission.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractPermissionResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupPermissionResource.java rename scm-webapp/src/main/java/sonia/scm/api/rest/resources/{SecurityConfigurationResource.java => SecuritySystemResource.java} (64%) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserPermissionResource.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/Permission.java b/scm-webapp/src/main/java/sonia/scm/api/rest/Permission.java new file mode 100644 index 0000000000..bf6437e97c --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/Permission.java @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2010, 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.api.rest; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Objects; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.Serializable; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @author Sebastian Sdorra + */ +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class Permission implements Serializable +{ + + /** Field description */ + private static final long serialVersionUID = 4320217034601679261L; + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + */ + public Permission() {} + + /** + * Constructs ... + * + * + * @param id + * @param value + */ + public Permission(String id, String value) + { + this.id = id; + this.value = value; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param obj + * + * @return + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final Permission other = (Permission) obj; + + return Objects.equal(id, other.id) && Objects.equal(value, other.value); + } + + /** + * Method description + * + * + * @return + */ + @Override + public int hashCode() + { + return Objects.hashCode(id, value); + } + + /** + * Method description + * + * + * @return + */ + @Override + public String toString() + { + //J- + return Objects.toStringHelper(this) + .add("id", id) + .add("value", value) + .toString(); + //J+ + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public String getId() + { + return id; + } + + /** + * Method description + * + * + * @return + */ + public String getValue() + { + return value; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private String id; + + /** Field description */ + private String value; +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractPermissionResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractPermissionResource.java new file mode 100644 index 0000000000..297145475c --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractPermissionResource.java @@ -0,0 +1,265 @@ +/** + * Copyright (c) 2010, 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.api.rest.resources; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Lists; + +import sonia.scm.api.rest.Permission; +import sonia.scm.security.AssignedPermission; +import sonia.scm.security.SecuritySystem; +import sonia.scm.security.StoredAssignedPermission; + +//~--- JDK imports ------------------------------------------------------------ + +import java.net.URI; + +import java.util.List; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; + +/** + * + * @author Sebastian Sdorra + * @since 1.31 + */ +public abstract class AbstractPermissionResource +{ + + /** + * Constructs ... + * + * + * @param securitySystem + * @param name + */ + protected AbstractPermissionResource(SecuritySystem securitySystem, + String name) + { + this.securitySystem = securitySystem; + this.name = name; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param permission + * + * @return + */ + protected abstract AssignedPermission transformPermission( + Permission permission); + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + protected abstract Predicate getPredicate(); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param uriInfo + * @param permission + * + * @return + */ + @POST + @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + public Response add(@Context UriInfo uriInfo, Permission permission) + { + AssignedPermission ap = transformPermission(permission); + StoredAssignedPermission sap = securitySystem.addPermission(ap); + URI uri = uriInfo.getAbsolutePathBuilder().path(sap.getId()).build(); + + return Response.created(uri).build(); + } + + /** + * Method description + * + * + * @param id + * + * @return + */ + @DELETE + @Path("{id}") + public Response delete(@PathParam("id") String id) + { + StoredAssignedPermission sap = getPermission(id); + + securitySystem.deletePermission(sap); + + return Response.noContent().build(); + } + + /** + * Method description + * + * + * @param id + * @param permission + * + * @return + */ + @PUT + @Path("{id}") + @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + public Response update(@PathParam("id") String id, Permission permission) + { + StoredAssignedPermission sap = getPermission(id); + + securitySystem.modifyPermission(new StoredAssignedPermission(sap.getId(), + transformPermission(permission))); + + return Response.noContent().build(); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param id + * + * @return + */ + @GET + @Path("{id}") + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + public Permission get(@PathParam("id") String id) + { + StoredAssignedPermission sap = getPermission(id); + + return new Permission(sap.getId(), sap.getPermission()); + } + + /** + * Method description + * + * + * @return + */ + @GET + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + public List getAll() + { + return getPermissions(getPredicate()); + } + + /** + * Method description + * + * + * @param id + * + * @return + */ + private StoredAssignedPermission getPermission(String id) + { + StoredAssignedPermission sap = securitySystem.getPermission(id); + + if (sap == null) + { + throw new WebApplicationException(Status.NOT_FOUND); + } + + if (!getPredicate().apply(sap)) + { + throw new WebApplicationException(Status.BAD_REQUEST); + } + + return sap; + } + + /** + * Method description + * + * + * @param predicate + * + * @return + */ + private List getPermissions( + Predicate predicate) + { + List permissions = + securitySystem.getPermissions(predicate); + + return Lists.transform(permissions, + new Function() + { + + @Override + public Permission apply(StoredAssignedPermission mgp) + { + return new Permission(mgp.getId(), mgp.getPermission()); + } + }); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + protected String name; + + /** Field description */ + private SecuritySystem securitySystem; +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupPermissionResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupPermissionResource.java new file mode 100644 index 0000000000..6468397f4a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupPermissionResource.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2010, 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.api.rest.resources; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Predicate; + +import sonia.scm.api.rest.Permission; +import sonia.scm.security.AssignedPermission; +import sonia.scm.security.SecuritySystem; + +/** + * + * @author Sebastian Sdorra + */ +public class GroupPermissionResource extends AbstractPermissionResource +{ + + /** + * Constructs ... + * + * + * @param securitySystem + * @param name + */ + public GroupPermissionResource(SecuritySystem securitySystem, String name) + { + super(securitySystem, name); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param permission + * + * @return + */ + @Override + protected AssignedPermission transformPermission(Permission permission) + { + return new AssignedPermission(name, true, permission.getValue()); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @Override + protected Predicate getPredicate() + { + return new GroupPredicate(name); + } + + //~--- inner classes -------------------------------------------------------- + + /** + * Class description + * + * + * @version Enter version here..., 13/05/01 + * @author Enter your name here... + */ + private static class GroupPredicate implements Predicate + { + + /** + * Constructs ... + * + * + * @param name + */ + public GroupPredicate(String name) + { + this.name = name; + } + + //~--- methods ------------------------------------------------------------ + + /** + * Method description + * + * + * @param input + * + * @return + */ + @Override + public boolean apply(AssignedPermission input) + { + return input.isGroupPermission() && input.getName().equals(name); + } + + //~--- fields ------------------------------------------------------------- + + /** Field description */ + private String name; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecurityConfigurationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecuritySystemResource.java similarity index 64% rename from scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecurityConfigurationResource.java rename to scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecuritySystemResource.java index 332f1c1bf5..6f9e638b14 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecurityConfigurationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecuritySystemResource.java @@ -37,32 +37,20 @@ import com.google.inject.Inject; import org.apache.shiro.SecurityUtils; -import org.codehaus.enunciate.jaxrs.TypeHint; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import sonia.scm.security.Role; -import sonia.scm.security.SecurityConfiguration; import sonia.scm.security.SecuritySystem; //~--- JDK imports ------------------------------------------------------------ -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import javax.ws.rs.PathParam; /** * * @author Sebastian Sdorra */ -@Path("security") -@ExternallyManagedLifecycle -public class SecurityConfigurationResource +@Path("security/permission") +public class SecuritySystemResource { /** @@ -72,9 +60,12 @@ public class SecurityConfigurationResource * @param system */ @Inject - public SecurityConfigurationResource(SecuritySystem system) + public SecuritySystemResource(SecuritySystem system) { this.system = system; + + // only administrators can use this resource + SecurityUtils.getSubject().checkRole(Role.ADMIN); } //~--- get methods ---------------------------------------------------------- @@ -83,50 +74,30 @@ public class SecurityConfigurationResource * Method description * * + * @param group + * * @return */ - @GET - @TypeHint(SecurityConfiguration.class) - @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) - public Response getConfiguration() + @Path("group/{group}") + public GroupPermissionResource getGroupSubResource( + @PathParam("group") String group) { - Response response = null; - - if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) - { - response = Response.ok(system.getConfiguration()).build(); - } - else - { - response = Response.status(Response.Status.FORBIDDEN).build(); - } - - return response; + return new GroupPermissionResource(system, group); } - //~--- set methods ---------------------------------------------------------- - /** * Method description * * - * @param uriInfo - * @param newConfig + * @param user * * @return */ - @POST - @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) - public Response setConfig(@Context UriInfo uriInfo, - SecurityConfiguration newConfig) + @Path("user/{user}") + public UserPermissionResource getUserSubResource( + @PathParam("user") String user) { - - // TODO replace by checkRole - SecurityUtils.getSubject().checkRole(Role.ADMIN); - - system.setConfiguration(newConfig); - - return Response.created(uriInfo.getRequestUri()).build(); + return new UserPermissionResource(system, user); } //~--- fields --------------------------------------------------------------- diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserPermissionResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserPermissionResource.java new file mode 100644 index 0000000000..da1ae99512 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserPermissionResource.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2010, 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.api.rest.resources; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Predicate; + +import sonia.scm.api.rest.Permission; +import sonia.scm.security.AssignedPermission; +import sonia.scm.security.SecuritySystem; + +/** + * + * @author Sebastian Sdorra + */ +public class UserPermissionResource extends AbstractPermissionResource +{ + + /** + * Constructs ... + * + * + * @param securitySystem + * @param name + */ + public UserPermissionResource(SecuritySystem securitySystem, String name) + { + super(securitySystem, name); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param permission + * + * @return + */ + @Override + protected AssignedPermission transformPermission(Permission permission) + { + return new AssignedPermission(name, permission.getValue()); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @Override + protected Predicate getPredicate() + { + return new UserPredicate(name); + } + + //~--- inner classes -------------------------------------------------------- + + /** + * Class description + * + * + * @version Enter version here..., 13/05/01 + * @author Enter your name here... + */ + private static class UserPredicate implements Predicate + { + + /** + * Constructs ... + * + * + * @param name + */ + public UserPredicate(String name) + { + this.name = name; + } + + //~--- methods ------------------------------------------------------------ + + /** + * Method description + * + * + * @param input + * + * @return + */ + @Override + public boolean apply(AssignedPermission input) + { + return !input.isGroupPermission() && input.getName().equals(name); + } + + //~--- fields ------------------------------------------------------------- + + /** Field description */ + private String name; + } +} From 6b3f57dd45c76da1aac9346a91243643d535e88f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 1 May 2013 18:28:35 +0200 Subject: [PATCH 14/25] added missing id parameter for log message --- .../main/java/sonia/scm/store/JAXBConfigurationEntryStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java index b8a02b68ea..6eeb25d96b 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java @@ -215,7 +215,7 @@ public class JAXBConfigurationEntryStore @Override public V get(String id) { - logger.trace("get item {} from configuration store"); + logger.trace("get item {} from configuration store", id); return entries.get(id); } From 3f9cc02ebb1f0d037c6b01f77e78d25654562160 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 1 May 2013 18:45:21 +0200 Subject: [PATCH 15/25] implementing ui for global group permissions --- .../resources/js/group/sonia.group.grid.js | 4 + .../sonia.security.permissionspanel.js | 180 +++++++++++++++++- .../src/main/webapp/resources/js/sonia.scm.js | 14 -- 3 files changed, 183 insertions(+), 15 deletions(-) diff --git a/scm-webapp/src/main/webapp/resources/js/group/sonia.group.grid.js b/scm-webapp/src/main/webapp/resources/js/group/sonia.group.grid.js index 17e2f90d9a..c82388d8c6 100644 --- a/scm-webapp/src/main/webapp/resources/js/group/sonia.group.grid.js +++ b/scm-webapp/src/main/webapp/resources/js/group/sonia.group.grid.js @@ -151,6 +151,10 @@ Sonia.group.Grid = Ext.extend(Sonia.rest.Grid, { scope: this } } + },{ + item: group, + xtype: 'permissionsPanel', + baseUrl: 'security/permission/group/' + group.name }]); } diff --git a/scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js b/scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js index 4d9940f3bb..1d4bbe9367 100644 --- a/scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js +++ b/scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js @@ -33,16 +33,194 @@ Sonia.security.PermissionsPanel = Ext.extend(Ext.Panel, { //TODO i18n titleText: 'Permissions', + + permissionStore: null, + baseUrl: null, + + addIcon: 'resources/images/add.png', + removeIcon: 'resources/images/delete.png', + helpIcon: 'resources/images/help.png', initComponent: function(){ + this.permissionStore = new Sonia.rest.JsonStore({ + proxy: new Ext.data.HttpProxy({ + api: { + read: restUrl + this.baseUrl + '.json' + }, + disableCaching: false + }), + idProperty: 'id', + fields: [ 'id', 'value'], + listeners: { + update: { + fn: this.modifyOrAddPermission, + scope: this + }, + remove: { + fn: this.removePermission, + scope: this + } + } + }); + + var availablePermissionStore = new Ext.data.JsonStore({ + fields: ['display-name', 'description', 'value'], + sortInfo: { + field: 'display-name' + } + }); + availablePermissionStore.loadData(state.availablePermissions); + + var permissionColModel = new Ext.grid.ColumnModel({ + columns: [{ + id: 'value', + header: 'Permission', + dataIndex: 'value', + renderer: this.renderPermission, + editor: new Ext.form.ComboBox({ + store: availablePermissionStore, + displayField: 'display-name', + valueField: 'value', + typeAhead: false, + editable: false, + triggerAction: 'all', + mode: 'local', + width: 250 + }) + }] + }); + + var selectionModel = new Ext.grid.RowSelectionModel({ + singleSelect: true + }); + var config = { title: this.titleText, - bodyCssClass: 'x-panel-mc' + bodyCssClass: 'x-panel-mc', + items: [{ + id: 'permissionGrid', + xtype: 'editorgrid', + clicksToEdit: 1, + frame: true, + width: '100%', + autoHeight: true, + autoScroll: false, + colModel: permissionColModel, + sm: selectionModel, + store: this.permissionStore, + viewConfig: { + forceFit: true, + markDirty: false + }, + tbar: [{ + text: this.addText, + scope: this, + icon: this.addIcon, + handler : function(){ + var Permission = this.permissionStore.recordType; + var grid = Ext.getCmp('permissionGrid'); + grid.stopEditing(); + this.permissionStore.insert(0, new Permission()); + grid.startEditing(0, 0); + } + },{ + text: this.removeText, + scope: this, + icon: this.removeIcon, + handler: function(){ + var grid = Ext.getCmp('permissionGrid'); + var selected = grid.getSelectionModel().getSelected(); + if ( selected ){ + this.permissionStore.remove(selected); + } + } + },'->',{ + id: 'permissionGridHelp', + xtype: 'box', + autoEl: { + tag: 'img', + src: this.helpIcon + } + }] + }] }; Ext.apply(this, Ext.apply(this.initialConfig, config)); Sonia.security.PermissionsPanel.superclass.initComponent.apply(this, arguments); + }, + + getIdFromResponse: function(response){ + var id = null; + var location = response.getResponseHeader('Location'); + if (location){ + var parts = location.split('/'); + id = parts[parts.length - 1]; + } + return id; + }, + + modifyOrAddPermission: function(store, record){ + console.log('------ modify --------'); + var id = record.get('id'); + if ( id ){ + this.modifyPermission(id, record); + } else { + this.addPermission(record); + } + }, + + addPermission: function(record){ + Ext.Ajax.request({ + url: restUrl + this.baseUrl + '.json', + method: 'POST', + jsonData: record.data, + scope: this, + success: function(response){ + var id = this.getIdFromResponse(response); + record.data.id = id; + }, + failure: function(result){ + } + }); + }, + + modifyPermission: function(id, record){ + Ext.Ajax.request({ + url: restUrl + this.baseUrl + '/' + id + '.json', + method: 'PUT', + jsonData: record.data, + scope: this, + success: function(){ + }, + failure: function(result){ + } + }); + }, + + removePermission: function(store, record, index){ + console.log('------ remove --------'); + Ext.Ajax.request({ + url: restUrl + this.baseUrl + '/' + record.get('id') + '.json', + method: 'DELETE', + scope: this, + success: function(){ + + }, + failure: function(result){ + } + }); + }, + + renderPermission: function(value){ + var ap = state.availablePermissions; + for (var i=0; i Date: Wed, 1 May 2013 19:17:43 +0200 Subject: [PATCH 16/25] start implementation of ui for global user permissions --- .../js/group/sonia.group.formpanel.js | 2 +- .../resources/js/group/sonia.group.grid.js | 3 +- .../webapp/resources/js/group/sonia.group.js | 4 +-- .../js/group/sonia.group.memberformpanel.js | 4 +-- .../resources/js/group/sonia.group.panel.js | 4 +-- .../group/sonia.group.propertiesformpanel.js | 4 +-- .../resources/js/user/sonia.user.formpanel.js | 6 ++-- .../resources/js/user/sonia.user.grid.js | 7 ++-- .../webapp/resources/js/user/sonia.user.js | 10 ++++-- .../resources/js/user/sonia.user.panel.js | 32 ++++++++----------- 10 files changed, 39 insertions(+), 37 deletions(-) diff --git a/scm-webapp/src/main/webapp/resources/js/group/sonia.group.formpanel.js b/scm-webapp/src/main/webapp/resources/js/group/sonia.group.formpanel.js index 5242342050..5fb96830b2 100644 --- a/scm-webapp/src/main/webapp/resources/js/group/sonia.group.formpanel.js +++ b/scm-webapp/src/main/webapp/resources/js/group/sonia.group.formpanel.js @@ -47,7 +47,7 @@ Sonia.group.FormPanel = Ext.extend(Sonia.rest.FormPanel,{ initComponent: function(){ this.addEvents('preCreate', 'created', 'preUpdate', 'updated', 'updateFailed', 'creationFailed'); - Sonia.repository.FormPanel.superclass.initComponent.apply(this, arguments); + Sonia.group.FormPanel.superclass.initComponent.apply(this, arguments); }, update: function(group){ diff --git a/scm-webapp/src/main/webapp/resources/js/group/sonia.group.grid.js b/scm-webapp/src/main/webapp/resources/js/group/sonia.group.grid.js index c82388d8c6..c6be5f151a 100644 --- a/scm-webapp/src/main/webapp/resources/js/group/sonia.group.grid.js +++ b/scm-webapp/src/main/webapp/resources/js/group/sonia.group.grid.js @@ -105,7 +105,7 @@ Sonia.group.Grid = Ext.extend(Sonia.rest.Grid, { renderMembers: function(members){ var out = ''; - if ( members != null ){ + if ( members !== null ){ var s = members.length; for ( var i=0; i Date: Wed, 1 May 2013 20:26:21 +0200 Subject: [PATCH 17/25] improve user interface --- .../resources/js/group/sonia.group.panel.js | 39 +++++++++---------- .../resources/js/rest/sonia.rest.formpanel.js | 6 +-- .../resources/js/rest/sonia.rest.panel.js | 3 +- .../sonia.security.permissionspanel.js | 22 +++++++---- .../resources/js/user/sonia.user.formpanel.js | 2 +- .../resources/js/user/sonia.user.panel.js | 2 - 6 files changed, 39 insertions(+), 35 deletions(-) diff --git a/scm-webapp/src/main/webapp/resources/js/group/sonia.group.panel.js b/scm-webapp/src/main/webapp/resources/js/group/sonia.group.panel.js index f0e11349fa..e57e00e6fa 100644 --- a/scm-webapp/src/main/webapp/resources/js/group/sonia.group.panel.js +++ b/scm-webapp/src/main/webapp/resources/js/group/sonia.group.panel.js @@ -50,26 +50,25 @@ Sonia.group.Panel = Ext.extend(Sonia.rest.Panel, { {xtype: 'tbbutton', text: this.reloadText, icon: this.reloadIcon, scope: this, handler: this.reload} ], items: [{ - id: 'groupGrid', - xtype: 'groupGrid', - region: 'center', - parentPanel: this - }, { - id: 'groupEditPanel', - xtype: 'tabpanel', - activeTab: 0, - height: 250, - split: true, - border: true, - region: 'south', - items: [{ - bodyCssClass: 'x-panel-mc', - title: this.titleText, - padding: 5, - html: this.emptyText - }] - } - ] + id: 'groupGrid', + xtype: 'groupGrid', + region: 'center', + parentPanel: this + }, { + id: 'groupEditPanel', + xtype: 'tabpanel', + activeTab: 0, + height: 250, + split: true, + border: true, + region: 'south', + items: [{ + bodyCssClass: 'x-panel-mc', + title: this.titleText, + padding: 5, + html: this.emptyText + }] + }] }; Ext.apply(this, Ext.apply(this.initialConfig, config)); diff --git a/scm-webapp/src/main/webapp/resources/js/rest/sonia.rest.formpanel.js b/scm-webapp/src/main/webapp/resources/js/rest/sonia.rest.formpanel.js index 4ae530c0bd..adaea25a7a 100644 --- a/scm-webapp/src/main/webapp/resources/js/rest/sonia.rest.formpanel.js +++ b/scm-webapp/src/main/webapp/resources/js/rest/sonia.rest.formpanel.js @@ -56,12 +56,12 @@ Sonia.rest.FormPanel = Ext.extend(Ext.FormPanel,{ {text: this.okText, formBind: true, scope: this, handler: this.submit}, {text: this.cancelText, scope: this, handler: this.cancel} ] - } + }; Ext.apply(this, Ext.apply(this.initialConfig, config)); Sonia.rest.FormPanel.superclass.initComponent.apply(this, arguments); - if ( this.item != null ){ + if ( this.item !== null ){ this.loadData(this.item); } }, @@ -77,7 +77,7 @@ Sonia.rest.FormPanel = Ext.extend(Ext.FormPanel,{ console.debug( 'form submitted' ); } var item = this.getForm().getFieldValues(); - if ( this.item != null ){ + if ( this.item !== null ){ this.update(item); } else { this.create(item); diff --git a/scm-webapp/src/main/webapp/resources/js/rest/sonia.rest.panel.js b/scm-webapp/src/main/webapp/resources/js/rest/sonia.rest.panel.js index 73c367fd85..210cedb9a7 100644 --- a/scm-webapp/src/main/webapp/resources/js/rest/sonia.rest.panel.js +++ b/scm-webapp/src/main/webapp/resources/js/rest/sonia.rest.panel.js @@ -39,6 +39,7 @@ Sonia.rest.Panel = Ext.extend(Ext.Panel, { addIcon: 'resources/images/add.png', removeIcon: 'resources/images/delete.png', reloadIcon: 'resources/images/reload.png', + helpIcon: 'resources/images/help.png', initComponent: function(){ @@ -48,7 +49,7 @@ Sonia.rest.Panel = Ext.extend(Ext.Panel, { enableTabScroll: true, region:'center', autoScroll: true - } + }; Ext.apply(this, Ext.apply(this.initialConfig, config)); Sonia.rest.Panel.superclass.initComponent.apply(this, arguments); diff --git a/scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js b/scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js index 1d4bbe9367..bf0ac985be 100644 --- a/scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js +++ b/scm-webapp/src/main/webapp/resources/js/security/sonia.security.permissionspanel.js @@ -31,16 +31,23 @@ Sonia.security.PermissionsPanel = Ext.extend(Ext.Panel, { + addText: 'Add', + removeText: 'Remove', + reloadText: 'Reload', + + // icons + addIcon: 'resources/images/add.png', + removeIcon: 'resources/images/delete.png', + reloadIcon: 'resources/images/reload.png', + helpIcon: 'resources/images/help.png', + + //TODO i18n titleText: 'Permissions', permissionStore: null, baseUrl: null, - addIcon: 'resources/images/add.png', - removeIcon: 'resources/images/delete.png', - helpIcon: 'resources/images/help.png', - initComponent: function(){ this.permissionStore = new Sonia.rest.JsonStore({ @@ -98,6 +105,7 @@ Sonia.security.PermissionsPanel = Ext.extend(Ext.Panel, { var config = { title: this.titleText, bodyCssClass: 'x-panel-mc', + padding: 5, items: [{ id: 'permissionGrid', xtype: 'editorgrid', @@ -145,7 +153,7 @@ Sonia.security.PermissionsPanel = Ext.extend(Ext.Panel, { }] }] }; - + Ext.apply(this, Ext.apply(this.initialConfig, config)); Sonia.security.PermissionsPanel.superclass.initComponent.apply(this, arguments); }, @@ -161,7 +169,6 @@ Sonia.security.PermissionsPanel = Ext.extend(Ext.Panel, { }, modifyOrAddPermission: function(store, record){ - console.log('------ modify --------'); var id = record.get('id'); if ( id ){ this.modifyPermission(id, record); @@ -198,8 +205,7 @@ Sonia.security.PermissionsPanel = Ext.extend(Ext.Panel, { }); }, - removePermission: function(store, record, index){ - console.log('------ remove --------'); + removePermission: function(store, record){ Ext.Ajax.request({ url: restUrl + this.baseUrl + '/' + record.get('id') + '.json', method: 'DELETE', diff --git a/scm-webapp/src/main/webapp/resources/js/user/sonia.user.formpanel.js b/scm-webapp/src/main/webapp/resources/js/user/sonia.user.formpanel.js index f632b85c8d..c52f262de4 100644 --- a/scm-webapp/src/main/webapp/resources/js/user/sonia.user.formpanel.js +++ b/scm-webapp/src/main/webapp/resources/js/user/sonia.user.formpanel.js @@ -109,7 +109,7 @@ Sonia.user.FormPanel = Ext.extend(Sonia.rest.FormPanel,{ helpText: this.activeHelpText, checked: true }); - + Ext.apply(this, Ext.apply(this.initialConfig, {items: items})); Sonia.user.FormPanel.superclass.initComponent.apply(this, arguments); }, diff --git a/scm-webapp/src/main/webapp/resources/js/user/sonia.user.panel.js b/scm-webapp/src/main/webapp/resources/js/user/sonia.user.panel.js index 8b0eee08ac..3341890f94 100644 --- a/scm-webapp/src/main/webapp/resources/js/user/sonia.user.panel.js +++ b/scm-webapp/src/main/webapp/resources/js/user/sonia.user.panel.js @@ -43,9 +43,7 @@ Sonia.user.Panel = Ext.extend(Sonia.rest.Panel, { userGrid: null, initComponent: function(){ - var config = { - bodyCssClass: 'x-panel-mc', tbar: [ {xtype: 'tbbutton', text: this.addText, icon: this.addIcon, scope: this, handler: this.showAddPanel}, {xtype: 'tbbutton', text: this.removeText, icon: this.removeIcon, scope: this, handler: this.removeUser}, From 88e9e83647d1c4b0910f51739f2dec0914f2c2c2 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 1 May 2013 20:57:47 +0200 Subject: [PATCH 18/25] remove permission objects if a group or a user is deleted --- .../scm/security/DefaultSecuritySystem.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java index cdf9ce26e5..b3d4be0c1a 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java @@ -36,6 +36,7 @@ package sonia.scm.security; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; +import com.google.common.eventbus.Subscribe; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -45,8 +46,12 @@ import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.HandlerEvent; +import sonia.scm.event.Subscriber; +import sonia.scm.group.GroupEvent; import sonia.scm.store.ConfigurationEntryStore; import sonia.scm.store.ConfigurationEntryStoreFactory; +import sonia.scm.user.UserEvent; //~--- JDK imports ------------------------------------------------------------ @@ -73,6 +78,7 @@ import javax.xml.bind.annotation.XmlRootElement; * @since 1.31 */ @Singleton +@Subscriber(async = true) public class DefaultSecuritySystem implements SecuritySystem { @@ -150,6 +156,54 @@ public class DefaultSecuritySystem implements SecuritySystem store.remove(id); } + /** + * Method description + * + * + * @param event + */ + @Subscribe + public void handleEvent(final UserEvent event) + { + if (event.getEventType() == HandlerEvent.DELETE) + { + deletePermissions(new Predicate() + { + + @Override + public boolean apply(AssignedPermission p) + { + return !p.isGroupPermission() + && event.getItem().getName().equals(p.getName()); + } + }); + } + } + + /** + * Method description + * + * + * @param event + */ + @Subscribe + public void handleEvent(final GroupEvent event) + { + if (event.getEventType() == HandlerEvent.DELETE) + { + deletePermissions(new Predicate() + { + + @Override + public boolean apply(AssignedPermission p) + { + return p.isGroupPermission() + && event.getItem().getName().equals(p.getName()); + } + }); + } + } + /** * Method description * @@ -270,6 +324,22 @@ public class DefaultSecuritySystem implements SecuritySystem SecurityUtils.getSubject().checkRole(Role.ADMIN); } + /** + * Method description + * + * + * @param predicate + */ + private void deletePermissions(Predicate predicate) + { + List permissions = getPermissions(predicate); + + for (StoredAssignedPermission permission : permissions) + { + deletePermission(permission); + } + } + /** * Method description * From b7b6c945742ce7bc48c15cfba717998ce4f2a57c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 1 May 2013 21:09:40 +0200 Subject: [PATCH 19/25] implementing events for security system --- .../store/StoredAssignedPermissionEvent.java | 91 +++++++++++++++++++ .../scm/security/DefaultSecuritySystem.java | 33 ++++++- 2 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/store/StoredAssignedPermissionEvent.java diff --git a/scm-core/src/main/java/sonia/scm/store/StoredAssignedPermissionEvent.java b/scm-core/src/main/java/sonia/scm/store/StoredAssignedPermissionEvent.java new file mode 100644 index 0000000000..821262caef --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/store/StoredAssignedPermissionEvent.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010, 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.store; + +//~--- non-JDK imports -------------------------------------------------------- + +import sonia.scm.HandlerEvent; +import sonia.scm.security.StoredAssignedPermission; + +/** + * + * @author Sebastian Sdorra + */ +public class StoredAssignedPermissionEvent +{ + + /** + * Constructs ... + * + * + * @param type + * @param permission + */ + public StoredAssignedPermissionEvent(HandlerEvent type, + StoredAssignedPermission permission) + { + this.type = type; + this.permission = permission; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public HandlerEvent getEventType() + { + return type; + } + + /** + * Method description + * + * + * @return + */ + public StoredAssignedPermission getPermission() + { + return permission; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private StoredAssignedPermission permission; + + /** Field description */ + private HandlerEvent type; +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java index b3d4be0c1a..1cb2fda6b2 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java @@ -47,10 +47,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.HandlerEvent; +import sonia.scm.event.ScmEventBus; import sonia.scm.event.Subscriber; import sonia.scm.group.GroupEvent; import sonia.scm.store.ConfigurationEntryStore; import sonia.scm.store.ConfigurationEntryStoreFactory; +import sonia.scm.store.StoredAssignedPermissionEvent; import sonia.scm.user.UserEvent; //~--- JDK imports ------------------------------------------------------------ @@ -127,7 +129,15 @@ public class DefaultSecuritySystem implements SecuritySystem String id = store.put(permission); - return new StoredAssignedPermission(id, permission); + StoredAssignedPermission sap = new StoredAssignedPermission(id, permission); + + //J- + ScmEventBus.getInstance().post( + new StoredAssignedPermissionEvent(HandlerEvent.CREATE, sap) + ); + //J+ + + return sap; } /** @@ -140,7 +150,12 @@ public class DefaultSecuritySystem implements SecuritySystem public void deletePermission(StoredAssignedPermission permission) { assertIsAdmin(); - deletePermission(permission.getId()); + store.remove(permission.getId()); + //J- + ScmEventBus.getInstance().post( + new StoredAssignedPermissionEvent(HandlerEvent.CREATE, permission) + ); + //J+ } /** @@ -153,7 +168,13 @@ public class DefaultSecuritySystem implements SecuritySystem public void deletePermission(String id) { assertIsAdmin(); - store.remove(id); + + AssignedPermission ap = store.get(id); + + if (ap != null) + { + deletePermission(new StoredAssignedPermission(id, ap)); + } } /** @@ -220,6 +241,12 @@ public class DefaultSecuritySystem implements SecuritySystem store.remove(permission.getId()); store.put(permission.getId(), new AssignedPermission(permission)); } + + //J- + ScmEventBus.getInstance().post( + new StoredAssignedPermissionEvent(HandlerEvent.CREATE, permission) + ); + //J+ } //~--- get methods ---------------------------------------------------------- From 5700f9ed064a580b08f8d825f551e961e72c22e0 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 1 May 2013 21:10:21 +0200 Subject: [PATCH 20/25] clear cache if permission has changed --- .../java/sonia/scm/security/ScmRealm.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java index 8bd9286fb9..b08160049f 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java @@ -76,6 +76,7 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryDAO; import sonia.scm.repository.RepositoryEvent; import sonia.scm.repository.RepositoryManager; +import sonia.scm.store.StoredAssignedPermissionEvent; import sonia.scm.user.User; import sonia.scm.user.UserDAO; import sonia.scm.user.UserEvent; @@ -198,6 +199,27 @@ public class ScmRealm extends AuthorizingRealm } } + /** + * Method description + * + * + * @param event + */ + @Subscribe + public void onEvent(StoredAssignedPermissionEvent event) + { + if (event.getEventType().isPost()) + { + if (logger.isDebugEnabled()) + { + logger.debug("clear cache, because permission {} has changed", + event.getPermission().getId()); + } + + cache.clear(); + } + } + /** * Method description * From f4b0aa37da63dfc5b0e64b76d313307a65fb164a Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 2 May 2013 08:16:02 +0200 Subject: [PATCH 21/25] move event to correct package, serializable, toString, equals and hashCode --- .../scm/security/AssignedPermission.java | 73 +++++++++++++++++- .../security/StoredAssignedPermission.java | 5 ++ .../StoredAssignedPermissionEvent.java | 76 ++++++++++++++++++- .../scm/security/DefaultSecuritySystem.java | 1 - .../java/sonia/scm/security/ScmRealm.java | 1 - 5 files changed, 150 insertions(+), 6 deletions(-) rename scm-core/src/main/java/sonia/scm/{store => security}/StoredAssignedPermissionEvent.java (64%) diff --git a/scm-core/src/main/java/sonia/scm/security/AssignedPermission.java b/scm-core/src/main/java/sonia/scm/security/AssignedPermission.java index 15ff1b7622..28e6780be0 100644 --- a/scm-core/src/main/java/sonia/scm/security/AssignedPermission.java +++ b/scm-core/src/main/java/sonia/scm/security/AssignedPermission.java @@ -31,8 +31,14 @@ package sonia.scm.security; +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Objects; + //~--- JDK imports ------------------------------------------------------------ +import java.io.Serializable; + import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -45,9 +51,14 @@ import javax.xml.bind.annotation.XmlRootElement; */ @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "assigned-permission") -public class AssignedPermission implements PermissionObject +public class AssignedPermission implements PermissionObject, Serializable { + /** Field description */ + private static final long serialVersionUID = -7411338422110323879L; + + //~--- constructors --------------------------------------------------------- + /** * Constructor is only visible for JAXB. * @@ -96,6 +107,66 @@ public class AssignedPermission implements PermissionObject this.permission = permission; } + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param obj + * + * @return + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final AssignedPermission other = (AssignedPermission) obj; + + return Objects.equal(name, other.name) + && Objects.equal(groupPermission, other.groupPermission) + && Objects.equal(permission, other.permission); + } + + /** + * Method description + * + * + * @return + */ + @Override + public int hashCode() + { + return Objects.hashCode(name, groupPermission, permission); + } + + /** + * Method description + * + * + * @return + */ + @Override + public String toString() + { + //J- + return Objects.toStringHelper(this) + .add("name", name) + .add("groupPermisison", groupPermission) + .add("permission", permission) + .toString(); + //J+ + } + //~--- get methods ---------------------------------------------------------- /** diff --git a/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java index e740c39187..74bf69f651 100644 --- a/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java +++ b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java @@ -47,6 +47,11 @@ import javax.xml.bind.annotation.XmlRootElement; public class StoredAssignedPermission extends AssignedPermission { + /** Field description */ + private static final long serialVersionUID = -4593919877023168090L; + + //~--- constructors --------------------------------------------------------- + /** * Constructor is only visible for JAXB. * diff --git a/scm-core/src/main/java/sonia/scm/store/StoredAssignedPermissionEvent.java b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermissionEvent.java similarity index 64% rename from scm-core/src/main/java/sonia/scm/store/StoredAssignedPermissionEvent.java rename to scm-core/src/main/java/sonia/scm/security/StoredAssignedPermissionEvent.java index 821262caef..8535484de4 100644 --- a/scm-core/src/main/java/sonia/scm/store/StoredAssignedPermissionEvent.java +++ b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermissionEvent.java @@ -29,20 +29,31 @@ -package sonia.scm.store; +package sonia.scm.security; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Objects; + import sonia.scm.HandlerEvent; -import sonia.scm.security.StoredAssignedPermission; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.Serializable; /** * * @author Sebastian Sdorra + * @since 1.31 */ -public class StoredAssignedPermissionEvent +public final class StoredAssignedPermissionEvent implements Serializable { + /** Field description */ + private static final long serialVersionUID = 706824497813169009L; + + //~--- constructors --------------------------------------------------------- + /** * Constructs ... * @@ -57,6 +68,65 @@ public class StoredAssignedPermissionEvent this.permission = permission; } + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param obj + * + * @return + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final StoredAssignedPermissionEvent other = + (StoredAssignedPermissionEvent) obj; + + return Objects.equal(type, other.type) + && Objects.equal(permission, other.permission); + } + + /** + * Method description + * + * + * @return + */ + @Override + public int hashCode() + { + return Objects.hashCode(type, permission); + } + + /** + * Method description + * + * + * @return + */ + @Override + public String toString() + { + //J- + return Objects.toStringHelper(this) + .add("type", type) + .add("permission", permission) + .toString(); + //J+ + } + //~--- get methods ---------------------------------------------------------- /** diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java index 1cb2fda6b2..2383e8c574 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java @@ -52,7 +52,6 @@ import sonia.scm.event.Subscriber; import sonia.scm.group.GroupEvent; import sonia.scm.store.ConfigurationEntryStore; import sonia.scm.store.ConfigurationEntryStoreFactory; -import sonia.scm.store.StoredAssignedPermissionEvent; import sonia.scm.user.UserEvent; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java index b08160049f..7f4c0d91fc 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java @@ -76,7 +76,6 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryDAO; import sonia.scm.repository.RepositoryEvent; import sonia.scm.repository.RepositoryManager; -import sonia.scm.store.StoredAssignedPermissionEvent; import sonia.scm.user.User; import sonia.scm.user.UserDAO; import sonia.scm.user.UserEvent; From c0eb4c3859b9b36332a9741a36f4a2610088c802 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 2 May 2013 08:43:23 +0200 Subject: [PATCH 22/25] javadoc --- .../scm/security/AssignedPermission.java | 56 ++++++++----------- .../scm/security/PermissionDescriptor.java | 36 +++++------- .../sonia/scm/security/PermissionObject.java | 9 +-- .../sonia/scm/security/SecuritySystem.java | 51 +++++++---------- .../security/StoredAssignedPermission.java | 15 ++--- .../StoredAssignedPermissionEvent.java | 39 +++++-------- .../scm/security/DefaultSecuritySystem.java | 15 ----- 7 files changed, 85 insertions(+), 136 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/security/AssignedPermission.java b/scm-core/src/main/java/sonia/scm/security/AssignedPermission.java index 28e6780be0..a5e21cc7a6 100644 --- a/scm-core/src/main/java/sonia/scm/security/AssignedPermission.java +++ b/scm-core/src/main/java/sonia/scm/security/AssignedPermission.java @@ -45,6 +45,7 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; /** + * Permission object which is assigned to a specific user or group. * * @author Sebastian Sdorra * @since 1.31 @@ -54,7 +55,7 @@ import javax.xml.bind.annotation.XmlRootElement; public class AssignedPermission implements PermissionObject, Serializable { - /** Field description */ + /** serial version uid */ private static final long serialVersionUID = -7411338422110323879L; //~--- constructors --------------------------------------------------------- @@ -66,10 +67,10 @@ public class AssignedPermission implements PermissionObject, Serializable public AssignedPermission() {} /** - * Constructs ... + * Constructs a new AssignedPermission. * * - * @param permission + * @param permission assigned permission */ public AssignedPermission(AssignedPermission permission) { @@ -79,11 +80,11 @@ public class AssignedPermission implements PermissionObject, Serializable } /** - * Constructs ... + * Constructs a new AssingnedPermmission. * * - * @param name - * @param permission + * @param name name of the user + * @param permission permission string */ public AssignedPermission(String name, String permission) { @@ -92,12 +93,12 @@ public class AssignedPermission implements PermissionObject, Serializable } /** - * Constructs ... + * Constructs a new AssingnedPermmission. * * - * @param name - * @param groupPermission - * @param permission + * @param name name of the user or group + * @param groupPermission true if the permission should be assigned to a group + * @param permission permission string */ public AssignedPermission(String name, boolean groupPermission, String permission) @@ -110,12 +111,7 @@ public class AssignedPermission implements PermissionObject, Serializable //~--- methods -------------------------------------------------------------- /** - * Method description - * - * - * @param obj - * - * @return + * {@inheritDoc} */ @Override public boolean equals(Object obj) @@ -138,10 +134,7 @@ public class AssignedPermission implements PermissionObject, Serializable } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public int hashCode() @@ -150,10 +143,7 @@ public class AssignedPermission implements PermissionObject, Serializable } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public String toString() @@ -170,10 +160,10 @@ public class AssignedPermission implements PermissionObject, Serializable //~--- get methods ---------------------------------------------------------- /** - * Method description + * Returns the name of the user or group which the permission is assigned. * * - * @return + * @return name of user or group */ @Override public String getName() @@ -182,10 +172,10 @@ public class AssignedPermission implements PermissionObject, Serializable } /** - * Method description + * Returns the string representation of the permission. * * - * @return + * @return string representation of the permission */ public String getPermission() { @@ -193,10 +183,10 @@ public class AssignedPermission implements PermissionObject, Serializable } /** - * Method description + * Returns true if the permission is assigned to a group. * * - * @return + * @return true if the permission is assigned to a group */ @Override public boolean isGroupPermission() @@ -206,13 +196,13 @@ public class AssignedPermission implements PermissionObject, Serializable //~--- fields --------------------------------------------------------------- - /** Field description */ + /** group permission indicator */ @XmlElement(name = "group-permission") private boolean groupPermission; - /** Field description */ + /** name of the user or group */ private String name; - /** Field description */ + /** string representation of the permission */ private String permission; } diff --git a/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java b/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java index bbb02eae02..8db5a9dcaf 100644 --- a/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java +++ b/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java @@ -45,6 +45,7 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; /** + * Descriptor for available permission objects. * * @author Sebastian Sdorra * @since 1.31 @@ -84,12 +85,7 @@ public class PermissionDescriptor implements Serializable //~--- methods -------------------------------------------------------------- /** - * Method description - * - * - * @param obj - * - * @return + * {@inheritDoc} */ @Override public boolean equals(Object obj) @@ -112,10 +108,7 @@ public class PermissionDescriptor implements Serializable } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public int hashCode() @@ -124,10 +117,7 @@ public class PermissionDescriptor implements Serializable } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public String toString() @@ -146,10 +136,10 @@ public class PermissionDescriptor implements Serializable //~--- get methods ---------------------------------------------------------- /** - * Method description + * Returns the description of the permission. * * - * @return + * @return description */ public String getDescription() { @@ -157,10 +147,10 @@ public class PermissionDescriptor implements Serializable } /** - * Method description + * Returns the display name of the permission. * * - * @return + * @return display name */ public String getDisplayName() { @@ -168,10 +158,10 @@ public class PermissionDescriptor implements Serializable } /** - * Method description + * Returns the string representation of the permission. * * - * @return + * @return string representation */ public String getValue() { @@ -180,13 +170,13 @@ public class PermissionDescriptor implements Serializable //~--- fields --------------------------------------------------------------- - /** Field description */ + /** description */ private String description; - /** Field description */ + /** display name */ @XmlElement(name = "display-name") private String displayName; - /** Field description */ + /** value */ private String value; } diff --git a/scm-core/src/main/java/sonia/scm/security/PermissionObject.java b/scm-core/src/main/java/sonia/scm/security/PermissionObject.java index 58b4f065ae..0b3025f16e 100644 --- a/scm-core/src/main/java/sonia/scm/security/PermissionObject.java +++ b/scm-core/src/main/java/sonia/scm/security/PermissionObject.java @@ -32,6 +32,7 @@ package sonia.scm.security; /** + * Interface for permission objects. * * @author Sebastian Sdorra * @since 1.31 @@ -40,18 +41,18 @@ public interface PermissionObject { /** - * Method description + * Returns the name of the user or group which the permission is assigned. * * - * @return + * @return name of user or group */ public String getName(); /** - * Method description + * Returns the id of the stored permission object. * * - * @return + * @return id of permission */ public boolean isGroupPermission(); } diff --git a/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java b/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java index 66ce48b061..6c59f54bee 100644 --- a/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java +++ b/scm-core/src/main/java/sonia/scm/security/SecuritySystem.java @@ -35,13 +35,12 @@ package sonia.scm.security; import com.google.common.base.Predicate; -import org.apache.shiro.subject.PrincipalCollection; - //~--- JDK imports ------------------------------------------------------------ import java.util.List; /** + * The SecuritySystem manages global permissions. * * @author Sebastian Sdorra * @since 1.31 @@ -50,84 +49,76 @@ public interface SecuritySystem { /** - * Method description + * Store a new permission. * * - * @param permission + * @param permission permission to be stored * - * @return + * @return stored permission */ public StoredAssignedPermission addPermission(AssignedPermission permission); /** - * Method description + * Delete stored permission. * * - * @param permission + * @param permission permission to be deleted */ public void deletePermission(StoredAssignedPermission permission); /** - * Method description + * Delete stored permission. * * - * @param id + * @param id id of the permission */ public void deletePermission(String id); /** - * Method description + * Modify stored permission. * * - * @param id - * @param permission + * @param permission stored permisison */ public void modifyPermission(StoredAssignedPermission permission); //~--- get methods ---------------------------------------------------------- /** - * Method description + * Return all stored permissions. * * - * @return + * @return stored permission */ public List getAllPermissions(); /** - * Method description + * Return all available permissions. * * - * @return + * @return available permissions */ public List getAvailablePermissions(); /** - * Method description + * Return the stored permission which is stored with the given id. * * - * @param id + * @param id id of the stored permission * - * @return + * @return stored permission */ public StoredAssignedPermission getPermission(String id); /** - * Method description + * Returns all stored permissions which are matched by the given + * {@link Predicate}. * * - * @param predicate + * @param predicate predicate to filter * - * @return + * @return filtered permissions */ public List getPermissions( Predicate predicate); - - /** - * Method description - * - * - * @return - */ - public PrincipalCollection getSystemAccount(); } diff --git a/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java index 74bf69f651..a1ab1fdee1 100644 --- a/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java +++ b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java @@ -38,6 +38,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; /** + * Permission object which is stored and assigned to a specific user or group. * * @author Sebastian Sdorra * @since 1.31 @@ -47,7 +48,7 @@ import javax.xml.bind.annotation.XmlRootElement; public class StoredAssignedPermission extends AssignedPermission { - /** Field description */ + /** serial version uid */ private static final long serialVersionUID = -4593919877023168090L; //~--- constructors --------------------------------------------------------- @@ -59,11 +60,11 @@ public class StoredAssignedPermission extends AssignedPermission public StoredAssignedPermission() {} /** - * Constructs ... + * Constructs a new StoredAssignedPermission. * * - * @param id - * @param permission + * @param id id of the permission object + * @param permission assigned permission object */ public StoredAssignedPermission(String id, AssignedPermission permission) { @@ -75,10 +76,10 @@ public class StoredAssignedPermission extends AssignedPermission //~--- get methods ---------------------------------------------------------- /** - * Method description + * Returns the id of the stored permission object. * * - * @return + * @return id of permission */ public String getId() { @@ -87,6 +88,6 @@ public class StoredAssignedPermission extends AssignedPermission //~--- fields --------------------------------------------------------------- - /** Field description */ + /** id */ private String id; } diff --git a/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermissionEvent.java b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermissionEvent.java index 8535484de4..530cc602a8 100644 --- a/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermissionEvent.java +++ b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermissionEvent.java @@ -42,6 +42,8 @@ import sonia.scm.HandlerEvent; import java.io.Serializable; /** + * Event which is fired after a {@link StoredAssignedPermission} was added, + * removed or changed. * * @author Sebastian Sdorra * @since 1.31 @@ -49,17 +51,17 @@ import java.io.Serializable; public final class StoredAssignedPermissionEvent implements Serializable { - /** Field description */ + /** serial version uid */ private static final long serialVersionUID = 706824497813169009L; //~--- constructors --------------------------------------------------------- /** - * Constructs ... + * Constructs a new StoredAssignedPermissionEvent. * * - * @param type - * @param permission + * @param type type of the event + * @param permission permission object which has changed */ public StoredAssignedPermissionEvent(HandlerEvent type, StoredAssignedPermission permission) @@ -71,12 +73,7 @@ public final class StoredAssignedPermissionEvent implements Serializable //~--- methods -------------------------------------------------------------- /** - * Method description - * - * - * @param obj - * - * @return + * {@inheritDoc} */ @Override public boolean equals(Object obj) @@ -99,10 +96,7 @@ public final class StoredAssignedPermissionEvent implements Serializable } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public int hashCode() @@ -111,10 +105,7 @@ public final class StoredAssignedPermissionEvent implements Serializable } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public String toString() @@ -130,10 +121,10 @@ public final class StoredAssignedPermissionEvent implements Serializable //~--- get methods ---------------------------------------------------------- /** - * Method description + * Return the type of the event. * * - * @return + * @return type of event */ public HandlerEvent getEventType() { @@ -141,10 +132,10 @@ public final class StoredAssignedPermissionEvent implements Serializable } /** - * Method description + * Returns the changed permission object. * * - * @return + * @return changed permission */ public StoredAssignedPermission getPermission() { @@ -153,9 +144,9 @@ public final class StoredAssignedPermissionEvent implements Serializable //~--- fields --------------------------------------------------------------- - /** Field description */ + /** changed permission */ private StoredAssignedPermission permission; - /** Field description */ + /** type of the event */ private HandlerEvent type; } diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java index 2383e8c574..789fd698f9 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java @@ -41,7 +41,6 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; -import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -325,20 +324,6 @@ public class DefaultSecuritySystem implements SecuritySystem return permissions.build(); } - /** - * Method description - * - * - * @return - */ - @Override - public PrincipalCollection getSystemAccount() - { - - // TODO - throw new UnsupportedOperationException("Not supported yet."); - } - //~--- methods -------------------------------------------------------------- /** From 62bfcbc78fcbb6d03fce64a52082f6d517c0c392 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 2 May 2013 18:29:00 +0200 Subject: [PATCH 23/25] add unit test for DefaultSecuritySystem --- .../main/java/sonia/scm/AbstractTestBase.java | 3 +- .../main/java/sonia/scm/util/MockUtil.java | 29 +- .../scm/security/DefaultSecuritySystem.java | 2 +- .../security/DefaultSecuritySystemTest.java | 333 ++++++++++++++++++ 4 files changed, 363 insertions(+), 4 deletions(-) create mode 100644 scm-webapp/src/test/java/sonia/scm/security/DefaultSecuritySystemTest.java diff --git a/scm-test/src/main/java/sonia/scm/AbstractTestBase.java b/scm-test/src/main/java/sonia/scm/AbstractTestBase.java index b991b67c4f..47ee59b075 100644 --- a/scm-test/src/main/java/sonia/scm/AbstractTestBase.java +++ b/scm-test/src/main/java/sonia/scm/AbstractTestBase.java @@ -151,6 +151,7 @@ public class AbstractTestBase try { preTearDown(); + clearSubject(); } finally { @@ -179,7 +180,7 @@ public class AbstractTestBase //~--- methods -------------------------------------------------------------- /** - * Clears Shiro's thread state, ensuring the thread remains clean for + * Clears Shiro's thread state, ensuring the thread remains clean for * future test execution. */ protected void clearSubject() diff --git a/scm-test/src/main/java/sonia/scm/util/MockUtil.java b/scm-test/src/main/java/sonia/scm/util/MockUtil.java index a53c9feaba..715ec39d71 100644 --- a/scm-test/src/main/java/sonia/scm/util/MockUtil.java +++ b/scm-test/src/main/java/sonia/scm/util/MockUtil.java @@ -41,6 +41,7 @@ import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.Subject.Builder; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -136,6 +137,20 @@ public final class MockUtil * @return */ public static Subject createUserSubject() + { + return createUserSubject(null); + } + + /** + * Method description + * + * + * + * @param securityManager + * @return + */ + public static Subject createUserSubject( + org.apache.shiro.mgt.SecurityManager securityManager) { SimplePrincipalCollection collection = new SimplePrincipalCollection(); User user = UserTestData.createTrillian(); @@ -143,8 +158,18 @@ public final class MockUtil collection.add(user.getName(), "junit"); collection.add(user, "junit"); - return new Subject.Builder().principals(collection).authenticated( - true).buildSubject(); + Builder builder; + + if (securityManager != null) + { + builder = new Subject.Builder(securityManager); + } + else + { + builder = new Subject.Builder(); + } + + return builder.principals(collection).authenticated(true).buildSubject(); } //~--- get methods ---------------------------------------------------------- diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java index 789fd698f9..2e1986d6d7 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java @@ -444,7 +444,7 @@ public class DefaultSecuritySystem implements SecuritySystem return classLoader; } - + //~--- inner classes -------------------------------------------------------- /** diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultSecuritySystemTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultSecuritySystemTest.java new file mode 100644 index 0000000000..516e24106c --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultSecuritySystemTest.java @@ -0,0 +1,333 @@ +/** + * Copyright (c) 2010, 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.Predicate; + +import org.apache.shiro.authz.UnauthorizedException; +import org.apache.shiro.mgt.DefaultSecurityManager; +import org.apache.shiro.realm.SimpleAccountRealm; + +import org.junit.Before; +import org.junit.Test; + +import sonia.scm.AbstractTestBase; +import sonia.scm.store.JAXBConfigurationEntryStoreFactory; +import sonia.scm.util.MockUtil; + +import static org.hamcrest.Matchers.*; + +import static org.junit.Assert.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.List; + +/** + * + * @author Sebastian Sdorra + */ +public class DefaultSecuritySystemTest extends AbstractTestBase +{ + + /** + * Method description + * + */ + @Before + public void createSecuritySystem() + { + JAXBConfigurationEntryStoreFactory factory = + new JAXBConfigurationEntryStoreFactory(new UUIDKeyGenerator(), + contextProvider); + + securitySystem = new DefaultSecuritySystem(factory); + + // ScmEventBus.getInstance().register(listener); + } + + /** + * Method description + * + */ + @Test + public void testAddPermission() + { + setAdminSubject(); + + StoredAssignedPermission sap = createPermission("trillian", false, + "repository:*:READ"); + + assertEquals("trillian", sap.getName()); + assertEquals("repository:*:READ", sap.getPermission()); + assertEquals(false, sap.isGroupPermission()); + } + + /** + * Method description + * + */ + @Test + public void testAvailablePermissions() + { + setAdminSubject(); + + List list = securitySystem.getAvailablePermissions(); + + assertNotNull(list); + assertThat(list.size(), greaterThan(0)); + } + + /** + * Method description + * + */ + @Test + public void testDeletePermission() + { + setAdminSubject(); + + StoredAssignedPermission sap = createPermission("trillian", false, + "repository:*:READ"); + + securitySystem.deletePermission(sap); + + assertNull(securitySystem.getPermission(sap.getId())); + } + + /** + * Method description + * + */ + @Test + public void testGetAllPermissions() + { + setAdminSubject(); + + StoredAssignedPermission trillian = createPermission("trillian", false, + "repository:*:READ"); + StoredAssignedPermission dent = createPermission("dent", false, + "repository:*:READ"); + StoredAssignedPermission marvin = createPermission("marvin", false, + "repository:*:READ"); + + List all = securitySystem.getAllPermissions(); + + assertEquals(3, all.size()); + assertThat(all, containsInAnyOrder(trillian, dent, marvin)); + } + + /** + * Method description + * + */ + @Test + public void testGetPermission() + { + setAdminSubject(); + + StoredAssignedPermission sap = createPermission("trillian", false, + "repository:*:READ"); + + StoredAssignedPermission other = securitySystem.getPermission(sap.getId()); + + assertEquals(sap.getId(), other.getId()); + assertEquals(sap, other); + } + + /** + * Method description + * + */ + @Test + public void testGetPermissionsWithPredicate() + { + setAdminSubject(); + + StoredAssignedPermission trillian = createPermission("trillian", false, + "repository:*:READ"); + StoredAssignedPermission dent = createPermission("dent", false, + "repository:*:READ"); + + createPermission("hitchhiker", true, "repository:*:READ"); + + List filtered = + securitySystem.getPermissions(new Predicate() + { + + @Override + public boolean apply(AssignedPermission input) + { + return !input.isGroupPermission(); + } + }); + + assertEquals(2, filtered.size()); + assertThat(filtered, containsInAnyOrder(trillian, dent)); + } + + /** + * Method description + * + */ + @Test + public void testModifyPermission() + { + setAdminSubject(); + + StoredAssignedPermission sap = createPermission("trillian", false, + "repository:*:READ"); + StoredAssignedPermission modified = + new StoredAssignedPermission(sap.getId(), + new AssignedPermission("trillian", "repository:*:WRITE")); + + securitySystem.modifyPermission(modified); + + sap = securitySystem.getPermission(modified.getId()); + + assertEquals(modified.getId(), sap.getId()); + assertEquals(modified, sap); + } + + /** + * Method description + * + */ + @Test(expected = UnauthorizedException.class) + public void testUnauthorizedAddPermission() + { + setUserSubject(); + createPermission("trillian", false, "repository:*:READ"); + } + + /** + * Method description + * + */ + @Test(expected = UnauthorizedException.class) + public void testUnauthorizedDeletePermission() + { + setAdminSubject(); + + StoredAssignedPermission sap = createPermission("trillian", false, + "repository:*:READ"); + + setUserSubject(); + securitySystem.deletePermission(sap); + } + + /** + * Method description + * + */ + @Test(expected = UnauthorizedException.class) + public void testUnauthorizedGetPermission() + { + setAdminSubject(); + + StoredAssignedPermission sap = createPermission("trillian", false, + "repository:*:READ"); + + setUserSubject(); + securitySystem.getPermission(sap.getId()); + } + + /** + * Method description + * + */ + @Test(expected = UnauthorizedException.class) + public void testUnauthorizedModifyPermission() + { + setAdminSubject(); + + StoredAssignedPermission sap = createPermission("trillian", false, + "repository:*:READ"); + + setUserSubject(); + + securitySystem.modifyPermission(sap); + } + + /** + * Method description + * + * + * @param name + * @param groupPermission + * @param value + * + * @return + */ + private StoredAssignedPermission createPermission(String name, + boolean groupPermission, String value) + { + AssignedPermission ap = new AssignedPermission(name, groupPermission, + value); + StoredAssignedPermission sap = securitySystem.addPermission(ap); + + assertNotNull(sap); + assertNotNull(sap.getId()); + + return sap; + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + */ + private void setAdminSubject() + { + setSubject(MockUtil.createAdminSubject()); + } + + /** + * Method description + * + */ + private void setUserSubject() + { + org.apache.shiro.mgt.SecurityManager sm = + new DefaultSecurityManager(new SimpleAccountRealm()); + + setSubject(MockUtil.createUserSubject(sm)); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private DefaultSecuritySystem securitySystem; +} From f56eaa2c74bb2e7b986bc252e33368cb548e52b4 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 2 May 2013 18:40:32 +0200 Subject: [PATCH 24/25] fix user create form --- .../src/main/webapp/resources/js/user/sonia.user.panel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/main/webapp/resources/js/user/sonia.user.panel.js b/scm-webapp/src/main/webapp/resources/js/user/sonia.user.panel.js index 3341890f94..0833009f40 100644 --- a/scm-webapp/src/main/webapp/resources/js/user/sonia.user.panel.js +++ b/scm-webapp/src/main/webapp/resources/js/user/sonia.user.panel.js @@ -102,7 +102,7 @@ Sonia.user.Panel = Ext.extend(Sonia.rest.Panel, { showAddPanel: function(){ Sonia.user.setEditPanel({ - xtype: 'userEditPanel', + xtype: 'userForm', title: this.titleText, padding: 5, onUpdate: { From 4c1ebc4d8dc56e6ef3e5115b350e0b3782c0d046 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 11 May 2013 19:07:28 +0200 Subject: [PATCH 25/25] close branch issue-340