diff --git a/pom.xml b/pom.xml index 06e38be..078a907 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ connector-grouper-rest - 0.7 + 1.0.1 jar Grouper REST Connector @@ -83,22 +83,22 @@ connector-rest com.evolveum.polygon - 1.4.2.14-SNAPSHOT + 1.5.0.0 org.apache.httpcomponents httpclient - 4.5.1 + 4.5.13 org.json json - 20160810 + 20190722 org.testng testng - 6.8 + 7.4.0 test diff --git a/src/main/assembly/connector.xml b/src/main/assembly/connector.xml index 6d149b4..ad2b63f 100644 --- a/src/main/assembly/connector.xml +++ b/src/main/assembly/connector.xml @@ -14,10 +14,10 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 + http://maven.apache.org/xsd/assembly-1.1.3.xsd"> connector diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/GroupProcessor.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GroupProcessor.java index cbd144a..b4c74c8 100644 --- a/src/main/java/com/evolveum/polygon/connector/grouper/rest/GroupProcessor.java +++ b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GroupProcessor.java @@ -25,20 +25,22 @@ import org.json.JSONObject; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; /** * Contains logic for handling operations on Group object class. */ public class GroupProcessor extends Processor { - public static final String OBJECT_CLASS_NAME = "Group"; + public static final String GROUP_OBJECT_CLASS_NAME = "Group"; + public static final String STEM_OBJECT_CLASS_NAME = "Stem"; public static final String ATTR_NAME = "name"; public static final String ATTR_UUID = "uuid"; private static final String ATTR_EXTENSION = J_EXTENSION; + private static final String ATTR_ATTRIBUTES_JSON = "attributesJSON"; + private static final String ATTR_DESCRIPTION = "description"; public static final String ATTR_MEMBER = "member"; + public static final String ATTR_ATTRIBUTES = "attributes"; private static final String DEFAULT_BASE_STEM = ":"; @@ -46,33 +48,62 @@ public class GroupProcessor extends Processor { super(configuration); } - ObjectClass getObjectClass() { - return new ObjectClass(OBJECT_CLASS_NAME); + ObjectClass getGroupObjectClass() + { + return new ObjectClass(GROUP_OBJECT_CLASS_NAME); } - void read(Filter filter, ResultsHandler handler, OperationOptions options) { - if (filter == null) { + ObjectClass getStemObjectClass() + { + return new ObjectClass(STEM_OBJECT_CLASS_NAME); + } + + /** + * Create a request to get a list of groups from Grouper + * @param filter The filter that determines the query. We only recognize an EqualsFilter + * @param handler A result handler to take the data + * @param options Options to process. + */ + void getGroups(Filter filter, ResultsHandler handler, OperationOptions options) + { + if (filter == null) + { getAllGroups(handler, options); - } else if (filter instanceof EqualsFilter) { + } + else if (filter instanceof EqualsFilter) + { Attribute attribute = ((EqualsFilter) filter).getAttribute(); - if (attribute != null) { + if (attribute != null) + { List values = attribute.getValue(); - if (values == null || values.isEmpty()) { + if (values == null || values.isEmpty()) + { throw new IllegalArgumentException("No attribute value to look for: " + attribute); - } else if (values.size() > 1) { + } + else if (values.size() > 1) + { throw new IllegalArgumentException("More than one attribute value to look for: " + attribute); } - if (attribute.is(Name.NAME) || attribute.is(ATTR_NAME)) { + if (attribute.is(Name.NAME) || attribute.is(ATTR_NAME)) + { getGroupByName((String) values.get(0), handler, options); - } else if (attribute.is(Uid.NAME) || attribute.is(ATTR_UUID)) { + } + else if (attribute.is(Uid.NAME) || attribute.is(ATTR_UUID)) + { getGroupByUuid((String) values.get(0), handler, options); - } else { + } + else + { throw new IllegalArgumentException("Equal filter used on unsupported attribute: " + attribute); } - } else { + } + else + { throw new IllegalArgumentException("Equal filter used with no attribute: " + filter); } - } else { + } + else + { throw new IllegalArgumentException("Unsupported filter: " + filter); } } @@ -144,30 +175,162 @@ private void getAllGroups(final ResultsHandler handler, final OperationOptions o } } - private void getAllGroupsNoMembers(ResultsHandler handler) { + private void getAllGroupsNoMembers(ResultsHandler handler) + { URIBuilder uriBuilder = getUriBuilderForGroups(); - try { + try + { HttpPost request = new HttpPost(uriBuilder.build()); String configuredBaseStem = configuration.getBaseStem(); - JSONObject body = new JSONObject() - .put(J_WS_REST_FIND_GROUPS_REQUEST, new JSONObject() - .put(J_WS_QUERY_FILTER, new JSONObject() - .put(J_QUERY_FILTER_TYPE, VAL_FIND_BY_STEM_NAME) - .put(J_STEM_NAME, configuredBaseStem != null ? configuredBaseStem : DEFAULT_BASE_STEM) - .put(J_STEM_NAME_SCOPE, VAL_ALL_IN_SUBTREE))); + JSONObject body = new JSONObject().put(J_WS_REST_FIND_GROUPS_REQUEST, new JSONObject() + .put(J_WS_QUERY_FILTER, new JSONObject() + .put(J_QUERY_FILTER_TYPE, VAL_FIND_BY_STEM_NAME) + .put(J_STEM_NAME, configuredBaseStem != null ? configuredBaseStem : DEFAULT_BASE_STEM) + .put(J_STEM_NAME_SCOPE, VAL_ALL_IN_SUBTREE))); executeFindGroups(request, body, handler); - } catch (RuntimeException | URISyntaxException e) { + } + catch (RuntimeException | URISyntaxException e) + { throw processException(e, uriBuilder, "Get all groups"); } } - private boolean executeFindGroups(HttpPost request, JSONObject body, ResultsHandler handler) { + /** + * Create a request to get a list of stems from Grouper + * @param filter The filter that determines the query. We only recognize an EqualsFilter + * @param handler A result handler to take the data + * @param options Options to process. + */ + void getStems(Filter filter, ResultsHandler handler, OperationOptions options) + { + if (filter == null) + { + getAllStems(handler, options); + } + else if (filter instanceof EqualsFilter) + { + Attribute attribute = ((EqualsFilter) filter).getAttribute(); + if (attribute != null) + { + List values = attribute.getValue(); + if (values == null || values.isEmpty()) + { + throw new IllegalArgumentException("No attribute value to look for: " + attribute); + } + else if (values.size() > 1) + { + throw new IllegalArgumentException("More than one attribute value to look for: " + attribute); + } + if (attribute.is(Name.NAME) || attribute.is(ATTR_NAME)) + { + getStemByName((String) values.get(0), handler, options); + } + else if (attribute.is(Uid.NAME) || attribute.is(ATTR_UUID)) + { + getStemByUuid((String) values.get(0), handler, options); + } + else + { + throw new IllegalArgumentException("Equal filter used on unsupported attribute: " + attribute); + } + } + else + { + throw new IllegalArgumentException("Equal filter used with no attribute: " + filter); + } + } + else + { + throw new IllegalArgumentException("Unsupported filter: " + filter); + } + } + + /** + * Retrieves a single grouper Stem by Name + * @param stemName The name of the grouper stem + * @param handler Results Handler collects the data + * @param options Operation Option is Ignored + */ + private void getStemByName(String stemName, ResultsHandler handler, OperationOptions options) + { + URIBuilder uriBuilder = getUriBuilderForStems(); + try + { + HttpPost request = new HttpPost(uriBuilder.build()); + String baseStem = configuration.getBaseStem(); + JSONObject body = new JSONObject().put(J_WS_REST_FIND_STEMS_LITE_REQUEST, new JSONObject() + .put(J_STEM_QUERY_FILTER_TYPE, VAL_FIND_BY_STEM_NAME) + .put(J_STEM_NAME, stemName) + .put("actAsSubjectId", "GrouperSystem")); + executeFindStems(request, body, handler); + } + catch (RuntimeException | URISyntaxException e) + { + throw processException(e, uriBuilder, "Get Stem Name: " + stemName); + } + } + + /** + * Retrieves a single grouper Stem by UUID + * @param stemUuid The UUID of the grouper stem + * @param handler Results Handler collects the data + * @param options Operation Option is Ignored + */ + private void getStemByUuid(String stemUuid, ResultsHandler handler, OperationOptions options) + { + URIBuilder uriBuilder = getUriBuilderForStems(); + try + { + HttpPost request = new HttpPost(uriBuilder.build()); + String baseStem = configuration.getBaseStem(); + JSONObject body = new JSONObject().put(J_WS_REST_FIND_STEMS_LITE_REQUEST, new JSONObject() + .put(J_STEM_QUERY_FILTER_TYPE, VAL_FIND_BY_STEM_UUID) + .put(J_STEM_UUID, stemUuid) + .put("actAsSubjectId", "GrouperSystem")); + executeFindStems(request, body, handler); + } + catch (RuntimeException | URISyntaxException e) + { + throw processException(e, uriBuilder, "Get Stem UUID: " + stemUuid); + } + } + + + /** + * Retrieves all grouper Stems that are children of the base stem + * @param handler Results Handler collects the data + * @param options Operation Option is Ignored + */ + private void getAllStems(ResultsHandler handler, OperationOptions options) + { + URIBuilder uriBuilder = getUriBuilderForStems(); + try + { + HttpPost request = new HttpPost(uriBuilder.build()); + String baseStem = configuration.getBaseStem(); + JSONObject body = new JSONObject().put(J_WS_REST_FIND_STEMS_LITE_REQUEST, new JSONObject() + .put(J_STEM_QUERY_FILTER_TYPE, VAL_FIND_BY_PARENT_STEM_NAME) + .put(J_PARENT_STEM_NAME, baseStem != null ? baseStem : DEFAULT_BASE_STEM) + .put(J_PARENT_STEM_SCOPE, VAL_ALL_IN_SUBTREE)); + executeFindStems(request, body, handler); + } + catch (RuntimeException | URISyntaxException e) + { + throw processException(e, uriBuilder, "Get all Stems"); + } + } + + private boolean executeFindGroups(HttpPost request, JSONObject body, ResultsHandler handler) + { JSONObject response = callRequest(request, body, null).getResponse(); checkSuccess(response, J_WS_FIND_GROUPS_RESULTS); JSONArray groups = getArray(response, false, J_WS_FIND_GROUPS_RESULTS, J_GROUP_RESULTS); - if (groups != null) { - for (Object group : groups) { - if (!handleGroupJsonObject(group, handler)) { + if (groups != null) + { + for (Object group : groups) + { + if (!handleGroupJsonObject(group, handler)) + { return false; } } @@ -175,6 +338,147 @@ private boolean executeFindGroups(HttpPost request, JSONObject body, ResultsHand return true; } + /** + * Execute a request to retrieve Stems from Grouper + * @param request HTTP POST Request + * @param body Post Body + * @param handler Result handler + * @return true when data is available + */ + private boolean executeFindStems(HttpPost request, JSONObject body, ResultsHandler handler) + { + JSONObject response = callRequest(request, body, null).getResponse(); + checkSuccess(response, J_WS_FIND_STEMS_RESULTS); + JSONArray stems = getArray(response, false, J_WS_FIND_STEMS_RESULTS, J_STEM_RESULTS); + if (stems != null) + { + for (Object stem : stems) + { + if (!handleStemJsonObject(stem, handler)) + { + return false; + } + } + } + return true; + } + /** + * Execute a request to retrieve Group attributes + * @param uuid The UUID of the group that contains attributes + * @return map containing the attributes for the specified group + */ + private Map getGroupAttributesByUUID(String uuid ) + { + URIBuilder uriBuilder = getUriBuilderForAttributeAssignments(); + HashMap data = new HashMap<>(); + try + { + HttpPost request = new HttpPost(uriBuilder.build()); + JSONObject body = new JSONObject().put(J_WS_REST_GET_ATTRIBUTE_ASSIGNMENTS, new JSONObject() + .put("wsOwnerGroupId", uuid) + .put("attributeAssignType", "group") + .put("includeAssignmentsOnAssignments", VAL_F) + .put("enabled", VAL_T) + .put("actAsSubjectId", "GrouperSystem")); + JSONObject response = callRequest(request, body, null).getResponse(); + data = getAttributesAsMap(response); + } + catch (Exception e) + { + LOG.error(e, "Failed to retrieve group attributes"); + throw new IllegalStateException("Attribute Assignment request was not successful"); + } + return data; + } + + /** + * Execute a request to retrieve Stems attributes + * @param uuid The UUID of the Stem that contains attributes + * @return a map containing the stem attributes + */ + private Map getStemAttributesByUUID(String uuid ) + { + URIBuilder uriBuilder = getUriBuilderForAttributeAssignments(); + HashMap data = new HashMap<>(); + try + { + HttpPost request = new HttpPost(uriBuilder.build()); + JSONObject body = new JSONObject().put(J_WS_REST_GET_ATTRIBUTE_ASSIGNMENTS, new JSONObject() + .put("wsOwnerStemId", uuid) + .put("attributeAssignType", "stem") + .put("includeAssignmentsOnAssignments", VAL_F) + .put("enabled", VAL_T) + .put("actAsSubjectId", "GrouperSystem")); + JSONObject response = callRequest(request, body, null).getResponse(); + data = getAttributesAsMap(response); + } + catch (Exception e) + { + LOG.error(e, "Failed to retrieve stem attributes"); + throw new IllegalStateException("Attribute Assignment request was not successful"); + } + return data; + } + + /** + * Check the Grouper WS response and retrieve attribute assignments as a HashMap + * @param response the data returned by the web service + * @return Map containing the name value pairs for each attribute in the response + */ + private HashMap getAttributesAsMap( JSONObject response ) + { + HashMap data = new HashMap(); + checkSuccess(response, J_WS_GET_ATTRIBUTE_RESULTS); + JSONArray attributes = getArray(response, false, J_WS_GET_ATTRIBUTE_RESULTS, J_ATTRIBUTE_ASSIGNMENTS); + if (attributes != null) + { + for (Object obj : attributes) + { + if ( obj instanceof JSONObject) + { + JSONObject attribute = (JSONObject) obj; + String name = getStringOrNull(attribute, "attributeDefNameName"); + if ( name != null ) + { + int idx = name.lastIndexOf(":"); + if ( idx >= 0 ) + { + name = name.substring(idx+1); + } + } + String value = ""; + Object values = getIfExists(attribute, "wsAttributeAssignValues"); + if ( values != null && values instanceof JSONArray) + { + JSONArray array = (JSONArray) values; + for( int i=0; i< array.length(); i++) + { + JSONObject jobj = (JSONObject) array.get(i); + Object val = getIfExists(jobj, "valueSystem"); + if ( val != null ) + { + if (i == 0) + { + value = val.toString(); + } + else + { + value = value + ", " +val.toString(); + } + } + } + } + + if ( name != null ) + { + data.put(name, value); + } + } + } + } + return data; + } + private boolean executeGetMembers(HttpPost request, JSONObject body, ResultsHandler handler) { CallResponse callResponse = callRequest(request, body, (statusCode, responseBody) -> { JSONObject errorResponse = new JSONObject(responseBody); @@ -196,8 +500,11 @@ private boolean executeGetMembers(HttpPost request, JSONObject body, ResultsHand checkSuccess(response, J_WS_GET_MEMBERS_RESULTS); JSONObject gObject = (JSONObject) get(response, J_WS_GET_MEMBERS_RESULTS, J_RESULTS, J_WS_GROUP); String name = getStringOrNull(gObject, J_NAME); - if (groupNameMatches(name)) { - ConnectorObjectBuilder builder = startGroupObjectBuilding(gObject, name); + String uuid = getStringOrNull(gObject, J_UUID); + if (nameMatches(name, configuration.getGroupIncludePattern(), configuration.getGroupExcludePattern())) + { + Map attributes = getGroupAttributesByUUID(uuid); + ConnectorObjectBuilder builder = startGroupObjectBuilding(gObject, name, attributes); List members = new ArrayList<>(); JSONArray membersJsonArray = getArray(response, false, J_WS_GET_MEMBERS_RESULTS, J_RESULTS, J_WS_SUBJECTS); if (membersJsonArray != null) { @@ -223,44 +530,148 @@ private boolean executeGetMembers(HttpPost request, JSONObject body, ResultsHand } } - private boolean handleGroupJsonObject(Object group, ResultsHandler handler) { - if (group instanceof JSONObject) { + private boolean handleGroupJsonObject(Object group, ResultsHandler handler) + { + if (group instanceof JSONObject) + { JSONObject gObject = (JSONObject) group; String name = getStringOrNull(gObject, J_NAME); - if (groupNameMatches(name)) { - return handler.handle(startGroupObjectBuilding(gObject, name).build()); - } else { + String uuid = getStringOrNull(gObject, J_UUID); + if (nameMatches(name, configuration.getGroupIncludePattern(), configuration.getGroupExcludePattern())) + { + Map attributes = getGroupAttributesByUUID(uuid); + return handler.handle(startGroupObjectBuilding(gObject, name, attributes).build()); + } + else + { return true; } - } else { + } + else + { throw new IllegalStateException("Expected group as JSONObject, got " + group); } } - private ConnectorObjectBuilder startGroupObjectBuilding(JSONObject gObject, String name) { + private boolean handleStemJsonObject(Object stem, ResultsHandler handler) + { + if (stem instanceof JSONObject) + { + JSONObject gObject = (JSONObject) stem; + String name = getStringOrNull(gObject, J_NAME); + String uuid = getStringOrNull(gObject, J_UUID); + if (nameMatches(name, configuration.getStemIncludePattern(), configuration.getStemExcludePattern())) + { + Map attributes = getStemAttributesByUUID(uuid); + return handler.handle(startStemObjectBuilding(gObject, name, attributes).build()); + } + else + { + return true; + } + } + else + { + throw new IllegalStateException("Expected group as JSONObject, got " + stem); + } + } + + private ConnectorObjectBuilder startGroupObjectBuilding(JSONObject gObject, String name, Map attributes) + { String extension = getStringOrNull(gObject, J_EXTENSION); String uuid = getStringOrNull(gObject, J_UUID); + String description = getStringOrNull(gObject, J_DESCRIPTION); ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); - builder.setObjectClass(getObjectClass()); + String jsonAttributes = null; + + builder.setObjectClass(getGroupObjectClass()); + builder.setUid(uuid); + builder.setName(name); + builder.addAttribute(ATTR_EXTENSION, extension); + builder.addAttribute(ATTR_DESCRIPTION, description); + + if ( attributes != null && attributes.size() > 0 ) + { + JSONObject json = new JSONObject(attributes); + jsonAttributes = json.toString(); + } + + builder.addAttribute(ATTR_ATTRIBUTES_JSON, jsonAttributes); + + return builder; + } + + private ConnectorObjectBuilder startStemObjectBuilding(JSONObject gObject, String name, Map attributes) + { + ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); + String extension = getStringOrNull(gObject, J_EXTENSION); + String uuid = getStringOrNull(gObject, J_UUID); + String description = getStringOrNull(gObject, J_DESCRIPTION); + String[] customNames = configuration.getStemAttributeNames(); + String jsonAttributes = null; + + builder.setObjectClass(getStemObjectClass()); builder.setUid(uuid); builder.setName(name); builder.addAttribute(ATTR_EXTENSION, extension); + builder.addAttribute(ATTR_DESCRIPTION, description); + + if ( attributes != null && attributes.size() > 0 ) + { + JSONObject json = new JSONObject(attributes); + jsonAttributes = json.toString(); + builder.addAttribute(ATTR_ATTRIBUTES_JSON, jsonAttributes); + if ( customNames != null && customNames.length > 0 ) + { + for (String aName: customNames) + { + if( attributes.containsKey(aName)) + { + String aValue = attributes.get(aName); + builder.addAttribute(aName, aValue); + } + } + } + } + else + { + builder.addAttribute(ATTR_ATTRIBUTES_JSON, jsonAttributes); + } return builder; } - void test() { - if (configuration.getTestStem() != null) { - checkStemExists(configuration.getTestStem()); + /** + * Test to confirm that the grouper web service is reachable and that the registry is available. + */ + public void test() throws RuntimeException + { + String testStem = configuration.getTestStem(); + String testGroup = configuration.getTestGroup(); + if ( testStem != null && testStem.trim().length() > 0 ) + { + checkStemExists( testStem.trim() ); } - if (configuration.getTestGroup() != null) { - checkGroupExists(configuration.getTestGroup()); + else + { + checkStemExists( configuration.getBaseStem().trim() ); + } + if ( testGroup!= null && testGroup.trim().length() > 0 ) + { + checkGroupExists( testGroup.trim() ); } } - private void checkStemExists(String stemName) { + /** + * Retrieve information from the specified grouper stem + * @param stemName The stem to be checked + * @throws ConnectorException when the stem is not found or a connection error occurs + */ + private void checkStemExists(String stemName) throws ConnectorException + { URIBuilder uriBuilder = getUriBuilderForStems(); JSONArray stems; - try { + try + { HttpPost request = new HttpPost(uriBuilder.build()); JSONObject body = new JSONObject() .put(J_WS_REST_FIND_STEMS_REQUEST, new JSONObject() @@ -270,26 +681,36 @@ private void checkStemExists(String stemName) { JSONObject response = callRequest(request, body, null).getResponse(); checkSuccess(response, J_WS_FIND_STEMS_RESULTS); stems = getArray(response, true, J_WS_FIND_STEMS_RESULTS, J_STEM_RESULTS); - } catch (RuntimeException | URISyntaxException e) { + } + catch (RuntimeException | URISyntaxException e) + { throw processException(e, uriBuilder, "Find stems request"); } - if (stems.length() == 0) { - throw new ConnectorException("Expected to find the stem '" + stemName + "', found none"); + if (stems.length() == 0) + { + throw new ConnectorException("Grouper Stem '" + stemName + "' not found"); } } - private void checkGroupExists(String groupName) { + private void checkGroupExists(String groupName) + { List groups = new ArrayList<>(); getGroupByName(groupName, groups::add, null); LOG.info("getGroupByName found {0} group(s): {1}", groups.size(), groups); - if (groups.isEmpty()) { + if (groups.isEmpty()) + { throw new ConnectorException("Expected to find the group '" + groupName + "', found none"); } } - ObjectClassInfoBuilder buildSchema() { + /** + * Build Schema for Grouper group objects + * @return A builder that defines the object class + */ + ObjectClassInfoBuilder buildGroupSchema() + { ObjectClassInfoBuilder builder = new ObjectClassInfoBuilder(); - builder.setType(OBJECT_CLASS_NAME); + builder.setType(GROUP_OBJECT_CLASS_NAME); builder.addAttributeInfo( new AttributeInfoBuilder(Name.NAME, String.class) .setNativeName(ATTR_NAME) @@ -303,15 +724,91 @@ ObjectClassInfoBuilder buildSchema() { builder.addAttributeInfo( new AttributeInfoBuilder(ATTR_EXTENSION, String.class) .build()); + builder.addAttributeInfo( + new AttributeInfoBuilder(ATTR_DESCRIPTION, String.class) + .setReadable(true) + .build()); + builder.addAttributeInfo( + new AttributeInfoBuilder(ATTR_ATTRIBUTES_JSON, String.class) + .setSubtype(AttributeInfo.Subtypes.STRING_JSON) + .setReturnedByDefault(true) + .build()); builder.addAttributeInfo( new AttributeInfoBuilder(ATTR_MEMBER, String.class) .setMultiValued(true) .setReturnedByDefault(false) .build()); + // Add Custom attributes + if ( configuration != null && configuration.getGroupAttributeNames() != null ) + { + String[] attributeNames = configuration.getGroupAttributeNames(); + if ( attributeNames.length > 0 ) + { + for( String attributeName : attributeNames ) + { + if ( attributeName != null && attributeName.trim().length() > 0 ) + { + builder.addAttributeInfo(new AttributeInfoBuilder(attributeName, String.class).build()); + } + } + } + } + return builder; + } + /** + * Build Schema for Grouper Stem objects + * @return An Object Class Builder + */ + ObjectClassInfoBuilder buildStemSchema() + { + ObjectClassInfoBuilder builder = new ObjectClassInfoBuilder(); + builder.setType(STEM_OBJECT_CLASS_NAME); + builder.addAttributeInfo( + new AttributeInfoBuilder(Name.NAME, String.class) + .setNativeName(ATTR_NAME) + .setRequired(true) + .build()); + builder.addAttributeInfo( + new AttributeInfoBuilder(Uid.NAME, String.class) + .setNativeName(ATTR_UUID) + .setRequired(true) + .build()); + builder.addAttributeInfo( + new AttributeInfoBuilder(ATTR_EXTENSION, String.class) + .build()); + builder.addAttributeInfo( + new AttributeInfoBuilder(ATTR_DESCRIPTION, String.class) + .build()); + builder.addAttributeInfo( + new AttributeInfoBuilder(ATTR_ATTRIBUTES_JSON, String.class) + .setSubtype(AttributeInfo.Subtypes.STRING_JSON) + .setReturnedByDefault(true) + .build()); + // Add Custom attributes + if ( configuration != null && configuration.getStemAttributeNames() != null ) + { + String[] attributeNames = configuration.getStemAttributeNames(); + if ( attributeNames.length > 0 ) + { + for( String attributeName : attributeNames ) + { + if ( attributeName != null && attributeName.trim().length() > 0 ) + { + builder.addAttributeInfo(new AttributeInfoBuilder(attributeName, String.class).build()); + } + } + } + } return builder; } - private boolean isGetMembers(OperationOptions options) { + /** + * Test whether members are to be returned with a Group request + * @param options The options to be evaluated to determine whether members are to be included + * @return true when members are required to be included in a group Object + */ + private boolean isGetMembers(OperationOptions options) + { String[] attrs = options != null ? options.getAttributesToGet() : null; return attrs != null && Arrays.asList(attrs).contains(ATTR_MEMBER); } diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConfiguration.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConfiguration.java index e797d36..fd2bb06 100644 --- a/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConfiguration.java +++ b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConfiguration.java @@ -39,8 +39,11 @@ public class GrouperConfiguration extends AbstractConfiguration implements State private String username; private GuardedString password; private Boolean ignoreSslValidation; - - private String baseStem; + private String baseStem = ":"; + private String[] stemAttributeNames; + private String[] stemIncludePattern; + private String[] stemExcludePattern; + private String[] groupAttributeNames; private String[] groupIncludePattern; private String[] groupExcludePattern; private String subjectSource; @@ -103,11 +106,24 @@ public String getBaseStem() { public void setBaseStem(String baseStem) { this.baseStem = baseStem; } + /** + * array of attribute names to import with a group. + * if not specified all attributes are included + */ + @ConfigurationProperty(order = 60, displayMessageKey = "groupAttributeNames.display", helpMessageKey = "groupAttributeNames.help") + public String[] getGroupAttributeNames() + { + return groupAttributeNames; + } + public void setGroupAttributeNames(String[] attributeName) + { + this.groupAttributeNames = attributeName; + } /** * Which groups should be visible to this connector? */ - @ConfigurationProperty(order = 60, displayMessageKey = "groupIncludePattern.display", helpMessageKey = "groupIncludePattern.help") + @ConfigurationProperty(order = 65, displayMessageKey = "groupIncludePattern.display", helpMessageKey = "groupIncludePattern.help") public String[] getGroupIncludePattern() { return groupIncludePattern; } @@ -163,6 +179,48 @@ public String getTestGroup() { public void setTestGroup(String testGroup) { this.testGroup = testGroup; } + /** + * array of attribute names to import with a Stem. + * if not specified all attributes are included + */ + @ConfigurationProperty(order = 51, displayMessageKey = "stemAttributeNames.display", helpMessageKey = "stemAttributeNames.help") + public String[] getStemAttributeNames() + { + return stemAttributeNames; + } + + public void setStemAttributeNames(String[] attributeName) + { + this.stemAttributeNames = attributeName; + } + /** + * Array of regular expressions which specifies the + * Sub Stems or Sub Folders of the Base Stem which are to be visible to the Connector. The default is all sub stems. + */ + @ConfigurationProperty(order = 52, displayMessageKey = "stemIncludePattern.display", helpMessageKey = "stemIncludePattern.help") + public String[] getStemIncludePattern() + { + return stemIncludePattern; + } + + public void setStemIncludePattern(String[] stemIncludePattern) + { + this.stemIncludePattern = stemIncludePattern; + } + /** + * Array of regular expressions which specifies the + * Sub Stems or Sub Folders of the Base Stem that should not be visible to the connector. + */ + @ConfigurationProperty(order = 54, displayMessageKey = "stemExcludePattern.display", helpMessageKey = "stemExcludePattern.help") + public String[] getStemExcludePattern() + { + return stemExcludePattern; + } + + public void setStemExcludePattern(String[] stemExcludePattern) + { + this.stemExcludePattern = stemExcludePattern; + } @Override public void validate() { @@ -189,10 +247,15 @@ public void release() { this.password = null; this.ignoreSslValidation = null; this.baseStem = null; + this.stemAttributeNames = null; + this.stemIncludePattern = null; + this.stemExcludePattern = null; + this.groupAttributeNames = null; this.groupIncludePattern = null; this.groupExcludePattern = null; this.subjectSource = null; this.testGroup = null; + this.testStem = null; } @Override @@ -206,6 +269,7 @@ public String toString() { ", groupExcludePattern=" + Arrays.toString(groupExcludePattern) + ", subjectSource='" + subjectSource + '\'' + ", testGroup='" + testGroup + '\'' + + ", testStem='" + testStem + '\'' + '}'; } } diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConnector.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConnector.java index 1280350..7fd7c5f 100644 --- a/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConnector.java +++ b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConnector.java @@ -17,6 +17,7 @@ import org.identityconnectors.common.CollectionUtil; import org.identityconnectors.common.logging.Log; +import org.identityconnectors.framework.common.FrameworkUtil; import org.identityconnectors.framework.common.exceptions.ConfigurationException; import org.identityconnectors.framework.common.exceptions.InvalidAttributeValueException; import org.identityconnectors.framework.common.objects.*; @@ -29,6 +30,8 @@ import org.identityconnectors.framework.spi.operations.SearchOp; import org.identityconnectors.framework.spi.operations.TestOp; +import java.util.Set; + /** * Configuration for the Grouper connector. */ @@ -64,7 +67,8 @@ public void dispose() { } @Override - public void test() { + public void test() throws RuntimeException + { LOG.info("Testing connection..."); groupProcessor.test(); LOG.ok("Testing finished successfully."); @@ -73,7 +77,8 @@ public void test() { @Override public Schema schema() { SchemaBuilder schemaBuilder = new SchemaBuilder(GrouperConnector.class); - schemaBuilder.defineObjectClass(groupProcessor.buildSchema().build()); + schemaBuilder.defineObjectClass(groupProcessor.buildGroupSchema().build()); + schemaBuilder.defineObjectClass(groupProcessor.buildStemSchema().build()); return schemaBuilder.build(); } @@ -82,35 +87,48 @@ public FilterTranslator createFilterTranslator(ObjectClass arg0, Operati return CollectionUtil::newList; } + /** + * @param objClass The object class to be populated by the search. Will never be null + * @param filter The query filter for the search + * @param handler The results handler that received the data + * @param options The operations options + */ @Override - public void executeQuery(ObjectClass objClass, Filter filter, ResultsHandler handler, OperationOptions options) { - LOG.info("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Execute Query-Parameters~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); - if (objClass == null) { + public void executeQuery(ObjectClass objClass, Filter filter, ResultsHandler handler, OperationOptions options) + { + if (objClass == null) + { LOG.error("Get operation failed: object class is not provided."); throw new InvalidAttributeValueException("Object class is not provided."); - } else if (!objClass.is(groupProcessor.getObjectClass().getObjectClassValue())) { + } + else if ( !objClass.is(groupProcessor.getGroupObjectClass().getObjectClassValue()) + && !objClass.is(groupProcessor.getStemObjectClass().getObjectClassValue()) + ) + { throw new IllegalArgumentException("Unsupported object class: " + objClass); - } else { - LOG.info("ObjectClass: {0}", objClass); } - if (handler == null) { + if (handler == null) + { LOG.error("Get operation failed: result handler is not provided."); throw new InvalidAttributeValueException("Result handler is not provided."); - } else { - LOG.info("Execute Query-Handler: {0}", handler); } - if (options == null) { + if (options == null) + { LOG.error("Get operation failed: options are not provided."); throw new InvalidAttributeValueException("Options are not provided."); - } else { - LOG.info("Options: {0}", options); } - LOG.info("Filter: {0}", filter); - LOG.info("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); - groupProcessor.read(filter, handler, options); + LOG.info("Execute Query:\n{0}\nHandler: {1}\n{2}\nFilter: {3}", objClass, handler.getClass().getSimpleName(), options, filter); + if ( objClass.is(groupProcessor.getGroupObjectClass().getObjectClassValue())) + { + groupProcessor.getGroups(filter, handler, options); + } + else if ( objClass.is(groupProcessor.getStemObjectClass().getObjectClassValue())) + { + groupProcessor.getStems(filter, handler, options); + } } } diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/Processor.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/Processor.java index 7025c29..ae36c43 100644 --- a/src/main/java/com/evolveum/polygon/connector/grouper/rest/Processor.java +++ b/src/main/java/com/evolveum/polygon/connector/grouper/rest/Processor.java @@ -16,7 +16,9 @@ package com.evolveum.polygon.connector.grouper.rest; import org.apache.commons.codec.binary.Base64; -import org.apache.http.client.methods.*; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.utils.URIBuilder; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; @@ -31,7 +33,7 @@ import org.json.JSONArray; import org.json.JSONObject; -import java.io.*; +import java.io.IOException; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; @@ -52,23 +54,31 @@ public class Processor { static final String J_WS_REST_GET_MEMBERS_REQUEST = "WsRestGetMembersRequest"; static final String J_WS_REST_FIND_GROUPS_REQUEST = "WsRestFindGroupsRequest"; static final String J_WS_REST_FIND_STEMS_REQUEST = "WsRestFindStemsRequest"; + static final String J_WS_REST_FIND_STEMS_LITE_REQUEST = "WsRestFindStemsLiteRequest"; + static final String J_WS_REST_GET_ATTRIBUTE_ASSIGNMENTS = "WsRestGetAttributeAssignmentsLiteRequest"; static final String J_WS_QUERY_FILTER = "wsQueryFilter"; static final String J_WS_STEM_QUERY_FILTER = "wsStemQueryFilter"; static final String J_STEM_QUERY_FILTER_TYPE = "stemQueryFilterType"; static final String J_INCLUDE_SUBJECT_DETAIL = "includeSubjectDetail"; static final String J_QUERY_FILTER_TYPE = "queryFilterType"; + static final String J_STEM_NAME = "stemName"; + static final String J_STEM_UUID = "stemUuid"; + static final String J_PARENT_STEM_NAME = "parentStemName"; static final String J_STEM_NAME_SCOPE = "stemNameScope"; + static final String J_PARENT_STEM_SCOPE = "parentStemNameScope"; static final String J_GROUP_NAME = "groupName"; static final String J_WS_FIND_GROUPS_RESULTS = "WsFindGroupsResults"; static final String J_WS_FIND_STEMS_RESULTS = "WsFindStemsResults"; static final String J_WS_GET_MEMBERS_RESULTS = "WsGetMembersResults"; + static final String J_WS_GET_ATTRIBUTE_RESULTS = "WsGetAttributeAssignmentsResults"; static final String J_RESULTS = "results"; static final String J_STEM_RESULTS = "stemResults"; static final String J_GROUP_RESULTS = "groupResults"; + static final String J_ATTRIBUTE_ASSIGNMENTS = "wsAttributeAssigns"; static final String J_WS_GROUP_LOOKUPS = "wsGroupLookups"; static final String J_RESULT_METADATA = "resultMetadata"; static final String J_RESULT_CODE = "resultCode"; @@ -82,14 +92,20 @@ public class Processor { static final String J_EXTENSION = "extension"; static final String J_SOURCE_ID = "sourceId"; static final String J_ID = "id"; + static final String J_DESCRIPTION="description"; - private static final String VAL_T = "T"; + static final String VAL_T = "T"; + static final String VAL_F = "F"; static final String VAL_FIND_BY_STEM_NAME = "FIND_BY_STEM_NAME"; + static final String VAL_FIND_BY_STEM_UUID= "FIND_BY_STEM_UUID"; + static final String VAL_FIND_BY_PARENT_STEM_NAME = "FIND_BY_PARENT_STEM_NAME"; + static final String VAL_FIND_BY_STEM_NAME_APPROXIMATE = "FIND_BY_STEM_NAME_APPROXIMATE"; static final String VAL_ALL_IN_SUBTREE = "ALL_IN_SUBTREE"; private static final String URI_BASE_PATH = "/grouper-ws/servicesRest/json/v2_4_000"; private static final String PATH_GROUPS = "/groups"; private static final String PATH_STEMS = "/stems"; + private static final String PATH_ATTRIBUTE_ASSIGNMENTS = "/attributeAssignments"; GrouperConfiguration configuration; @@ -97,19 +113,37 @@ public class Processor { this.configuration = configuration; } - CallResponse callRequest(HttpEntityEnclosingRequestBase request, JSONObject payload, ErrorHandler errorHandler) { + /** + * Execute an HTTP Call to Grouper and handle the response + * @param request HTTP Client method + * @param payload JSON Object containing the POST Body + * @param errorHandler Functional Interface to handle errors + * @return Call Response containing the HTTP response status and the body of the + */ + CallResponse callRequest(HttpEntityEnclosingRequestBase request, JSONObject payload, ErrorHandler errorHandler) + { + CallResponse callResponse; request.addHeader("Content-Type", Processor.CONTENT_TYPE_JSON); request.addHeader("Authorization", "Basic " + getAuthEncoded()); request.setEntity(new ByteArrayEntity(payload.toString().getBytes(StandardCharsets.UTF_8))); - LOG.info("Payload: {0}", payload); // we don't log the whole request, as it contains the (encoded) password - try (CloseableHttpResponse response = execute(request)) { - LOG.info("Response: {0}", response); - return processResponse(response, errorHandler); - } catch (IOException e) { + // Log the request body. We do not log request header since it contains an (encoded) password + LOG.info("Payload: {0}", payload.toString()); + // Execute the request + try (CloseableHttpResponse response = execute(request)) + { + callResponse = processResponse(response, errorHandler); + if (callResponse.isSuccess() && callResponse.getResponse() != null ) + { + LOG.info("Response:\n {0}", callResponse.getResponse().toString(4)); + } + } + catch (IOException e) + { String msg = "Request failed: problem occurred during execute request with uri: " + request.getURI() + ": \n\t" + e.getLocalizedMessage(); LOG.error("{0}", msg); throw new ConnectorIOException(msg, e); } + return callResponse; } private String getAuthEncoded() { @@ -128,10 +162,18 @@ private String getAuthEncoded() { return Base64.encodeBase64String((username + ":" + password).getBytes()); } - private CloseableHttpResponse execute(HttpUriRequest request) { - try { + /** + * Performs a HTTP request + * @param request The request to be executed + * @return The response + */ + private CloseableHttpResponse execute(HttpUriRequest request) + { + try + { HttpClientBuilder clientBuilder = HttpClientBuilder.create(); - if (Boolean.TRUE.equals(configuration.getIgnoreSslValidation())) { + if (Boolean.TRUE.equals(configuration.getIgnoreSslValidation())) + { SSLContextBuilder sslCtxBuilder = new SSLContextBuilder(); sslCtxBuilder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslCtxBuilder.build(), NoopHostnameVerifier.INSTANCE); @@ -140,12 +182,13 @@ private CloseableHttpResponse execute(HttpUriRequest request) { } CloseableHttpClient client = clientBuilder.build(); CloseableHttpResponse response = client.execute(request); - LOG.ok("response code: {0}", response.getStatusLine().getStatusCode()); - // DO NOT CLOSE response HERE !!! + // DO NOT CLOSE response HERE. It should be handled by the caller return response; - } catch (IOException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { + } + catch (IOException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) + { String msg = "Execution of the request failed: problem occurred during HTTP client execution: \n\t" + e.getLocalizedMessage(); - LOG.error("{0}", msg, e); + LOG.error(e, "{0}", msg); throw new ConnectorIOException(msg); } } @@ -157,59 +200,89 @@ private CloseableHttpResponse execute(HttpUriRequest request) { * * @return true if the processing can continue */ - private CallResponse processResponse(CloseableHttpResponse response, ErrorHandler errorHandler) throws IOException { + private CallResponse processResponse(CloseableHttpResponse response, ErrorHandler errorHandler) throws IOException + { int statusCode = response.getStatusLine().getStatusCode(); - LOG.info("Status code: {0}", statusCode); + // Too verbose + // LOG.info("Status code: {0}", statusCode); String result = null; - try { + try + { result = EntityUtils.toString(response.getEntity()); - LOG.info("Response body: {0}", result); - } catch (IOException e) { - if (statusCode >= 200 && statusCode <= 299) { + // Too verbose + // LOG.info("Response body: {0}", result); + } + catch (IOException e) + { + if (statusCode >= 200 && statusCode <= 299) + { throw e; - } else { - LOG.warn("cannot read response body: {0}", e, e); + } + else + { + LOG.warn(e, "cannot read response body: {0}", e.getMessage()); } } - if (statusCode >= 200 && statusCode <= 299) { + if (statusCode >= 200 && statusCode <= 299) + { return CallResponse.ok(result); } - if (statusCode == 401 || statusCode == 403) { - // sometimes there are binary data in responseBody - closeResponse(response); + + if (statusCode == 401 || statusCode == 403) + { + // Identify an authentication error and throw Invalid Credential Exception String msg = "HTTP error " + statusCode + " " + response.getStatusLine().getReasonPhrase() + " : Authentication failure."; LOG.error("{0}", msg); + // sometimes there are binary data in responseBody + closeResponse(response); throw new InvalidCredentialException(msg); } String msg = "HTTP error " + statusCode + " " + response.getStatusLine().getReasonPhrase() + " : " + result; closeResponse(response); - try { - if (statusCode == 400 || statusCode == 405 || statusCode == 406) { + try + { + if (statusCode == 400 || statusCode == 405 || statusCode == 406) + { throw new ConnectorIOException(msg); - } else if (statusCode == 402 || statusCode == 407) { + } + else if (statusCode == 402 || statusCode == 407) + { throw new PermissionDeniedException(msg); - } else if (statusCode == 404 || statusCode == 410) { + } + else if (statusCode == 404 || statusCode == 410) + { throw new UnknownUidException(msg); - } else if (statusCode == 408) { + } + else if (statusCode == 408) + { throw new OperationTimeoutException(msg); - } else if (statusCode == 412) { + } + else if (statusCode == 412) + { throw new PreconditionFailedException(msg); - } else if (statusCode == 418) { + } + else if (statusCode == 418) + { throw new UnsupportedOperationException("Sorry, no coffee: " + msg); } - if (errorHandler != null) { - try { + if (errorHandler != null) + { + try + { CallResponse callResponse = errorHandler.handleError(statusCode, result); - if (callResponse != null) { + if (callResponse != null) + { return callResponse; } - } catch (Exception e) { + } + catch (Exception e) + { // TODO Consider improving this throw new ConnectorException("Exception while handling error. Original message: " + msg + ", exception: " + e.getMessage(), e); @@ -218,7 +291,9 @@ private CallResponse processResponse(CloseableHttpResponse response, ErrorHandle throw new ConnectorException(msg); - } catch (Exception e) { + } + catch (Exception e) + { LOG.error("{0}", msg); throw e; } @@ -251,6 +326,9 @@ URIBuilder getUriBuilderForStems() { return getUriBuilderRelative(PATH_STEMS); } + URIBuilder getUriBuilderForAttributeAssignments() { + return getUriBuilderRelative(PATH_ATTRIBUTE_ASSIGNMENTS); + } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void checkSuccess(JSONObject response, String rootName) { @@ -352,23 +430,27 @@ private String getString(JSONObject object, String item) { return (String) get(object, item); // todo error handling } - boolean groupNameMatches(String name) { - if (name == null) { + boolean nameMatches(String name, String[] includes, String[] excludes) + { + if (name == null) + { return false; } - String[] includes = configuration.getGroupIncludePattern(); - String[] excludes = configuration.getGroupExcludePattern(); - return (includes == null || includes.length == 0 || groupNameMatches(name, includes)) && - !groupNameMatches(name, excludes); + return (includes == null || includes.length == 0 || patternMatches(name, includes)) && + !patternMatches(name, excludes); } - private boolean groupNameMatches(String name, String[] patterns) { - if (patterns == null) { + private boolean patternMatches(String name, String[] patterns) + { + if (patterns == null) + { return false; } - for (String pattern : patterns) { + for (String pattern : patterns) + { Pattern compiled = Pattern.compile(pattern); - if (compiled.matcher(name).matches()) { + if (compiled.matcher(name).matches()) + { return true; } } diff --git a/src/main/resources/com/evolveum/polygon/connector/grouper/rest/Messages.properties b/src/main/resources/com/evolveum/polygon/connector/grouper/rest/Messages.properties index 2c7bbf0..e3eb7a2 100644 --- a/src/main/resources/com/evolveum/polygon/connector/grouper/rest/Messages.properties +++ b/src/main/resources/com/evolveum/polygon/connector/grouper/rest/Messages.properties @@ -26,20 +26,32 @@ password.help=Password of the user that is used to access the Grouper REST servi baseStem.display=Base stem baseStem.help=The stem whose content is to be visible to this connector. The default is ":" (the whole tree). +stemIncludePattern.display=Stems to Include +stemIncludePattern.help=Sub Stems or Sub Folders of the Base Stem which are to visible to the Connector. The default is ".*" which is all sub stems. + +stemExcludePattern.display=Stems to Exclude +stemExcludePattern.help=Sub Stems or Sub Folders of the Base Stem that should not be visible to the connector. Specify with regular expressions. + +stemAttributeNames.display=Stem Attribute Names to Include +stemAttributeNames.help=Attribute names to import with a Stem (ie. Folder). All attribute assignments are included by default in a JSON string + groupIncludePattern.display=Groups to include -groupIncludePattern.help=Groups that should be visible to this connector. Specify them using regular expressions like "ref:.*". If nothing is specified, all groups under root stem are included. +groupIncludePattern.help=Groups that should be visible to this connector. Specify them using regular expressions like "ref:.*". If nothing is specified, all groups under base stem are included. groupExcludePattern.display=Groups to exclude groupExcludePattern.help=Groups that should not be visible to this connector. Specify them using regular expressions like ".*_(includes|excludes|systemOfRecord|systemOfRecordAndIncludes)". +groupAttributeNames.display=Group Attribute Names to Include +groupAttributeNames.help=Attribute names to import with a Group. All attributes assignments are included by default in a JSON string + ignoreSslValidation.display=Ignore SSL validation ignoreSslValidation.help=Whether to ignore SSL validation issues when connecting to the Grouper REST service. Do not use in production. subjectSource.display=Subject source -subjectSource.help=The source of subjects that will be visible by this connector. +subjectSource.help=The sourceId of subjects in Grouper that will be visible by this connector. testStem.display=Test stem -testStem.help=Stem whose accessibility is checked during Test connection operation (if specified). +testStem.help=Stem whose accessibility is checked during Test connection operation. If not specified the base stem is used. testGroup.display=Test group testGroup.help=Group whose accessibility is checked during Test connection operation (if specified). diff --git a/src/test/java/com/evolveum/polygon/connector/grouper/test/AbstractTest.java b/src/test/java/com/evolveum/polygon/connector/grouper/test/AbstractTest.java index f69c9e0..abf42fe 100644 --- a/src/test/java/com/evolveum/polygon/connector/grouper/test/AbstractTest.java +++ b/src/test/java/com/evolveum/polygon/connector/grouper/test/AbstractTest.java @@ -36,18 +36,23 @@ class AbstractTest { // Test configuration static final String TEST_USER = "banderson"; static final String TEST_GROUP = "etc:sysadmingroup"; + static final String TEST_STEM = "app:sympa:internet2:SpacexStarship"; static final String TEST_GROUP_NON_EXISTENT = "etc:thisGroupDoesNotExist"; static final String TEST_UUID_NON_EXISTENT = "dd089842948329438249284928289XXX"; // Connector configuration - private static final String BASE_URL = "https://localhost:9443"; + private static final String BASE_URL = "https://abc.workbench.incommon.org"; private static final String ADMIN_USERNAME = TEST_USER; - private static final String ADMIN_PASSWORD = "password"; - private static final String BASE_STEM = "etc"; + private static final String ADMIN_PASSWORD = "password1"; + private static final String BASE_STEM = "app:sympa"; + private static final String[] STEM_ATTRIBUTE_NAMES = null; + private static final String[] STEM_INCLUDE_PATTERN = { ".*" }; + private static final String[] STEM_EXCLUDE_PATTERN = { ".*(attribute|config)" }; + private static final String[] GROUP_ATTRIBUTE_NAMES = null; private static final String[] GROUP_INCLUDE_PATTERN = { ".*" }; private static final String[] GROUP_EXCLUDE_PATTERN = { ".*_(includes|excludes|systemOfRecord|systemOfRecordAndIncludes)" }; private static final String SUBJECT_SOURCE = "ldap"; - private static final String TEST_STEM = ":"; + final GrouperConnector grouperConnector = new GrouperConnector(); final OperationOptions options = new OperationOptions(new HashMap<>()); @@ -70,8 +75,12 @@ GrouperConfiguration getConfiguration() { config.setBaseUrl(BASE_URL); config.setUsername(ADMIN_USERNAME); config.setPassword(new GuardedString(ADMIN_PASSWORD.toCharArray())); - config.setIgnoreSslValidation(true); + config.setIgnoreSslValidation(false); config.setBaseStem(BASE_STEM); + config.setStemAttributeNames(STEM_ATTRIBUTE_NAMES); + config.setStemIncludePattern(STEM_INCLUDE_PATTERN); + config.setStemExcludePattern(STEM_EXCLUDE_PATTERN); + config.setGroupAttributeNames(GROUP_ATTRIBUTE_NAMES); config.setGroupIncludePattern(GROUP_INCLUDE_PATTERN); config.setGroupExcludePattern(GROUP_EXCLUDE_PATTERN); config.setSubjectSource(SUBJECT_SOURCE); diff --git a/src/test/java/com/evolveum/polygon/connector/grouper/test/GroupTest.java b/src/test/java/com/evolveum/polygon/connector/grouper/test/GroupTest.java index e6ac163..e315246 100644 --- a/src/test/java/com/evolveum/polygon/connector/grouper/test/GroupTest.java +++ b/src/test/java/com/evolveum/polygon/connector/grouper/test/GroupTest.java @@ -23,40 +23,41 @@ import org.identityconnectors.framework.common.objects.filter.FilterBuilder; import org.testng.annotations.Test; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; +import java.util.*; import static com.evolveum.polygon.connector.grouper.rest.GroupProcessor.ATTR_NAME; import static com.evolveum.polygon.connector.grouper.rest.GroupProcessor.ATTR_UUID; import static org.identityconnectors.framework.common.objects.OperationOptions.OP_ATTRIBUTES_TO_GET; import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; /** * Tests the group object class. See the superclass for the environment needed. */ public class GroupTest extends AbstractTest { - private static final ObjectClass OC_GROUP = new ObjectClass(GroupProcessor.OBJECT_CLASS_NAME); + private static final ObjectClass OC_GROUP = new ObjectClass(GroupProcessor.GROUP_OBJECT_CLASS_NAME); + private static final ObjectClass OC_STEM = new ObjectClass(GroupProcessor.STEM_OBJECT_CLASS_NAME); private String uuid; + private String stemUuid; @Test(priority = 100) public void initialization() { grouperConnector.init(getConfiguration()); } - @Test(priority = 110) + @Test(priority = 110, dependsOnMethods = {"initialization"}) public void testSchema() { grouperConnector.schema(); } - @Test(priority = 120) + @Test(priority = 120, dependsOnMethods = {"initialization", "testSchema" }) public void testTestOperation() { grouperConnector.test(); } - @Test(priority = 200) + @Test(priority = 200, dependsOnMethods = {"initialization", "testSchema" }) public void testFindByGroupName() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder @@ -68,8 +69,33 @@ public void testFindByGroupName() { System.out.println("Found group: " + group); uuid = group.getUid().getUidValue(); } - - @Test(priority = 210) + + @Test(priority = 205, dependsOnMethods = {"initialization", "testSchema" }) + public void testStemByName() { + results.clear(); + AttributeFilter filter = (EqualsFilter) FilterBuilder + .equalTo(AttributeBuilder.build(ATTR_NAME, TEST_STEM)); + + grouperConnector.executeQuery(OC_STEM, filter, handler, options); + assertEquals("Wrong # of Stems retrieved", results.size(), 1); + ConnectorObject stem = results.get(0); + System.out.println("Found stem: " + stem); + stemUuid = stem.getUid().getUidValue(); + } + + @Test(priority = 207, dependsOnMethods = {"initialization", "testSchema", "testStemByName" }) + public void testFindStemByUUID() { + results.clear(); + AttributeFilter filter = (EqualsFilter) FilterBuilder + .equalTo(AttributeBuilder.build(ATTR_UUID, stemUuid)); + + grouperConnector.executeQuery(OC_STEM, filter, handler, options); + assertEquals("Wrong # of Stems retrieved", results.size(), 1); + ConnectorObject stem = results.get(0); + System.out.println("Found stem: " + stem); + } + + @Test(priority = 210, dependsOnMethods = {"initialization", "testSchema" }) public void testFindByGroupNameNonExistent() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder @@ -79,7 +105,7 @@ public void testFindByGroupNameNonExistent() { assertEquals("Wrong # of groups retrieved", results.size(), 0); } - @Test(priority = 220) + @Test(priority = 220, dependsOnMethods = {"initialization", "testSchema" }) public void testFindByGroupNameWithMembers() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder @@ -93,7 +119,7 @@ public void testFindByGroupNameWithMembers() { assertEquals("Wrong members", Collections.singletonList(TEST_USER), members); } - @Test(priority = 230) + @Test(priority = 230, dependsOnMethods = {"initialization", "testSchema" }) public void testFindByGroupNameWithMembersNonExistent() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder @@ -103,7 +129,7 @@ public void testFindByGroupNameWithMembersNonExistent() { assertEquals("Wrong # of groups retrieved", results.size(), 0); } - @Test(priority = 240) + @Test(priority = 240, dependsOnMethods = {"initialization", "testSchema", "testFindByGroupName" }) public void testFindByGroupUuid() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder @@ -115,7 +141,7 @@ public void testFindByGroupUuid() { System.out.println("Found group: " + group); } - @Test(priority = 250) + @Test(priority = 250, dependsOnMethods = {"initialization", "testSchema" }) public void testFindByGroupUuidNonExistent() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder @@ -125,7 +151,7 @@ public void testFindByGroupUuidNonExistent() { assertEquals("Wrong # of groups retrieved", results.size(), 0); } - @Test(priority = 260) + @Test(priority = 260, dependsOnMethods = {"initialization", "testSchema", "testFindByGroupName" }) public void testFindByGroupUuidWihMembers() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder @@ -138,7 +164,7 @@ public void testFindByGroupUuidWihMembers() { assertEquals("Wrong members", Collections.singletonList(TEST_USER), getMembers(group)); } - @Test(priority = 250) + @Test(priority = 250, dependsOnMethods = {"initialization", "testSchema" }) public void testFindByGroupUuidWihMembersNonExistent() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder @@ -148,7 +174,7 @@ public void testFindByGroupUuidWihMembersNonExistent() { assertEquals("Wrong # of groups retrieved", results.size(), 0); } - @Test(priority = 280) + @Test(priority = 280, dependsOnMethods = {"initialization", "testSchema" }) public void testGetAllGroups() { results.clear(); grouperConnector.executeQuery(OC_GROUP, null, handler, options); @@ -157,7 +183,16 @@ public void testGetAllGroups() { } } - @Test(priority = 290) + @Test(priority = 290, dependsOnMethods = {"initialization", "testSchema" }) + public void testGetAllStems() { + results.clear(); + grouperConnector.executeQuery(OC_STEM, null, handler, options); + assertNotNull(results); + for (ConnectorObject stem : results) { + System.out.println("Found stem: " + stem); + } + } + @Test(priority = 300, dependsOnMethods = {"initialization", "testSchema" }) public void testGetAllGroupsWithMembers() { results.clear(); grouperConnector.executeQuery(OC_GROUP, null, handler, getMembersOptions()); @@ -166,7 +201,7 @@ public void testGetAllGroupsWithMembers() { } } - @Test(priority = 900) + @Test(priority = 900, dependsOnMethods = {"initialization", "testSchema", "testFindByGroupName" }) public void dispose() { grouperConnector.dispose(); } @@ -179,7 +214,16 @@ private OperationOptions getMembersOptions() { private List getMembers(ConnectorObject group) { Attribute attribute = group.getAttributeByName(GroupProcessor.ATTR_MEMBER); - //noinspection unchecked - return attribute != null ? (List) (List) attribute.getValue() : Collections.emptyList(); + List members = Collections.emptyList(); + if ( attribute != null && attribute.getValue() != null && attribute.getValue().size() > 0 ) + { + List list = attribute.getValue(); + members = new ArrayList(); + for( Object obj : list ) + { + members.add(Objects.toString(obj, null)); + } + } + return members; } } \ No newline at end of file