Adding GoogleApps support option
Project: http://git-wip-us.apache.org/repos/asf/syncope/repo Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/84a1ae0d Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/84a1ae0d Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/84a1ae0d Branch: refs/heads/master Commit: 84a1ae0d8a0bd27bc95aafdc03462951198dc0b4 Parents: f292d86 Author: Francesco Chicchiriccò <ilgro...@apache.org> Authored: Wed Sep 20 14:01:41 2017 +0200 Committer: Francesco Chicchiriccò <ilgro...@apache.org> Committed: Wed Sep 20 14:10:41 2017 +0200 ---------------------------------------------------------------------- .../GoogleAppsPropagationActions.java | 159 +++++++++++++++++ .../java/pushpull/GoogleAppsPullActions.java | 173 +++++++++++++++++++ pom.xml | 6 + .../concepts/provisioning/propagation.adoc | 9 + .../concepts/provisioning/pull.adoc | 9 + 5 files changed, 356 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/84a1ae0d/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java new file mode 100644 index 0000000..1da3578 --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.syncope.core.provisioning.java.propagation; + +import java.util.HashSet; +import java.util.Set; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException; +import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO; +import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.AnyUtils; +import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.PlainSchema; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.persistence.api.entity.task.TaskExec; +import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.propagation.PropagationActions; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.Name; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +/** + * This class is required during setup of an External Resource based on the ConnId + * <a href="https://github.com/Tirasa/ConnIdGoogleAppsBundle">GoogleApps connector</a>. + * + * It manages: + * <ol> + * <li>the id provided by Google, which will need to be used for all subsequent operations</li> + * <li>the e-mail address</li> + * </ol> + */ +public class GoogleAppsPropagationActions implements PropagationActions { + + private static final Logger LOG = LoggerFactory.getLogger(GoogleAppsPropagationActions.class); + + @Autowired + private PlainSchemaDAO plainSchemaDAO; + + @Autowired + protected PlainAttrValueDAO plainAttrValueDAO; + + @Autowired + private UserDAO userDAO; + + @Autowired + private EntityFactory entityFactory; + + @Autowired + private AnyUtilsFactory anyUtilsFactory; + + protected String getEmailSchema() { + return "email"; + } + + protected String getGoogleAppsIdSchema() { + return "GoogleAppsId"; + } + + @Transactional + @Override + public void before(final PropagationTask task, final ConnectorObject beforeObj) { + if (task.getOperation() == ResourceOperation.DELETE || task.getOperation() == ResourceOperation.NONE) { + return; + } + if (AnyTypeKind.USER != task.getAnyTypeKind()) { + return; + } + + Set<Attribute> attrs = new HashSet<>(task.getAttributes()); + + // ensure to set __NAME__ value to user's email (e.g. primary e-mail address) + User user = userDAO.find(task.getEntityKey()); + if (user == null) { + LOG.error("Could not find user {}, skipping", task.getEntityKey()); + } else { + Name name = AttributeUtil.getNameFromAttributes(attrs); + if (name != null) { + attrs.remove(name); + } + attrs.add(new Name(user.getPlainAttr(getEmailSchema()).get().getValuesAsStrings().get(0))); + } + + task.setAttributes(attrs); + } + + @Transactional + @Override + public void after(final PropagationTask task, final TaskExec execution, final ConnectorObject afterObj) { + if (task.getOperation() == ResourceOperation.DELETE || task.getOperation() == ResourceOperation.NONE) { + return; + } + if (AnyTypeKind.USER != task.getAnyTypeKind()) { + return; + } + + User user = userDAO.find(task.getEntityKey()); + if (user == null) { + LOG.error("Could not find user {}, skipping", task.getEntityKey()); + } else { + boolean modified = false; + AnyUtils anyUtils = anyUtilsFactory.getInstance(user); + + PlainSchema googleAppsId = plainSchemaDAO.find(getGoogleAppsIdSchema()); + if (googleAppsId == null) { + LOG.error("Could not find schema {}, skipping", getGoogleAppsIdSchema()); + } else { + // set back the __UID__ received by Google + UPlainAttr attr = user.getPlainAttr(getGoogleAppsIdSchema()).orElse(null); + if (attr == null) { + attr = entityFactory.newEntity(UPlainAttr.class); + attr.setSchema(googleAppsId); + attr.setOwner(user); + user.add(attr); + + try { + attr.add(afterObj.getUid().getUidValue(), anyUtils); + modified = true; + } catch (InvalidPlainAttrValueException e) { + LOG.error("Invalid value for attribute {}: {}", + googleAppsId.getKey(), afterObj.getUid().getUidValue(), e); + } + } else { + LOG.debug("User {} has already {} assigned: {}", + user, googleAppsId.getKey(), attr.getValuesAsStrings()); + } + } + + if (modified) { + userDAO.save(user); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/84a1ae0d/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GoogleAppsPullActions.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GoogleAppsPullActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GoogleAppsPullActions.java new file mode 100644 index 0000000..7afbada --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GoogleAppsPullActions.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.syncope.core.provisioning.java.pushpull; + +import java.util.HashMap; +import java.util.Map; +import org.apache.syncope.common.lib.patch.AnyPatch; +import org.apache.syncope.common.lib.patch.StringReplacePatchItem; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException; +import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.AnyUtils; +import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.PlainSchema; +import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport; +import org.apache.syncope.core.provisioning.api.pushpull.PullActions; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +/** + * This class is required during setup of an External Resource based on the ConnId + * <a href="https://github.com/Tirasa/ConnIdGoogleAppsBundle">GoogleApps connector</a>. + * + * It manages: + * <ol> + * <li>the id provided by Google in response to create, which will need to be used for all subsequent operations</li> + * <li>the e-mail address</li> + * </ol> + */ +public class GoogleAppsPullActions implements PullActions { + + private static final Logger LOG = LoggerFactory.getLogger(GoogleAppsPullActions.class); + + @Autowired + private PlainSchemaDAO plainSchemaDAO; + + @Autowired + private UserDAO userDAO; + + @Autowired + private EntityFactory entityFactory; + + @Autowired + private AnyUtilsFactory anyUtilsFactory; + + private final Map<String, String> googleAppsIds = new HashMap<>(); + + protected String getEmailSchema() { + return "email"; + } + + protected String getGoogleAppsIdSchema() { + return "GoogleAppsId"; + } + + @Override + public SyncDelta beforeProvision( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final EntityTO entity) throws JobExecutionException { + + if (!(entity instanceof UserTO)) { + return delta; + } + + UserTO userTO = (UserTO) entity; + if (userTO.getUsername() == null) { + userTO.setUsername(delta.getObject().getName().getNameValue()); + } + + return delta; + } + + @Override + public <P extends AnyPatch> SyncDelta beforeUpdate( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final EntityTO entity, + final P anyPatch) throws JobExecutionException { + + if (!(anyPatch instanceof UserPatch)) { + return delta; + } + + UserPatch userPatch = (UserPatch) anyPatch; + if (userPatch.getUsername() == null) { + userPatch.setUsername(new StringReplacePatchItem.Builder(). + value(delta.getObject().getName().getNameValue()).build()); + } + + return delta; + } + + @Transactional + @Override + public void after( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final EntityTO entity, + final ProvisioningReport result) throws JobExecutionException { + + if (!(entity instanceof UserTO)) { + return; + } + + googleAppsIds.put(entity.getKey(), delta.getUid().getUidValue()); + } + + @Transactional + @Override + public void afterAll(final ProvisioningProfile<?, ?> profile) throws JobExecutionException { + googleAppsIds.entrySet().forEach((entry) -> { + User user = userDAO.find(entry.getKey()); + if (user == null) { + LOG.error("Could not find user {}, skipping", entry.getKey()); + } else { + AnyUtils anyUtils = anyUtilsFactory.getInstance(user); + + // 1. stores the __UID__ received by Google + PlainSchema googleAppsId = plainSchemaDAO.find(getGoogleAppsIdSchema()); + if (googleAppsId == null) { + LOG.error("Could not find schema googleAppsId, skipping"); + } else { + UPlainAttr attr = user.getPlainAttr(getGoogleAppsIdSchema()).orElse(null); + if (attr == null) { + attr = entityFactory.newEntity(UPlainAttr.class); + attr.setSchema(googleAppsId); + attr.setOwner(user); + user.add(attr); + + try { + attr.add(entry.getValue(), anyUtils); + userDAO.save(user); + } catch (InvalidPlainAttrValueException e) { + LOG.error("Invalid value for attribute {}: {}", + googleAppsId.getKey(), entry.getValue(), e); + } + } else { + LOG.debug("User {} has already a googleAppsId assigned: {}", user, attr.getValuesAsStrings()); + } + } + } + }); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/84a1ae0d/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index fef46f4..cbb292d 100644 --- a/pom.xml +++ b/pom.xml @@ -363,6 +363,7 @@ under the License. <connid.csvdir.version>0.8.6</connid.csvdir.version> <connid.ldap.version>1.5.2</connid.ldap.version> <connid.ad.version>1.3.4</connid.ad.version> + <connid.googleapps.version>1.4.1-SNAPSHOT</connid.googleapps.version> <cxf.version>3.2.0</cxf.version> @@ -1719,6 +1720,11 @@ under the License. <artifactId>net.tirasa.connid.bundles.ad</artifactId> <version>${connid.ad.version}</version> </artifactItem> + <artifactItem> + <groupId>net.tirasa.connid.bundles</groupId> + <artifactId>net.tirasa.connid.bundles.googleapps</artifactId> + <version>${connid.googleapps.version}</version> + </artifactItem> </artifactItems> </configuration> </plugin> http://git-wip-us.apache.org/repos/asf/syncope/blob/84a1ae0d/src/main/asciidoc/reference-guide/concepts/provisioning/propagation.adoc ---------------------------------------------------------------------- diff --git a/src/main/asciidoc/reference-guide/concepts/provisioning/propagation.adoc b/src/main/asciidoc/reference-guide/concepts/provisioning/propagation.adoc index 649e4f8..9c066f1 100644 --- a/src/main/asciidoc/reference-guide/concepts/provisioning/propagation.adoc +++ b/src/main/asciidoc/reference-guide/concepts/provisioning/propagation.adoc @@ -110,4 +110,13 @@ endif::[] the cipher algorithm associated with the password must match the value of `Password cipher algorithm` for the https://connid.atlassian.net/wiki/display/BASE/Database+Table#DatabaseTable-ConfigurationProperties[DatabaseTable connector bundle^]. +| +ifeval::["{snapshotOrRelease}" == "release"] +https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java[GoogleAppsPropagationActions^] +endif::[] +ifeval::["{snapshotOrRelease}" == "snapshot"] +https://github.com/apache/syncope/tree/master/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java[GoogleAppsPropagationActions^] +endif::[] +| Required for setup of an External Resource based on the https://connid.atlassian.net/wiki/display/BASE/Google+Apps#GoogleApps-Configuration[ConnId GoogleApps connector bundle^]. + |=== http://git-wip-us.apache.org/repos/asf/syncope/blob/84a1ae0d/src/main/asciidoc/reference-guide/concepts/provisioning/pull.adoc ---------------------------------------------------------------------- diff --git a/src/main/asciidoc/reference-guide/concepts/provisioning/pull.adoc b/src/main/asciidoc/reference-guide/concepts/provisioning/pull.adoc index 8358dbb..8f5f9c7 100644 --- a/src/main/asciidoc/reference-guide/concepts/provisioning/pull.adoc +++ b/src/main/asciidoc/reference-guide/concepts/provisioning/pull.adoc @@ -134,4 +134,13 @@ endif::[] the cipher algorithm associated with the password must match the value of `Password cipher algorithm` for the https://connid.atlassian.net/wiki/display/BASE/Database+Table#DatabaseTable-ConfigurationProperties[DatabaseTable connector bundle^]. +| +ifeval::["{snapshotOrRelease}" == "release"] +https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GoogleAppsPullActions.java[GoogleAppsPullActions^] +endif::[] +ifeval::["{snapshotOrRelease}" == "snapshot"] +https://github.com/apache/syncope/tree/master/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GoogleAppsPullActions.java[GoogleAppsPullActions^] +endif::[] +| Required for setup of an External Resource based on the https://connid.atlassian.net/wiki/display/BASE/Google+Apps#GoogleApps-Configuration[ConnId GoogleApps connector bundle^]. + |===