diff --git a/magnolia-configuration/pom.xml b/magnolia-configuration/pom.xml index cb397d5..7402500 100644 --- a/magnolia-configuration/pom.xml +++ b/magnolia-configuration/pom.xml @@ -4,7 +4,7 @@ magnolia-project info.magnolia - 5.4.14 + 5.4.14-MAGNOLIA-7028 ../pom.xml diff --git a/magnolia-core/pom.xml b/magnolia-core/pom.xml index 7510e7d..390e8a9 100644 --- a/magnolia-core/pom.xml +++ b/magnolia-core/pom.xml @@ -3,7 +3,7 @@ magnolia-project info.magnolia - 5.4.14 + 5.4.14-MAGNOLIA-7028 ../pom.xml 4.0.0 diff --git a/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlGroupManager.java b/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlGroupManager.java index 789e758..4afb31d 100644 --- a/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlGroupManager.java +++ b/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlGroupManager.java @@ -34,9 +34,9 @@ package info.magnolia.cms.security; import static info.magnolia.cms.security.SecurityConstants.*; - import info.magnolia.context.MgnlContext; import info.magnolia.jcr.iterator.FilteringPropertyIterator; +import info.magnolia.jcr.util.NodeNameHelper; import info.magnolia.jcr.util.NodeTypes; import info.magnolia.jcr.util.NodeUtil; import info.magnolia.repository.RepositoryConstants; @@ -47,6 +47,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import javax.inject.Inject; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; @@ -65,6 +66,18 @@ import org.slf4j.LoggerFactory; public class MgnlGroupManager extends RepositoryBackedSecurityManager implements GroupManager { private static final Logger log = LoggerFactory.getLogger(MgnlGroupManager.class); + + @Inject + public MgnlGroupManager(NodeNameHelper nodeNameHelper) { + super(nodeNameHelper); + } + + /** + * @deprecated since 5.5.3, use {@link #MgnlGroupManager(NodeNameHelper)} instead. + */ + @Deprecated + public MgnlGroupManager() { + } @Override public Group createGroup(final String name) throws AccessDeniedException { diff --git a/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlRoleManager.java b/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlRoleManager.java index 9077ecb..83a31e2 100644 --- a/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlRoleManager.java +++ b/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlRoleManager.java @@ -35,12 +35,13 @@ package info.magnolia.cms.security; import info.magnolia.cms.core.Content; import info.magnolia.cms.core.HierarchyManager; -import info.magnolia.cms.core.Path; import info.magnolia.context.MgnlContext; import info.magnolia.jcr.iterator.SameChildNodeTypeIterator; +import info.magnolia.jcr.util.NodeNameHelper; import info.magnolia.jcr.util.NodeTypes; import info.magnolia.repository.RepositoryConstants; +import javax.inject.Inject; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; @@ -60,6 +61,18 @@ public class MgnlRoleManager extends RepositoryBackedSecurityManager implements public static final String NODE_ACLROLES = "acl_userroles"; private static final Logger log = LoggerFactory.getLogger(MgnlRoleManager.class); + + @Inject + public MgnlRoleManager(NodeNameHelper nodeNameHelper) { + super(nodeNameHelper); + } + + /** + * @deprecated since 5.5.3, use {@link #MgnlRoleManager(NodeNameHelper)} instead. + */ + @Deprecated + public MgnlRoleManager() { + } @Override public Role getRole(final String name) { @@ -104,7 +117,8 @@ public class MgnlRoleManager extends RepositoryBackedSecurityManager implements Node roleNode = session.getNode(parentPath).addNode(name, NodeTypes.Role.NAME); final Node acls = roleNode.addNode(NODE_ACLROLES, NodeTypes.ContentNode.NAME); // read only access to the role itself - Node acl = acls.addNode(Path.getUniqueLabel(session, acls.getPath(), "0"), NodeTypes.ContentNode.NAME); + Node acl = acls.addNode(nodeNameHelper.getUniqueName(session, acls.getPath(), "0"), NodeTypes.ContentNode.NAME); + acl.setProperty("path", roleNode.getPath()); acl.setProperty("permissions", Permission.READ); @@ -207,7 +221,7 @@ public class MgnlRoleManager extends RepositoryBackedSecurityManager implements Node roleNode = session.getNodeByIdentifier(role.getId()); Node aclNode = getAclNode(roleNode, workspace); if (!existsPermission(aclNode, path, permission)) { - String nodeName = Path.getUniqueLabel(session, aclNode.getPath(), "0"); + String nodeName = nodeNameHelper.getUniqueName(session, aclNode.getPath(), "0"); Node node = aclNode.addNode(nodeName, NodeTypes.ContentNode.NAME); node.setProperty("path", path); node.setProperty("permissions", permission); diff --git a/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlUser.java b/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlUser.java index 21fe7ec..75e2355 100644 --- a/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlUser.java +++ b/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlUser.java @@ -33,17 +33,14 @@ */ package info.magnolia.cms.security; -import static info.magnolia.cms.security.SecurityConstants.*; - import info.magnolia.cms.core.Content; +import info.magnolia.cms.util.DeprecationUtil; import java.io.Serializable; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Map; -import java.util.Set; -import java.util.TreeSet; import org.apache.jackrabbit.util.ISO8601; import org.slf4j.Logger; @@ -60,8 +57,10 @@ public class MgnlUser extends AbstractUser implements User, Serializable { private static final Logger log = LoggerFactory.getLogger(MgnlUser.class); private final Map properties; - private final Collection groups; - private final Collection roles; + private final Collection directGroups; + private final Collection directRoles; + private final Collection allGroups; + private final Collection allRoles; private final String name; private final String language; @@ -71,28 +70,56 @@ public class MgnlUser extends AbstractUser implements User, Serializable { private String uuid; private final String realm; - - - public MgnlUser(String name, String realm, Collection groups, Collection roles, Map properties) { + + /** + * This constructor is mainly used by {@link MgnlUserManager} or by custom extensions of this object. + * If you extend MgnlUser, chances are you need to provide your own {@link MgnlUserManager} as well. + * + * @see MgnlUserManager#getUser(String) + * @see MgnlUserManager#newUserInstance(javax.jcr.Node) + * + */ + public MgnlUser(String name, String realm, Collection directGroups, Collection directRoles, Map properties, String path, String uuid, Collection allGroups, Collection allRoles) { + log.debug("MAGNOLIA-7028 MgnlUser"); this.name = name; - this.roles = Collections.unmodifiableCollection(roles); - this.groups = Collections.unmodifiableCollection(groups); - this.properties = Collections.unmodifiableMap(properties); + this.directRoles = directRoles; + this.directGroups = directGroups; + this.allRoles = allRoles; + this.allGroups = allGroups; + this.properties = properties; this.realm = realm; //shortcut some often accessed props so we don't have to search hashmap for them. - language = properties.get(MgnlUserManager.PROPERTY_LANGUAGE); - String enbld = properties.get(MgnlUserManager.PROPERTY_ENABLED); + this.language = properties.get(MgnlUserManager.PROPERTY_LANGUAGE); + + final String enabledByDefault = properties.get(MgnlUserManager.PROPERTY_ENABLED); // all accounts are enabled by default and prop doesn't exist if the account was not disabled before - enabled = enbld == null ? true : Boolean.parseBoolean(properties.get(MgnlUserManager.PROPERTY_ENABLED)); - encodedPassword = properties.get(MgnlUserManager.PROPERTY_PASSWORD); - } + this.enabled = enabledByDefault == null ? true : Boolean.parseBoolean(properties.get(MgnlUserManager.PROPERTY_ENABLED)); - public MgnlUser(String name, String realm, Collection groups, Collection roles, Map properties, String path, String uuid) { - this(name, realm, groups, roles, properties); + this.encodedPassword = properties.get(MgnlUserManager.PROPERTY_PASSWORD); this.path = path; this.uuid = uuid; } + + /** + * @deprecated since 5.5.5. Please use {@link #MgnlUser(String, String, Collection, Collection, Map, String, String, Collection, Collection)} instead. + */ + @Deprecated + public MgnlUser(String name, String realm, Collection directGroups, Collection directRoles, Map properties, String path, String uuid) { + this(name, realm, directGroups, directRoles, properties, path, uuid, Collections. emptySet(), Collections. emptySet()); + final String reason = "This constructor is deprecated since Magnolia 5.5.5.\n" + + "Be warned that instantiating directly MgnlUser with this constructor will result in an inconsistent object missing all roles and groups.\n" + + "Please use new public constructor instead."; + DeprecationUtil.isDeprecated(reason); + } + + /** + * @deprecated since 5.5.5. Please use {@link #MgnlUser(String, String, Collection, Collection, Map, String, String, Collection, Collection)} instead. + */ + @Deprecated + public MgnlUser(String name, String realm, Collection directGroups, Collection directRoles, Map properties) { + this(name, realm, directGroups, directRoles, properties, null, null); + } /** * Is this user in a specified group? @@ -102,8 +129,8 @@ public class MgnlUser extends AbstractUser implements User, Serializable { */ @Override public boolean inGroup(String groupName) { - log.debug("inGroup({})", groupName); - return SecuritySupport.Factory.getInstance().getUserManager(getRealm()).hasAny(getName(), groupName, NODE_GROUPS); + log.debug("MAGNOLIA-7028 inGroup({})", groupName); + return allGroups.contains(groupName); } /** @@ -152,7 +179,8 @@ public class MgnlUser extends AbstractUser implements User, Serializable { */ @Override public boolean hasRole(String roleName) { - return SecuritySupport.Factory.getInstance().getUserManager(getRealm()).hasAny(getName(), roleName, NODE_ROLES); + log.debug("MAGNOLIA-7028 inRole({})", roleName); + return allRoles.contains(roleName); } @Override @@ -216,61 +244,26 @@ public class MgnlUser extends AbstractUser implements User, Serializable { @Override public Collection getGroups() { - log.debug("getGroups()"); - return groups; + log.debug("MAGNOLIA-7028 getGroups()"); + return Collections.unmodifiableCollection(directGroups); } @Override public Collection getAllGroups() { - // TODO: if the user is just a simple bean, then this method doesn't belong here anymore!!!! - // should be moved to user manager or to group manager??? - log.debug("get groups for {}", getName()); - - final Set allGroups = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - final Collection groups = getGroups(); - - // FYI: can't initialize upfront as the instance of the user class needs to be created BEFORE repo is ready - GroupManager man = SecuritySupport.Factory.getInstance().getGroupManager(); - - // add all subgroups - addSubgroups(allGroups, man, groups); - - return allGroups; + log.debug("MAGNOLIA-7028 get all groups (direct and transitive) for {}", getName()); + return Collections.unmodifiableCollection(allGroups); } @Override public Collection getRoles() { - log.debug("getRoles()"); - return roles; + log.debug("MAGNOLIA-7028 getRoles()"); + return Collections.unmodifiableCollection(directRoles); } @Override public Collection getAllRoles() { - // TODO: if the user is just a simple bean, then this method doesn't belong here anymore!!!! - log.debug("get roles for {}", getName()); - - final Set allRoles = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - final Collection roles = getRoles(); - - // add all direct user groups - allRoles.addAll(roles); - - Collection allGroups = getAllGroups(); - - // FYI: can't initialize upfront as the instance of the user class needs to be created BEFORE repo is ready - GroupManager man = SecuritySupport.Factory.getInstance().getGroupManager(); - - // add roles from all groups - for (String group : allGroups) { - try { - allRoles.addAll(man.getGroup(group).getRoles()); - } catch (AccessDeniedException e) { - log.debug("Skipping denied group {} for user {}", group, getName(), e); - } catch (UnsupportedOperationException e) { - log.debug("Skipping unsupported getGroup() for group {} and user {}", group, getName(), e); - } - } - return allRoles; + log.debug("MAGNOLIA-7028 get all roles (direct and transitive) for {}", getName()); + return Collections.unmodifiableCollection(allRoles); } public String getPath() { @@ -282,33 +275,6 @@ public class MgnlUser extends AbstractUser implements User, Serializable { this.path = path; } - /** - * Any group from the groups is checked for the subgroups only if it is not in the allGroups yet. This is to prevent infinite loops in case of cyclic group assignment. - */ - private void addSubgroups(final Set allGroups, GroupManager man, Collection groups) { - for (String groupName : groups) { - // check if this group was not already added to prevent infinite loops - if (!allGroups.contains(groupName)) { - allGroups.add(groupName); - try { - Group group = man.getGroup(groupName); - if (group == null) { - log.error("Failed to resolve group {} for user {}.", groupName, name); - continue; - } - Collection subgroups = group.getGroups(); - // and recursively add more subgroups - addSubgroups(allGroups, man, subgroups); - } catch (AccessDeniedException e) { - log.debug("Skipping denied group {} for user {}", groupName, getName(), e); - } catch (UnsupportedOperationException e) { - log.debug("Skipping unsupported getGroup() for group {} and user {}", groupName, getName(), e); - } - - } - } - } - public String getRealm() { return realm; } diff --git a/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlUserManager.java b/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlUserManager.java index 0b4c92b..35257c2 100644 --- a/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlUserManager.java +++ b/magnolia-core/src/main/java/info/magnolia/cms/security/MgnlUserManager.java @@ -33,19 +33,20 @@ */ package info.magnolia.cms.security; -import static info.magnolia.cms.security.SecurityConstants.*; - +import static info.magnolia.cms.security.SecurityConstants.NODE_GROUPS; +import static info.magnolia.cms.security.SecurityConstants.NODE_ROLES; import info.magnolia.cms.core.Content; import info.magnolia.cms.core.HierarchyManager; -import info.magnolia.cms.core.Path; import info.magnolia.cms.security.auth.ACL; import info.magnolia.cms.util.ContentUtil; import info.magnolia.context.MgnlContext; import info.magnolia.jcr.iterator.FilteringPropertyIterator; +import info.magnolia.jcr.util.NodeNameHelper; import info.magnolia.jcr.util.NodeTypes; import info.magnolia.jcr.util.NodeUtil; import info.magnolia.jcr.util.PropertyUtil; import info.magnolia.jcr.wrapper.MgnlPropertySettingNodeWrapper; +import info.magnolia.objectfactory.Components; import info.magnolia.repository.RepositoryConstants; import java.util.ArrayList; @@ -61,6 +62,7 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import javax.inject.Inject; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; @@ -107,7 +109,17 @@ public class MgnlUserManager extends RepositoryBackedSecurityManager implements /** * There should be no need to instantiate this class except maybe for testing. Manual instantiation might cause manager not to be initialized properly. */ + @Inject + public MgnlUserManager(NodeNameHelper nodeNameHelper) { + super(nodeNameHelper); + } + + /** + * @deprecated since 5.5.3, use {@link #MgnlUserManager(NodeNameHelper)} instead. + */ + @Deprecated public MgnlUserManager() { + this(Components.getComponent(NodeNameHelper.class)); } @Override @@ -410,7 +422,8 @@ public class MgnlUserManager extends RepositoryBackedSecurityManager implements final String handle = userNode.getPath(); final Node acls = userNode.addNode(NODE_ACLUSERS, NodeTypes.ContentNode.NAME); // read only access to the node itself - Node acl = acls.addNode(Path.getUniqueLabel(session, acls.getPath(), "0"), NodeTypes.ContentNode.NAME); + Node acl = acls.addNode(nodeNameHelper.getUniqueName(session, acls.getPath(), "0"), NodeTypes.ContentNode.NAME); + acl.setProperty("path", handle); acl.setProperty("permissions", Permission.READ); // those who had access to their nodes should get access to their own props @@ -424,7 +437,7 @@ public class MgnlUserManager extends RepositoryBackedSecurityManager implements addWrite(handle, PROPERTY_TIMEZONE, acls); } session.save(); - return new MgnlUser(userNode.getName(), getRealmName(), Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_MAP, userNode.getPath(), userNode.getIdentifier()); + return new MgnlUser(userNode.getName(), getRealmName(), Collections. emptySet(), Collections. emptySet(), Collections.emptyMap(), userNode.getPath(), userNode.getIdentifier(), Collections. emptySet(), Collections. emptySet()); } @Override @@ -535,7 +548,7 @@ public class MgnlUserManager extends RepositoryBackedSecurityManager implements } private Node addWrite(String parentPath, String property, Node acls) throws PathNotFoundException, RepositoryException, AccessDeniedException { - Node acl = acls.addNode(Path.getUniqueLabel(acls.getSession(), acls.getPath(), "0"), NodeTypes.ContentNode.NAME); + Node acl = acls.addNode(nodeNameHelper.getUniqueName(acls.getSession(), acls.getPath(), "0"), NodeTypes.ContentNode.NAME); acl.setProperty("path", parentPath + "/" + property); acl.setProperty("permissions", Permission.ALL); return acl; @@ -577,15 +590,22 @@ public class MgnlUserManager extends RepositoryBackedSecurityManager implements Set roles = collectUniquePropertyNames(privilegedUserNode, ROLES_NODE_NAME, RepositoryConstants.USER_ROLES, false); Set groups = collectUniquePropertyNames(privilegedUserNode, GROUPS_NODE_NAME, RepositoryConstants.USER_GROUPS, false); - Map properties = new HashMap(); + final String userName = privilegedUserNode.getName(); + // SecuritySupport cannot be injected cause it's not ready during Magnolia init phase + final GroupManager groupManager = Components.getComponent(SecuritySupport.class).getGroupManager(); + // groups must be resolved first as finding roles depends on them + Set allGroups = aggregateDirectAndTransitiveGroups(groups, userName, groupManager); + Set allRoles = aggregateDirectAndTransitiveRoles(roles, allGroups, userName, groupManager); + + + Map properties = new HashMap<>(); for (PropertyIterator iter = new FilteringPropertyIterator(privilegedUserNode.getProperties(), NodeUtil.ALL_PROPERTIES_EXCEPT_JCR_AND_MGNL_FILTER); iter.hasNext(); ) { Property prop = iter.nextProperty(); // TODO: should we check and skip binary props in case someone adds image to the user? properties.put(prop.getName(), prop.getString()); } - MgnlUser user = new MgnlUser(privilegedUserNode.getName(), getRealmName(), groups, roles, properties, privilegedUserNode.getPath(), privilegedUserNode.getIdentifier()); - return user; + return new MgnlUser(userName, getRealmName(), groups, roles, properties, privilegedUserNode.getPath(), privilegedUserNode.getIdentifier(), allGroups, allRoles); } @Override @@ -618,8 +638,8 @@ public class MgnlUserManager extends RepositoryBackedSecurityManager implements /** * Collects all property names of given type, sorting them (case insensitive) and removing duplicates in the process. */ - private Set collectUniquePropertyNames(Node rootNode, String subnodeName, String repositoryName, boolean isDeep) { - final SortedSet set = new TreeSet(String.CASE_INSENSITIVE_ORDER); + protected Set collectUniquePropertyNames(Node rootNode, String subnodeName, String repositoryName, boolean isDeep) { + final SortedSet set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); String path = null; try { path = rootNode.getPath(); @@ -750,4 +770,67 @@ public class MgnlUserManager extends RepositoryBackedSecurityManager implements } return users; } + + /** + * @return a Set of all roles, sorting them (case insensitive), directly assigned and transitive for this user. + * @see #newUserInstance(Node) + */ + protected Set aggregateDirectAndTransitiveRoles(Collection roles, Collection allGroups, String name, GroupManager groupManager) { + final SortedSet set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + set.addAll(roles); + + // add roles from all groups + for (String group : allGroups){ + try { + set.addAll(groupManager.getGroup(group).getRoles()); + } catch (javax.jcr.AccessDeniedException e) { + log.debug("Skipping denied group {} for user {}", group, name, e); + } catch (UnsupportedOperationException e) { + log.debug("Skipping unsupported getGroup() for group {} and user {}", group, name, e); + } + } + + return set; + } + + /** + * @return a Set of all groups, directly assigned and transitive for this user. + * @see #newUserInstance(Node) + */ + protected Set aggregateDirectAndTransitiveGroups(Collection groups, String name, GroupManager groupManager) { + final SortedSet set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + + // add all subgroups + addSubgroups(set, groupManager, groups, name); + + return set; + } + + /** + * Any group from the groups is checked for the subgroups only if it is not in the allGroups yet. This is to prevent infinite loops in case of cyclic group assignment. + */ + private void addSubgroups(final Set allGroups, GroupManager groupManager, Collection groups, String name) { + + for (String groupName : groups) { + // check if this group was not already added to prevent infinite loops + if (!allGroups.contains(groupName)) { + allGroups.add(groupName); + try { + Group group = groupManager.getGroup(groupName); + if (group == null) { + log.error("Failed to resolve group {} for user {}.", groupName, name); + return; // skips current iteration + } + Collection subgroups = group.getGroups(); + // and recursively add more subgroups + addSubgroups(allGroups, groupManager, subgroups, name); + } catch (javax.jcr.AccessDeniedException e) { + log.debug("Skipping denied group {} for user {}", groupName, name, e); + } catch (UnsupportedOperationException e) { + log.debug("Skipping unsupported getGroup() for group {} and user {}", groupName, name, e); + } + } + } + + } } diff --git a/magnolia-core/src/main/java/info/magnolia/cms/security/RepositoryBackedSecurityManager.java b/magnolia-core/src/main/java/info/magnolia/cms/security/RepositoryBackedSecurityManager.java index a6bbb8b..c3ed336 100644 --- a/magnolia-core/src/main/java/info/magnolia/cms/security/RepositoryBackedSecurityManager.java +++ b/magnolia-core/src/main/java/info/magnolia/cms/security/RepositoryBackedSecurityManager.java @@ -34,12 +34,11 @@ package info.magnolia.cms.security; import static info.magnolia.cms.security.SecurityConstants.NODE_ROLES; - -import info.magnolia.cms.core.Path; import info.magnolia.cms.security.auth.ACL; import info.magnolia.cms.util.SimpleUrlPattern; import info.magnolia.cms.util.UrlPattern; import info.magnolia.context.MgnlContext; +import info.magnolia.jcr.util.NodeNameHelper; import info.magnolia.jcr.util.NodeTypes; import info.magnolia.module.InstallContextImpl; import info.magnolia.module.InstallStatus; @@ -55,6 +54,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import javax.inject.Inject; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; @@ -89,6 +89,21 @@ public abstract class RepositoryBackedSecurityManager { * Name of the subnodes hosting roles. */ static final String ROLES_NODE_NAME = "roles"; + + protected final NodeNameHelper nodeNameHelper; + + @Inject + public RepositoryBackedSecurityManager(NodeNameHelper nodeNameHelper) { + this.nodeNameHelper = nodeNameHelper; + } + + /** + * @deprecated since 5.5.3, use {@link #RepositoryBackedSecurityManager(NodeNameHelper)} instead. + */ + @Deprecated + public RepositoryBackedSecurityManager() { + this(Components.getComponent(NodeNameHelper.class)); + } public boolean hasAny(final String principalName, final String resourceName, final String resourceTypeName) { @@ -178,7 +193,7 @@ public abstract class RepositoryBackedSecurityManager { Node node = principalNode.getNode(resourceTypeName); // add corresponding ID // used only to get the unique label - String newName = Path.getUniqueLabel(session, node.getPath(), "0"); + String newName = nodeNameHelper.getUniqueName(session, node.getPath(), "0"); node.setProperty(newName, nodeID); session.save(); } diff --git a/magnolia-core/src/main/java/info/magnolia/cms/security/SecuritySupportObservedComponentFactory.java b/magnolia-core/src/main/java/info/magnolia/cms/security/SecuritySupportObservedComponentFactory.java index 22696e7..0374ffc 100644 --- a/magnolia-core/src/main/java/info/magnolia/cms/security/SecuritySupportObservedComponentFactory.java +++ b/magnolia-core/src/main/java/info/magnolia/cms/security/SecuritySupportObservedComponentFactory.java @@ -124,7 +124,8 @@ public class SecuritySupportObservedComponentFactory extends ObservedComponentFa */ static class InitPhaseUser extends MgnlUser { private InitPhaseUser(final String name) { - super(name, Realm.REALM_SYSTEM.getName(), Collections.emptyList(), Collections.emptyList(), Collections.emptyMap()); + super(name, Realm.REALM_SYSTEM.getName(), Collections.emptySet(), Collections.emptySet(), Collections.emptyMap(), null, null, Collections.emptySet(), Collections.emptySet()); + } } diff --git a/magnolia-core/src/main/java/info/magnolia/cms/util/SelectorUtil.java b/magnolia-core/src/main/java/info/magnolia/cms/util/SelectorUtil.java index 88c821e..73e3864 100644 --- a/magnolia-core/src/main/java/info/magnolia/cms/util/SelectorUtil.java +++ b/magnolia-core/src/main/java/info/magnolia/cms/util/SelectorUtil.java @@ -36,9 +36,12 @@ package info.magnolia.cms.util; import info.magnolia.context.MgnlContext; /** - * Util class to handle selectors. A selector is the part between the first {@link info.magnolia.cms.core.Path#SELECTOR_DELIMITER} and the extension of an URI. + * Util class to handle selectors. A selector is the part between the first {@link #SELECTOR_DELIMITER} and the extension of an URI. */ public class SelectorUtil { + + public static final String SELECTOR_DELIMITER = "~"; + /** *

* get selector as requested from the URI. The selector is the part between the handle and the extension. diff --git a/magnolia-core/src/main/java/info/magnolia/init/MagnoliaConfigurationProperties.java b/magnolia-core/src/main/java/info/magnolia/init/MagnoliaConfigurationProperties.java index b8f2ad9..acf43db 100644 --- a/magnolia-core/src/main/java/info/magnolia/init/MagnoliaConfigurationProperties.java +++ b/magnolia-core/src/main/java/info/magnolia/init/MagnoliaConfigurationProperties.java @@ -72,7 +72,35 @@ package info.magnolia.init; */ public interface MagnoliaConfigurationProperties extends PropertySource { + String MAGNOLIA_REPOSITORIES_HOME = "magnolia.repositories.home"; + + String MAGNOLIA_REPOSITORIES_CONFIG = "magnolia.repositories.config"; + + String MAGNOLIA_REPOSITORIES_CLUSTER_CONFIG = "magnolia.repositories.jackrabbit.cluster.config"; + + String MAGNOLIA_REPOSITORIES_CLUSTER_MASTER = "magnolia.repositories.jackrabbit.cluster.master"; + + String MAGNOLIA_EXCHANGE_HISTORY = "magnolia.exchange.history"; + + String MAGNOLIA_UPLOAD_TMPDIR = "magnolia.upload.tmpdir"; + + String MAGNOLIA_CACHE_STARTDIR = "magnolia.cache.startdir"; + + String MAGNOLIA_APP_ROOTDIR = "magnolia.app.rootdir"; + + String MAGNOLIA_BOOTSTRAP_ROOTDIR = "magnolia.bootstrap.dir"; + + String MAGNOLIA_BOOTSTRAP_SAMPLES = "magnolia.bootstrap.samples"; + + String MAGNOLIA_UTF8_ENABLED = "magnolia.utf8.enabled"; + + String MAGNOLIA_WEBAPP = "magnolia.webapp"; + + String MAGNOLIA_SERVERNAME = "magnolia.servername"; + + String MAGNOLIA_CONTEXTPATH = "magnolia.contextpath"; + PropertySource getPropertySource(String key); void init() throws Exception; -} +} \ No newline at end of file diff --git a/magnolia-core/src/main/java/info/magnolia/jcr/util/NodeNameHelper.java b/magnolia-core/src/main/java/info/magnolia/jcr/util/NodeNameHelper.java new file mode 100644 index 0000000..96f37df --- /dev/null +++ b/magnolia-core/src/main/java/info/magnolia/jcr/util/NodeNameHelper.java @@ -0,0 +1,267 @@ +/** + * This file Copyright (c) 2017 Magnolia International + * Ltd. (http://www.magnolia-cms.com). All rights reserved. + * + * + * This file is dual-licensed under both the Magnolia + * Network Agreement and the GNU General Public License. + * You may elect to use one or the other of these licenses. + * + * This file is distributed in the hope that it will be + * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. + * Redistribution, except as permitted by whichever of the GPL + * or MNA you select, is prohibited. + * + * 1. For the GPL license (GPL), you can redistribute and/or + * modify this file under the terms of the GNU General + * Public License, Version 3, as published by the Free Software + * Foundation. You should have received a copy of the GNU + * General Public License, Version 3 along with this program; + * if not, write to the Free Software Foundation, Inc., 51 + * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * 2. For the Magnolia Network Agreement (MNA), this file + * and the accompanying materials are made available under the + * terms of the MNA which accompanies this distribution, and + * is available at http://www.magnolia-cms.com/mna.html + * + * Any modifications to this file must keep this entire header + * intact. + * + */ +package info.magnolia.jcr.util; + +import info.magnolia.cms.util.SelectorUtil; +import info.magnolia.init.MagnoliaConfigurationProperties; + +import java.nio.charset.StandardCharsets; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; + +/** + * Util component for creation of an unique and validated node name. + */ +@Singleton +public class NodeNameHelper { + + private final String DEFAULT_UNTITLED_NODE_NAME = "untitled"; + + private final MagnoliaConfigurationProperties magnoliaConfigurationProperties; + + @Inject + public NodeNameHelper(MagnoliaConfigurationProperties magnoliaConfigurationProperties) { + this.magnoliaConfigurationProperties = magnoliaConfigurationProperties; + } + + /** + * Checks if an item already exists with given name in JCR, and returns a unique name; + * if necessary, an increment is appended to the name. + * + *

Given the following tree:

+ *
+     * /
+     * ├── a
+     * ├── b
+     * └── b0
+     *
+     * NodeUtil.getUniqueName(session, "/", "a")  = "a0"
+     * NodeUtil.getUniqueName(session, "/", "b")  = "b1"
+     * NodeUtil.getUniqueName(session, "/", "b0") = "b1"
+     * NodeUtil.getUniqueName(session, "/", "c")  = "c"
+     * 
+ */ + public String getUniqueName(Session session, String parentPath, String name) throws RepositoryException { + if (parentPath.equals("/")) { + parentPath = StringUtils.EMPTY; + } + while (session.itemExists(parentPath + "/" + name)) { + name = createUniqueName(name); + } + return name; + } + + /** + * Checks if an item already exists with given name in JCR, and returns a unique name; + * if necessary, an increment is inserted into the name, before the given extension. + * + *

Given the following tree:

+ *
+     * /
+     * ├── a.txt
+     * ├── b.txt
+     * └── b0.txt
+     *
+     * NodeUtil.getUniqueName(session, "/", "a.txt", "txt")  = "a0.txt"
+     * NodeUtil.getUniqueName(session, "/", "b.txt", "txt")  = "b1.txt"
+     * NodeUtil.getUniqueName(session, "/", "b0.txt", "txt") = "b1.txt"
+     * NodeUtil.getUniqueName(session, "/", "c.txt", "txt")  = "c.txt"
+     * NodeUtil.getUniqueName(session, "/", "a.foo", "txt")  = "a.foo"
+     * 
+ */ + public String getUniqueName(Session session, String parentPath, String name, String extension) throws RepositoryException { + if (StringUtils.isNotEmpty(extension) && extension.equals(FilenameUtils.getExtension(name))) { + parentPath = "/".equals(parentPath) ? StringUtils.EMPTY : parentPath; + String basename = FilenameUtils.getBaseName(name); + String fullName = name; + + while (session.itemExists(parentPath + "/" + fullName)) { + basename = createUniqueName(basename); + fullName = basename + FilenameUtils.EXTENSION_SEPARATOR + extension; + } + return fullName; + } + + return getUniqueName(session, parentPath, name); + } + + public String getUniqueName(Node parent, String name) throws RepositoryException { + while (parent.hasNode(name) || parent.hasProperty(name)) { + name = createUniqueName(name); + } + return name; + } + + /** + * Replace illegal characters based on magnolia property magnolia.ut8.enabled. + * + * @return validated label + */ + public String getValidatedName(String name) { + String charset = StringUtils.EMPTY; + if (magnoliaConfigurationProperties.getBooleanProperty(MagnoliaConfigurationProperties.MAGNOLIA_UTF8_ENABLED)) { + charset = StandardCharsets.UTF_8.name(); + } + return getValidatedName(name, charset); + } + + /** + * If charset equals UTF-8, replaces the following characters with a dash - : + *

+ * Jackrabbit not allowed {@code 32: [ ] 91: [[] 93: []] 42: [*] 34: ["] 58 [:] 92: [\] 39 :[']} + *

+ * URL not valid {@code 59: [;] 47: [/] 63: [?] 43: [+] 37: [%] 33: [!] 35:[#] 94: [^]}. + *

+ * Otherwise, replaces illegal characters with a dash - except for {@code [_] [0-9], [A-Z], [a-z], [-], [_], [.]}. + *

+ * Please notice that a valid name can not begin with dot or period [.]. + * + * @return a validated name for a node. + */ + public String getValidatedName(String name, String charset) { + if (StringUtils.isEmpty(name)) { + return DEFAULT_UNTITLED_NODE_NAME; + } + final StringBuilder newLabel = new StringBuilder(name.length()); + + // name cannot begin with . (dot) + int ch = name.charAt(0); + if (!isCharValid(ch, charset) || ch == 46) { + newLabel.append("-"); + } else { + newLabel.append(name.charAt(0)); + } + + for (int i = 1; i < name.length(); i++) { + int charCode = name.charAt(i); + if (isCharValid(charCode, charset)) { + newLabel.append(name.charAt(i)); + } else { + newLabel.append("-"); + } + } + if (newLabel.length() == 0) { + newLabel.append(DEFAULT_UNTITLED_NODE_NAME); + } + return newLabel.toString(); + } + + private boolean isCharValid(int charCode, String charset) { + // TODO fgrilli: we now allow dots (.) in JR local names but actually in JR 2.0 other chars could be allowed as well + // (see http://www.day.com/specs/jcr/2.0/3_Repository_Model.html paragraph 2.2 and org.apache.jackrabbit.util.XMLChar.isValid()). + // Also, now that we're on java 6 and JR 2.0 should the check for the charset be dropped? + + // http://www.ietf.org/rfc/rfc1738.txt + // safe = "$" | "-" | "_" | "." | "+" + // extra = "!" | "*" | "'" | "(" | ")" | "," + // national = "{" | "}" | "|" | "\" | "^" | "~" | "[" | "]" | "`" + // punctuation = "<" | ">" | "#" | "%" | <"> + // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" + + if (StandardCharsets.UTF_8.name().equals(charset)) { + // jackrabbit not allowed 32: [ ] 91: [[] 93: []] 42: [*] 34: ["] 46: [.] 58 [:] 92: [\] 39 :['] + // url not valid 59: [;] 47: [/] 63: [?] 43: [+] 37: [%] 33: [!] 35:[#] + if (charCode != 32 + && charCode != '[' + && charCode != ']' + && charCode != '*' + && charCode != '"' + && charCode != ':' + && charCode != 92 + && charCode != 39 + && charCode != ';' + && charCode != '/' + && charCode != '?' + && charCode != '+' + && charCode != '%' + && charCode != '!' + && charCode != '#' + && charCode != '@' + && charCode != '=' + && charCode != '&' + && charCode != SelectorUtil.SELECTOR_DELIMITER.charAt(0)) { + return true; + } + } else { + // charCodes: 48-57: [0-9]; 65-90: [A-Z]; 97-122: [a-z]; 45: [-]; 95:[_] + if (((charCode >= 48) && (charCode <= 57)) + || ((charCode >= 65) && (charCode <= 90)) + || ((charCode >= 97) && (charCode <= 122)) + || charCode == 45 + || charCode == 46 + || charCode == 95) { + return true; + } + + } + return false; + } + + private String createUniqueName(String baseName) { + int pos; + for (pos = baseName.length() - 1; pos >= 0; pos--) { + char c = baseName.charAt(pos); + if (c < '0' || c > '9') { + break; + } + } + String base; + int cnt; + if (pos == -1) { + if (baseName.length() > 1) { + pos = baseName.length() - 2; + } + } + if (pos == -1) { + base = baseName; + cnt = -1; + } else { + pos++; + base = baseName.substring(0, pos); + if (pos == baseName.length()) { + cnt = -1; + } else { + cnt = Integer.parseInt(baseName.substring(pos)); + } + } + return (base + ++cnt); + } +} diff --git a/magnolia-core/src/main/resources/META-INF/magnolia/core.xml b/magnolia-core/src/main/resources/META-INF/magnolia/core.xml index ee95789..e8281e2 100644 --- a/magnolia-core/src/main/resources/META-INF/magnolia/core.xml +++ b/magnolia-core/src/main/resources/META-INF/magnolia/core.xml @@ -188,6 +188,11 @@ info.magnolia.cms.core.version.CopyUtil singleton + + info.magnolia.jcr.util.NodeNameHelper + info.magnolia.jcr.util.NodeNameHelper + singleton + info.magnolia.voting.Voting diff --git a/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlGroupTest.java b/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlGroupTest.java index d2056e9..897faa3 100644 --- a/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlGroupTest.java +++ b/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlGroupTest.java @@ -35,8 +35,9 @@ package info.magnolia.cms.security; import static org.junit.Assert.*; import static org.mockito.Mockito.*; - import info.magnolia.context.MgnlContext; +import info.magnolia.init.MagnoliaConfigurationProperties; +import info.magnolia.jcr.util.NodeNameHelper; import info.magnolia.module.InstallContextImpl; import info.magnolia.module.InstallStatus; import info.magnolia.repository.RepositoryConstants; @@ -51,12 +52,13 @@ import org.junit.Test; public class MgnlGroupTest { private MgnlGroupManager gman; - + @Before public void setUp() throws Exception { final SecuritySupportImpl sec = new SecuritySupportImpl(); - sec.setGroupManager(new MgnlGroupManager()); - sec.setRoleManager(new MgnlRoleManager()); + final NodeNameHelper nodeNameHelper = new NodeNameHelper(mock(MagnoliaConfigurationProperties.class)); + sec.setGroupManager(new MgnlGroupManager(nodeNameHelper)); + sec.setRoleManager(new MgnlRoleManager(nodeNameHelper)); ComponentsTestUtil.setInstance(SecuritySupport.class, sec); MockUtil.initMockContext(); MockUtil.createAndSetHierarchyManager(RepositoryConstants.USERS, getClass().getResourceAsStream("sample-users.properties")); @@ -66,7 +68,7 @@ public class MgnlGroupTest { final InstallContextImpl context = mock(InstallContextImpl.class); when(context.getStatus()).thenReturn(InstallStatus.installDone); ComponentsTestUtil.setInstance(InstallContextImpl.class, context); - gman = new MgnlGroupManager(); + gman = new MgnlGroupManager(nodeNameHelper); } @After diff --git a/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlUserManagerTest.java b/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlUserManagerTest.java index a958058..3dca5e2 100644 --- a/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlUserManagerTest.java +++ b/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlUserManagerTest.java @@ -38,10 +38,11 @@ import static info.magnolia.test.hamcrest.NodeMatchers.hasNode; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; - import info.magnolia.context.MgnlContext; import info.magnolia.context.SystemContext; +import info.magnolia.init.MagnoliaConfigurationProperties; import info.magnolia.jcr.nodebuilder.NodeBuilderUtil; +import info.magnolia.jcr.util.NodeNameHelper; import info.magnolia.jcr.util.NodeTypes; import info.magnolia.module.InstallContextImpl; import info.magnolia.module.InstallStatus; @@ -72,11 +73,13 @@ public class MgnlUserManagerTest { private MgnlUserManager userManager; private SystemContext ctx; private MockSession session; + private NodeNameHelper nodeNameHelper; @Before public void setUp() throws Exception { this.ctx = mock(SystemContext.class); - this.userManager = new MgnlUserManager(); + this.nodeNameHelper = new NodeNameHelper(mock(MagnoliaConfigurationProperties.class)); + this.userManager = new MgnlUserManager(nodeNameHelper); this.session = new MockSession(RepositoryConstants.USERS); SecuritySupport securitySupport = mock(SecuritySupport.class); @@ -102,7 +105,7 @@ public class MgnlUserManagerTest { @Test public void testUsernameIsValidatedUponCreation() { final String justCheckingIfValidateUsernameIsCalledMessage = "Yes! I wanted this method to be called !"; - final MgnlUserManager hm = new MgnlUserManager() { + final MgnlUserManager hm = new MgnlUserManager(nodeNameHelper) { @Override protected void validateUsername(String name) { throw new IllegalArgumentException(justCheckingIfValidateUsernameIsCalledMessage); @@ -133,7 +136,7 @@ public class MgnlUserManagerTest { @Test public void testUsernameCantBeNull() { try { - new MgnlUserManager().validateUsername(null); + new MgnlUserManager(nodeNameHelper).validateUsername(null); fail("should have failed"); } catch (IllegalArgumentException e) { assertEquals("null is not a valid username.", e.getMessage()); @@ -143,7 +146,7 @@ public class MgnlUserManagerTest { @Test public void testUsernameCantBeEmpty() { try { - new MgnlUserManager().validateUsername(""); + new MgnlUserManager(nodeNameHelper).validateUsername(""); fail("should have failed"); } catch (IllegalArgumentException e) { assertEquals(" is not a valid username.", e.getMessage()); @@ -153,7 +156,7 @@ public class MgnlUserManagerTest { @Test public void testUsernameCantBeBlank() { try { - new MgnlUserManager().validateUsername(" "); + new MgnlUserManager(nodeNameHelper).validateUsername(" "); fail("should have failed"); } catch (IllegalArgumentException e) { assertEquals(" is not a valid username.", e.getMessage()); @@ -164,7 +167,7 @@ public class MgnlUserManagerTest { public void testFindPrincipalNode() throws RepositoryException { // GIVEN final String userName = "test"; - MgnlUserManager um = new MgnlUserManager(); + MgnlUserManager um = new MgnlUserManager(nodeNameHelper); um.setRealmName(Realm.REALM_ALL.getName()); diff --git a/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlUserTest.java b/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlUserTest.java index 5ef629d..604ddda 100644 --- a/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlUserTest.java +++ b/magnolia-core/src/test/java/info/magnolia/cms/security/MgnlUserTest.java @@ -35,8 +35,9 @@ package info.magnolia.cms.security; import static org.junit.Assert.*; import static org.mockito.Mockito.*; - import info.magnolia.context.MgnlContext; +import info.magnolia.init.MagnoliaConfigurationProperties; +import info.magnolia.jcr.util.NodeNameHelper; import info.magnolia.module.InstallContextImpl; import info.magnolia.module.InstallStatus; import info.magnolia.repository.RepositoryConstants; @@ -54,12 +55,14 @@ import com.google.common.collect.ImmutableMap; public class MgnlUserTest { private UserManager uman; private SecuritySupportImpl sec; + private NodeNameHelper nodeNameHelper; @Before public void setUp() throws Exception { sec = new SecuritySupportImpl(); - sec.setGroupManager(new MgnlGroupManager()); - sec.setRoleManager(new MgnlRoleManager()); + nodeNameHelper = new NodeNameHelper(mock(MagnoliaConfigurationProperties.class)); + sec.setGroupManager(new MgnlGroupManager(nodeNameHelper)); + sec.setRoleManager(new MgnlRoleManager(nodeNameHelper)); createAndSetMgnlUserManager("test", RepositoryConstants.USERS); ComponentsTestUtil.setInstance(SecuritySupport.class, sec); @@ -181,7 +184,7 @@ public class MgnlUserTest { } private void createAndSetMgnlUserManager(final String realmName, final String repositoryName) { - uman = new MgnlUserManager() { + uman = new MgnlUserManager(nodeNameHelper) { { setRealmName(realmName); } diff --git a/magnolia-core/src/test/java/info/magnolia/cms/security/RescueSecuritySupportTest.java b/magnolia-core/src/test/java/info/magnolia/cms/security/RescueSecuritySupportTest.java index 6a97909..616cd39 100644 --- a/magnolia-core/src/test/java/info/magnolia/cms/security/RescueSecuritySupportTest.java +++ b/magnolia-core/src/test/java/info/magnolia/cms/security/RescueSecuritySupportTest.java @@ -35,9 +35,9 @@ package info.magnolia.cms.security; import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; - import info.magnolia.cms.security.RescueSecuritySupport.RescueUserManager; import info.magnolia.repository.RepositoryConstants; +import info.magnolia.test.ComponentsTestUtil; import info.magnolia.test.MgnlTestCase; import info.magnolia.test.mock.MockUtil; @@ -55,6 +55,7 @@ public class RescueSecuritySupportTest extends MgnlTestCase { public void setUp() throws Exception { super.setUp(); securitySupport = new RescueSecuritySupport(); + ComponentsTestUtil.setInstance(SecuritySupport.class, securitySupport); MockUtil.createAndSetHierarchyManager(RepositoryConstants.USERS, getClass().getResourceAsStream("sample-users.properties")); MockUtil.createAndSetHierarchyManager(RepositoryConstants.USER_GROUPS, getClass().getResourceAsStream("sample-usergroups.properties")); MockUtil.createAndSetHierarchyManager(RepositoryConstants.USER_ROLES, getClass().getResourceAsStream("sample-userroles.properties")); diff --git a/magnolia-core/src/test/java/info/magnolia/cms/security/SecuritySupportObservedComponentFactoryTest.java b/magnolia-core/src/test/java/info/magnolia/cms/security/SecuritySupportObservedComponentFactoryTest.java index bb769c1..08f0e1c 100644 --- a/magnolia-core/src/test/java/info/magnolia/cms/security/SecuritySupportObservedComponentFactoryTest.java +++ b/magnolia-core/src/test/java/info/magnolia/cms/security/SecuritySupportObservedComponentFactoryTest.java @@ -34,9 +34,11 @@ package info.magnolia.cms.security; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; import info.magnolia.context.MgnlContext; import info.magnolia.context.SystemContext; +import info.magnolia.init.MagnoliaConfigurationProperties; import info.magnolia.jcr.node2bean.Node2BeanProcessor; import info.magnolia.jcr.node2bean.Node2BeanTransformer; import info.magnolia.jcr.node2bean.TypeMapping; @@ -65,6 +67,7 @@ public class SecuritySupportObservedComponentFactoryTest { final SystemContext ctx = new MockContext(); MgnlContext.setInstance(ctx); ComponentsTestUtil.setInstance(SystemContext.class, ctx); + ComponentsTestUtil.setInstance(MagnoliaConfigurationProperties.class, mock(MagnoliaConfigurationProperties.class)); MockUtil.createAndSetHierarchyManager(RepositoryConstants.CONFIG, "/foo"); MockUtil.createAndSetHierarchyManager(RepositoryConstants.USERS, "/foo"); @@ -76,6 +79,8 @@ public class SecuritySupportObservedComponentFactoryTest { ComponentsTestUtil.setImplementation(Node2BeanProcessor.class, Node2BeanProcessorImpl.class); ComponentsTestUtil.setImplementation(Node2BeanTransformer.class, Node2BeanTransformerImpl.class); + ComponentsTestUtil.setImplementation(SecuritySupport.class, SecuritySupportImpl.class); + factory = new SecuritySupportObservedComponentFactory(); } @@ -111,4 +116,4 @@ public class SecuritySupportObservedComponentFactoryTest { // THEN assertTrue(securitySupport instanceof SecuritySupportImpl); } -} +} \ No newline at end of file diff --git a/magnolia-core/src/test/java/info/magnolia/cms/security/SecurityTest.java b/magnolia-core/src/test/java/info/magnolia/cms/security/SecurityTest.java index 7a6eeea..a4b8f9a 100644 --- a/magnolia-core/src/test/java/info/magnolia/cms/security/SecurityTest.java +++ b/magnolia-core/src/test/java/info/magnolia/cms/security/SecurityTest.java @@ -33,11 +33,14 @@ */ package info.magnolia.cms.security; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; import static org.mockito.Mockito.*; -import info.magnolia.cms.security.auth.PrincipalCollectionImpl; +import info.magnolia.cms.security.auth.PrincipalCollection; import info.magnolia.context.MgnlContext; +import info.magnolia.init.MagnoliaConfigurationProperties; +import info.magnolia.jcr.util.NodeNameHelper; import info.magnolia.module.InstallContextImpl; import info.magnolia.module.InstallStatus; import info.magnolia.repository.RepositoryConstants; @@ -45,10 +48,12 @@ import info.magnolia.test.ComponentsTestUtil; import info.magnolia.test.mock.MockUtil; import java.security.Principal; -import java.util.Iterator; import javax.security.auth.Subject; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -57,6 +62,10 @@ public class SecurityTest { private final class DummyUserManager extends MgnlUserManager { + private DummyUserManager(NodeNameHelper nodeNameHelper) { + super(nodeNameHelper); + } + @Override public User getAnonymousUser() { return getUser("anonymous"); @@ -71,8 +80,9 @@ public class SecurityTest { @Before public void setUp() throws Exception { final SecuritySupportImpl sec = new SecuritySupportImpl(); - sec.addUserManager(Realm.REALM_SYSTEM.getName(), new DummyUserManager()); - sec.setRoleManager(new MgnlRoleManager()); + NodeNameHelper nodeNameHelper = new NodeNameHelper(mock(MagnoliaConfigurationProperties.class)); + sec.addUserManager(Realm.REALM_SYSTEM.getName(), new DummyUserManager(nodeNameHelper)); + sec.setRoleManager(new MgnlRoleManager(nodeNameHelper)); ComponentsTestUtil.setInstance(SecuritySupport.class, sec); MockUtil.initMockContext(); MockUtil.createAndSetHierarchyManager(RepositoryConstants.USERS, getClass().getResourceAsStream("anonymous-user.properties")); @@ -90,15 +100,31 @@ public class SecurityTest { } @Test - public void testMergePrincipals() throws AccessDeniedException { + public void mergePrincipals() throws Exception { Subject subject = Security.getAnonymousSubject(); - assertEquals("anonymous", subject.getPrincipals(User.class).iterator().next().getName()); - - Iterator principalCollectionIter = subject.getPrincipals(PrincipalCollectionImpl.class).iterator(); - Iterator principalIter = principalCollectionIter.next().iterator(); - assertEquals("website", principalIter.next().getName()); - assertEquals("data", principalIter.next().getName()); - assertEquals("imaging", principalIter.next().getName()); - assertFalse(principalIter.hasNext()); + assertThat(subject.getPrincipals(User.class).iterator().next().getName(), equalTo("anonymous")); + + PrincipalCollection principals = subject.getPrincipals(PrincipalCollection.class).iterator().next(); + + assertThat(principals, containsInAnyOrder(aclNamed("website"), aclNamed("data"), aclNamed("imaging"))); + } + + private Matcher aclNamed(final String name) { + return new TypeSafeMatcher() { + @Override + public void describeTo(final Description description) { + description.appendText("getName should return ").appendValue(name); + } + + @Override + protected void describeMismatchSafely(final Principal item, final Description mismatchDescription) { + mismatchDescription.appendText(" was ").appendValue(item.getName()); + } + + @Override + protected boolean matchesSafely(final Principal item) { + return item.getName().equals(name); + } + }; } -} +} \ No newline at end of file diff --git a/magnolia-core/src/test/java/info/magnolia/jcr/util/NodeNameHelperTest.java b/magnolia-core/src/test/java/info/magnolia/jcr/util/NodeNameHelperTest.java new file mode 100644 index 0000000..2aa676e --- /dev/null +++ b/magnolia-core/src/test/java/info/magnolia/jcr/util/NodeNameHelperTest.java @@ -0,0 +1,151 @@ +/** + * This file Copyright (c) 2017 Magnolia International + * Ltd. (http://www.magnolia-cms.com). All rights reserved. + * + * + * This file is dual-licensed under both the Magnolia + * Network Agreement and the GNU General Public License. + * You may elect to use one or the other of these licenses. + * + * This file is distributed in the hope that it will be + * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. + * Redistribution, except as permitted by whichever of the GPL + * or MNA you select, is prohibited. + * + * 1. For the GPL license (GPL), you can redistribute and/or + * modify this file under the terms of the GNU General + * Public License, Version 3, as published by the Free Software + * Foundation. You should have received a copy of the GNU + * General Public License, Version 3 along with this program; + * if not, write to the Free Software Foundation, Inc., 51 + * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * 2. For the Magnolia Network Agreement (MNA), this file + * and the accompanying materials are made available under the + * terms of the MNA which accompanies this distribution, and + * is available at http://www.magnolia-cms.com/mna.html + * + * Any modifications to this file must keep this entire header + * intact. + * + */ +package info.magnolia.jcr.util; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +import info.magnolia.init.MagnoliaConfigurationProperties; +import info.magnolia.repository.RepositoryConstants; +import info.magnolia.test.mock.jcr.MockSession; + +import javax.jcr.Node; +import javax.jcr.Session; + +import org.junit.Before; +import org.junit.Test; + +public class NodeNameHelperTest { + + private NodeNameHelper nodeNameHelper; + private Session session; + + @Before + public void setUp() { + // GIVEN + nodeNameHelper = new NodeNameHelper(mock(MagnoliaConfigurationProperties.class)); + session = new MockSession(RepositoryConstants.CONFIG); + } + + @Test + public void getValidatedName() throws Exception { + // WHEN-THEN + // plain chars tests + assertEquals("f", nodeNameHelper.getValidatedName("f", null)); + assertEquals("fo", nodeNameHelper.getValidatedName("fo", null)); + assertEquals("foo", nodeNameHelper.getValidatedName("foo", null)); + + // dot tests + assertEquals("foo.bar", nodeNameHelper.getValidatedName("foo.bar", null)); + assertEquals("foo..bar", nodeNameHelper.getValidatedName("foo..bar", null)); + // local names beginning with dot are not allowed + assertEquals("-foo", nodeNameHelper.getValidatedName(".foo", null)); + assertEquals("-.foo", nodeNameHelper.getValidatedName("..foo", null)); + + // invalid or special chars tests + assertEquals("f-oo", nodeNameHelper.getValidatedName("f$oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f*oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f[oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f]oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f;oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f:oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f\"oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f'oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f#oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f!oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f+oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f?oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f/oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f%oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f oo", null)); // if you can't see it, that's a space (ascii code 32) + assertEquals("f-oo", nodeNameHelper.getValidatedName("f-oo", null)); + assertEquals("f_oo", nodeNameHelper.getValidatedName("f_oo", null)); + assertEquals("f-oo", nodeNameHelper.getValidatedName("f~oo", null)); + + // (alpha)numeric chars tests + assertEquals("0", nodeNameHelper.getValidatedName("0", null)); + assertEquals("0foo", nodeNameHelper.getValidatedName("0foo", null)); + assertEquals("123", nodeNameHelper.getValidatedName("123", null)); + assertEquals("foo0", nodeNameHelper.getValidatedName("foo0", null)); + + // uppercase test + assertEquals("FOO", nodeNameHelper.getValidatedName("FOO", null)); + + // empty or blank labels + assertEquals("untitled", nodeNameHelper.getValidatedName(null, null)); + assertEquals("untitled", nodeNameHelper.getValidatedName("", null)); + assertEquals("----", nodeNameHelper.getValidatedName(" ", null)); + } + + @Test + public void getUniqueName() throws Exception { + // GIVEN + Node parent = session.getRootNode(); + parent.addNode("a"); + parent.addNode("b"); + parent.addNode("b0"); + + // WHEN-THEN + // from node + assertThat(nodeNameHelper.getUniqueName(parent, "a"), equalTo("a0")); + assertThat(nodeNameHelper.getUniqueName(parent, "b"), equalTo("b1")); + assertThat(nodeNameHelper.getUniqueName(parent, "b0"), equalTo("b1")); + assertThat(nodeNameHelper.getUniqueName(parent, "c"), equalTo("c")); + + // from session + assertThat(nodeNameHelper.getUniqueName(session, parent.getPath(), "a"), equalTo("a0")); + assertThat(nodeNameHelper.getUniqueName(session, parent.getPath(), "b"), equalTo("b1")); + assertThat(nodeNameHelper.getUniqueName(session, parent.getPath(), "b0"), equalTo("b1")); + assertThat(nodeNameHelper.getUniqueName(session, parent.getPath(), "c"), equalTo("c")); + } + + @Test + public void getUniqueNameWithExtension() throws Exception { + // GIVEN + Node parent = session.getRootNode(); + parent.addNode("a.txt"); + parent.addNode("b.txt"); + parent.addNode("b0.txt"); + + // WHEN-THEN + assertThat(nodeNameHelper.getUniqueName(session, parent.getPath(), "a.txt", "txt"), equalTo("a0.txt")); + assertThat(nodeNameHelper.getUniqueName(session, parent.getPath(), "b.txt", "txt"), equalTo("b1.txt")); + assertThat(nodeNameHelper.getUniqueName(session, parent.getPath(), "b0.txt", "txt"), equalTo("b1.txt")); + assertThat(nodeNameHelper.getUniqueName(session, parent.getPath(), "c.txt", "txt"), equalTo("c.txt")); + assertThat(nodeNameHelper.getUniqueName(session, parent.getPath(), "a.foo", "txt"), equalTo("a.foo")); + assertThat(nodeNameHelper.getUniqueName(session, parent.getPath(), "a.txt", "bar"), equalTo("a.txt0")); + } +} \ No newline at end of file diff --git a/magnolia-core/src/test/java/info/magnolia/module/delta/RemoveDuplicatePermissionTaskTest.java b/magnolia-core/src/test/java/info/magnolia/module/delta/RemoveDuplicatePermissionTaskTest.java index ea77fd3..f6f5b85 100644 --- a/magnolia-core/src/test/java/info/magnolia/module/delta/RemoveDuplicatePermissionTaskTest.java +++ b/magnolia-core/src/test/java/info/magnolia/module/delta/RemoveDuplicatePermissionTaskTest.java @@ -35,13 +35,14 @@ package info.magnolia.module.delta; import static org.junit.Assert.*; import static org.mockito.Mockito.*; - import info.magnolia.cms.security.MgnlRoleManager; import info.magnolia.cms.security.Security; import info.magnolia.cms.security.SecuritySupport; import info.magnolia.cms.security.SecuritySupportImpl; import info.magnolia.context.MgnlContext; +import info.magnolia.init.MagnoliaConfigurationProperties; import info.magnolia.jcr.iterator.SameChildNodeTypeIterator; +import info.magnolia.jcr.util.NodeNameHelper; import info.magnolia.module.InstallContextImpl; import info.magnolia.module.InstallStatus; import info.magnolia.module.ModuleRegistryImpl; @@ -63,7 +64,8 @@ public class RemoveDuplicatePermissionTaskTest { @Before public void setUp() throws Exception { final SecuritySupportImpl sec = new SecuritySupportImpl(); - sec.setRoleManager(new MgnlRoleManager()); + final NodeNameHelper nodeNameHelper = new NodeNameHelper(mock(MagnoliaConfigurationProperties.class)); + sec.setRoleManager(new MgnlRoleManager(nodeNameHelper)); ComponentsTestUtil.setInstance(SecuritySupport.class, sec); MockUtil.initMockContext(); MockUtil.createAndSetHierarchyManager(RepositoryConstants.USER_ROLES, getClass().getResourceAsStream("sample-userroles.properties")); diff --git a/magnolia-freemarker-support/pom.xml b/magnolia-freemarker-support/pom.xml index 681c82d..719cd04 100644 --- a/magnolia-freemarker-support/pom.xml +++ b/magnolia-freemarker-support/pom.xml @@ -4,7 +4,7 @@ info.magnolia magnolia-project - 5.4.14 + 5.4.14-MAGNOLIA-7028 ../pom.xml info.magnolia.core diff --git a/magnolia-i18n/pom.xml b/magnolia-i18n/pom.xml index 1fa9fb1..a408924 100644 --- a/magnolia-i18n/pom.xml +++ b/magnolia-i18n/pom.xml @@ -3,7 +3,7 @@ magnolia-project info.magnolia - 5.4.14 + 5.4.14-MAGNOLIA-7028 ../pom.xml 4.0.0 diff --git a/magnolia-jaas/pom.xml b/magnolia-jaas/pom.xml index bfcb338..21d0503 100644 --- a/magnolia-jaas/pom.xml +++ b/magnolia-jaas/pom.xml @@ -3,7 +3,7 @@ magnolia-project info.magnolia - 5.4.14 + 5.4.14-MAGNOLIA-7028 ../pom.xml 4.0.0 diff --git a/magnolia-rendering/pom.xml b/magnolia-rendering/pom.xml index 7993812..4eb3b85 100644 --- a/magnolia-rendering/pom.xml +++ b/magnolia-rendering/pom.xml @@ -3,7 +3,7 @@ magnolia-project info.magnolia - 5.4.14 + 5.4.14-MAGNOLIA-7028 ../pom.xml 4.0.0 diff --git a/magnolia-resource-loader/pom.xml b/magnolia-resource-loader/pom.xml index 2358427..9fd22b6 100644 --- a/magnolia-resource-loader/pom.xml +++ b/magnolia-resource-loader/pom.xml @@ -4,7 +4,7 @@ magnolia-project info.magnolia - 5.4.14 + 5.4.14-MAGNOLIA-7028 ../pom.xml diff --git a/magnolia-templating-jsp/pom.xml b/magnolia-templating-jsp/pom.xml index 52e991d..4787d08 100644 --- a/magnolia-templating-jsp/pom.xml +++ b/magnolia-templating-jsp/pom.xml @@ -4,7 +4,7 @@ magnolia-project info.magnolia - 5.4.14 + 5.4.14-MAGNOLIA-7028 ../pom.xml magnolia-templating-jsp diff --git a/magnolia-templating/pom.xml b/magnolia-templating/pom.xml index 7cc38bf..ca50143 100644 --- a/magnolia-templating/pom.xml +++ b/magnolia-templating/pom.xml @@ -4,7 +4,7 @@ magnolia-project info.magnolia - 5.4.14 + 5.4.14-MAGNOLIA-7028 ../pom.xml magnolia-templating diff --git a/pom.xml b/pom.xml index e3ecf7f..37e88d4 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ magnolia-project pom Magnolia Main Project (Parent) - 5.4.14 + 5.4.14-MAGNOLIA-7028 Magnolia is an open-source enterprise class Content Management System developed by Magnolia International Ltd., based on the standard API