diff --git a/README.md b/README.md index f1df27d..53d0b11 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,15 @@ # midPoint-Grouper_connector -Two connectors to be maintained: a REST connector and the Groovy scripts for an AMQP "connector" +This is a connector that can read groups from a Grouper instance using REST calls. +Currently it supports these searches only: +- fetching all groups, +- fetching a group by name, +- fetching a group by UUID. + +When fetching a group, a client can choose whether to get basic group data only (name, UUID, extension) or whether +to obtain a list of group members as well. + +Besides `search` operation the following ones are supported: +- `schema` +- `test` + +This connector was tested with Grouper 2.4. \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9916a84..8baa810 100644 --- a/pom.xml +++ b/pom.xml @@ -23,11 +23,11 @@ <artifactId>connector-parent</artifactId> <groupId>com.evolveum.polygon</groupId> <version>1.4.2.14</version> - <relativePath></relativePath> + <relativePath/> </parent> <artifactId>connector-grouper-rest</artifactId> - <version>0.4</version> + <version>0.5</version> <packaging>jar</packaging> <name>Grouper REST Connector</name> @@ -35,6 +35,7 @@ <properties> <connectorPackage>com.evolveum.polygon.connector.grouper.rest</connectorPackage> <connectorClass>GrouperRestConnector</connectorClass> + <project.source.version>1.8</project.source.version> </properties> <repositories> @@ -84,7 +85,6 @@ <groupId>com.evolveum.polygon</groupId> <version>1.4.2.14-SNAPSHOT</version> </dependency> - <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> @@ -101,36 +101,5 @@ <version>6.8</version> <scope>test</scope> </dependency> - <!--dependency> - <groupId>com.metaparadigm</groupId> - <artifactId>json-rpc</artifactId> - <version>1.0</version> - </dependency--> - <dependency> - <groupId>batik</groupId> - <artifactId>batik-swing</artifactId> - <version>1.6</version> - </dependency> - <dependency> - <groupId>batik</groupId> - <artifactId>batik-rasterizer</artifactId> - <version>1.6</version> - </dependency> - <dependency> - <groupId>xml-apis</groupId> - <artifactId>xml-apis</artifactId> - <version>1.3.04</version> - </dependency> - <dependency> - <groupId>xml-apis</groupId> - <artifactId>xml-apis-ext</artifactId> - <version>1.3.04</version> - </dependency> - <!--dependency> - <groupId>net.tirasa.connid</groupId> - <artifactId>connector-framework</artifactId> - <version>1.4.3.0-SNAPSHOT</version> - </dependency--> </dependencies> - </project> diff --git a/src/main/assembly/connector.xml b/src/main/assembly/connector.xml index efca6d1..6d149b4 100644 --- a/src/main/assembly/connector.xml +++ b/src/main/assembly/connector.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - ~ Copyright (c) 2010-2014 Evolveum + ~ Copyright (c) 2019 Evolveum ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/AbstractGroupProcessor.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/AbstractGroupProcessor.java deleted file mode 100644 index 52fd5d2..0000000 --- a/src/main/java/com/evolveum/polygon/connector/grouper/rest/AbstractGroupProcessor.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.evolveum.polygon.connector.grouper.rest; - -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.utils.URIBuilder; -import org.identityconnectors.framework.common.objects.*; -import org.identityconnectors.framework.common.objects.filter.EqualsFilter; -import org.identityconnectors.framework.common.objects.filter.Filter; -import org.json.JSONArray; -import org.json.JSONObject; - -import java.net.URISyntaxException; - -import static com.evolveum.polygon.connector.grouper.rest.Processor.*; - -/** - * - */ -public abstract class AbstractGroupProcessor { - - protected static final String ATTR_EXTENSION = "extension"; - protected final Processor processor; - - public AbstractGroupProcessor(Processor processor) { - this.processor = processor; - } - - void read(Filter filter, ResultsHandler handler, OperationOptions options) { - if (filter == null) { - getAllGroups(handler, options); - } else if (filter instanceof EqualsFilter && ((EqualsFilter) filter).getAttribute() instanceof Name) { - Attribute name = ((EqualsFilter) filter).getAttribute(); - if (name != null) { - if (name.getValue() == null || name.getValue().isEmpty()) { - throw new IllegalArgumentException("No group name to look for"); - } else if (name.getValue().size() > 1) { - throw new IllegalArgumentException("More than one group name to look for: " + name.getValue()); - } else { - getGroupByName((String) name.getValue().get(0), handler, options); - } - } else { - processor.throwNullAttrException(filter); - } - } else if (filter instanceof EqualsFilter && ((EqualsFilter) filter).getAttribute() instanceof Uid) { - Attribute name = ((EqualsFilter) filter).getAttribute(); - if (name != null) { - if (name.getValue() == null || name.getValue().isEmpty()) { - throw new IllegalArgumentException("No group UUID to look for"); - } else if (name.getValue().size() > 1) { - throw new IllegalArgumentException("More than one group UUID to look for: " + name.getValue()); - } else { - getGroupByUuid((String) name.getValue().get(0), handler, options); - } - } else { - processor.throwNullAttrException(filter); - } - } else { - throw new IllegalArgumentException("Unsupported filter: " + filter); - } - } - - boolean getGroupByUuid(String uuid, ResultsHandler handler, OperationOptions options) { - return getGroupByUuid(uuid, handler); - } - - void getGroupByName(String name, ResultsHandler handler, OperationOptions options) { - getGroupByName(name, handler); - } - - abstract void getAllGroups(ResultsHandler handler, OperationOptions options); - - boolean executeFindGroupsResponse(HttpPost request, JSONObject body, ResultsHandler handler) { - System.out.println("Request = " + body.toString()); - JSONObject response = processor.callRequest(request, body, true, CONTENT_TYPE_JSON); - System.out.println("Got response: " + response); - processor.checkSuccess(response, "WsFindGroupsResults"); - JSONArray groups = processor.getArray(response, false, "WsFindGroupsResults", "groupResults"); - if (groups != null) { - for (Object group : groups) { - if (!handleGroupJsonObject(group, handler)) { - return false; - } - } - } - return true; - } - - void executeFindGroupsAsMembersResponse(HttpPost request, JSONObject body, ResultsHandler handler) { - System.out.println("Request = " + body.toString()); - JSONObject response = processor.callRequest(request, body, true, CONTENT_TYPE_JSON); - System.out.println("Got response: " + response); - processor.checkSuccess(response, WS_GET_MEMBERS_RESULTS); - JSONArray groups = processor.getArray(response, WS_GET_MEMBERS_RESULTS, RESULTS, WS_SUBJECTS); - for (Object group : groups) { - if (!handleGroupAsMemberJsonObject(group, handler)) { - return; - } - } - } - - void getGroupByName(String groupName, ResultsHandler handler) { - URIBuilder uriBuilder = processor.getURIBuilder() - .setPath(URI_BASE_PATH + PATH_GROUPS); - try { - HttpPost request = new HttpPost(uriBuilder.build()); - JSONObject body = new JSONObject() - .put("WsRestFindGroupsRequest", new JSONObject() - .put("wsGroupLookups", new JSONObject[] { new JSONObject() - .put("groupName", groupName) })); - executeFindGroupsResponse(request, body, handler); - } catch (RuntimeException | URISyntaxException e) { - throw processor.processException(e, uriBuilder, "Get all groups"); - } - } - - boolean getGroupByUuid(String groupUuid, ResultsHandler handler) { - URIBuilder uriBuilder = processor.getURIBuilder() - .setPath(URI_BASE_PATH + PATH_GROUPS); - try { - HttpPost request = new HttpPost(uriBuilder.build()); - JSONObject body = new JSONObject() - .put("WsRestFindGroupsRequest", new JSONObject() - .put("wsGroupLookups", new JSONObject[] { new JSONObject() - .put("uuid", groupUuid) })); - return executeFindGroupsResponse(request, body, handler); - } catch (RuntimeException | URISyntaxException e) { - throw processor.processException(e, uriBuilder, "Get all groups"); - } - } - - private boolean handleGroupJsonObject(Object group, ResultsHandler handler) { - if (group instanceof JSONObject) { - JSONObject gObject = (JSONObject) group; - String name = processor.getStringOrNull(gObject, "name"); - if (processor.groupNameMatches(name)) { - String extension = processor.getStringOrNull(gObject, "extension"); - String uuid = processor.getStringOrNull(gObject, "uuid"); - ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); - builder.setObjectClass(getObjectClass()); - builder.setUid(uuid); - builder.setName(name); - builder.addAttribute(ATTR_EXTENSION, extension); - return handler.handle(builder.build()); - } else { - return true; - } - } else { - throw new IllegalStateException("Expected group as JSONObject, got " + group); - } - } - - private boolean handleGroupAsMemberJsonObject(Object group, ResultsHandler handler) { - if (group instanceof JSONObject) { - JSONObject gObject = (JSONObject) group; - String sourceId = processor.getStringOrNull(gObject, "sourceId"); - if (sourceId == null || !sourceId.equals(getConfiguration().getGroupSource())) { - LOG.info("Skipping non-group member (source={0})", sourceId); - return true; - } - String name = processor.getStringOrNull(gObject, "name"); - if (processor.groupNameMatches(name)) { - String id = processor.getStringOrNull(gObject, "id"); - ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); - builder.setObjectClass(getObjectClass()); - builder.setUid(id); - builder.setName(name); - return handler.handle(builder.build()); - } else { - return true; - } - } else { - throw new IllegalStateException("Expected group as JSONObject, got " + group); - } - } - - protected abstract ObjectClass getObjectClass(); - - protected GrouperConfiguration getConfiguration() { - return processor.configuration; - } - - void test() { - URIBuilder uriBuilder = processor.getURIBuilder().setPath(URI_BASE_PATH + PATH_GROUPS); - try { - HttpPost request = new HttpPost(uriBuilder.build()); - JSONObject body = new JSONObject() - .put("WsRestGetMembersRequest", new JSONObject() - .put("wsGroupLookups", new JSONObject[] { new JSONObject() - .put("groupName", getConfiguration().getSuperGroup()) }) - .put("includeSubjectDetail", true) - .put("memberFilter", "Immediate")); - System.out.println("Request = " + body.toString()); - JSONObject response = processor.callRequest(request, body, true, CONTENT_TYPE_JSON); - System.out.println("Got response: " + response); - processor.checkSuccess(response, WS_GET_MEMBERS_RESULTS); - JSONArray groups = processor.getArray(response, WS_GET_MEMBERS_RESULTS, RESULTS, WS_SUBJECTS); - System.out.println("Super-group members found: " + groups.length()); - } catch (RuntimeException | URISyntaxException e) { - throw processor.processException(e, uriBuilder, "Test"); - } - } -} diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/AccountProcessor.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/AccountProcessor.java deleted file mode 100644 index fe87bb7..0000000 --- a/src/main/java/com/evolveum/polygon/connector/grouper/rest/AccountProcessor.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - ****************************************************************************** - * Copyright 2017 Evolveum - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. - ******************************************************************************/ -package com.evolveum.polygon.connector.grouper.rest; - -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.utils.URIBuilder; -import org.identityconnectors.framework.common.objects.*; -import org.identityconnectors.framework.common.objects.filter.EqualsFilter; -import org.identityconnectors.framework.common.objects.filter.Filter; -import org.json.JSONArray; -import org.json.JSONObject; - -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - -import static com.evolveum.polygon.connector.grouper.rest.Processor.*; - -/** - * @author surmanek - * @author mederly - * - */ -class AccountProcessor { - - private final Processor processor; - - private static final String ATTR_GROUP = "group"; - - AccountProcessor(Processor processor) { - this.processor = processor; - } - - ObjectClassInfoBuilder buildSchema() { - ObjectClassInfoBuilder builder = new ObjectClassInfoBuilder(); - AttributeInfoBuilder groups = new AttributeInfoBuilder(ATTR_GROUP, String.class); - groups.setMultiValued(true); - builder.addAttributeInfo(groups.build()); - return builder; - } - - void read(Filter filter, ResultsHandler handler, OperationOptions options) { - if (filter == null) { - getAllUsers(handler); - } else if (filter instanceof EqualsFilter && - (((EqualsFilter) filter).getAttribute() instanceof Name || ((EqualsFilter) filter).getAttribute() instanceof Uid)) { - Attribute name = ((EqualsFilter) filter).getAttribute(); - if (name != null) { - if (name.getValue() == null || name.getValue().isEmpty()) { - throw new IllegalArgumentException("No ID to look for"); - } else if (name.getValue().size() > 1) { - throw new IllegalArgumentException("More than one ID to look for: " + name.getValue()); - } else { - getUser((String) name.getValue().get(0), handler); - } - } else { - processor.throwNullAttrException(filter); - } - } else { - throw new IllegalArgumentException("Unsupported filter: " + filter); - } - } - - private void getAllUsers(ResultsHandler handler) { - URIBuilder uriBuilder = processor.getURIBuilder() - .setPath(URI_BASE_PATH + PATH_GROUPS); - try { - HttpPost request = new HttpPost(uriBuilder.build()); - JSONObject body = new JSONObject() - .put(WS_REST_GET_MEMBERS_REQUEST, new JSONObject() - .put(WS_GROUP_LOOKUPS, new JSONObject[] { - new JSONObject().put(GROUP_NAME, getConfiguration().getSuperGroup()) })); - System.out.println("Request = " + body.toString()); - JSONObject response = processor.callRequest(request, body, true, CONTENT_TYPE_JSON); - System.out.println("Got response: " + response); - processor.checkSuccess(response, WS_GET_MEMBERS_RESULTS); - JSONArray subjects = processor.getArray(response, WS_GET_MEMBERS_RESULTS, RESULTS, WS_SUBJECTS); - List<String> ids = selectSubjectIds(subjects); - System.out.println("Subject IDs found: " + ids); - for (String id : ids) { - ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); - builder.setUid(id); - builder.setName(id); - AttributeBuilder groupBuilder = new AttributeBuilder().setName(ATTR_GROUP); - groupBuilder.setAttributeValueCompleteness(AttributeValueCompleteness.INCOMPLETE); - builder.addAttribute(groupBuilder.build()); - if (!handler.handle(builder.build())) { - return; - } - } - } catch (RuntimeException | URISyntaxException e) { - throw processor.processException(e, uriBuilder, "Get all users"); - } - } - - private List<String> selectSubjectIds(JSONArray subjects) { - List<String> rv = new ArrayList<>(subjects.length()); - for (Object subject : subjects) { - if (subject instanceof JSONObject) { - JSONObject subjObject = (JSONObject) subject; - if (processor.isSuccess(subjObject)) { - String sourceId = processor.getStringOrNull(subjObject, "sourceId"); - if (getConfiguration().getSubjectSource().equals(sourceId)) { - rv.add(processor.getString(subjObject, "id")); - } - } else { - LOG.warn("Skipping not-success subject from response: {0}", subject); - } - } else { - throw new IllegalStateException("Expected subject as JSONObject, got " + subject); - } - } - return rv; - } - - private void getUser(String id, ResultsHandler handler) { - URIBuilder uriBuilder = processor.getURIBuilder() - .setPath(URI_BASE_PATH + PATH_SUBJECTS); - try { - HttpPost request = new HttpPost(uriBuilder.build()); - JSONObject body = new JSONObject() - .put("WsRestGetGroupsRequest", new JSONObject() - .put("subjectLookups", new JSONObject[] { - new JSONObject().put("subjectId", id) })); - System.out.println("Request = " + body.toString()); - JSONObject response = processor.callRequest(request, body, true, CONTENT_TYPE_JSON); - System.out.println("Got response: " + response); - processor.checkSuccess(response, WS_GET_GROUPS_RESULTS); - JSONArray groups = processor.getArray(response, WS_GET_GROUPS_RESULTS, RESULTS, WS_GROUPS); - - ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); - builder.setUid(id); - builder.setName(id); - builder.addAttribute(ATTR_GROUP, selectGroupNames(groups)); - handler.handle(builder.build()); - } catch (RuntimeException | URISyntaxException e) { - throw processor.processException(e, uriBuilder, "Get all users"); - } - } - - private List<String> selectGroupNames(JSONArray groups) { - List<String> rv = new ArrayList<>(); - for (Object group : groups) { - if (group instanceof JSONObject) { - JSONObject gObject = (JSONObject) group; - String name = processor.getStringOrNull(gObject, "name"); - if (processor.groupNameMatches(name)) { - rv.add(name); - } - } else { - throw new IllegalStateException("Expected group as JSONObject, got " + group); - } - } - return rv; - } - - private GrouperConfiguration getConfiguration() { - return processor.configuration; - } -} 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 new file mode 100644 index 0000000..d085482 --- /dev/null +++ b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GroupProcessor.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.polygon.connector.grouper.rest; + +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URIBuilder; +import org.identityconnectors.framework.common.exceptions.ConnectorException; +import org.identityconnectors.framework.common.objects.*; +import org.identityconnectors.framework.common.objects.filter.EqualsFilter; +import org.identityconnectors.framework.common.objects.filter.Filter; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 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 ATTR_NAME = "name"; + public static final String ATTR_UUID = "uuid"; + private static final String ATTR_EXTENSION = J_EXTENSION; + public static final String ATTR_MEMBER = "member"; + + private static final String DEFAULT_EXPORT_STEM = ":"; + + GroupProcessor(GrouperConfiguration configuration) { + super(configuration); + } + + ObjectClass getObjectClass() { + return new ObjectClass(OBJECT_CLASS_NAME); + } + + void read(Filter filter, ResultsHandler handler, OperationOptions options) { + if (filter == null) { + getAllGroups(handler, options); + } else if (filter instanceof EqualsFilter) { + Attribute attribute = ((EqualsFilter) filter).getAttribute(); + if (attribute != null) { + List<Object> 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)) { + getGroupByName((String) values.get(0), handler, options); + } else if (attribute.is(Uid.NAME) || attribute.is(ATTR_UUID)) { + getGroupByUuid((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); + } + } + + private void getGroupByName(String name, ResultsHandler handler, OperationOptions options) { + URIBuilder uriBuilder = getUriBuilderForGroups(); + if (!isGetMembers(options)) { + try { + HttpPost request = new HttpPost(uriBuilder.build()); + JSONObject body = new JSONObject() + .put(J_WS_REST_FIND_GROUPS_REQUEST, new JSONObject() + .put(J_WS_GROUP_LOOKUPS, new JSONObject[] { new JSONObject() + .put(J_GROUP_NAME, name) })); + executeFindGroups(request, body, handler); + } catch (RuntimeException | URISyntaxException e) { + throw processException(e, uriBuilder, "Get group by name (no members)"); + } + } else { + try { + HttpPost request = new HttpPost(uriBuilder.build()); + JSONObject body = new JSONObject() + .put(J_WS_REST_GET_MEMBERS_REQUEST, new JSONObject() + .put(J_WS_GROUP_LOOKUPS, new JSONObject[] { new JSONObject() + .put(J_GROUP_NAME, name) }) + .put(J_INCLUDE_SUBJECT_DETAIL, true)); + executeGetMembers(request, body, handler); + } catch (RuntimeException | URISyntaxException e) { + throw processException(e, uriBuilder, "Get group by name (with members)"); + } + } + } + + private boolean getGroupByUuid(String uuid, ResultsHandler handler, OperationOptions options) { + if (!isGetMembers(options)) { + URIBuilder uriBuilder = getUriBuilderForGroups(); + try { + HttpPost request = new HttpPost(uriBuilder.build()); + JSONObject body = new JSONObject() + .put(J_WS_REST_FIND_GROUPS_REQUEST, new JSONObject() + .put(J_WS_GROUP_LOOKUPS, new JSONObject[] { new JSONObject() + .put(J_UUID, uuid) })); + return executeFindGroups(request, body, handler); + } catch (RuntimeException | URISyntaxException e) { + throw processException(e, uriBuilder, "Get group by UUID (no members)"); + } + } else { + URIBuilder uriBuilder = getUriBuilderForGroups(); + try { + HttpPost request = new HttpPost(uriBuilder.build()); + JSONObject body = new JSONObject() + .put(J_WS_REST_GET_MEMBERS_REQUEST, new JSONObject() + .put(J_WS_GROUP_LOOKUPS, new JSONObject[] { new JSONObject() + .put(J_UUID, uuid) }) + .put(J_INCLUDE_SUBJECT_DETAIL, true)); + return executeGetMembers(request, body, handler); + } catch (RuntimeException | URISyntaxException e) { + throw processException(e, uriBuilder, "Get group by UUID (with members)"); + } + } + } + + private void getAllGroups(final ResultsHandler handler, final OperationOptions options) { + boolean getMembers = isGetMembers(options); + if (!getMembers) { + getAllGroupsNoMembers(handler); + } else { + ResultsHandler localHandler = connectorObject -> getGroupByUuid(connectorObject.getUid().getUidValue(), handler, options); + getAllGroupsNoMembers(localHandler); + } + } + + private void getAllGroupsNoMembers(ResultsHandler handler) { + URIBuilder uriBuilder = getUriBuilderForGroups(); + try { + HttpPost request = new HttpPost(uriBuilder.build()); + String configuredExportStem = configuration.getExportStem(); + 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, configuredExportStem != null ? configuredExportStem : DEFAULT_EXPORT_STEM) + .put(J_STEM_NAME_SCOPE, VAL_ALL_IN_SUBTREE))); + executeFindGroups(request, body, handler); + } catch (RuntimeException | URISyntaxException e) { + throw processException(e, uriBuilder, "Get all groups"); + } + } + + private boolean executeFindGroups(HttpPost request, JSONObject body, ResultsHandler handler) { + JSONObject response = callRequest(request, body); + 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)) { + return false; + } + } + } + return true; + } + + private boolean executeGetMembers(HttpPost request, JSONObject body, ResultsHandler handler) { + JSONObject response = callRequest(request, body); + 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); + List<String> members = new ArrayList<>(); + JSONArray membersJsonArray = getArray(response, false, J_WS_GET_MEMBERS_RESULTS, J_RESULTS, J_WS_SUBJECTS); + if (membersJsonArray != null) { + for (Object memberObject : membersJsonArray) { + JSONObject member = (JSONObject) memberObject; + String sourceId = getStringOrNull(member, J_SOURCE_ID); + if (sourceId == null || !sourceId.equals(configuration.getSubjectSource())) { + LOG.info("Skipping member with wrong source (e.g. one that is not a person) (source={0})", sourceId); + } else { + String subjectId = getStringOrNull(member, J_ID); + if (subjectId != null) { + members.add(subjectId); + } else { + LOG.info("Skipping unnamed member (source={0})", member); + } + } + } + builder.addAttribute(ATTR_MEMBER, members); + } + return handler.handle(builder.build()); + } else { + return true; + } + } + + 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 { + return true; + } + } else { + throw new IllegalStateException("Expected group as JSONObject, got " + group); + } + } + + private ConnectorObjectBuilder startGroupObjectBuilding(JSONObject gObject, String name) { + String extension = getStringOrNull(gObject, J_EXTENSION); + String uuid = getStringOrNull(gObject, J_UUID); + ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); + builder.setObjectClass(getObjectClass()); + builder.setUid(uuid); + builder.setName(name); + builder.addAttribute(ATTR_EXTENSION, extension); + return builder; + } + + void test() { + if (configuration.getTestStem() != null) { + checkStemExists(configuration.getTestStem()); + } + if (configuration.getTestGroup() != null) { + checkGroupExists(configuration.getTestGroup()); + } + } + + private void checkStemExists(String stemName) { + URIBuilder uriBuilder = getUriBuilderForStems(); + JSONArray stems; + try { + HttpPost request = new HttpPost(uriBuilder.build()); + JSONObject body = new JSONObject() + .put(J_WS_REST_FIND_STEMS_REQUEST, new JSONObject() + .put(J_WS_STEM_QUERY_FILTER, new JSONObject() + .put(J_STEM_QUERY_FILTER_TYPE, VAL_FIND_BY_STEM_NAME) + .put(J_STEM_NAME, stemName))); + JSONObject response = callRequest(request, body); + checkSuccess(response, J_WS_FIND_STEMS_RESULTS); + stems = getArray(response, true, J_WS_FIND_STEMS_RESULTS, J_STEM_RESULTS); + } 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"); + } + } + + private void checkGroupExists(String groupName) { + List<ConnectorObject> groups = new ArrayList<>(); + getGroupByName(groupName, groups::add, null); + LOG.info("getGroupByName found {0} group(s): {1}", groups.size(), groups); + if (groups.isEmpty()) { + throw new ConnectorException("Expected to find the group '" + groupName + "', found none"); + } + } + + ObjectClassInfoBuilder buildSchema() { + ObjectClassInfoBuilder builder = new ObjectClassInfoBuilder(); + builder.setType(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_MEMBER, String.class) + .setMultiValued(true) + .setReturnedByDefault(false) + .build()); + return builder; + } + + 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 8c4dbdc..3563f1f 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 @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2016 Evolveum +/* + * Copyright (c) 2019 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,196 +32,217 @@ */ @SuppressWarnings("WeakerAccess") public class GrouperConfiguration extends AbstractConfiguration implements StatefulConfiguration { - - private static final Log LOG = Log.getLog(GrouperConfiguration.class); - - private static final String DEFAULT_GROUP_SOURCE_ID = "g:gsa"; - - private String baseUrl; - private String username; - private GuardedString password; - private String superGroup; - private String[] groupIncludePattern; - private String[] groupExcludePattern; - private Boolean ignoreSslValidation; - private String subjectSource; - private String groupSource; - private String exportStem; - - // getter and setter methods for "baseUrl" attribute: - @ConfigurationProperty(order = 1, displayMessageKey = "baseUrl.display", helpMessageKey = "baseUrl.help", required = true) - public String getBaseUrl() { - return baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - - // getter and setter methods for "username" attribute: - @ConfigurationProperty(order = 2, displayMessageKey = "username.display", helpMessageKey = "username.help", required = true) - public String getUsername() { - return username; - } - - public void setUsername(String name) { - this.username = name; - } - - private String stringPassword = ""; - - @ConfigurationProperty(order = 3, displayMessageKey = "password.display", helpMessageKey = "password.help", required = true, confidential = false) - public GuardedString getPassword() { - return password; - } - - public void setPassword(GuardedString password) { - this.password = password; - } - - public String getStringPassword() { - password.access(new GuardedString.Accessor() { - @Override - public void access(char[] clearChars) { - stringPassword = new String(clearChars); - } - }); - return stringPassword; - } - - /** - * "Super group" that marks groups and users that are to be visible through this connector. - * - * Used for Account and Group object classes; ignored for PlainGroup object class. - */ - @ConfigurationProperty(order = 4, displayMessageKey = "superGroup.display", helpMessageKey = "superGroup.help", required = true) - public String getSuperGroup() { - return superGroup; - } - - public void setSuperGroup(String superGroup) { - this.superGroup = superGroup; - } - - /** - * Used to limit group membership to a subset of all groups. Applicable to Account object class. - */ - @ConfigurationProperty(order = 5, displayMessageKey = "groupIncludePattern.display", helpMessageKey = "groupIncludePattern.help", required = true) - public String[] getGroupIncludePattern() { - return groupIncludePattern; - } - - public void setGroupIncludePattern(String[] groupIncludePattern) { - this.groupIncludePattern = groupIncludePattern; - } - - /** - * Used to limit group membership to a subset of all groups. Applicable to Account object class. - */ - @ConfigurationProperty(order = 6, displayMessageKey = "groupExcludePattern.display", helpMessageKey = "groupExcludePattern.help", required = true) - public String[] getGroupExcludePattern() { - return groupExcludePattern; - } - - @SuppressWarnings("unused") - public void setGroupExcludePattern(String[] groupExcludePattern) { - this.groupExcludePattern = groupExcludePattern; - } - - @ConfigurationProperty(order = 7, displayMessageKey = "ignoreSslValidation.display", helpMessageKey = "ignoreSslValidation.help") - public Boolean getIgnoreSslValidation() { - return ignoreSslValidation; - } - - public void setIgnoreSslValidation(Boolean ignoreSslValidation) { - this.ignoreSslValidation = ignoreSslValidation; - } - - /** - * Used to limit subjects returned by this connector. Applicable to Account and PlainGroup object class. - */ - @ConfigurationProperty(order = 8, displayMessageKey = "subjectSource.display", helpMessageKey = "subjectSource.help", required = true) - public String getSubjectSource() { - return subjectSource; - } - - public void setSubjectSource(String subjectSource) { - this.subjectSource = subjectSource; - } - - /** - * Used to limit groups returned by this connector. Applicable to Group object class. Usually not needed to change. - */ - @ConfigurationProperty(order = 9, displayMessageKey = "groupSource.display", helpMessageKey = "groupSource.help") - public String getGroupSource() { - return groupSource != null ? groupSource : DEFAULT_GROUP_SOURCE_ID; - } - - @SuppressWarnings("unused") - public void setGroupSource(String groupSource) { - this.groupSource = groupSource; - } - - /** - * Used to specify root stem for groups returned by this connector. Applicable to PlainGroup object class. - */ - @ConfigurationProperty(order = 10, displayMessageKey = "exportStem.display", helpMessageKey = "exportStem.help") - public String getExportStem() { - return exportStem; - } - - public void setExportStem(String exportStem) { - this.exportStem = exportStem; - } - - @Override - public void validate() { - String exceptionMsg; - if (baseUrl == null || StringUtil.isBlank(baseUrl)) { - exceptionMsg = "Base url is not provided."; - } else if (username == null || StringUtil.isBlank(username)) { - exceptionMsg = "Name is not provided."; - } else if (password == null) { - exceptionMsg = "Password is not provided."; - } else if (superGroup == null) { - exceptionMsg = "Super group is not provided."; - } else if (groupIncludePattern == null || groupIncludePattern.length == 0) { - exceptionMsg = "Group include pattern is not provided."; - } else if (subjectSource == null) { - exceptionMsg = "Subject source is not provided."; - } else { - return; - } - LOG.error("{0}", exceptionMsg); - throw new ConfigurationException(exceptionMsg); - } - - @Override - public void release() { - LOG.info("The release of configuration resources is being performed"); - this.baseUrl = null; - this.password = null; - this.username = null; - this.superGroup = null; - this.groupIncludePattern = null; - this.groupExcludePattern = null; - this.subjectSource = null; - this.groupSource = null; - this.exportStem = null; - } - - @Override - public String toString() { - return "GrouperConfiguration{" + - "baseUrl='" + baseUrl + '\'' + - ", username='" + username + '\'' + - ", superGroup='" + superGroup + '\'' + - ", groupIncludePattern=" + Arrays.toString(groupIncludePattern) + - ", groupExcludePattern=" + Arrays.toString(groupExcludePattern) + - ", ignoreSslValidation=" + ignoreSslValidation + - ", subjectSource='" + subjectSource + '\'' + - ", groupSource='" + groupSource + '\'' + - ", exportStem='" + exportStem + '\'' + - '}'; - } + + private static final Log LOG = Log.getLog(GrouperConfiguration.class); + + private static final String DEFAULT_GROUP_SOURCE_ID = "g:gsa"; + + private String baseUrl; + private String username; + private GuardedString password; + private Boolean ignoreSslValidation; + + private String exportStem; + private String[] groupIncludePattern; + private String[] groupExcludePattern; + private String subjectSource; + private String testStem; + private String testGroup; + + // deprecated (to be removed) + private String superGroup; + private String groupSource; + + @ConfigurationProperty(order = 10, displayMessageKey = "baseUrl.display", helpMessageKey = "baseUrl.help", required = true) + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + @ConfigurationProperty(order = 20, displayMessageKey = "username.display", helpMessageKey = "username.help", required = true) + public String getUsername() { + return username; + } + + public void setUsername(String name) { + this.username = name; + } + + @ConfigurationProperty(order = 30, displayMessageKey = "password.display", helpMessageKey = "password.help", required = true, confidential = true) + public GuardedString getPassword() { + return password; + } + + public void setPassword(GuardedString password) { + this.password = password; + } + + public String getPasswordPlain() { + StringBuilder plain = new StringBuilder(); + password.access(clearChars -> plain.append(new String(clearChars))); + return plain.toString(); + } + + /** + * Should we ignore SSL validation issues when connecting to the Grouper REST service? Do not use in production. + */ + @ConfigurationProperty(order = 40, displayMessageKey = "ignoreSslValidation.display", helpMessageKey = "ignoreSslValidation.help") + public Boolean getIgnoreSslValidation() { + return ignoreSslValidation; + } + + public void setIgnoreSslValidation(Boolean ignoreSslValidation) { + this.ignoreSslValidation = ignoreSslValidation; + } + + /** + * Used to specify root stem for groups returned by this connector. The default is ":" (the whole tree). + */ + @ConfigurationProperty(order = 50, displayMessageKey = "exportStem.display", helpMessageKey = "exportStem.help") + public String getExportStem() { + return exportStem; + } + + public void setExportStem(String exportStem) { + this.exportStem = exportStem; + } + + /** + * Which groups should be visible to this connector? + */ + @ConfigurationProperty(order = 60, displayMessageKey = "groupIncludePattern.display", helpMessageKey = "groupIncludePattern.help") + public String[] getGroupIncludePattern() { + return groupIncludePattern; + } + + public void setGroupIncludePattern(String[] groupIncludePattern) { + this.groupIncludePattern = groupIncludePattern; + } + + /** + * Which groups should be hidden (invisible) to this connector? + */ + @ConfigurationProperty(order = 70, displayMessageKey = "groupExcludePattern.display", helpMessageKey = "groupExcludePattern.help") + public String[] getGroupExcludePattern() { + return groupExcludePattern; + } + + public void setGroupExcludePattern(String[] groupExcludePattern) { + this.groupExcludePattern = groupExcludePattern; + } + + /** + * Used to limit subjects returned by this connector. + */ + @ConfigurationProperty(order = 80, displayMessageKey = "subjectSource.display", helpMessageKey = "subjectSource.help", required = true) + public String getSubjectSource() { + return subjectSource; + } + + public void setSubjectSource(String subjectSource) { + this.subjectSource = subjectSource; + } + + /** + * Used to specify stem that is fetched during Test Connection (if any). + */ + @ConfigurationProperty(order = 90, displayMessageKey = "testStem.display", helpMessageKey = "testStem.help") + public String getTestStem() { + return testStem; + } + + public void setTestStem(String testStem) { + this.testStem = testStem; + } + + /** + * Used to specify group that is fetched during Test Connection (if any). + */ + @ConfigurationProperty(order = 100, displayMessageKey = "testGroup.display", helpMessageKey = "testGroup.help") + public String getTestGroup() { + return testGroup; + } + + public void setTestGroup(String testGroup) { + this.testGroup = testGroup; + } + + /** + * Currently unused. It is kept here just to avoid breaking older configurations. Will be removed eventually. + */ + @Deprecated + @ConfigurationProperty(order = 9900, displayMessageKey = "superGroup.display", helpMessageKey = "superGroup.help") + public String getSuperGroup() { + return superGroup; + } + + @Deprecated + public void setSuperGroup(String superGroup) { + this.superGroup = superGroup; + } + + /** + * Currently unused. It is kept here just to avoid breaking older configurations. Will be removed eventually. + */ + @Deprecated + @ConfigurationProperty(order = 9901, displayMessageKey = "groupSource.display", helpMessageKey = "groupSource.help") + public String getGroupSource() { + return groupSource != null ? groupSource : DEFAULT_GROUP_SOURCE_ID; + } + + @Deprecated + public void setGroupSource(String groupSource) { + this.groupSource = groupSource; + } + + + @Override + public void validate() { + String exceptionMsg; + if (StringUtil.isBlank(baseUrl)) { + exceptionMsg = "Base URL is not provided."; + } else if (StringUtil.isBlank(username)) { + exceptionMsg = "Name is not provided."; + } else if (password == null) { + exceptionMsg = "Password is not provided."; + } else if (subjectSource == null) { + exceptionMsg = "Subject source is not provided."; + } else { + return; + } + LOG.error("{0}", exceptionMsg); + throw new ConfigurationException(exceptionMsg); + } + + @Override + public void release() { + this.baseUrl = null; + this.username = null; + this.password = null; + this.ignoreSslValidation = null; + this.exportStem = null; + this.groupIncludePattern = null; + this.groupExcludePattern = null; + this.subjectSource = null; + this.testGroup = null; + this.superGroup = null; + this.groupSource = null; + } + + @Override + public String toString() { + return "GrouperConfiguration{" + + "baseUrl='" + baseUrl + '\'' + + ", username='" + username + '\'' + + ", ignoreSslValidation=" + ignoreSslValidation + + ", exportStem='" + exportStem + '\'' + + ", groupIncludePattern=" + Arrays.toString(groupIncludePattern) + + ", groupExcludePattern=" + Arrays.toString(groupExcludePattern) + + ", subjectSource='" + subjectSource + '\'' + + ", testGroup='" + testGroup + '\'' + + '}'; + } } 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 f770ebc..1280350 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 @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2017 Evolveum +/* + * Copyright (c) 2019 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,121 +20,97 @@ import org.identityconnectors.framework.common.exceptions.ConfigurationException; import org.identityconnectors.framework.common.exceptions.InvalidAttributeValueException; import org.identityconnectors.framework.common.objects.*; -import org.identityconnectors.framework.common.objects.filter.*; +import org.identityconnectors.framework.common.objects.filter.Filter; +import org.identityconnectors.framework.common.objects.filter.FilterTranslator; import org.identityconnectors.framework.spi.Configuration; import org.identityconnectors.framework.spi.Connector; import org.identityconnectors.framework.spi.ConnectorClass; -import org.identityconnectors.framework.spi.operations.*; - -import java.util.List; +import org.identityconnectors.framework.spi.operations.SchemaOp; +import org.identityconnectors.framework.spi.operations.SearchOp; +import org.identityconnectors.framework.spi.operations.TestOp; /** - * @author surmanek - * @author mederly - * + * Configuration for the Grouper connector. */ @ConnectorClass(displayNameKey = "GrouperConnector.rest.display", configurationClass = GrouperConfiguration.class) public class GrouperConnector implements TestOp, SchemaOp, Connector, SearchOp<Filter> { - private static final Log LOG = Log.getLog(GrouperConnector.class); - private GrouperConfiguration configuration; - private Processor processor; - private AccountProcessor accountProcessor; - private StandardGroupProcessor standardGroupProcessor; - private PlainGroupProcessor plainGroupProcessor; - - private static final String PROJECT_NAME = "PROJECT"; - private static final String ATTR_GROUPS = "groups"; - - @Override - public GrouperConfiguration getConfiguration() { - return configuration; - } - - @Override - public void init(Configuration configuration) { - if (configuration == null) { - LOG.error("Initialization of the configuration failed: Configuration is not provided."); - throw new ConfigurationException( - "Initialization of the configuration failed: Configuration is not provided."); - } - this.configuration = (GrouperConfiguration) configuration; - this.configuration.validate(); - this.processor = new Processor(this.configuration); - this.accountProcessor = new AccountProcessor(processor); - this.standardGroupProcessor = new StandardGroupProcessor(processor); - this.plainGroupProcessor = new PlainGroupProcessor(processor); - } - - @Override - public void dispose() { - configuration = null; - processor = null; - accountProcessor = null; - standardGroupProcessor = null; - plainGroupProcessor = null; - } - - @Override - public void test() { - LOG.info("Testing connection..."); - standardGroupProcessor.test(); - LOG.ok("Testing finished successfully."); - } - - @Override - public Schema schema() { - SchemaBuilder schemaBuilder = new SchemaBuilder(GrouperConnector.class); - - schemaBuilder.defineObjectClass(accountProcessor.buildSchema().build()); - schemaBuilder.defineObjectClass(standardGroupProcessor.buildSchema().build()); - schemaBuilder.defineObjectClass(plainGroupProcessor.buildSchema().build()); - - return schemaBuilder.build(); - } - - @Override - public FilterTranslator<Filter> createFilterTranslator(ObjectClass arg0, OperationOptions arg1) { - return new FilterTranslator<Filter>() { - @Override - public List<Filter> translate(Filter filter) { - return CollectionUtil.newList(filter); - } - }; - } - - @Override - public void executeQuery(ObjectClass objClass, Filter filter, ResultsHandler handler, OperationOptions options) { - LOG.info("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Execute Query-Parameters~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); - if (objClass == null) { - LOG.error("Get operation failed: Attribute Object Class is not provided."); - throw new InvalidAttributeValueException("Attribute Object Class is not provided."); - } else - LOG.info("ObjectClass: {0}", objClass.toString()); - - if (handler == null) { - LOG.error("Get operation failed: Attribute Result Handler is not provided."); - throw new InvalidAttributeValueException("Attribute Result Handler is not provided."); - } else - LOG.info("Execute Query-Handler: {0}", handler.toString()); - - if (options == null) { - LOG.error("Get operation failed: Attribute Options is not provided."); - throw new InvalidAttributeValueException("Attribute Options is not provided."); - } else - LOG.info("Options: {0}", options.toString()); - - LOG.info("Filter: {0}", filter); - LOG.info("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); - - if (objClass.is(ObjectClass.ACCOUNT_NAME)) { - accountProcessor.read(filter, handler, options); - } else if (objClass.is(ObjectClass.GROUP_NAME)) { - standardGroupProcessor.read(filter, handler, options); - } else if (objClass.is(plainGroupProcessor.getObjectClass().getObjectClassValue())) { - plainGroupProcessor.read(filter, handler, options); - } - } - - + private static final Log LOG = Log.getLog(GrouperConnector.class); + + private GrouperConfiguration configuration; + private GroupProcessor groupProcessor; + + @Override + public GrouperConfiguration getConfiguration() { + return configuration; + } + + @Override + public void init(Configuration configuration) { + if (configuration == null) { + LOG.error("Initialization of the configuration failed: Configuration is not provided."); + throw new ConfigurationException( + "Initialization of the configuration failed: Configuration is not provided."); + } + this.configuration = (GrouperConfiguration) configuration; + this.configuration.validate(); + this.groupProcessor = new GroupProcessor(this.configuration); + } + + @Override + public void dispose() { + configuration = null; + groupProcessor = null; + } + + @Override + public void test() { + LOG.info("Testing connection..."); + groupProcessor.test(); + LOG.ok("Testing finished successfully."); + } + + @Override + public Schema schema() { + SchemaBuilder schemaBuilder = new SchemaBuilder(GrouperConnector.class); + schemaBuilder.defineObjectClass(groupProcessor.buildSchema().build()); + return schemaBuilder.build(); + } + + @Override + public FilterTranslator<Filter> createFilterTranslator(ObjectClass arg0, OperationOptions arg1) { + return CollectionUtil::newList; + } + + @Override + public void executeQuery(ObjectClass objClass, Filter filter, ResultsHandler handler, OperationOptions options) { + LOG.info("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Execute Query-Parameters~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + 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())) { + throw new IllegalArgumentException("Unsupported object class: " + objClass); + } else { + LOG.info("ObjectClass: {0}", objClass); + } + + 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) { + 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); + } } diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/PlainGroupProcessor.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/PlainGroupProcessor.java deleted file mode 100644 index b9ebbf5..0000000 --- a/src/main/java/com/evolveum/polygon/connector/grouper/rest/PlainGroupProcessor.java +++ /dev/null @@ -1,184 +0,0 @@ -/******************************************************************************* - * Copyright 2017 Evolveum - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. - ******************************************************************************/ -package com.evolveum.polygon.connector.grouper.rest; - -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.utils.URIBuilder; -import org.identityconnectors.framework.common.objects.*; -import org.json.JSONArray; -import org.json.JSONObject; - -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static com.evolveum.polygon.connector.grouper.rest.Processor.*; - -/** - * @author surmanek - * @author mederly - * - */ -public class PlainGroupProcessor extends AbstractGroupProcessor { - - public static final String OBJECT_CLASS_NAME = "PlainGroup"; - public static final String ATTR_MEMBER = "member"; - - PlainGroupProcessor(Processor processor) { - super(processor); - } - - ObjectClassInfoBuilder buildSchema() { - ObjectClassInfoBuilder builder = new ObjectClassInfoBuilder(); - - builder.setType(OBJECT_CLASS_NAME); - AttributeInfoBuilder extension = new AttributeInfoBuilder(ATTR_EXTENSION, String.class); - builder.addAttributeInfo(extension.build()); - - AttributeInfoBuilder member = new AttributeInfoBuilder(ATTR_MEMBER, String.class); - member.setMultiValued(true); - member.setReturnedByDefault(false); - builder.addAttributeInfo(member.build()); - - return builder; - } - - protected void getAllGroups(final ResultsHandler handler, final OperationOptions options) { - boolean isGetMembers = isGetMembers(options); - if (!isGetMembers) { - getAllGroupsNoMembers(handler); - } else { - ResultsHandler localHandler = new ResultsHandler() { - @Override - public boolean handle(ConnectorObject connectorObject) { - return getGroupByUuid(connectorObject.getUid().getUidValue(), handler, options); - } - }; - getAllGroupsNoMembers(localHandler); - } - } - - private void getAllGroupsNoMembers(ResultsHandler handler) { - URIBuilder uriBuilder = processor.getURIBuilder() - .setPath(URI_BASE_PATH + PATH_GROUPS); - try { - HttpPost request = new HttpPost(uriBuilder.build()); - JSONObject body = new JSONObject() - .put("WsRestFindGroupsRequest", new JSONObject() - .put("wsQueryFilter", new JSONObject() - .put("queryFilterType", "FIND_BY_STEM_NAME") - .put("stemName", getConfiguration().getExportStem()) - .put("stemNameScope", "ALL_IN_SUBTREE"))); - executeFindGroupsResponse(request, body, handler); - } catch (RuntimeException | URISyntaxException e) { - throw processor.processException(e, uriBuilder, "Get all groups"); - } - } - - @Override - boolean getGroupByUuid(String uuid, ResultsHandler handler, OperationOptions options) { - if (!isGetMembers(options)) { - return getGroupByUuid(uuid, handler); - } else { - URIBuilder uriBuilder = processor.getURIBuilder() - .setPath(URI_BASE_PATH + PATH_GROUPS); - try { - HttpPost request = new HttpPost(uriBuilder.build()); - JSONObject body = new JSONObject() - .put("WsRestGetMembersRequest", new JSONObject() - .put("wsGroupLookups", new JSONObject[] { new JSONObject() - .put("uuid", uuid) }) - .put("includeSubjectDetail", true)); - return executeGetGroupWithMembersResponse(request, body, handler); - } catch (RuntimeException | URISyntaxException e) { - throw processor.processException(e, uriBuilder, "Get all groups"); - } - - } - } - - @Override - void getGroupByName(String name, ResultsHandler handler, OperationOptions options) { - if (!isGetMembers(options)) { - getGroupByName(name, handler); - } else { - URIBuilder uriBuilder = processor.getURIBuilder() - .setPath(URI_BASE_PATH + PATH_GROUPS); - try { - HttpPost request = new HttpPost(uriBuilder.build()); - JSONObject body = new JSONObject() - .put("WsRestGetMembersRequest", new JSONObject() - .put("wsGroupLookups", new JSONObject[] { new JSONObject() - .put("groupName", name) }) - .put("includeSubjectDetail", true)); - executeGetGroupWithMembersResponse(request, body, handler); - } catch (RuntimeException | URISyntaxException e) { - throw processor.processException(e, uriBuilder, "Get all groups"); - } - } - } - - private boolean executeGetGroupWithMembersResponse(HttpPost request, JSONObject body, ResultsHandler handler) { - System.out.println("Request = " + body.toString()); - JSONObject response = processor.callRequest(request, body, true, CONTENT_TYPE_JSON); - System.out.println("Got response: " + response); - processor.checkSuccess(response, WS_GET_MEMBERS_RESULTS); - - JSONObject gObject = (JSONObject) processor.get(response, WS_GET_MEMBERS_RESULTS, RESULTS, WS_GROUP); - String name = processor.getStringOrNull(gObject, "name"); - if (processor.groupNameMatches(name)) { - String extension = processor.getStringOrNull(gObject, "extension"); - String uuid = processor.getStringOrNull(gObject, "uuid"); - ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); - builder.setObjectClass(getObjectClass()); - builder.setUid(uuid); - builder.setName(name); - builder.addAttribute(ATTR_EXTENSION, extension); - - List<String> subjects = new ArrayList<>(); - JSONArray members = processor.getArray(response, false, WS_GET_MEMBERS_RESULTS, RESULTS, WS_SUBJECTS); - if (members != null) { - for (Object memberObject : members) { - JSONObject member = (JSONObject) memberObject; - String sourceId = processor.getStringOrNull(member, "sourceId"); - if (sourceId == null || !sourceId.equals(getConfiguration().getSubjectSource())) { - LOG.info("Skipping non-person member (source={0})", sourceId); - continue; - } - String subjectId = processor.getStringOrNull(member, "id"); - if (subjectId != null) { - subjects.add(subjectId); - } else { - LOG.info("Skipping unnamed member (source={0})", member); - } - } - builder.addAttribute(ATTR_MEMBER, subjects); - } - return handler.handle(builder.build()); - } else { - return true; - } - } - - @Override - protected ObjectClass getObjectClass() { - return new ObjectClass(OBJECT_CLASS_NAME); - } - - private boolean isGetMembers(OperationOptions options) { - String[] attrs = options.getAttributesToGet(); - return attrs != null && Arrays.asList(attrs).contains(ATTR_MEMBER); - } -} 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 9fa18b4..dd965e1 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 @@ -1,20 +1,21 @@ -/******************************************************************************* - * Copyright 2017 Evolveum - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. +/* + * Copyright (c) 2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. - ******************************************************************************/ + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.evolveum.polygon.connector.grouper.rest; import org.apache.commons.codec.binary.Base64; -import org.apache.http.HttpEntity; import org.apache.http.client.methods.*; import org.apache.http.client.utils.URIBuilder; import org.apache.http.conn.ssl.NoopHostnameVerifier; @@ -27,180 +28,128 @@ import org.apache.http.util.EntityUtils; import org.identityconnectors.common.logging.Log; import org.identityconnectors.framework.common.exceptions.*; -import org.identityconnectors.framework.common.objects.*; -import org.identityconnectors.framework.common.objects.filter.*; import org.json.JSONArray; import org.json.JSONObject; import java.io.*; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; -import java.util.Set; import java.util.regex.Pattern; /** - * @author surmanek - * + * Contains generic logic for handling REST operations over Grouper. */ public class Processor { static final Log LOG = Log.getLog(GrouperConnector.class); - static final String CONTENT_TYPE_JSON = "application/json; charset=utf-8"; - public static final String WS_FIND_STEMS_RESULTS = "WsFindStemsResults"; - public static final String RESULT_METADATA = "resultMetadata"; - public static final String SUCCESS = "success"; - public static final String STEM_RESULTS = "stemResults"; - public static final String PATH_STEMS = "/stems"; - public static final String WS_GET_MEMBERS_RESULTS = "WsGetMembersResults"; - public static final String RESULTS = "results"; - public static final String WS_SUBJECTS = "wsSubjects"; - public static final String WS_REST_GET_MEMBERS_REQUEST = "WsRestGetMembersRequest"; - public static final String WS_GROUP_LOOKUPS = "wsGroupLookups"; - public static final String GROUP_NAME = "groupName"; - public static final String WS_GET_GROUPS_RESULTS = "WsGetGroupsResults"; - public static final String WS_GROUPS = "wsGroups"; - public static final String WS_GROUP = "wsGroup"; - GrouperConfiguration configuration; - - public static final String URI_BASE_PATH = "/grouper-ws/servicesRest/json/v2_4_000"; - public static final String PATH_GROUPS = "/groups"; - public static final String PATH_SUBJECTS = "/subjects"; - - public Processor(GrouperConfiguration configuration) { - this.configuration = configuration; - } - //put objects of array1 to the end of array2 - private JSONArray concatJSONArrays(JSONArray array1, JSONArray array2){ - for (Object obj : array1){ - array2.put(obj); - } - return array2; - } - - JSONObject callRequest(HttpEntityEnclosingRequestBase request, JSONObject jo, Boolean parseResult, - String contentType) { - // don't log request here - password field !!! - if (contentType != null) - request.addHeader("Content-Type", contentType); - request.addHeader("Authorization", "Basic " + authEncoding()); - HttpEntity entity; - try { - entity = new ByteArrayEntity(jo.toString().getBytes("UTF-8")); - } catch (UnsupportedEncodingException e1) { - String exceptionMsg = "Creating request entity failed: problem occurred during entity encoding."; - LOG.error("{0}", exceptionMsg); - throw new ConnectorIOException(exceptionMsg); - } - request.setEntity(entity); - try (CloseableHttpResponse response = execute(request)){ - - - - //LOG.ok("Request: {0}", request.toString()); - //response = execute(request); - LOG.ok("Response: {0}", response); - processResponseErrors(response); + private static final String CONTENT_TYPE_JSON = "application/json; charset=utf-8"; + + 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_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_NAME_SCOPE = "stemNameScope"; + 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_RESULTS = "results"; + static final String J_STEM_RESULTS = "stemResults"; + static final String J_GROUP_RESULTS = "groupResults"; + static final String J_WS_GROUP_LOOKUPS = "wsGroupLookups"; + private static final String J_RESULT_METADATA = "resultMetadata"; + private static final String J_SUCCESS = "success"; + + static final String J_WS_SUBJECTS = "wsSubjects"; + static final String J_WS_GROUP = "wsGroup"; + + static final String J_UUID = "uuid"; + static final String J_NAME = "name"; + static final String J_EXTENSION = "extension"; + static final String J_SOURCE_ID = "sourceId"; + static final String J_ID = "id"; + + private static final String VAL_T = "T"; + static final String VAL_FIND_BY_STEM_NAME = "FIND_BY_STEM_NAME"; + 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"; - if (!parseResult) { - return null; - } - - String result = EntityUtils.toString(response.getEntity()); + GrouperConfiguration configuration; - LOG.ok("Response body: {0}", result); - return new JSONObject(result); - } catch (IOException e) { - StringBuilder exceptionMsg = new StringBuilder(); - exceptionMsg.append("Request failed: problem occured during execute request with uri: ") - .append(request.getURI()).append(": \n\t").append(e.getLocalizedMessage()); - LOG.error("{0}", exceptionMsg.toString()); - throw new ConnectorIOException(exceptionMsg.toString(), e); - } + Processor(GrouperConfiguration configuration) { + this.configuration = configuration; } - JSONObject callRequest(HttpRequestBase request, Boolean parseResult, String contentType) { - // don't log request here - password field !!! - //CloseableHttpResponse response = null; - LOG.ok("request URI: {0}", request.getURI()); - request.addHeader("Content-Type", contentType); - request.addHeader("Authorization", "Basic " + authEncoding()); - try (CloseableHttpResponse response = execute(request)){ - - LOG.ok("Response: {0}", response); + JSONObject callRequest(HttpEntityEnclosingRequestBase request, JSONObject payload) { + 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); processResponseErrors(response); - if (!parseResult) { - //closeResponse(response); - return null; - } - // DO NOT USE getEntity() TWICE!!! String result = EntityUtils.toString(response.getEntity()); - //closeResponse(response); - LOG.ok("Response body: {0}", result); + LOG.info("Response body: {0}", result); return new JSONObject(result); } catch (IOException e) { - StringBuilder exceptionMsg = new StringBuilder(); - exceptionMsg.append("Request failed: problem occured during execute request with uri: ") - .append(request.getURI()).append(": \n\t").append(e.getLocalizedMessage()); - //closeResponse(response); - LOG.error("{0}", exceptionMsg.toString()); - throw new ConnectorIOException(exceptionMsg.toString(), 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); } } - String authEncoding() { + private String getAuthEncoded() { String username = configuration.getUsername(); - String password = configuration.getStringPassword(); + String password = configuration.getPasswordPlain(); if (username == null || username.equals("")) { - LOG.error("Authentication failed: Username is not provided."); - throw new InvalidCredentialException("Authentication failed: Username is not provided."); + String msg = "Authentication failed: No user name specified"; + LOG.error("{0}", msg); + throw new InvalidCredentialException(msg); } if (password == null || password.equals("")) { - LOG.error("Authentication failed: Password is not provided."); - throw new InvalidPasswordException("Authentication failed: Password is not provided."); + String msg = "Authentication failed: No password specified"; + LOG.error("{0}", msg); + throw new InvalidPasswordException(msg); } - StringBuilder nameAndPasswd = new StringBuilder(); - nameAndPasswd.append(username).append(":").append(password); - // String nameAndPasswd = "administrator:training" - String encoding = Base64.encodeBase64String(nameAndPasswd.toString().getBytes()); - return encoding; + return Base64.encodeBase64String((username + ":" + password).getBytes()); } - CloseableHttpResponse execute(HttpUriRequest request) { + private CloseableHttpResponse execute(HttpUriRequest request) { try { HttpClientBuilder clientBuilder = HttpClientBuilder.create(); if (Boolean.TRUE.equals(configuration.getIgnoreSslValidation())) { SSLContextBuilder sslCtxBuilder = new SSLContextBuilder(); - sslCtxBuilder.loadTrustMaterial(null, new TrustStrategy() { - public boolean isTrusted(X509Certificate[] chain, String authType) { - return true; - } - }); + sslCtxBuilder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslCtxBuilder.build(), NoopHostnameVerifier.INSTANCE); clientBuilder.setSSLSocketFactory(factory); - System.out.println("Ignoring SSL validation"); + LOG.warn("Ignoring SSL validation: avoid this in production"); } CloseableHttpClient client = clientBuilder.build(); CloseableHttpResponse response = client.execute(request); - // print response code: - LOG.ok("response code: {0}", String.valueOf(response.getStatusLine().getStatusCode())); - // client.close(); + LOG.ok("response code: {0}", response.getStatusLine().getStatusCode()); // DO NOT CLOSE response HERE !!! return response; } catch (IOException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { - StringBuilder exceptionMsg = new StringBuilder(); - exceptionMsg.append("Execution of the request failed: problem occurred during HTTP client execution: \n\t") - .append(e.getLocalizedMessage()); - LOG.error("{0}", exceptionMsg.toString(), e); - e.printStackTrace(); - throw new ConnectorIOException(exceptionMsg.toString()); + String msg = "Execution of the request failed: problem occurred during HTTP client execution: \n\t" + e.getLocalizedMessage(); + LOG.error("{0}", msg, e); + throw new ConnectorIOException(msg); } } @@ -209,84 +158,48 @@ public boolean isTrusted(X509Certificate[] chain, String authType) { * method throws the ConnId exception that is the most appropriate match for * the error. */ - void processResponseErrors(CloseableHttpResponse response) { + private void processResponseErrors(CloseableHttpResponse response) { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode >= 200 && statusCode <= 299) { return; } + + if (statusCode == 401 || statusCode == 403) { + // sometimes there are binary data in responseBody + closeResponse(response); + String msg = "HTTP error " + statusCode + " " + response.getStatusLine().getReasonPhrase() + " : Authentication failure."; + LOG.error("{0}", msg); + throw new InvalidCredentialException(msg); + } + String responseBody = null; try { responseBody = EntityUtils.toString(response.getEntity()); } catch (IOException e) { LOG.warn("cannot read response body: {0}", e, e); } - - StringBuilder message = new StringBuilder(); - message.append("HTTP error ").append(statusCode).append(" ").append(response.getStatusLine().getReasonPhrase()) - .append(" : ").append(responseBody); - if (statusCode == 401 || statusCode == 403) { - StringBuilder anauthorizedMessage = new StringBuilder(); // response - // body - // of - // status - // code - // 401 - // contains - // binary - // data. - anauthorizedMessage.append("HTTP error ").append(statusCode).append(" ") - .append(response.getStatusLine().getReasonPhrase()) - .append(" : Provided credentials are incorrect."); - closeResponse(response); - LOG.error("{0}", anauthorizedMessage.toString()); - throw new InvalidCredentialException(anauthorizedMessage.toString()); - } - LOG.error("{0}", message.toString()); - if ((statusCode == 400 || statusCode == 404) && message.toString().contains("already")) { - closeResponse(response); - LOG.error("{0}", message.toString()); - throw new AlreadyExistsException(message.toString()); - } + String msg = "HTTP error " + statusCode + " " + response.getStatusLine().getReasonPhrase() + " : " + responseBody; + LOG.error("{0}", msg); + closeResponse(response); if (statusCode == 400 || statusCode == 405 || statusCode == 406) { - closeResponse(response); - LOG.error("{0}", message.toString()); - throw new ConnectorIOException(message.toString()); - } - if (statusCode == 402 || statusCode == 407) { - closeResponse(response); - LOG.error("{0}", message.toString()); - throw new PermissionDeniedException(message.toString()); - } - if (statusCode == 404 || statusCode == 410) { - closeResponse(response); - LOG.error("{0}", message.toString()); - throw new UnknownUidException(message.toString()); - } - if (statusCode == 408) { - closeResponse(response); - LOG.error("{0}", message.toString()); - throw new OperationTimeoutException(message.toString()); - } - if (statusCode == 412) { - closeResponse(response); - LOG.error("{0}", message.toString()); - throw new PreconditionFailedException(message.toString()); - } - if (statusCode == 418) { - closeResponse(response); - LOG.error("{0}", message.toString()); - throw new UnsupportedOperationException("Sorry, no coffee: " + message.toString()); + throw new ConnectorIOException(msg); + } else if (statusCode == 402 || statusCode == 407) { + throw new PermissionDeniedException(msg); + } else if (statusCode == 404 || statusCode == 410) { + throw new UnknownUidException(msg); + } else if (statusCode == 408) { + throw new OperationTimeoutException(msg); + } else if (statusCode == 412) { + throw new PreconditionFailedException(msg); + } else if (statusCode == 418) { + throw new UnsupportedOperationException("Sorry, no coffee: " + msg); + } else { + throw new ConnectorException(msg); } - - closeResponse(response); - LOG.error("{0}", message.toString()); - throw new ConnectorException(message.toString()); } - void closeResponse(CloseableHttpResponse response) { + private void closeResponse(CloseableHttpResponse response) { // to avoid pool waiting - if (response == null) - return; try { response.close(); } catch (IOException e) { @@ -294,138 +207,43 @@ void closeResponse(CloseableHttpResponse response) { } } - // filter json objects by substring: - JSONArray substringFiltering(JSONArray inputJsonArray, String attrName, String subValue) { - JSONArray jsonArrayOut = new JSONArray(); - // String attrName = attribute.getName().toString(); - // LOGGER.info("\n\tSubstring filtering: {0} ({1})", attrName, - // subValue); - for (int i = 0; i < inputJsonArray.length(); i++) { - JSONObject jsonObject = inputJsonArray.getJSONObject(i); - if (!jsonObject.has(attrName)) { - LOG.warn("\n\tProcessing JSON Object does not contain attribute {0}.", attrName); - return null; - } - if (jsonObject.has(attrName) && (jsonObject.get(attrName)).toString().contains(subValue)) { - // LOG.ok("value: {0}, subValue: {1} - MATCH: {2}", - // jsonObject.get(attrName).toString(), subValue, "YES"); - jsonArrayOut.put(jsonObject); - } - // else LOG.ok("value: {0}, subValue: {1} - MATCH: {2}", - // jsonObject.getString(attrName), subValue, "NO"); - } - return jsonArrayOut; - } - - // method called when attribute of query filter is null: - void throwNullAttrException(Filter query) { - StringBuilder exceptionMsg = new StringBuilder(); - exceptionMsg - .append("Get operation failed: problem occurred because of not provided attribute of query filter: ") - .append(query); - LOG.error("{0}", exceptionMsg.toString()); - throw new InvalidAttributeValueException(exceptionMsg.toString()); - } - - // create uri from base host: - URIBuilder getURIBuilder() { + private URIBuilder getUriBuilderRelative(String path) { try { URIBuilder uri = new URIBuilder(configuration.getBaseUrl()); - uri.setPath(URI_BASE_PATH); + uri.setPath(URI_BASE_PATH + path); return uri; } catch (URISyntaxException e) { throw new IllegalStateException(e.getMessage(), e); // todo } } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - <T> T addAttr(ConnectorObjectBuilder builder, String attrName, T attrVal) { - if (attrVal != null) { - builder.addAttribute(attrName, attrVal); - } - return attrVal; - } - - String getStringAttr(Set<Attribute> attributes, String attrName) throws InvalidAttributeValueException { - return getAttr(attributes, attrName, String.class); - } - - <T> T getAttr(Set<Attribute> attributes, String attrName, Class<T> type) - throws InvalidAttributeValueException { - return getAttr(attributes, attrName, type, null); + URIBuilder getUriBuilderForGroups() { + return getUriBuilderRelative(PATH_GROUPS); } - - @SuppressWarnings("unchecked") - private <T> T getAttr(Set<Attribute> attributes, String attrName, Class<T> type, T defaultVal) - throws InvalidAttributeValueException { - for (Attribute attr : attributes) { - if (attrName.equals(attr.getName())) { - List<Object> vals = attr.getValue(); - if (vals == null || vals.isEmpty()) { - // set empty value - return null; - } - if (vals.size() == 1) { - Object val = vals.get(0); - if (val == null) { - // set empty value - return null; - } - if (type.isAssignableFrom(val.getClass())) { - return (T) val; - } - StringBuilder exceptionMsg = new StringBuilder(); - exceptionMsg.append("Unsupported type ").append(val.getClass()).append(" for attribute ") - .append(attrName).append(", value: ").append(val); - LOG.error("{0}", exceptionMsg.toString()); - throw new InvalidAttributeValueException(exceptionMsg.toString()); - } - StringBuilder exceptionMsg = new StringBuilder(); - exceptionMsg.append("More than one value for attribute ").append(attrName).append(", values: ") - .append(vals); - LOG.error("{0}", exceptionMsg.toString()); - throw new InvalidAttributeValueException(exceptionMsg.toString()); - } - } - // set default value when attrName not in changed attributes - return defaultVal; + URIBuilder getUriBuilderForStems() { + return getUriBuilderRelative(PATH_STEMS); } - void getIfExists(JSONObject jsonObj, String attr, ConnectorObjectBuilder builder, boolean isMultiValue) { - if (jsonObj.has(attr) && jsonObj.get(attr) != null && !JSONObject.NULL.equals(jsonObj.get(attr))) { - if (isMultiValue) { - JSONArray attrJSONArray = jsonObj.getJSONArray(attr); - if (attrJSONArray != null) { - int size = attrJSONArray.length(); - ArrayList<String> attrStringArray = new ArrayList<String>(); - for (int i = 0; i < size; i++) { - attrStringArray.add(attrJSONArray.get(i).toString()); - } - builder.addAttribute(attr, attrStringArray.toArray()); - } - } else - addAttr(builder, attr, jsonObj.get(attr)); - } - } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - public void checkSuccess(JSONObject response, String rootName) { - Object success = get(response, rootName, RESULT_METADATA, SUCCESS); - if (!"T".equals(success)) { + void checkSuccess(JSONObject response, String rootName) { + Object success = get(response, rootName, J_RESULT_METADATA, J_SUCCESS); + if (!VAL_T.equals(success)) { throw new IllegalStateException("Request was not successful: " + success); } } + @SuppressWarnings("unused") public Object getIfExists(JSONObject object, String... items) { return get(object, false, items); } - public Object get(JSONObject object, String... items) { + Object get(JSONObject object, String... items) { return get(object, true, items); } - public Object get(JSONObject object, boolean mustExist, String... items) { + private Object get(JSONObject object, boolean mustExist, String... items) { if (items.length == 0) { throw new IllegalArgumentException("Empty item path"); } @@ -468,11 +286,12 @@ public Object get(JSONObject object, boolean mustExist, String... items) { } } - public JSONArray getArray(JSONObject object, String... items) { + @SuppressWarnings("unused") + JSONArray getArray(JSONObject object, String... items) { return getArray(object, true, items); } - public JSONArray getArray(JSONObject object, boolean mustExist, String... items) { + JSONArray getArray(JSONObject object, boolean mustExist, String... items) { Object rv = get(object, mustExist, items); if (rv == null) { assert !mustExist; @@ -484,19 +303,18 @@ public JSONArray getArray(JSONObject object, boolean mustExist, String... items) } } - public ConnectorException processException(Exception e, URIBuilder uriBuilder, final String operationName) { - StringBuilder exceptionMsg = new StringBuilder(); - exceptionMsg.append(operationName).append(" failed: problem occurred during executing URI: ").append(uriBuilder) - .append("\n\t").append(e.getLocalizedMessage()); - LOG.error("{0}", exceptionMsg.toString()); - return new ConnectorException(exceptionMsg.toString(), e); + ConnectorException processException(Exception e, URIBuilder uriBuilder, final String operationName) { + String msg = operationName + " failed: problem occurred during executing URI: " + uriBuilder + "\n\t" + e.getMessage(); + LOG.error("{0}", msg); + return new ConnectorException(msg, e); } + @SuppressWarnings("unused") public boolean isSuccess(JSONObject object) { - return "T".equals(getStringOrNull(object, SUCCESS)); + return VAL_T.equals(getStringOrNull(object, J_SUCCESS)); } - public String getStringOrNull(JSONObject object, String item) { + String getStringOrNull(JSONObject object, String item) { if (object.has(item)) { return getString(object, item); } else { @@ -504,7 +322,7 @@ public String getStringOrNull(JSONObject object, String item) { } } - public String getString(JSONObject object, String item) { + private String getString(JSONObject object, String item) { return (String) get(object, item); // todo error handling } @@ -512,8 +330,10 @@ boolean groupNameMatches(String name) { if (name == null) { return false; } - return groupNameMatches(name, configuration.getGroupIncludePattern()) && - !groupNameMatches(name, configuration.getGroupExcludePattern()); + String[] includes = configuration.getGroupIncludePattern(); + String[] excludes = configuration.getGroupExcludePattern(); + return (includes == null || includes.length == 0 || groupNameMatches(name, includes)) && + !groupNameMatches(name, excludes); } private boolean groupNameMatches(String name, String[] patterns) { diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/StandardGroupProcessor.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/StandardGroupProcessor.java deleted file mode 100644 index 9507c5e..0000000 --- a/src/main/java/com/evolveum/polygon/connector/grouper/rest/StandardGroupProcessor.java +++ /dev/null @@ -1,68 +0,0 @@ -/******************************************************************************* - * Copyright 2017 Evolveum - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. - ******************************************************************************/ -package com.evolveum.polygon.connector.grouper.rest; - -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.utils.URIBuilder; -import org.identityconnectors.framework.common.objects.*; -import org.json.JSONArray; -import org.json.JSONObject; - -import java.net.URISyntaxException; - -import static com.evolveum.polygon.connector.grouper.rest.Processor.*; - -/** - * @author surmanek - * @author mederly - * - */ -class StandardGroupProcessor extends AbstractGroupProcessor { - - StandardGroupProcessor(Processor processor) { - super(processor); - } - - ObjectClassInfoBuilder buildSchema() { - ObjectClassInfoBuilder builder = new ObjectClassInfoBuilder(); - - builder.setType(ObjectClass.GROUP_NAME); - AttributeInfoBuilder extension = new AttributeInfoBuilder(ATTR_EXTENSION, String.class); - builder.addAttributeInfo(extension.build()); - - return builder; - } - - protected void getAllGroups(ResultsHandler handler, OperationOptions options) { - URIBuilder uriBuilder = processor.getURIBuilder() - .setPath(URI_BASE_PATH + PATH_GROUPS); - try { - HttpPost request = new HttpPost(uriBuilder.build()); - JSONObject body = new JSONObject() - .put("WsRestGetMembersRequest", new JSONObject() - .put("wsGroupLookups", new JSONObject[] { new JSONObject() - .put("groupName", getConfiguration().getSuperGroup()) }) - .put("includeSubjectDetail", true) - .put("memberFilter", "Immediate")); - executeFindGroupsAsMembersResponse(request, body, handler); - } catch (RuntimeException | URISyntaxException e) { - throw processor.processException(e, uriBuilder, "Get all groups"); - } - } - - @Override - protected ObjectClass getObjectClass() { - return ObjectClass.GROUP; - } -} 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 1fe1993..572bfe4 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 @@ -1,8 +1,47 @@ +# +# Copyright (c) 2019 Evolveum +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +baseUrl.display=Base URL +baseUrl.help=URL on which the Grouper REST service can be accessed. An example: https://localhost:9443. + username.display=Username -username.help=Please provide the administrator user name for the jira enabled service you are logging into +username.help=Name of the user that is used to access the Grouper REST service. password.display=Password -password.help=Please provide the administrator password used to connect to the jira enabled service +password.help=Password of the user that is used to access the Grouper REST service. -baseUrl.display=Base URL -baseUrl.help=Please provide the base url for access to resources (e.g. host:port/rest/api/2/user) \ No newline at end of file +exportStem.display=Root stem +exportStem.help=The stem that is to be exported (visible) to this connector. The default is ":" (the whole tree). + +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. + +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)". + +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. + +testStem.display=Test stem +testStem.help=Stem whose accessibility is checked during Test connection operation (if specified). + +testGroup.display=Test group +testGroup.help=Group whose accessibility is checked during Test connection operation (if specified). + +GrouperConnector.rest.display=Grouper connector \ No newline at end of file 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 new file mode 100644 index 0000000..e8d405e --- /dev/null +++ b/src/test/java/com/evolveum/polygon/connector/grouper/test/AbstractTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.polygon.connector.grouper.test; + +import com.evolveum.polygon.connector.grouper.rest.GrouperConfiguration; +import com.evolveum.polygon.connector.grouper.rest.GrouperConnector; +import org.identityconnectors.common.security.GuardedString; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.OperationOptions; +import org.identityconnectors.framework.common.objects.SearchResult; +import org.identityconnectors.framework.spi.SearchResultsHandler; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Superclass for connector tests. These tests require running Grouper instance, with a group of TEST_GROUP having + * a single user TEST_USER in subject source SUBJECT_SOURCE. + */ +class AbstractTest { + + // Test configuration + static final String TEST_USER = "banderson"; + static final String TEST_GROUP = "etc:sysadmingroup"; + + // Connector configuration + private static final String BASE_URL = "https://192.168.56.101:9443"; + private static final String ADMIN_USERNAME = TEST_USER; + private static final String ADMIN_PASSWORD = "password"; + private static final String EXPORT_STEM = "etc"; + 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<>()); + + final ArrayList<ConnectorObject> results = new ArrayList<>(); + SearchResultsHandler handler = new SearchResultsHandler() { + @Override + public boolean handle(ConnectorObject connectorObject) { + results.add(connectorObject); + return true; + } + + @Override + public void handleResult(SearchResult result) { + } + }; + + GrouperConfiguration getConfiguration() { + GrouperConfiguration config = new GrouperConfiguration(); + config.setBaseUrl(BASE_URL); + config.setUsername(ADMIN_USERNAME); + config.setPassword(new GuardedString(ADMIN_PASSWORD.toCharArray())); + config.setIgnoreSslValidation(true); + config.setExportStem(EXPORT_STEM); + config.setGroupIncludePattern(GROUP_INCLUDE_PATTERN); + config.setGroupExcludePattern(GROUP_EXCLUDE_PATTERN); + config.setSubjectSource(SUBJECT_SOURCE); + config.setTestStem(TEST_STEM); + config.setTestGroup(TEST_GROUP); + return config; + } +} diff --git a/src/test/java/com/evolveum/polygon/connector/grouper/test/AccountTests.java b/src/test/java/com/evolveum/polygon/connector/grouper/test/AccountTests.java deleted file mode 100644 index a5e190a..0000000 --- a/src/test/java/com/evolveum/polygon/connector/grouper/test/AccountTests.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.evolveum.polygon.connector.grouper.test; -/******************************************************************************* - * Copyright 2019 Evolveum - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. - ******************************************************************************/ - -import org.identityconnectors.framework.common.objects.*; -import org.identityconnectors.framework.common.objects.filter.AttributeFilter; -import org.identityconnectors.framework.common.objects.filter.EqualsFilter; -import org.identityconnectors.framework.common.objects.filter.FilterBuilder; -import org.testng.annotations.Test; - -import static org.testng.AssertJUnit.assertEquals; - -/** - * @author surmanek - * @author mederly - * - */ -public class AccountTests extends GrouperTestHelper { - - @Test(priority = 1) - public void initTest() { - grouperConnector.init(getConfiguration()); - cleanUp(); - } - - @Test(priority = 3) - public void schemaTest() { - grouperConnector.schema(); - } - - @Test(priority = 3) - public void testTest() { - grouperConnector.test(); - } - - @Test(priority = 4) - public void nameEqualsFilteringForAccountsTest() { - // filtering: - results.clear(); - AttributeFilter filter = (EqualsFilter) FilterBuilder - .equalTo(AttributeBuilder.build(Name.NAME, "banderson")); - - grouperConnector.executeQuery(accountObjectClass, filter, handler, options); - assertEquals("Wrong # of users retrieved", results.size(), 1); - ConnectorObject user = results.get(0); - System.out.println("Found user: " + user); - } - - @Test(priority = 6) - public void listingAccountsTest() { - results.clear(); - grouperConnector.executeQuery(accountObjectClass, null, handler, options); - - assertEquals("Wrong # of users retrieved", results.size(), 1); - ConnectorObject user = results.get(0); - System.out.println("Found user: " + user); - } - - - @Test(priority = 20) - public void disposeTest() { - grouperConnector.dispose(); - } - - private void cleanUp() { - results.clear(); - } -} diff --git a/src/test/java/com/evolveum/polygon/connector/grouper/test/PlainGroupTests.java b/src/test/java/com/evolveum/polygon/connector/grouper/test/GroupTest.java similarity index 57% rename from src/test/java/com/evolveum/polygon/connector/grouper/test/PlainGroupTests.java rename to src/test/java/com/evolveum/polygon/connector/grouper/test/GroupTest.java index b9722e3..4972329 100644 --- a/src/test/java/com/evolveum/polygon/connector/grouper/test/PlainGroupTests.java +++ b/src/test/java/com/evolveum/polygon/connector/grouper/test/GroupTest.java @@ -1,68 +1,68 @@ -package com.evolveum.polygon.connector.grouper.test; -/******************************************************************************* - * Copyright 2019 Evolveum - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. +/* + * Copyright (c) 2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. - ******************************************************************************/ + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.polygon.connector.grouper.test; -import com.evolveum.polygon.connector.grouper.rest.PlainGroupProcessor; +import com.evolveum.polygon.connector.grouper.rest.GroupProcessor; import org.identityconnectors.framework.common.objects.*; import org.identityconnectors.framework.common.objects.filter.AttributeFilter; import org.identityconnectors.framework.common.objects.filter.EqualsFilter; import org.identityconnectors.framework.common.objects.filter.FilterBuilder; import org.testng.annotations.Test; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; +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; /** - * @author surmanek - * @author mederly - * + * Tests the group object class. See the superclass for the environment needed. */ -public class PlainGroupTests extends GrouperTestHelper { +public class GroupTest extends AbstractTest { - private static final ObjectClass PLAIN_GROUP = new ObjectClass(PlainGroupProcessor.OBJECT_CLASS_NAME); + private static final ObjectClass OC_GROUP = new ObjectClass(GroupProcessor.OBJECT_CLASS_NAME); private String uuid; @Test(priority = 1) - public void initTest() { + public void initialization() { grouperConnector.init(getConfiguration()); - cleanUp(); } @Test(priority = 2) - public void schemaTest() { + public void testSchema() { grouperConnector.schema(); } @Test(priority = 3) - public void testTest() { + public void testTestOperation() { grouperConnector.test(); } @Test(priority = 4) - public void findByGroupName() { - // filtering: + public void testFindByGroupName() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder - .equalTo(AttributeBuilder.build(Name.NAME, "etc:sysadmingroup")); + .equalTo(AttributeBuilder.build(ATTR_NAME, TEST_GROUP)); - grouperConnector.executeQuery(PLAIN_GROUP, filter, handler, options); + grouperConnector.executeQuery(OC_GROUP, filter, handler, options); assertEquals("Wrong # of groups retrieved", results.size(), 1); ConnectorObject group = results.get(0); System.out.println("Found group: " + group); @@ -70,60 +70,57 @@ public void findByGroupName() { } @Test(priority = 10) - public void findByGroupNameWithMembers() { - // filtering: + public void testFindByGroupNameWithMembers() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder - .equalTo(AttributeBuilder.build(Name.NAME, "etc:sysadmingroup")); - - grouperConnector.executeQuery(PLAIN_GROUP, filter, handler, getMembersOptions()); + .equalTo(AttributeBuilder.build(ATTR_NAME, TEST_GROUP)); + + grouperConnector.executeQuery(OC_GROUP, filter, handler, getMembersOptions()); assertEquals("Wrong # of groups retrieved", results.size(), 1); ConnectorObject group = results.get(0); System.out.println("Found group: " + group); List<String> members = getMembers(group); - assertEquals("Wrong members", Collections.singletonList("banderson"), members); + assertEquals("Wrong members", Collections.singletonList(TEST_USER), members); } - + @Test(priority = 12) - public void findByGroupUuid() { - // filtering: + public void testFindByGroupUuid() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder - .equalTo(AttributeBuilder.build(Uid.NAME, uuid)); + .equalTo(AttributeBuilder.build(ATTR_UUID, uuid)); - grouperConnector.executeQuery(PLAIN_GROUP, filter, handler, options); + grouperConnector.executeQuery(OC_GROUP, filter, handler, options); assertEquals("Wrong # of groups retrieved", results.size(), 1); ConnectorObject group = results.get(0); System.out.println("Found group: " + group); } @Test(priority = 13) - public void findByGroupUuidWihMembers() { - // filtering: + public void testFindByGroupUuidWihMembers() { results.clear(); AttributeFilter filter = (EqualsFilter) FilterBuilder - .equalTo(AttributeBuilder.build(Uid.NAME, uuid)); + .equalTo(AttributeBuilder.build(ATTR_UUID, uuid)); - grouperConnector.executeQuery(PLAIN_GROUP, filter, handler, getMembersOptions()); + grouperConnector.executeQuery(OC_GROUP, filter, handler, getMembersOptions()); assertEquals("Wrong # of groups retrieved", results.size(), 1); ConnectorObject group = results.get(0); System.out.println("Found group: " + group); - assertEquals("Wrong members", Collections.singletonList("banderson"), getMembers(group)); + assertEquals("Wrong members", Collections.singletonList(TEST_USER), getMembers(group)); } @Test(priority = 14) - public void allGroups() { + public void testGetAllGroups() { results.clear(); - grouperConnector.executeQuery(PLAIN_GROUP, null, handler, options); + grouperConnector.executeQuery(OC_GROUP, null, handler, options); for (ConnectorObject group : results) { System.out.println("Found group: " + group); } } @Test(priority = 16) - public void allGroupsWithMembers() { + public void testGetAllGroupsWithMembers() { results.clear(); - grouperConnector.executeQuery(PLAIN_GROUP, null, handler, getMembersOptions()); + grouperConnector.executeQuery(OC_GROUP, null, handler, getMembersOptions()); for (ConnectorObject group : results) { System.out.println("Found group: " + group); } @@ -134,19 +131,15 @@ public void dispose() { grouperConnector.dispose(); } - private void cleanUp() { - results.clear(); - } - private OperationOptions getMembersOptions() { HashMap<String, Object> map = new HashMap<>(); - map.put(OP_ATTRIBUTES_TO_GET, new String[] { PlainGroupProcessor.ATTR_MEMBER }); + map.put(OP_ATTRIBUTES_TO_GET, new String[] { GroupProcessor.ATTR_MEMBER }); return new OperationOptions(map); } private List<String> getMembers(ConnectorObject group) { - Attribute attribute = group.getAttributeByName(PlainGroupProcessor.ATTR_MEMBER); + Attribute attribute = group.getAttributeByName(GroupProcessor.ATTR_MEMBER); //noinspection unchecked - return attribute != null ? (List<String>) (List) attribute.getValue() : Collections.<String>emptyList(); + return attribute != null ? (List<String>) (List) attribute.getValue() : Collections.emptyList(); } -} +} \ No newline at end of file diff --git a/src/test/java/com/evolveum/polygon/connector/grouper/test/GrouperTestHelper.java b/src/test/java/com/evolveum/polygon/connector/grouper/test/GrouperTestHelper.java deleted file mode 100644 index 450090c..0000000 --- a/src/test/java/com/evolveum/polygon/connector/grouper/test/GrouperTestHelper.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.evolveum.polygon.connector.grouper.test; -/******************************************************************************* - * Copyright 2017 Evolveum - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. - ******************************************************************************/ - -import com.evolveum.polygon.connector.grouper.rest.GrouperConfiguration; -import com.evolveum.polygon.connector.grouper.rest.GrouperConnector; -import org.identityconnectors.common.logging.Log; -import org.identityconnectors.common.security.GuardedString; -import org.identityconnectors.framework.common.objects.ConnectorObject; -import org.identityconnectors.framework.common.objects.ObjectClass; -import org.identityconnectors.framework.common.objects.OperationOptions; -import org.identityconnectors.framework.common.objects.SearchResult; -import org.identityconnectors.framework.spi.SearchResultsHandler; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * @author surmanek - * @author mederly - */ -public class GrouperTestHelper { - - private static final String BASE_URL = "https://192.168.56.101:9443"; - private static final String ADMIN_USERNAME = "banderson"; - private static final String ADMIN_PASSWORD = "password"; - private static final String SUPER_GROUP = "etc:sysadmingroup"; - private static final String ROOT_STEM = "etc"; - private static final String SUBJECT_SOURCE = "ldap"; - - protected final GrouperConnector grouperConnector = new GrouperConnector(); - protected final OperationOptions options = new OperationOptions(new HashMap<String, Object>()); - protected final ObjectClass accountObjectClass = ObjectClass.ACCOUNT; - - protected final ArrayList<ConnectorObject> results = new ArrayList<>(); - protected SearchResultsHandler handler = new SearchResultsHandler() { - @Override - public boolean handle(ConnectorObject connectorObject) { - results.add(connectorObject); - return true; - } - - @Override - public void handleResult(SearchResult result) { - } - }; - - //group variables: - protected static final ObjectClass groupObjectClass = ObjectClass.GROUP; - protected final ArrayList<ConnectorObject> groupResults = new ArrayList<>(); - protected SearchResultsHandler groupHandler = new SearchResultsHandler() { - @Override - public boolean handle(ConnectorObject connectorObject) { - groupResults.add(connectorObject); - return true; - } - - @Override - public void handleResult(SearchResult result) { - } - }; - - //project variables: - protected static final ObjectClass projectObjectClass = new ObjectClass("PROJECT"); - protected final ArrayList<ConnectorObject> projectResults = new ArrayList<>(); - protected SearchResultsHandler projectHandler = new SearchResultsHandler() { - @Override - public boolean handle(ConnectorObject connectorObject) { - projectResults.add(connectorObject); - return true; - } - - @Override - public void handleResult(SearchResult result) { - } - }; - - protected GrouperConfiguration getConfiguration() { - GrouperConfiguration config = new GrouperConfiguration(); - config.setBaseUrl(BASE_URL); - config.setUsername(ADMIN_USERNAME); - config.setPassword(new GuardedString(ADMIN_PASSWORD.toCharArray())); - config.setSuperGroup(SUPER_GROUP); - config.setGroupIncludePattern(new String[] { ".*" }); - config.setGroupExcludePattern(new String[] { ".*_(includes|excludes|systemOfRecord|systemOfRecordAndIncludes)" }); - config.setIgnoreSslValidation(true); - config.setSubjectSource(SUBJECT_SOURCE); - config.setExportStem("ref"); - return config; - } -} diff --git a/src/test/java/com/evolveum/polygon/connector/grouper/test/StandardGroupTests.java b/src/test/java/com/evolveum/polygon/connector/grouper/test/StandardGroupTests.java deleted file mode 100644 index e906a20..0000000 --- a/src/test/java/com/evolveum/polygon/connector/grouper/test/StandardGroupTests.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.evolveum.polygon.connector.grouper.test; -/******************************************************************************* - * Copyright 2019 Evolveum - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. - ******************************************************************************/ - -import org.identityconnectors.framework.common.objects.*; -import org.identityconnectors.framework.common.objects.filter.AttributeFilter; -import org.identityconnectors.framework.common.objects.filter.EqualsFilter; -import org.identityconnectors.framework.common.objects.filter.FilterBuilder; -import org.testng.annotations.Test; - -import static org.testng.AssertJUnit.assertEquals; - -/** - * @author surmanek - * @author mederly - * - */ -public class StandardGroupTests extends GrouperTestHelper { - - private String uuid; - - @Test(priority = 1) - public void initTest() { - grouperConnector.init(getConfiguration()); - cleanUp(); - } - - @Test(priority = 2) - public void schemaTest() { - grouperConnector.schema(); - } - - @Test(priority = 3) - public void testTest() { - grouperConnector.test(); - } - - @Test(priority = 4) - public void findByGroupName() { - // filtering: - results.clear(); - AttributeFilter filter = (EqualsFilter) FilterBuilder - .equalTo(AttributeBuilder.build(Name.NAME, "etc:sysadmingroup")); - - grouperConnector.executeQuery(ObjectClass.GROUP, filter, handler, options); - assertEquals("Wrong # of groups retrieved", results.size(), 1); - ConnectorObject group = results.get(0); - System.out.println("Found group: " + group); - uuid = group.getUid().getUidValue(); - } - - @Test(priority = 5) - public void findByGroupUuid() { - // filtering: - results.clear(); - AttributeFilter filter = (EqualsFilter) FilterBuilder - .equalTo(AttributeBuilder.build(Uid.NAME, uuid)); - - grouperConnector.executeQuery(ObjectClass.GROUP, filter, handler, options); - assertEquals("Wrong # of groups retrieved", results.size(), 1); - ConnectorObject group = results.get(0); - System.out.println("Found group: " + group); - } - - @Test(priority = 6) - public void allGroupsTest() { - results.clear(); - grouperConnector.executeQuery(ObjectClass.GROUP, null, handler, options); - // most probably here will be no groups, as etc:sysadmingroup has no direct group members - for (ConnectorObject group : results) { - System.out.println("Found group: " + group); - } - } - - - @Test(priority = 20) - public void disposeTest() { - grouperConnector.dispose(); - } - - private void cleanUp() { - results.clear(); - } -}