Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SYNCOPE-1809] Remove uidOnCreate attribute on delete propagation #640

Merged
merged 2 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,6 @@ public interface AnyUtils {
Set<ExternalResource> getAllResources(Any<?> any);

void addAttr(PlainAttrValidationManager validator, String key, PlainSchema schema, String value);

void removeAttr(String key, PlainSchema schema);
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,11 @@ public AnyUtilsFactory anyUtilsFactory(
final @Lazy UserDAO userDAO,
final @Lazy GroupDAO groupDAO,
final @Lazy AnyObjectDAO anyObjectDAO,
final @Lazy PlainAttrDAO plainAttrDAO,
final @Lazy PlainAttrValueDAO plainAttrValueDAO,
final @Lazy EntityFactory entityFactory) {

return new JPAAnyUtilsFactory(userDAO, groupDAO, anyObjectDAO, entityFactory);
return new JPAAnyUtilsFactory(userDAO, groupDAO, anyObjectDAO, plainAttrDAO, plainAttrValueDAO, entityFactory);
}

@ConditionalOnMissingBean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,15 @@
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
import org.apache.syncope.core.persistence.api.entity.AnyUtils;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.GroupablePlainAttr;
import org.apache.syncope.core.persistence.api.entity.PlainAttr;
import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
Expand Down Expand Up @@ -125,6 +128,10 @@ public static boolean matchesFieldName(final String candidate) {

protected final AnyObjectDAO anyObjectDAO;

protected final PlainAttrDAO plainAttrDAO;

protected final PlainAttrValueDAO plainAttrValueDAO;

protected final EntityFactory entityFactory;

protected final AnyTypeKind anyTypeKind;
Expand All @@ -135,13 +142,17 @@ protected JPAAnyUtils(
final UserDAO userDAO,
final GroupDAO groupDAO,
final AnyObjectDAO anyObjectDAO,
final PlainAttrDAO plainAttrDAO,
final PlainAttrValueDAO plainAttrValueDAO,
final EntityFactory entityFactory,
final AnyTypeKind anyTypeKind,
final boolean linkedAccount) {

this.userDAO = userDAO;
this.groupDAO = groupDAO;
this.anyObjectDAO = anyObjectDAO;
this.plainAttrDAO = plainAttrDAO;
this.plainAttrValueDAO = plainAttrValueDAO;
this.entityFactory = entityFactory;
this.anyTypeKind = anyTypeKind;
this.linkedAccount = linkedAccount;
Expand Down Expand Up @@ -454,4 +465,23 @@ public void addAttr(
LOG.debug("{} has already {} set: {}", any, schema.getKey(), attr.getValuesAsStrings());
}
}

@Transactional
@Override
public void removeAttr(final String key, final PlainSchema schema) {
Any any = dao().find(key);

any.getPlainAttr(schema.getKey()).ifPresentOrElse(attr -> {
PlainAttr<?> plainAttr = (PlainAttr<?>) attr;
any.remove(plainAttr);
plainAttr.setOwner(null);
if (plainAttr instanceof GroupablePlainAttr) {
((GroupablePlainAttr) plainAttr).setMembership(null);
}
plainAttrValueDAO.deleteAll(plainAttr, this);
plainAttrDAO.delete(plainAttr);

dao().save(any);
}, () -> LOG.warn("Any {} does not contain {} PLAIN attribute", key, schema.getKey()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.AnyUtils;
Expand All @@ -41,6 +43,10 @@ public class JPAAnyUtilsFactory implements AnyUtilsFactory {

protected final AnyObjectDAO anyObjectDAO;

protected final PlainAttrDAO plainAttrDAO;

protected final PlainAttrValueDAO plainAttrValueDAO;

protected final EntityFactory entityFactory;

protected final Map<AnyTypeKind, AnyUtils> instances = new HashMap<>(3);
Expand All @@ -51,11 +57,15 @@ public JPAAnyUtilsFactory(
final UserDAO userDAO,
final GroupDAO groupDAO,
final AnyObjectDAO anyObjectDAO,
final PlainAttrDAO plainAttrDAO,
final PlainAttrValueDAO plainAttrValueDAO,
final EntityFactory entityFactory) {

this.userDAO = userDAO;
this.groupDAO = groupDAO;
this.anyObjectDAO = anyObjectDAO;
this.plainAttrDAO = plainAttrDAO;
this.plainAttrValueDAO = plainAttrValueDAO;
this.entityFactory = entityFactory;
}

Expand All @@ -65,7 +75,14 @@ public AnyUtils getInstance(final AnyTypeKind anyTypeKind) {
synchronized (instances) {
instance = instances.get(anyTypeKind);
if (instance == null) {
instance = new JPAAnyUtils(userDAO, groupDAO, anyObjectDAO, entityFactory, anyTypeKind, false);
instance = new JPAAnyUtils(userDAO,
groupDAO,
anyObjectDAO,
plainAttrDAO,
plainAttrValueDAO,
entityFactory,
anyTypeKind,
false);
ApplicationContextProvider.getBeanFactory().autowireBean(instance);
instances.put(anyTypeKind, instance);
}
Expand Down Expand Up @@ -96,8 +113,14 @@ public AnyUtils getInstance(final Any<?> any) {
public AnyUtils getLinkedAccountInstance() {
synchronized (this) {
if (linkedAccountInstance == null) {
linkedAccountInstance = new JPAAnyUtils(
userDAO, groupDAO, anyObjectDAO, entityFactory, AnyTypeKind.USER, true);
linkedAccountInstance = new JPAAnyUtils(userDAO,
groupDAO,
anyObjectDAO,
plainAttrDAO,
plainAttrValueDAO,
entityFactory,
AnyTypeKind.USER,
true);
}
}
return linkedAccountInstance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ protected Uid doCreate(
taskInfo.getResource().getProvisionByAnyType(taskInfo.getAnyType()).
filter(provision -> provision.getUidOnCreate() != null).
ifPresent(provision -> {
LOG.debug("Adding uidOnCreate [{}] attribute to [{}] on create", provision.getUidOnCreate(),
taskInfo.getEntityKey());
AnyUtils anyUtils = anyUtilsFactory.getInstance(taskInfo.getAnyTypeKind());
anyUtils.addAttr(
validator,
Expand Down Expand Up @@ -383,6 +385,19 @@ protected Uid delete(

connector.delete(objectClass, uid, null, propagationAttempted);
result = uid;
taskInfo.getResource()
.getProvisionByAnyType(taskInfo.getAnyType())
.filter(provision -> provision.getUidOnCreate() != null)
.ifPresent(provision -> {
LOG.debug("Removing uidOnCreate [{}] attribute from [{}] on delete",
andrea-patricelli marked this conversation as resolved.
Show resolved Hide resolved
provision.getUidOnCreate(), taskInfo.getEntityKey());
AnyUtils anyUtils = anyUtilsFactory.getInstance(taskInfo.getAnyTypeKind());
anyUtils.removeAttr(taskInfo.getEntityKey(), plainSchemaDAO.find(provision.getUidOnCreate()));
publisher.publishEvent(new EntityLifecycleEvent<>(this,
SyncDeltaType.UPDATE,
anyUtils.dao().find(taskInfo.getEntityKey()),
AuthContextUtils.getDomain()));
});
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,23 +55,27 @@
import org.apache.syncope.common.lib.request.MembershipUR;
import org.apache.syncope.common.lib.request.PasswordPatch;
import org.apache.syncope.common.lib.request.ResourceAR;
import org.apache.syncope.common.lib.request.ResourceDR;
import org.apache.syncope.common.lib.request.StringPatchItem;
import org.apache.syncope.common.lib.request.StringReplacePatchItem;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.request.UserUR;
import org.apache.syncope.common.lib.to.AnyTypeClassTO;
import org.apache.syncope.common.lib.to.ConnObject;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.ImplementationTO;
import org.apache.syncope.common.lib.to.Item;
import org.apache.syncope.common.lib.to.Mapping;
import org.apache.syncope.common.lib.to.MembershipTO;
import org.apache.syncope.common.lib.to.PlainSchemaTO;
import org.apache.syncope.common.lib.to.PropagationStatus;
import org.apache.syncope.common.lib.to.ProvisioningResult;
import org.apache.syncope.common.lib.to.RealmTO;
import org.apache.syncope.common.lib.to.ResourceTO;
import org.apache.syncope.common.lib.to.RoleTO;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.AttrSchemaType;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.ExecStatus;
Expand All @@ -83,6 +87,8 @@
import org.apache.syncope.common.lib.types.PatchOperation;
import org.apache.syncope.common.lib.types.PolicyType;
import org.apache.syncope.common.lib.types.ResourceAssociationAction;
import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
import org.apache.syncope.common.lib.types.SchemaType;
import org.apache.syncope.common.rest.api.RESTHeaders;
import org.apache.syncope.common.rest.api.beans.RealmQuery;
import org.apache.syncope.common.rest.api.service.UserService;
Expand Down Expand Up @@ -1634,4 +1640,72 @@ public void issueSYNCOPE1793() {
resource(RESOURCE_NAME_NOPROPAGATION).action(ResourceAssociationAction.ASSIGN).build());
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
}

@Test
public void issueSYNCOPE1809() throws IOException {
// 1. add a new schema externalKey and update provision accordingly
PlainSchemaTO externalKeySchemaTO = new PlainSchemaTO();
externalKeySchemaTO.setKey("externalKey");
externalKeySchemaTO.setType(AttrSchemaType.String);
externalKeySchemaTO.setReadonly(true);
SCHEMA_SERVICE.create(SchemaType.PLAIN, externalKeySchemaTO);
try {
AnyTypeClassTO minimalUser = ANY_TYPE_CLASS_SERVICE.read("minimal user");
minimalUser.getPlainSchemas().add(externalKeySchemaTO.getKey());
ANY_TYPE_CLASS_SERVICE.update(minimalUser);
ResourceTO restResourceTO = RESOURCE_SERVICE.read(RESOURCE_NAME_REST);
restResourceTO.getProvision(AnyTypeKind.USER.name())
.ifPresent(p -> p.setUidOnCreate(externalKeySchemaTO.getKey()));
RESOURCE_SERVICE.update(restResourceTO);
UserCR userCR = UserITCase.getUniqueSample("rest@syncope.apache.org");
userCR.getResources().clear();
userCR.getResources().add(RESOURCE_NAME_REST);

// 2. create
ProvisioningResult<UserTO> result = createUser(userCR);
assertEquals(1, result.getPropagationStatuses().size());
assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
assertEquals(RESOURCE_NAME_REST, result.getPropagationStatuses().get(0).getResource());
assertEquals("surname", result.getEntity().getPlainAttr("surname").get().getValues().get(0));
// externalKey is going to be populated on create
assertTrue(result.getEntity().getPlainAttr("externalKey").isPresent());
assertEquals(result.getEntity().getKey(),
result.getEntity().getPlainAttr("externalKey").get().getValues().get(0));
// 3. remove resource from the user
result = updateUser(new UserUR.Builder(result.getEntity()
.getKey()).resource(new StringPatchItem.Builder().value(RESOURCE_NAME_REST)
.operation(PatchOperation.DELETE)
.build()).build());
assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
// externalKey is going to be removed on resource unassignment
assertFalse(result.getEntity().getPlainAttr("externalKey").isPresent());

// 4. create a new user and deprovision, attribute is cleared
userCR = UserITCase.getUniqueSample("rest@syncope.apache.org");
userCR.getResources().clear();
userCR.getResources().add(RESOURCE_NAME_REST);
result = createUser(userCR);
assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
// this time fire a deprovision
assertNotNull(parseBatchResponse(USER_SERVICE.deassociate(new ResourceDR.Builder().key(result.getEntity()
.getKey()).action(ResourceDeassociationAction.DEPROVISION).resource(RESOURCE_NAME_REST).build())));
UserTO restUserTO = USER_SERVICE.read(result.getEntity().getKey());
assertFalse(restUserTO.getPlainAttr("externalKey").isPresent());

// 5. create a new user and unlink, attribute is not cleared since provisioning hasn't been fired
userCR = UserITCase.getUniqueSample("rest@syncope.apache.org");
userCR.getResources().clear();
userCR.getResources().add(RESOURCE_NAME_REST);
result = createUser(userCR);
assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
// this time deprovision
assertNotNull(parseBatchResponse(USER_SERVICE.deassociate(new ResourceDR.Builder().key(result.getEntity()
.getKey()).action(ResourceDeassociationAction.UNLINK).resource(RESOURCE_NAME_REST).build())));
restUserTO = USER_SERVICE.read(result.getEntity().getKey());
assertTrue(restUserTO.getPlainAttr("externalKey").isPresent());
} finally {
// remove additional externalKey schema
SCHEMA_SERVICE.delete(SchemaType.PLAIN, externalKeySchemaTO.getKey());
}
}
}
Loading