diff --git a/perun-base/src/main/java/cz/metacentrum/perun/core/api/BeansUtils.java b/perun-base/src/main/java/cz/metacentrum/perun/core/api/BeansUtils.java index cc7de32fca..ebd06ecec1 100644 --- a/perun-base/src/main/java/cz/metacentrum/perun/core/api/BeansUtils.java +++ b/perun-base/src/main/java/cz/metacentrum/perun/core/api/BeansUtils.java @@ -334,6 +334,49 @@ public static Map deserializeStringToMap(String text) { return map; } + /** + * From the given list, parses all values separated by the comma ',' char. + * The value has to end with the comma ',' char. + * + * All escaped commas '{backslash},' will not be used to split the value. If there is a + * backslash character: + * * it must be either follow by the comma ',' meaning that this + * comma is not used for split; or + * * it must be paired with another backslash '{backslash}{backslash}'. In that case, this double + * backslash in the result value would be replaced by a single back slash. + * + * Example: + * input: 'value1,val{backslash}ue2,val{backslash},ue3,' + * result: ['value1', 'val{backslash}ue2', val,ue3'] + * + * @param value value to be parsed + * @return list of parsed values + */ + public static List parseEscapedListValue(String value) { + String[] array = value.split(Character.toString(LIST_DELIMITER), -1); + List listValue = new ArrayList(); + //join items which was splited on escaped LIST_DELIMITER + for(int i = 0; i < array.length -1; i++) { //itarate to lenght -1 ... last array item is always empty + String item = array[i]; + while(item.matches("^(.*[^\\\\])?(\\\\\\\\)*\\\\$")) { //item last char is '\' . Next item start with ',', so we need to concat this items. + item = item.substring(0, item.length()-1); //cut off last char ('\') + try { + item = item.concat(Character.toString(LIST_DELIMITER)).concat(array[i+1]); + i++; + } catch(ArrayIndexOutOfBoundsException ex) { + throw new ConsistencyErrorException("Bad format in attribute value", ex); + } + } + //unescape + item = item.replaceAll("\\\\([\\\\" + Character.toString(LIST_DELIMITER) + "])", "$1"); + if(item.equals("\\0")) item = null; + + //return updated item back to list + listValue.add(item); + } + return listValue; + } + /** * Converts string representation of an attribute value to correct java object * @@ -370,30 +413,7 @@ public static Object stringToAttributeValue(String stringValue, String type) { } else if(attributeClass.equals(Boolean.class)) { return Boolean.parseBoolean(stringValue); } else if(attributeClass.equals(ArrayList.class)) { - String[] array = stringValue.split(Character.toString(LIST_DELIMITER), -1); - List attributeValue = new ArrayList(); - - //join items which was splited on escaped LIST_DELIMITER - for(int i = 0; i < array.length -1; i++) { //itarate to lenght -1 ... last array item is always empty - String item = array[i]; - while(item.matches("^(.*[^\\\\])?(\\\\\\\\)*\\\\$")) { //item last char is '\' . Next item start with ',', so we need to concat this items. - item = item.substring(0, item.length()-1); //cut off last char ('\') - try { - item = item.concat(Character.toString(LIST_DELIMITER)).concat(array[i+1]); - i++; - } catch(ArrayIndexOutOfBoundsException ex) { - throw new ConsistencyErrorException("Bad format in attribute value", ex); - } - } - //unescape - item = item.replaceAll("\\\\([\\\\" + Character.toString(LIST_DELIMITER) + "])", "$1"); - if(item.equals("\\0")) item = null; - - //return updated item back to list - attributeValue.add(item); - } - - return attributeValue; + return parseEscapedListValue(stringValue); } else if(attributeClass.equals(LinkedHashMap.class)) { String[] array = stringValue.split(Character.toString(LIST_DELIMITER), -1); Map attributeValue = new LinkedHashMap(); diff --git a/perun-core/src/main/java/cz/metacentrum/perun/core/blImpl/AttributesManagerBlImpl.java b/perun-core/src/main/java/cz/metacentrum/perun/core/blImpl/AttributesManagerBlImpl.java index 0e8a4556b9..9f70d81e0c 100644 --- a/perun-core/src/main/java/cz/metacentrum/perun/core/blImpl/AttributesManagerBlImpl.java +++ b/perun-core/src/main/java/cz/metacentrum/perun/core/blImpl/AttributesManagerBlImpl.java @@ -93,6 +93,7 @@ import cz.metacentrum.perun.core.impl.Utils; import cz.metacentrum.perun.core.impl.modules.attributes.urn_perun_entityless_attribute_def_def_namespace_GIDRanges; import cz.metacentrum.perun.core.impl.modules.attributes.urn_perun_facility_attribute_def_virt_GIDRanges; +import cz.metacentrum.perun.core.impl.modules.attributes.urn_perun_group_attribute_def_def_groupStructureResources; import cz.metacentrum.perun.core.impl.modules.attributes.urn_perun_member_attribute_def_def_suspensionInfo; import cz.metacentrum.perun.core.implApi.AttributesManagerImplApi; import cz.metacentrum.perun.core.implApi.modules.attributes.AttributesModuleImplApi; @@ -7489,6 +7490,14 @@ protected void initialize() throws InternalErrorException { rights.add(new AttributeRights(-1, Role.GROUPADMIN, Collections.singletonList(ActionType.READ))); attributes.put(attr, rights); + //urn:perun:group:attribute-def:def:groupStructureResources + attr = new urn_perun_group_attribute_def_def_groupStructureResources().getAttributeDefinition(); + //set attribute rights (with dummy id of attribute - not known yet) + rights = new ArrayList<>(); + rights.add(new AttributeRights(-1, Role.VOADMIN, Arrays.asList(ActionType.READ, ActionType.WRITE))); + rights.add(new AttributeRights(-1, Role.GROUPADMIN, Collections.singletonList(ActionType.READ))); + attributes.put(attr, rights); + //urn:perun:facility:attribute-def:def:login-namespace attr = new AttributeDefinition(); attr.setNamespace(AttributesManager.NS_FACILITY_ATTR_DEF); diff --git a/perun-core/src/main/java/cz/metacentrum/perun/core/blImpl/GroupsManagerBlImpl.java b/perun-core/src/main/java/cz/metacentrum/perun/core/blImpl/GroupsManagerBlImpl.java index 36741c075a..94a3d60934 100644 --- a/perun-core/src/main/java/cz/metacentrum/perun/core/blImpl/GroupsManagerBlImpl.java +++ b/perun-core/src/main/java/cz/metacentrum/perun/core/blImpl/GroupsManagerBlImpl.java @@ -64,6 +64,7 @@ import cz.metacentrum.perun.core.api.exceptions.ExtSourceNotExistsException; import cz.metacentrum.perun.core.api.exceptions.ExtSourceUnsupportedOperationException; import cz.metacentrum.perun.core.api.exceptions.ExtendMembershipException; +import cz.metacentrum.perun.core.api.exceptions.GroupAlreadyAssignedException; import cz.metacentrum.perun.core.api.exceptions.GroupAlreadyRemovedException; import cz.metacentrum.perun.core.api.exceptions.GroupAlreadyRemovedFromResourceException; import cz.metacentrum.perun.core.api.exceptions.GroupExistsException; @@ -93,6 +94,7 @@ import cz.metacentrum.perun.core.api.exceptions.PasswordDeletionFailedException; import cz.metacentrum.perun.core.api.exceptions.PasswordOperationTimeoutException; import cz.metacentrum.perun.core.api.exceptions.RelationExistsException; +import cz.metacentrum.perun.core.api.exceptions.ResourceNotExistsException; import cz.metacentrum.perun.core.api.exceptions.UserExtSourceExistsException; import cz.metacentrum.perun.core.api.exceptions.UserExtSourceNotExistsException; import cz.metacentrum.perun.core.api.exceptions.UserNotAdminException; @@ -122,6 +124,7 @@ import java.time.temporal.TemporalUnit; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; @@ -161,6 +164,7 @@ public class GroupsManagerBlImpl implements GroupsManagerBl { private final ArrayList groupStructureSynchronizerThreads; private static final String A_G_D_AUTHORITATIVE_GROUP = AttributesManager.NS_GROUP_ATTR_DEF + ":authoritativeGroup"; private static final String A_G_D_EXPIRATION_RULES = AttributesManager.NS_GROUP_ATTR_DEF + ":groupMembershipExpirationRules"; + private static final String A_G_D_GROUP_STRUCTURE_RESOURCES = AttributesManager.NS_GROUP_ATTR_DEF + ":groupStructureResources"; private static final String A_MG_D_MEMBERSHIP_EXPIRATION = AttributesManager.NS_MEMBER_GROUP_ATTR_DEF + ":groupMembershipExpiration"; private static final String A_M_V_LOA = AttributesManager.NS_MEMBER_ATTR_VIRT + ":loa"; private static final List statusesAffectedBySynchronization = Arrays.asList(Status.DISABLED, Status.EXPIRED, Status.INVALID); @@ -1742,11 +1746,144 @@ public List synchronizeGroupStructure(PerunSession sess, Group baseGroup setUpSynchronizationAttributesForAllSubGroups(sess, baseGroup, source, loginAttributeDefinition, loginPrefix); + syncResourcesForSynchronization(sess, baseGroup, loginAttributeDefinition, skippedGroups); + log.info("Group structure synchronization {}: ended.", baseGroup); return skippedGroups; } + /** + * Sync resources from groupStructureResources attribute to the group + * tree with the root in the given base group. If some resources are not found + * or the assignment failed, there is a new message added to the skippedMessages list. + * + * @param sess perun session + * @param baseGroup group structure sync base group + * @param skippedMessages list where are added messages about skipped operations + */ + private void syncResourcesForSynchronization(PerunSession sess, Group baseGroup, AttributeDefinition loginAttr, + List skippedMessages) { + Attribute syncedResourcesAttr; + + try { + syncedResourcesAttr = perunBl.getAttributesManagerBl() + .getAttribute(sess, baseGroup, A_G_D_GROUP_STRUCTURE_RESOURCES); + } catch (WrongAttributeAssignmentException | AttributeNotExistsException e) { + throw new InternalErrorException("Failed to obtain the groupStructureResources attribute for structure synchronization.", e); + } + + if (syncedResourcesAttr.getValue() == null) { + // no resources should be synced + return; + } + + // load all subgroups with logins once, so it can be reused in other methods + Map groupsByLogins = getAllSubGroupsWithLogins(sess, baseGroup, loginAttr); + + syncedResourcesAttr.valueAsMap().forEach((resourceId, groupLogins) -> + syncResourceInStructure(sess, resourceId, groupLogins, baseGroup, groupsByLogins, skippedMessages)); + } + + /** + * Assign resource with given id to groups with logins defined in parameter 'rawGroupLogins'. + * + * If the rawGroupLogins value is empty, then the resource is assigned to the whole structure. + * The rawGroupLogins are in format: 'login1,login2,login3,'. If the login contains a '\' or ',' + * characters, they must be escaped by the '\' character. + * + * @param sess perun session + * @param rawResourceId id of a resource which should be set + * @param rawGroupLogins group login separated with a comma ',' + * @param baseGroup base group which is used if the rawGroupLogins is empty + * @param groupsByLogins map containing groups with by their logins + * @param skippedMessages a list with skipped messages, other messages are added to this list + */ + private void syncResourceInStructure(PerunSession sess, String rawResourceId, String rawGroupLogins, + Group baseGroup, Map groupsByLogins, + List skippedMessages) { + int resourceId = Integer.parseInt(rawResourceId); + try { + Resource resource = perunBl.getResourcesManagerBl().getResourceById(sess, resourceId); + List groupLogins; + if (rawGroupLogins == null || rawGroupLogins.isEmpty()) { + // if no group logins are specified, assign the resource to the whole tree + groupLogins = Collections.singletonList(rawGroupLogins); + } else { + groupLogins = BeansUtils.parseEscapedListValue(rawGroupLogins); + } + groupLogins.forEach(login -> + syncResourceInStructure(sess, resource, baseGroup, login, groupsByLogins, skippedMessages)); + } catch (ResourceNotExistsException e) { + log.error("Assigning groups to a resource was skipped during group structure synchronization, because the resource wasn't found.", e); + skippedMessages.add("Assigning groups to resource with id'" + resourceId + "' skipped because it was not found."); + } + } + + /** + * Assign given resource to a group with given login, and to all of + * its subgroups. + * + * If the login value is empty, then the resource is assigned to + * the whole structure, except for the base group. + * + * @param sess perun session + * @param resource a resource which should be set + * @param login group login + * @param baseGroup base group which is used if the rawGroupLogins is empty + * @param groupsByLogins map containing groups with by their logins + * @param skippedMessages a list with skipped messages, other messages are added to this list + */ + private void syncResourceInStructure(PerunSession sess, Resource resource, Group baseGroup, String login, + Map groupsByLogins, List skippedMessages) { + Group rootGroup; + boolean assigningToTheBaseGroup = login == null || login.isEmpty(); + + if (assigningToTheBaseGroup) { + rootGroup = baseGroup; + } else { + rootGroup = groupsByLogins.get(login); + if (rootGroup == null) { + skippedMessages.add("Resource with id '" + resource.getId() + "' was skipped for group with login '" + + login + "' no group with this login was found."); + return; + } + } + + List groupsToAssign = perunBl.getGroupsManagerBl().getAllSubGroups(sess, rootGroup); + + if (!assigningToTheBaseGroup) { + groupsToAssign.add(rootGroup); + } + + assignGroupsToResource(sess, groupsToAssign, resource, skippedMessages); + } + + /** + * Assign resource to the given groups. If any of the assignments fails, + * information is added to the given skippedMessages. If some of the groups + * is already assigned, the group is skipped silently. + * + * @param sess perun session + * @param groups groups which should be assigned to the given resource + * @param resource resource + * @param skippedMessages list where are added messages about skipped operations + */ + private void assignGroupsToResource(PerunSession sess, Collection groups, Resource resource, + List skippedMessages) { + Set groupsToAssign = new HashSet<>(groups); + groupsToAssign.removeAll(perunBl.getResourcesManagerBl().getAssignedGroups(sess, resource)); + + groupsToAssign.forEach(group -> { + try { + perunBl.getResourcesManagerBl().assignGroupToResource(sess, group, resource); + } catch (WrongAttributeValueException | WrongReferenceAttributeValueException | GroupAlreadyAssignedException | GroupResourceMismatchException e) { + log.error("Failed to assign group during group structure synchronization. Group {}, resource {}", group, resource); + skippedMessages.add("Skipped assignment of a resource to a group. Group id: " + group.getId() + ", resource id: " + resource.getId()); + } + }); + } + @Override public void forceGroupSynchronization(PerunSession sess, Group group) throws GroupSynchronizationAlreadyRunningException, GroupSynchronizationNotEnabledException { //Check if the group should be synchronized (attribute synchronizationEnabled is set to 'true') diff --git a/perun-core/src/main/java/cz/metacentrum/perun/core/impl/modules/attributes/urn_perun_group_attribute_def_def_groupStructureResources.java b/perun-core/src/main/java/cz/metacentrum/perun/core/impl/modules/attributes/urn_perun_group_attribute_def_def_groupStructureResources.java new file mode 100644 index 0000000000..37c8e612ca --- /dev/null +++ b/perun-core/src/main/java/cz/metacentrum/perun/core/impl/modules/attributes/urn_perun_group_attribute_def_def_groupStructureResources.java @@ -0,0 +1,104 @@ +package cz.metacentrum.perun.core.impl.modules.attributes; + +import cz.metacentrum.perun.core.api.Attribute; +import cz.metacentrum.perun.core.api.AttributeDefinition; +import cz.metacentrum.perun.core.api.AttributesManager; +import cz.metacentrum.perun.core.api.Group; +import cz.metacentrum.perun.core.api.Resource; +import cz.metacentrum.perun.core.api.Vo; +import cz.metacentrum.perun.core.api.exceptions.InternalErrorException; +import cz.metacentrum.perun.core.api.exceptions.VoNotExistsException; +import cz.metacentrum.perun.core.api.exceptions.WrongAttributeAssignmentException; +import cz.metacentrum.perun.core.api.exceptions.WrongAttributeValueException; +import cz.metacentrum.perun.core.api.exceptions.WrongReferenceAttributeValueException; +import cz.metacentrum.perun.core.impl.PerunSessionImpl; +import cz.metacentrum.perun.core.implApi.modules.attributes.GroupAttributesModuleAbstract; +import cz.metacentrum.perun.core.implApi.modules.attributes.GroupAttributesModuleImplApi; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * @author Vojtech Sassmann + */ +public class urn_perun_group_attribute_def_def_groupStructureResources extends GroupAttributesModuleAbstract implements GroupAttributesModuleImplApi { + + private static final Pattern resourceIdPattern = Pattern.compile("^[1-9][0-9]*$"); + private static final Pattern invalidEscapePattern = + Pattern.compile("(" + "([^\\\\]|^)(\\\\\\\\)*)\\\\([^,\\\\]|$)"); + + @Override + public void checkAttributeSyntax(PerunSessionImpl sess, Group group, Attribute attribute) throws WrongAttributeValueException { + //Null value is ok, means no settings for group + if(attribute.getValue() == null) return; + + LinkedHashMap attrValues = attribute.valueAsMap(); + + boolean hasInvalidResourceName = attrValues.keySet().stream() + .anyMatch(this::invalidResourceName); + + if (hasInvalidResourceName) { + throw new WrongAttributeValueException(attribute, group, "Some of the specified resource ids has an invalid format."); + } + for (String rawGroupLogins : attrValues.values()) { + // null or empty value means the whole group tree + if (rawGroupLogins == null || rawGroupLogins.isEmpty()) { + continue; + } + Matcher m = invalidEscapePattern.matcher(rawGroupLogins); + if (m.find()) { + throw new WrongAttributeValueException(attribute, group, "Group logins format contains invalid escape sequence."); + } + if (!rawGroupLogins.endsWith(",")) { + throw new WrongAttributeValueException(attribute, group, "Each group login has to end with a comma ','."); + } + } + } + + @Override + public void checkAttributeSemantics(PerunSessionImpl sess, Group group, Attribute attribute) throws WrongReferenceAttributeValueException, WrongAttributeAssignmentException { + //Null value is ok, means no settings for group + if(attribute.getValue() == null) return; + + LinkedHashMap attrValues = attribute.valueAsMap(); + Vo vo; + try { + vo = sess.getPerunBl().getVosManagerBl().getVoById(sess, group.getVoId()); + } catch (VoNotExistsException e) { + throw new InternalErrorException("Failed to find group's vo.", e); + } + List voResources = sess.getPerunBl().getResourcesManagerBl().getResources(sess, vo); + Set voResourceIds = voResources.stream() + .map(Resource::getId) + .collect(Collectors.toSet()); + + for (String rawId : attrValues.keySet()) { + int id = Integer.parseInt(rawId); + if (!voResourceIds.contains(id)) { + throw new WrongReferenceAttributeValueException(attribute, "There is no resource with id '" + id + "' assigned to the groups vo: " + vo); + } + } + } + + private boolean invalidResourceName(String value) { + return !resourceIdPattern.matcher(value).matches(); + } + + @Override + public AttributeDefinition getAttributeDefinition() { + AttributeDefinition attr = new AttributeDefinition(); + attr.setNamespace(AttributesManager.NS_GROUP_ATTR_DEF); + attr.setType(LinkedHashMap.class.getName()); + attr.setFriendlyName("groupStructureResources"); + attr.setDisplayName("Group structure synchronization resources"); + attr.setDescription("Defines, which resources (map keys) should be auto assigned, and to which groups " + + "(map values). Each group login should end with the `,` character (even the last one, eg: " + + "`login1,login2,`). If some of the group logins contains a comma ',' or backslash '\\', you have to " + + "escape it with the backslash '\\' character."); + return attr; + } +} \ No newline at end of file diff --git a/perun-core/src/test/java/cz/metacentrum/perun/core/entry/GroupStructureSynchronizationIntegrationTest.java b/perun-core/src/test/java/cz/metacentrum/perun/core/entry/GroupStructureSynchronizationIntegrationTest.java index 1e59ebecc9..6eac97f8e8 100644 --- a/perun-core/src/test/java/cz/metacentrum/perun/core/entry/GroupStructureSynchronizationIntegrationTest.java +++ b/perun-core/src/test/java/cz/metacentrum/perun/core/entry/GroupStructureSynchronizationIntegrationTest.java @@ -6,9 +6,11 @@ import cz.metacentrum.perun.core.api.AttributesManager; import cz.metacentrum.perun.core.api.ExtSource; import cz.metacentrum.perun.core.api.ExtSourcesManager; +import cz.metacentrum.perun.core.api.Facility; import cz.metacentrum.perun.core.api.Group; import cz.metacentrum.perun.core.api.GroupsManager; import cz.metacentrum.perun.core.api.PerunSession; +import cz.metacentrum.perun.core.api.Resource; import cz.metacentrum.perun.core.api.Vo; import cz.metacentrum.perun.core.bl.AttributesManagerBl; import cz.metacentrum.perun.core.bl.ExtSourcesManagerBl; @@ -30,6 +32,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -57,10 +60,14 @@ public class GroupStructureSynchronizationIntegrationTest extends AbstractPerunI private static final String EXT_SOURCE_NAME = "GroupSyncExtSource"; private static final String ADDITIONAL_STRING = "additionalString"; private static final String ADDITIONAL_LIST = "additionalList"; + private static final String A_G_D_SYNC_RESOURCES = AttributesManager.NS_GROUP_ATTR_DEF + ":groupStructureResources"; private final Group baseGroup = new Group("baseGroup", "I am base group"); private Vo vo; private ExtSource extSource = new ExtSource(0, EXT_SOURCE_NAME, ExtSourcesManager.EXTSOURCE_LDAP); + private Resource resource1; + private Resource resource2; + private Facility facility; //This annotation is used so spied extSourceManagerBl is used in the perun object. //Mocks are not injected yet. They are injected to perun when initMocks method is called. @@ -95,6 +102,8 @@ public void setUpBeforeEveryMethod() throws Exception { vo = setUpVo(); setUpBaseGroup(vo); + setUpFacility(); + setUpResources(); MockitoAnnotations.initMocks(this); @@ -104,6 +113,7 @@ public void setUpBeforeEveryMethod() throws Exception { doNothing().when(extSourceManagerBl).addExtSource(any(PerunSession.class), any(Group.class), any(ExtSource.class)); } + @After public void cleanUp() { perun.setExtSourcesManagerBl(extSourceManagerBlBackup); @@ -595,6 +605,159 @@ public void additionalAttributeIsUpdated() throws Exception { assertThat(updatedAttribute.getValue()).isEqualTo(updatedValue); } + @Test + public void resourceIsNotSetToTheBaseGroupWhenNoLoginIsSpecified() throws Exception { + System.out.println(CLASS_NAME + "resourceIsSetToTheBaseGroupWhenNoLoginIsSpecified"); + + final Group subBaseGroup = new Group("group1", "child of base group"); + groupsManagerBl.createGroup(sess, baseGroup, subBaseGroup); + setLoginToGroup(baseGroup, subBaseGroup, "group1"); + setSynchronizationResourcesAttribute(resource1.getId()); + + final TestGroup subBaseTestGroup = + new TestGroup("group1", "group1", null, subBaseGroup.getDescription()); + List> subjects = Collections.singletonList(subBaseTestGroup.toMap()); + when(essa.getSubjectGroups(anyMap())).thenReturn(subjects); + + groupsManagerBl.synchronizeGroupStructure(sess, baseGroup); + + List baseGroupResources = perun.getResourcesManagerBl().getAssignedResources(sess, baseGroup); + + assertThat(baseGroupResources).doesNotContain(resource1); + } + + @Test + public void resourceIsSetToTheSubgroupsOfTheBaseGroup() throws Exception { + System.out.println(CLASS_NAME + "resourceIsSetToTheSubgroupsOfTheBaseGroup"); + + final Group subBaseGroup = new Group("group1", "child of base group"); + groupsManagerBl.createGroup(sess, baseGroup, subBaseGroup); + setLoginToGroup(baseGroup, subBaseGroup, "group1"); + + setSynchronizationResourcesAttribute(resource1.getId()); + + final TestGroup subBaseTestGroup = + new TestGroup("group1", "group1", null, subBaseGroup.getDescription()); + List> subjects = Collections.singletonList(subBaseTestGroup.toMap()); + when(essa.getSubjectGroups(anyMap())).thenReturn(subjects); + + groupsManagerBl.synchronizeGroupStructure(sess, baseGroup); + + List subGroupResources = perun.getResourcesManagerBl().getAssignedResources(sess, subBaseGroup); + + assertThat(subGroupResources).contains(resource1); + } + + @Test + public void resourceIsSetToASubGroupInTheStructure() throws Exception { + System.out.println(CLASS_NAME + "resourceIsSetToASubGroupInTheStructure"); + + final Group subGroup = new Group("group1", "child of base group"); + groupsManagerBl.createGroup(sess, baseGroup, subGroup); + setLoginToGroup(baseGroup, subGroup, "group1"); + + final Group otherSubGroup = new Group("group2", "child of base group"); + groupsManagerBl.createGroup(sess, baseGroup, otherSubGroup); + setLoginToGroup(baseGroup, otherSubGroup, "group2"); + + setSynchronizationResourcesAttribute(resource1.getId(), "group1"); + + final TestGroup subBaseTestGroup = + new TestGroup("group1", "group1", null, subGroup.getDescription()); + final TestGroup otherSubBaseTestGroup = + new TestGroup("group2", "group2", null, otherSubGroup.getDescription()); + List> subjects = Arrays.asList(subBaseTestGroup.toMap(), otherSubBaseTestGroup.toMap()); + when(essa.getSubjectGroups(anyMap())).thenReturn(subjects); + + groupsManagerBl.synchronizeGroupStructure(sess, baseGroup); + + List baseGroupResources = perun.getResourcesManagerBl().getAssignedResources(sess, baseGroup); + List subGroupResources = perun.getResourcesManagerBl().getAssignedResources(sess, subGroup); + List otherSubGroupResources = perun.getResourcesManagerBl().getAssignedResources(sess, otherSubGroup); + + assertThat(baseGroupResources).doesNotContain(resource1); + assertThat(subGroupResources).contains(resource1); + assertThat(otherSubGroupResources).doesNotContain(resource1); + } + + @Test + public void resourceIsSetToMultipleTrees() throws Exception { + System.out.println(CLASS_NAME + "resourceIsSetToMultipleTrees"); + + final Group subGroup = new Group("group1", "child of base group"); + groupsManagerBl.createGroup(sess, baseGroup, subGroup); + setLoginToGroup(baseGroup, subGroup, "group1"); + + final Group otherSubGroup = new Group("group2", "child of base group"); + groupsManagerBl.createGroup(sess, baseGroup, otherSubGroup); + setLoginToGroup(baseGroup, otherSubGroup, "group2"); + + setSynchronizationResourcesAttribute(resource1.getId(), "group1", "group2"); + + final TestGroup subBaseTestGroup = + new TestGroup("group1", "group1", null, subGroup.getDescription()); + final TestGroup otherSubBaseTestGroup = + new TestGroup("group2", "group2", null, otherSubGroup.getDescription()); + List> subjects = Arrays.asList(subBaseTestGroup.toMap(), otherSubBaseTestGroup.toMap()); + when(essa.getSubjectGroups(anyMap())).thenReturn(subjects); + + groupsManagerBl.synchronizeGroupStructure(sess, baseGroup); + + List baseGroupResources = perun.getResourcesManagerBl().getAssignedResources(sess, baseGroup); + List subGroupResources = perun.getResourcesManagerBl().getAssignedResources(sess, subGroup); + List otherSubGroupResources = perun.getResourcesManagerBl().getAssignedResources(sess, otherSubGroup); + + assertThat(baseGroupResources).doesNotContain(resource1); + assertThat(subGroupResources).contains(resource1); + assertThat(otherSubGroupResources).contains(resource1); + } + + @Test + public void multipleResourcesAreSetToMultipleTrees() throws Exception { + System.out.println(CLASS_NAME + "resourceIsSetToMultipleTrees"); + + final Group subGroup = new Group("group1", "child of base group"); + groupsManagerBl.createGroup(sess, baseGroup, subGroup); + setLoginToGroup(baseGroup, subGroup, "group1"); + + final Group otherSubGroup = new Group("group2", "child of base group"); + groupsManagerBl.createGroup(sess, baseGroup, otherSubGroup); + setLoginToGroup(baseGroup, otherSubGroup, "group2"); + + setSynchronizationResourcesAttribute(resource1.getId(), "group1"); + setSynchronizationResourcesAttribute(resource2.getId(), "group2"); + + final TestGroup subBaseTestGroup = + new TestGroup("group1", "group1", null, subGroup.getDescription()); + final TestGroup otherSubBaseTestGroup = + new TestGroup("group2", "group2", null, otherSubGroup.getDescription()); + List> subjects = Arrays.asList(subBaseTestGroup.toMap(), otherSubBaseTestGroup.toMap()); + when(essa.getSubjectGroups(anyMap())).thenReturn(subjects); + + groupsManagerBl.synchronizeGroupStructure(sess, baseGroup); + + List baseGroupResources = perun.getResourcesManagerBl().getAssignedResources(sess, baseGroup); + List subGroupResources = perun.getResourcesManagerBl().getAssignedResources(sess, subGroup); + List otherSubGroupResources = perun.getResourcesManagerBl().getAssignedResources(sess, otherSubGroup); + + assertThat(baseGroupResources).doesNotContain(resource1, resource2); + assertThat(subGroupResources).containsOnly(resource1); + assertThat(otherSubGroupResources).containsOnly(resource2); + } + + private void setSynchronizationResourcesAttribute(int resourceId, String... logins) throws Exception { + Attribute attribute = perun.getAttributesManagerBl().getAttribute(sess, baseGroup, A_G_D_SYNC_RESOURCES); + if (attribute.getValue() == null) { + attribute.setValue(new LinkedHashMap<>()); + } + StringBuilder groupLogins = new StringBuilder(); + for (String login : logins) { + groupLogins.append(login).append(","); + } + attribute.valueAsMap().put(String.valueOf(resourceId), groupLogins.toString()); + perun.getAttributesManagerBl().setAttribute(sess, baseGroup, attribute); + } + private Vo setUpVo() throws Exception { Vo newVo = new Vo(0, "UserManagerTestVo", "UMTestVo"); @@ -806,4 +969,18 @@ private AttributeDefinition setGroupAttribute(String name, String type) throws E private AttributeDefinition setGroupAttribute(String name) throws Exception { return setGroupAttribute(name, String.class.getName()); } + + private void setUpResources() throws Exception { + resource1 = new Resource(-1, "resource1", "", facility.getId()); + resource1 = perun.getResourcesManagerBl().createResource(sess, resource1, vo, facility); + + resource2 = new Resource(-1, "resource2", "", facility.getId()); + resource2 = perun.getResourcesManagerBl().createResource(sess, resource2, vo, facility); + } + + private Facility setUpFacility() throws Exception { + facility = new Facility(-1, "Facility"); + facility = perun.getFacilitiesManagerBl().createFacility(sess, facility); + return facility; + } } diff --git a/perun-core/src/test/java/cz/metacentrum/perun/core/impl/modules/attributes/urn_perun_group_attribute_def_def_groupStructureResourcesTest.java b/perun-core/src/test/java/cz/metacentrum/perun/core/impl/modules/attributes/urn_perun_group_attribute_def_def_groupStructureResourcesTest.java new file mode 100644 index 0000000000..24427017e1 --- /dev/null +++ b/perun-core/src/test/java/cz/metacentrum/perun/core/impl/modules/attributes/urn_perun_group_attribute_def_def_groupStructureResourcesTest.java @@ -0,0 +1,307 @@ +package cz.metacentrum.perun.core.impl.modules.attributes; + +import cz.metacentrum.perun.core.api.Attribute; +import cz.metacentrum.perun.core.api.Group; +import cz.metacentrum.perun.core.api.Resource; +import cz.metacentrum.perun.core.api.Vo; +import cz.metacentrum.perun.core.api.exceptions.WrongAttributeValueException; +import cz.metacentrum.perun.core.api.exceptions.WrongReferenceAttributeValueException; +import cz.metacentrum.perun.core.impl.PerunSessionImpl; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Vojtech Sassmann + */ +public class urn_perun_group_attribute_def_def_groupStructureResourcesTest { + + private static final String CLASS_NAME = urn_perun_group_attribute_def_def_groupStructureResources.class.getName() + "."; + private urn_perun_group_attribute_def_def_groupStructureResources classInstance; + private Attribute attribute; + private PerunSessionImpl sess; + private Group group; + private Resource validResource; + private Vo vo; + + @Before + public void setUp() throws Exception { + group = new Group("group", "description"); + vo = new Vo(10, "vo", "vo"); + group.setVoId(vo.getId()); + classInstance = new urn_perun_group_attribute_def_def_groupStructureResources(); + attribute = new Attribute(classInstance.getAttributeDefinition()); + sess = mock(PerunSessionImpl.class, RETURNS_DEEP_STUBS); + + validResource = new Resource(19, "resource", "", -1); + validResource.setVoId(vo.getId()); + + when(sess.getPerunBl().getVosManagerBl().getVoById(sess, vo.getId())) + .thenReturn(vo); + + when(sess.getPerunBl().getResourcesManagerBl().getResources(sess, vo)) + .thenReturn(Collections.singletonList(validResource)); + } + + // ---- Syntax ---- // + + @Test + public void testGroupLoginsInvalidSingleEscape() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsInvalidSingleEscape"); + testInvalidGroupLoginsSyntax("\\"); + } + + @Test + public void testGroupLoginsInvalidEndEscape() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsInvalidEndEscape"); + testInvalidGroupLoginsSyntax("login1,\\"); + } + + @Test + public void testGroupLoginsInvalidStartEscape() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsInvalidStartEscape"); + testInvalidGroupLoginsSyntax("\\login1,"); + } + + @Test + public void testGroupLoginsInvalidEscape() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsInvalidEscape"); + testInvalidGroupLoginsSyntax("lo\\gin1,"); + } + + @Test + public void testGroupLoginsInvalidEscapeAfterCorrectBackslashEscape() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsInvalidEscapeAfterCorrectBackslashEscape"); + testInvalidGroupLoginsSyntax("lo\\\\\\gin1,"); + } + + @Test + public void testGroupLoginsInvalidEscapeAfterCorrectCommaEscape() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsInvalidEscapeAfterCorrectCommaEscape"); + testInvalidGroupLoginsSyntax("lo\\,\\gin1,"); + } + + @Test + public void testGroupLoginsNoCommaAtTheEnd() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsNoCommaAtTheEnd"); + testInvalidGroupLoginsSyntax("login1,login2"); + } + + @Test + public void testGroupLoginsValidCommaAfterBackSlash() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsValidCommaAfterBackSlash"); + testValidGroupLoginsSyntax("login1\\\\\\,login2,"); + } + + @Test + public void testGroupLoginsValidCommaAfterTwoBackSlashes() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsValidCommaAfterTwoBackSlashes"); + testValidGroupLoginsSyntax("login1\\\\\\\\,login2,"); + } + + @Test + public void testGroupLoginsValidBackslashEscape() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsValidBackslashEscape"); + testValidGroupLoginsSyntax("lo\\\\gin1,"); + } + + @Test + public void testGroupLoginsValidCommaEscape() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsValidCommaEscape"); + testValidGroupLoginsSyntax("lo\\,gin1,"); + } + + @Test + public void testGroupLoginsValidCommaAfterEscapeCommaEscape() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsValidCommaAfterEscapeCommaEscape"); + testValidGroupLoginsSyntax("lo\\,,gin1,"); + } + + @Test + public void testGroupLoginsValidMultipleLogins() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsValidMultipleLogins"); + testValidGroupLoginsSyntax("gro\\,up1,group2,group3,group4,"); + } + + @Test + public void testGroupLoginsValidEmptyValue() throws Exception { + System.out.println(CLASS_NAME + "testGroupLoginsValidEmptyValue"); + testValidGroupLoginsSyntax(""); + } + + @Test + public void testInvalidGroupLoginAfterValidOne() throws Exception { + System.out.println(CLASS_NAME + "testInvalidGroupLoginAfterValidOne"); + testInvalidMultipleGroupLoginsSyntax("", "missingComma"); + } + + @Test + public void testValidMultipleGroupLogins() throws Exception { + System.out.println(CLASS_NAME + "testValidMultipleGroupLogins"); + testValidMultipleGroupLoginsSyntax("", "valid,"); + } + + @Test + public void testNullValueIsValidInSyntax() throws Exception { + System.out.println(CLASS_NAME + "testNullValueIsValidInSyntax"); + attribute.setValue(null); + classInstance.checkAttributeSyntax(sess, group, attribute); + } + + @Test + public void testValidResourceIdSyntax() throws Exception { + System.out.println(CLASS_NAME + "testValidResourceIdSyntax"); + testValidResourceIdsSyntax("123"); + } + + @Test + public void testMultipleValidResourceIdSyntax() throws Exception { + System.out.println(CLASS_NAME + "testMultipleValidResourceIdSyntax"); + testValidResourceIdsSyntax("123", "23"); + } + + @Test + public void testInValidCharInResourceIdSyntax() throws Exception { + System.out.println(CLASS_NAME + "testInValidCharInResourceIdSyntax"); + testInValidResourceIdsSyntax("#123"); + } + + @Test + public void testInValidResourceIdWithValidSyntax() throws Exception { + System.out.println(CLASS_NAME + "testInValidResourceIdWithValidSyntax"); + testInValidResourceIdsSyntax("123", "#123"); + } + + @Test + public void testInValidResourceIdEmptySyntax() throws Exception { + System.out.println(CLASS_NAME + "testInValidResourceIdEmptySyntax"); + testInValidResourceIdsSyntax(""); + } + + + // ---- Semantics ---- // + + + @Test + public void testNullValueIsValidInSemantics() throws Exception { + System.out.println(CLASS_NAME + "testNullValueIsValidInSemantics"); + attribute.setValue(null); + classInstance.checkAttributeSemantics(sess, group, attribute); + } + + @Test + public void testValidResourceIdInSemantics() throws Exception { + System.out.println(CLASS_NAME + "testValidResourceIdInSemantics"); + testValidResourceSemantics(String.valueOf(validResource.getId())); + } + + @Test + public void testInValidResourceIdInSemantics() throws Exception { + System.out.println(CLASS_NAME + "testInValidResourceIdInSemantics"); + testInValidResourceSemantics("2344"); + } + + @Test + public void testInValidResourceIdWithValidInSemantics() throws Exception { + System.out.println(CLASS_NAME + "testInValidResourceIdWithValidInSemantics"); + testInValidResourceSemantics(String.valueOf(validResource.getId()), "2344"); + } + + + + // ---- Private methods ---- // + + + + private void testValidResourceSemantics(String... values) throws Exception { + HashMap attrValue = new LinkedHashMap<>(); + for (String value : values) { + attrValue.put(value, "login,"); + } + attribute.setValue(attrValue); + + classInstance.checkAttributeSemantics(sess, group, attribute); + } + + private void testInValidResourceSemantics(String... values) throws Exception { + HashMap attrValue = new LinkedHashMap<>(); + for (String value : values) { + attrValue.put(value, "login,"); + } + attribute.setValue(attrValue); + + assertThatExceptionOfType(WrongReferenceAttributeValueException.class) + .isThrownBy(() -> classInstance.checkAttributeSemantics(sess, group, attribute)); + } + + private void testValidResourceIdsSyntax(String... values) throws Exception { + HashMap attrValue = new LinkedHashMap<>(); + for (String value : values) { + attrValue.put(value, "login,"); + } + attribute.setValue(attrValue); + + classInstance.checkAttributeSyntax(sess, group, attribute); + } + + private void testInValidResourceIdsSyntax(String... values) throws Exception { + HashMap attrValue = new LinkedHashMap<>(); + for (String value : values) { + attrValue.put(value, "login,"); + } + attribute.setValue(attrValue); + + assertThatExceptionOfType(WrongAttributeValueException.class) + .isThrownBy(() -> classInstance.checkAttributeSyntax(sess, group, attribute)); + } + + private void testValidGroupLoginsSyntax(String value) throws Exception { + HashMap attrValue = new LinkedHashMap<>(); + attrValue.put("1", value); + attribute.setValue(attrValue); + + classInstance.checkAttributeSyntax(sess, group, attribute); + } + + private void testInvalidGroupLoginsSyntax(String value) { + HashMap attrValue = new LinkedHashMap<>(); + attrValue.put("1", value); + attribute.setValue(attrValue); + + assertThatExceptionOfType(WrongAttributeValueException.class) + .isThrownBy(() -> classInstance.checkAttributeSyntax(sess, group, attribute)); + } + + private void testInvalidMultipleGroupLoginsSyntax(String... values) { + HashMap attrValue = new LinkedHashMap<>(); + int resourceId = 1; + + for (String value : values) { + attrValue.put(String.valueOf(resourceId++), value); + attribute.setValue(attrValue); + } + + assertThatExceptionOfType(WrongAttributeValueException.class) + .isThrownBy(() -> classInstance.checkAttributeSyntax(sess, group, attribute)); + } + + private void testValidMultipleGroupLoginsSyntax(String... values) throws WrongAttributeValueException { + HashMap attrValue = new LinkedHashMap<>(); + int resourceId = 1; + + for (String value : values) { + attrValue.put(String.valueOf(resourceId), value); + attribute.setValue(attrValue); + } + + classInstance.checkAttributeSyntax(sess, group, attribute); + } +}