[SYNCOPE-671] Merge from 1_2_X
Project: http://git-wip-us.apache.org/repos/asf/syncope/repo Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/4cd6f5dc Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/4cd6f5dc Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/4cd6f5dc Branch: refs/heads/master Commit: 4cd6f5dc673497b83c56873f007f61e405f40f2a Parents: 22a9e12 4c90ea8 Author: Francesco Chicchiriccò <[email protected]> Authored: Sun May 24 19:07:20 2015 +0200 Committer: Francesco Chicchiriccò <[email protected]> Committed: Sun May 24 19:07:20 2015 +0200 ---------------------------------------------------------------------- .../java/DefaultUserProvisioningManager.java | 9 ++++++++- .../processor/UserConfirmPwdResetProcessor.java | 13 ++++++++++++- .../syncope/fit/core/reference/UserSelfITCase.java | 15 +++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/4cd6f5dc/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java index c1eff8c,0000000..2f959b5 mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java @@@ -1,362 -1,0 +1,369 @@@ +/* + * 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; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.mod.MembershipMod; +import org.apache.syncope.common.lib.mod.StatusMod; +import org.apache.syncope.common.lib.mod.UserMod; +import org.apache.syncope.common.lib.to.PropagationStatus; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.UserProvisioningManager; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.workflow.api.UserWorkflowAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +public class DefaultUserProvisioningManager implements UserProvisioningManager { + + private static final Logger LOG = LoggerFactory.getLogger(UserProvisioningManager.class); + + @Autowired + protected UserWorkflowAdapter uwfAdapter; + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + protected PropagationTaskExecutor taskExecutor; + + @Autowired + protected VirAttrHandler virtAttrHandler; + + @Autowired + protected UserDAO userDAO; + + @Override + public Pair<Long, List<PropagationStatus>> create(final UserTO userTO) { + return create(userTO, true, false, null, Collections.<String>emptySet()); + } + + @Override + public Pair<Long, List<PropagationStatus>> create(final UserTO userTO, final boolean storePassword) { + return create(userTO, storePassword, false, null, Collections.<String>emptySet()); + } + + @Override + public Pair<Long, List<PropagationStatus>> create(final UserTO userTO, final boolean storePassword, + final boolean disablePwdPolicyCheck, final Boolean enabled, final Set<String> excludedResources) { + + WorkflowResult<Pair<Long, Boolean>> created; + try { + created = uwfAdapter.create(userTO, disablePwdPolicyCheck, enabled, storePassword); + } catch (PropagationException e) { + throw e; + } + + List<PropagationTask> tasks = propagationManager.getUserCreateTasks( + created.getResult().getKey(), + created.getResult().getValue(), + created.getPropByRes(), + userTO.getPassword(), + userTO.getVirAttrs(), + userTO.getMemberships(), + excludedResources); + PropagationReporter propagationReporter = + ApplicationContextProvider.getApplicationContext().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return new ImmutablePair<>(created.getResult().getKey(), propagationReporter.getStatuses()); + } + + @Override + public Pair<Long, List<PropagationStatus>> update(final UserMod userMod) { + return update(userMod, false); + } + + @Override + public Pair<Long, List<PropagationStatus>> update(final UserMod userMod, final boolean removeMemberships) { + WorkflowResult<Pair<UserMod, Boolean>> updated = uwfAdapter.update(userMod); + + List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(updated); + if (tasks.isEmpty()) { + // SYNCOPE-459: take care of user virtual attributes ... + final PropagationByResource propByResVirAttr = virtAttrHandler.fillVirtual( + updated.getResult().getKey().getKey(), + userMod.getVirAttrsToRemove(), + userMod.getVirAttrsToUpdate()); + // SYNCOPE-501: update only virtual attributes (if any of them changed), password propagation is + // not required, take care also of membership virtual attributes + boolean addOrUpdateMemberships = false; + for (MembershipMod membershipMod : userMod.getMembershipsToAdd()) { + if (!virtAttrHandler.fillMembershipVirtual( + updated.getResult().getKey().getKey(), + membershipMod.getGroup(), + null, + membershipMod.getVirAttrsToRemove(), + membershipMod.getVirAttrsToUpdate(), + false).isEmpty()) { + addOrUpdateMemberships = true; + } + } + tasks.addAll(!propByResVirAttr.isEmpty() || addOrUpdateMemberships || removeMemberships + ? propagationManager.getUserUpdateTasks(updated, false, null) + : Collections.<PropagationTask>emptyList()); + } + PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext(). + getBean(PropagationReporter.class); + if (!tasks.isEmpty()) { + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + } + + Pair<Long, List<PropagationStatus>> result = new ImmutablePair<>( + updated.getResult().getKey().getKey(), propagationReporter.getStatuses()); + return result; + } + + @Override + public List<PropagationStatus> delete(final Long userKey) { + return delete(userKey, Collections.<String>emptySet()); + } + + @Override + public List<PropagationStatus> delete(final Long subjectId, final Set<String> excludedResources) { + // Note here that we can only notify about "delete", not any other + // task defined in workflow process definition: this because this + // information could only be available after uwfAdapter.delete(), which + // will also effectively remove user from db, thus making virtually + // impossible by NotificationManager to fetch required user information + List<PropagationTask> tasks = propagationManager.getUserDeleteTasks(subjectId, excludedResources); + + PropagationReporter propagationReporter = + ApplicationContextProvider.getApplicationContext().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + try { + uwfAdapter.delete(subjectId); + } catch (PropagationException e) { + throw e; + } + + return propagationReporter.getStatuses(); + } + + @Override + public Long unlink(final UserMod userMod) { + WorkflowResult<Pair<UserMod, Boolean>> updated = uwfAdapter.update(userMod); + return updated.getResult().getKey().getKey(); + } + + @Override + public Long link(final UserMod subjectMod) { + return uwfAdapter.update(subjectMod).getResult().getKey().getKey(); + } + + @Override + public Pair<Long, List<PropagationStatus>> activate(final User user, final StatusMod statusMod) { + WorkflowResult<Long> updated = statusMod.isOnSyncope() + ? uwfAdapter.activate(user.getKey(), statusMod.getToken()) + : new WorkflowResult<>(user.getKey(), null, statusMod.getType().name().toLowerCase()); + + return new ImmutablePair<>(updated.getResult(), propagateStatus(user, statusMod)); + } + + @Override + public Pair<Long, List<PropagationStatus>> reactivate(final User user, final StatusMod statusMod) { + WorkflowResult<Long> updated = statusMod.isOnSyncope() + ? uwfAdapter.reactivate(user.getKey()) + : new WorkflowResult<>(user.getKey(), null, statusMod.getType().name().toLowerCase()); + + return new ImmutablePair<>(updated.getResult(), propagateStatus(user, statusMod)); + } + + @Override + public Pair<Long, List<PropagationStatus>> suspend(final User user, final StatusMod statusMod) { + WorkflowResult<Long> updated = statusMod.isOnSyncope() + ? uwfAdapter.suspend(user.getKey()) + : new WorkflowResult<>(user.getKey(), null, statusMod.getType().name().toLowerCase()); + + return new ImmutablePair<>(updated.getResult(), propagateStatus(user, statusMod)); + } + + protected List<PropagationStatus> propagateStatus(final User user, final StatusMod statusMod) { + Collection<String> noPropResourceNames = + CollectionUtils.removeAll(userDAO.findAllResourceNames(user), statusMod.getResourceNames()); + + List<PropagationTask> tasks = propagationManager.getUserUpdateTasks( + user, statusMod.getType() != StatusMod.ModType.SUSPEND, noPropResourceNames); + PropagationReporter propReporter = + ApplicationContextProvider.getApplicationContext().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propReporter.onPrimaryResourceFailure(tasks); + } + + return propReporter.getStatuses(); + + } + + @Override + public void innerSuspend(final User user, final boolean propagate) { + final WorkflowResult<Long> updated = uwfAdapter.suspend(user); + + // propagate suspension if and only if it is required by policy + if (propagate) { + UserMod userMod = new UserMod(); + userMod.setKey(updated.getResult()); + + final List<PropagationTask> tasks = propagationManager.getUserUpdateTasks( + new WorkflowResult<Pair<UserMod, Boolean>>( + new ImmutablePair<>(userMod, Boolean.FALSE), + updated.getPropByRes(), updated.getPerformedTasks())); + + taskExecutor.execute(tasks); + } + } + + @Override + public List<PropagationStatus> deprovision(final Long userKey, final Collection<String> resources) { + final User user = userDAO.authFetch(userKey); + + List<PropagationTask> tasks = propagationManager.getUserDeleteTasks( + userKey, + new HashSet<>(resources), + CollectionUtils.removeAll(userDAO.findAllResourceNames(user), resources)); + PropagationReporter propagationReporter = + ApplicationContextProvider.getApplicationContext().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return propagationReporter.getStatuses(); + } + + @Override + public Pair<Long, List<PropagationStatus>> update(final UserMod userMod, final Long key, + final ProvisioningResult result, final Boolean enabled, final Set<String> excludedResources) { + + WorkflowResult<Pair<UserMod, Boolean>> updated; + try { + updated = uwfAdapter.update(userMod); + } catch (Exception e) { + LOG.error("Update of user {} failed, trying to sync its status anyway (if configured)", key, e); + + result.setStatus(ProvisioningResult.Status.FAILURE); + result.setMessage("Update failed, trying to sync status anyway (if configured)\n" + e.getMessage()); + + updated = new WorkflowResult<Pair<UserMod, Boolean>>( + new ImmutablePair<>(userMod, false), new PropagationByResource(), + new HashSet<String>()); + } + + if (enabled != null) { + User user = userDAO.find(key); + + WorkflowResult<Long> enableUpdate = null; + if (user.isSuspended() == null) { + enableUpdate = uwfAdapter.activate(key, null); + } else if (enabled && user.isSuspended()) { + enableUpdate = uwfAdapter.reactivate(key); + } else if (!enabled && !user.isSuspended()) { + enableUpdate = uwfAdapter.suspend(key); + } + + if (enableUpdate != null) { + if (enableUpdate.getPropByRes() != null) { + updated.getPropByRes().merge(enableUpdate.getPropByRes()); + updated.getPropByRes().purge(); + } + updated.getPerformedTasks().addAll(enableUpdate.getPerformedTasks()); + } + } + + List<PropagationTask> tasks = propagationManager.getUserUpdateTasks( + updated, updated.getResult().getKey().getPassword() != null, excludedResources); + PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext(). + getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return new ImmutablePair<>(updated.getResult().getKey().getKey(), + propagationReporter.getStatuses()); + + } + + @Override + public void requestPasswordReset(final Long id) { + uwfAdapter.requestPasswordReset(id); + } + + @Override + public void confirmPasswordReset(final User user, final String token, final String password) { + uwfAdapter.confirmPasswordReset(user.getKey(), token, password); + - List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(user, null, null); ++ UserMod userMod = new UserMod(); ++ userMod.setKey(user.getKey()); ++ userMod.setPassword(password); ++ ++ List<PropagationTask> tasks = propagationManager.getUserUpdateTasks( ++ new WorkflowResult<Pair<UserMod, Boolean>>( ++ new ImmutablePair<UserMod, Boolean>(userMod, null), null, "confirmPasswordReset"), ++ true, null); + PropagationReporter propReporter = + ApplicationContextProvider.getApplicationContext().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propReporter.onPrimaryResourceFailure(tasks); + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/4cd6f5dc/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java ---------------------------------------------------------------------- diff --cc ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java index 903f882,0000000..f0c6851 mode 100644,000000..100644 --- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java +++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java @@@ -1,61 -1,0 +1,72 @@@ +/* + * 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.camel.processor; + +import java.util.List; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; ++import org.apache.commons.lang3.tuple.ImmutablePair; ++import org.apache.commons.lang3.tuple.Pair; ++import org.apache.syncope.common.lib.mod.UserMod; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.persistence.api.entity.user.User; ++import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class UserConfirmPwdResetProcessor implements Processor { + + private static final Logger LOG = LoggerFactory.getLogger(UserConfirmPwdResetProcessor.class); + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + protected PropagationTaskExecutor taskExecutor; + + @Override + public void process(final Exchange exchange) { + User user = exchange.getProperty("user", User.class); + - List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(user, null, null); ++ UserMod userMod = new UserMod(); ++ userMod.setKey(user.getKey()); ++ userMod.setPassword(exchange.getProperty("password", String.class)); ++ ++ List<PropagationTask> tasks = propagationManager.getUserUpdateTasks( ++ new WorkflowResult<Pair<UserMod, Boolean>>( ++ new ImmutablePair<UserMod, Boolean>(userMod, null), null, "confirmPasswordReset"), ++ true, null); + PropagationReporter propReporter = + ApplicationContextProvider.getApplicationContext().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propReporter.onPrimaryResourceFailure(tasks); + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/4cd6f5dc/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserSelfITCase.java ---------------------------------------------------------------------- diff --cc fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserSelfITCase.java index f01a15c,0000000..14e2f9f mode 100644,000000..100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserSelfITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserSelfITCase.java @@@ -1,349 -1,0 +1,364 @@@ +/* + * 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.fit.core.reference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; ++import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.InputStream; +import java.security.AccessControlException; +import java.util.Map; +import java.util.Set; +import javax.ws.rs.core.Response; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.cxf.helpers.IOUtils; +import org.apache.syncope.client.lib.SyncopeClient; +import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.mod.AttrMod; +import org.apache.syncope.common.lib.mod.MembershipMod; +import org.apache.syncope.common.lib.mod.StatusMod; +import org.apache.syncope.common.lib.mod.UserMod; +import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.to.WorkflowFormPropertyTO; +import org.apache.syncope.common.lib.to.WorkflowFormTO; +import org.apache.syncope.common.lib.types.ClientExceptionType; +import org.apache.syncope.common.lib.types.SubjectType; +import org.apache.syncope.common.rest.api.Preference; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.common.rest.api.service.UserSelfService; +import org.apache.syncope.common.rest.api.service.UserService; +import org.junit.Assume; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; ++import org.springframework.jdbc.core.JdbcTemplate; + +@FixMethodOrder(MethodSorters.JVM) +public class UserSelfITCase extends AbstractITCase { + + @Test + public void selfRegistrationAllowed() { + assertTrue(syncopeService.info().isSelfRegAllowed()); + } + + @Test + public void create() { + Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers(syncopeService)); + + // 1. self-registration as admin: failure + try { + userSelfService.create(UserITCase.getUniqueSampleTO("[email protected]"), true); + fail(); + } catch (AccessControlException e) { + assertNotNull(e); + } + + // 2. self-registration as anonymous: works + SyncopeClient anonClient = clientFactory.createAnonymous(); + UserTO self = anonClient.getService(UserSelfService.class). + create(UserITCase.getUniqueSampleTO("[email protected]"), true). + readEntity(UserTO.class); + assertNotNull(self); + assertEquals("createApproval", self.getStatus()); + } + + @Test + public void createAndApprove() { + Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers(syncopeService)); + + // self-create user with membership: goes 'createApproval' with resources and membership but no propagation + UserTO userTO = UserITCase.getUniqueSampleTO("[email protected]"); + MembershipTO membership = new MembershipTO(); + membership.setGroupKey(3L); + userTO.getMemberships().add(membership); + userTO.getResources().add(RESOURCE_NAME_TESTDB); + + SyncopeClient anonClient = clientFactory.createAnonymous(); + userTO = anonClient.getService(UserSelfService.class). + create(userTO, true). + readEntity(UserTO.class); + assertNotNull(userTO); + assertEquals("createApproval", userTO.getStatus()); + assertFalse(userTO.getMemberships().isEmpty()); + assertFalse(userTO.getResources().isEmpty()); + + try { + resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, userTO.getKey()); + fail(); + } catch (SyncopeClientException e) { + assertEquals(ClientExceptionType.NotFound, e.getType()); + } + + // now approve and verify that propagation has happened + WorkflowFormTO form = userWorkflowService.getFormForUser(userTO.getKey()); + form = userWorkflowService.claimForm(form.getTaskId()); + Map<String, WorkflowFormPropertyTO> props = form.getPropertyMap(); + props.get("approve").setValue(Boolean.TRUE.toString()); + form.getProperties().clear(); + form.getProperties().addAll(props.values()); + userTO = userWorkflowService.submitForm(form); + assertNotNull(userTO); + assertEquals("active", userTO.getStatus()); + assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, userTO.getKey())); + } + + @Test + public void read() { + UserService userService2 = clientFactory.create("rossini", ADMIN_PWD).getService(UserService.class); + + try { + userService2.read(1L); + fail(); + } catch (AccessControlException e) { + assertNotNull(e); + } + + Pair<Map<String, Set<String>>, UserTO> self = clientFactory.create("rossini", ADMIN_PWD).self(); + assertEquals("rossini", self.getValue().getUsername()); + } + + @Test + public void updateWithoutApproval() { + // 1. create user as admin + UserTO created = createUser(UserITCase.getUniqueSampleTO("[email protected]")); + assertNotNull(created); + assertFalse(created.getUsername().endsWith("XX")); + + // 2. self-update (username) - works + UserMod userMod = new UserMod(); + userMod.setUsername(created.getUsername() + "XX"); + + SyncopeClient authClient = clientFactory.create(created.getUsername(), "password123"); + UserTO updated = authClient.getService(UserSelfService.class).update(created.getKey(), userMod). + readEntity(UserTO.class); + assertNotNull(updated); + assertEquals(ActivitiDetector.isActivitiEnabledForUsers(syncopeService) + ? "active" : "created", updated.getStatus()); + assertTrue(updated.getUsername().endsWith("XX")); + } + + @Test + public void updateWithApproval() { + Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers(syncopeService)); + + // 1. create user as admin + UserTO created = createUser(UserITCase.getUniqueSampleTO("[email protected]")); + assertNotNull(created); + assertFalse(created.getUsername().endsWith("XX")); + + // 2. self-update (username + memberships + resource) - works but needs approval + MembershipMod membershipMod = new MembershipMod(); + membershipMod.setGroup(7L); + AttrMod testAttrMod = new AttrMod(); + testAttrMod.setSchema("testAttribute"); + testAttrMod.getValuesToBeAdded().add("a value"); + membershipMod.getPlainAttrsToUpdate().add(testAttrMod); + + UserMod userMod = new UserMod(); + userMod.setUsername(created.getUsername() + "XX"); + userMod.getMembershipsToAdd().add(membershipMod); + userMod.getResourcesToAdd().add(RESOURCE_NAME_TESTDB); + userMod.setPassword("newPassword123"); + StatusMod statusMod = new StatusMod(); + statusMod.setOnSyncope(false); + statusMod.getResourceNames().add(RESOURCE_NAME_TESTDB); + userMod.setPwdPropRequest(statusMod); + + SyncopeClient authClient = clientFactory.create(created.getUsername(), "password123"); + UserTO updated = authClient.getService(UserSelfService.class).update(created.getKey(), userMod). + readEntity(UserTO.class); + assertNotNull(updated); + assertEquals("updateApproval", updated.getStatus()); + assertFalse(updated.getUsername().endsWith("XX")); + assertTrue(updated.getMemberships().isEmpty()); + + // no propagation happened + assertTrue(updated.getResources().isEmpty()); + try { + resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, updated.getKey()); + fail(); + } catch (SyncopeClientException e) { + assertEquals(ClientExceptionType.NotFound, e.getType()); + } + + // 3. approve self-update as admin + WorkflowFormTO form = userWorkflowService.getFormForUser(updated.getKey()); + form = userWorkflowService.claimForm(form.getTaskId()); + Map<String, WorkflowFormPropertyTO> props = form.getPropertyMap(); + props.get("approve").setValue(Boolean.TRUE.toString()); + form.getProperties().clear(); + form.getProperties().addAll(props.values()); + updated = userWorkflowService.submitForm(form); + assertNotNull(updated); + assertEquals("active", updated.getStatus()); + assertTrue(updated.getUsername().endsWith("XX")); + assertEquals(1, updated.getMemberships().size()); + + // check that propagation also happened + assertTrue(updated.getResources().contains(RESOURCE_NAME_TESTDB)); + assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, updated.getKey())); + } + + @Test + public void delete() { + UserTO created = createUser(UserITCase.getUniqueSampleTO("[email protected]")); + assertNotNull(created); + + SyncopeClient authClient = clientFactory.create(created.getUsername(), "password123"); + UserTO deleted = authClient.getService(UserSelfService.class).delete().readEntity(UserTO.class); + assertNotNull(deleted); + assertEquals(ActivitiDetector.isActivitiEnabledForUsers(syncopeService) + ? "deleteApproval" : null, deleted.getStatus()); + } + + @Test + public void issueSYNCOPE373() { + UserTO userTO = adminClient.self().getValue(); + assertEquals(ADMIN_UNAME, userTO.getUsername()); + } + + @Test + public void noContent() throws IOException { + Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers(syncopeService)); + + SyncopeClient anonClient = clientFactory.createAnonymous(); + UserSelfService noContentService = anonClient.prefer(UserSelfService.class, Preference.RETURN_NO_CONTENT); + + UserTO user = UserITCase.getUniqueSampleTO("[email protected]"); + + Response response = noContentService.create(user, true); + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + assertEquals(Preference.RETURN_NO_CONTENT.toString(), response.getHeaderString(RESTHeaders.PREFERENCE_APPLIED)); + assertEquals(StringUtils.EMPTY, IOUtils.toString((InputStream) response.getEntity())); + } + + @Test + public void passwordReset() { + // 0. ensure that password request DOES require security question + configurationService.set("passwordReset.securityQuestion", + attrTO("passwordReset.securityQuestion", "true")); + + // 1. create an user with security question and answer + UserTO user = UserITCase.getUniqueSampleTO("[email protected]"); + user.setSecurityQuestion(1L); + user.setSecurityAnswer("Rossi"); ++ user.getResources().add(RESOURCE_NAME_TESTDB); + createUser(user); + ++ // verify propagation (including password) on external db ++ JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource); ++ String pwdOnResource = jdbcTemplate.queryForObject("SELECT password FROM test WHERE id=?", String.class, ++ user.getUsername()); ++ assertTrue(StringUtils.isNotBlank(pwdOnResource)); ++ + // 2. verify that new user is able to authenticate + SyncopeClient authClient = clientFactory.create(user.getUsername(), "password123"); + UserTO read = authClient.self().getValue(); + assertNotNull(read); + + // 3. request password reset (as anonymous) providing the expected security answer + SyncopeClient anonClient = clientFactory.createAnonymous(); + try { + anonClient.getService(UserSelfService.class).requestPasswordReset(user.getUsername(), "WRONG"); + fail(); + } catch (SyncopeClientException e) { + assertEquals(ClientExceptionType.InvalidSecurityAnswer, e.getType()); + } + anonClient.getService(UserSelfService.class).requestPasswordReset(user.getUsername(), "Rossi"); + + // 4. get token (normally sent via e-mail, now reading as admin) + String token = userService.read(read.getKey()).getToken(); + assertNotNull(token); + + // 5. confirm password reset + try { + anonClient.getService(UserSelfService.class).confirmPasswordReset("WRONG TOKEN", "newPassword"); + fail(); + } catch (SyncopeClientException e) { + assertEquals(ClientExceptionType.NotFound, e.getType()); + assertTrue(e.getMessage().contains("WRONG TOKEN")); + } + anonClient.getService(UserSelfService.class).confirmPasswordReset(token, "newPassword123"); + + // 6. verify that password was reset and token removed + authClient = clientFactory.create(user.getUsername(), "newPassword123"); + read = authClient.self().getValue(); + assertNotNull(read); + assertNull(read.getToken()); ++ ++ // 7. verify that password was changed on external resource ++ String newPwdOnResource = jdbcTemplate.queryForObject("SELECT password FROM test WHERE id=?", String.class, ++ user.getUsername()); ++ assertTrue(StringUtils.isNotBlank(newPwdOnResource)); ++ assertNotEquals(pwdOnResource, newPwdOnResource); + } + + @Test + public void passwordResetWithoutSecurityQuestion() { + // 0. disable security question for password reset + configurationService.set("passwordReset.securityQuestion", + attrTO("passwordReset.securityQuestion", "false")); + + // 1. create an user with security question and answer + UserTO user = UserITCase.getUniqueSampleTO("[email protected]"); + createUser(user); + + // 2. verify that new user is able to authenticate + SyncopeClient authClient = clientFactory.create(user.getUsername(), "password123"); + UserTO read = authClient.self().getValue(); + assertNotNull(read); + + // 3. request password reset (as anonymous) with no security answer + SyncopeClient anonClient = clientFactory.createAnonymous(); + anonClient.getService(UserSelfService.class).requestPasswordReset(user.getUsername(), null); + + // 4. get token (normally sent via e-mail, now reading as admin) + String token = userService.read(read.getKey()).getToken(); + assertNotNull(token); + + // 5. confirm password reset + try { + anonClient.getService(UserSelfService.class).confirmPasswordReset("WRONG TOKEN", "newPassword"); + fail(); + } catch (SyncopeClientException e) { + assertEquals(ClientExceptionType.NotFound, e.getType()); + assertTrue(e.getMessage().contains("WRONG TOKEN")); + } + anonClient.getService(UserSelfService.class).confirmPasswordReset(token, "newPassword123"); + + // 6. verify that password was reset and token removed + authClient = clientFactory.create(user.getUsername(), "newPassword123"); + read = authClient.self().getValue(); + assertNotNull(read); + assertNull(read.getToken()); + + // 7. re-enable security question for password reset + configurationService.set("passwordReset.securityQuestion", + attrTO("passwordReset.securityQuestion", "true")); + } + +}
