Skip to content
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 @@ -217,6 +217,17 @@ public List<LinkedAccountTO> getLinkedAccounts() {
return linkedAccounts;
}

@JsonIgnore
public boolean requiresApproval() {
return getUManager() != null
|| getGManager() != null
|| !getResources().isEmpty()
|| !getRelationships().isEmpty()
|| !memberships.isEmpty()
|| !roles.isEmpty()
|| !linkedAccounts.isEmpty();
}

@Override
public int hashCode() {
return new HashCodeBuilder().
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,17 @@ public boolean isEmpty() {
return isEmptyNotConsideringPassword() && password == null;
}

@JsonIgnore
public boolean requiresApproval() {
return getUManager() != null
|| getGManager() != null
|| !getResources().isEmpty()
|| !getRelationships().isEmpty()
|| !memberships.isEmpty()
|| !roles.isEmpty()
|| !linkedAccounts.isEmpty();
}

@Override
public int hashCode() {
return new HashCodeBuilder().
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,16 @@ public UserSelfLogic(
+ "and not(hasRole('" + IdRepoEntitlement.MUST_CHANGE_PASSWORD + "'))")
@Transactional(readOnly = true)
public Self read() {
UserTO authenticatedUser = binder.getAuthenticatedUserTO();
UserTO authenticated = binder.getAuthenticatedUserTO();

return new Self(
authenticatedUser,
authenticated,
POJOHelper.serialize(AuthContextUtils.getAuthorizations()),
POJOHelper.serialize(delegationDAO.findValidDelegating(
authenticatedUser.getKey(), OffsetDateTime.now())));
POJOHelper.serialize(delegationDAO.findValidDelegating(authenticated.getKey(), OffsetDateTime.now())));
}

@PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
public ProvisioningResult<UserTO> create(final UserCR createReq, final boolean nullPriorityAsync) {
public ProvisioningResult<UserTO> create(final UserCR userCR, final boolean nullPriorityAsync) {
if (!confParamOps.get(
AuthContextUtils.getDomain(), StandardConfParams.SELF_REGISTRATION_ALLOWED, false, boolean.class)) {

Expand All @@ -148,7 +147,7 @@ public ProvisioningResult<UserTO> create(final UserCR createReq, final boolean n
throw sce;
}

return doCreate(createReq, true, nullPriorityAsync);
return doCreate(userCR, true, nullPriorityAsync);
}

@PreAuthorize("isAuthenticated() "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ public DefaultUserWorkflowAdapter(
this.notificationManager = notificationManager;
}

protected void throwApprovalRequired(final String executor, final boolean condition) {
if (!securityProperties.getAdminUser().equals(executor)
&& confParamOps.get(
AuthContextUtils.getDomain(), "default.workflow.requires.approval", true, boolean.class)
&& condition) {

throw new WorkflowException(new UnsupportedOperationException("This operation requires approval"));
}
}

@Override
protected UserWorkflowResult<Pair<String, Boolean>> doCreate(
final UserCR userCR,
Expand All @@ -88,6 +98,8 @@ protected UserWorkflowResult<Pair<String, Boolean>> doCreate(
final String creator,
final String context) {

throwApprovalRequired(creator, userCR.requiresApproval());

User user = entityFactory.newEntity(User.class);
dataBinder.create(user, userCR);

Expand Down Expand Up @@ -148,6 +160,8 @@ protected UserWorkflowResult<String> doActivate(
protected UserWorkflowResult<Pair<UserUR, Boolean>> doUpdate(
final User user, final UserUR userUR, final String updater, final String context) {

throwApprovalRequired(updater, userUR.requiresApproval());

UserWorkflowResult.PropagationInfo propInfo = dataBinder.update(user, userUR);

metadata(user, updater, context);
Expand Down
655 changes: 563 additions & 92 deletions ext/flowable/flowable-bpmn/src/main/resources/userWorkflow.bpmn20.xml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion fit/core-reference/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1393,7 +1393,7 @@ under the License.
<inherited>true</inherited>
<configuration>
<container>
<containerId>wildfly39x</containerId>
<containerId>wildfly40x</containerId>
<zipUrlInstaller>
<url>https://github.com/wildfly/wildfly/releases/download/${wildfly.version}/wildfly-${wildfly.version}.zip</url>
<downloadDir>${settings.localRepository}/org/codehaus/cargo/cargo-container-archives</downloadDir>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
persistence.metaDataFactory=jpa(URLs=vfs:${project.build.directory}/cargo/configurations/wildfly39x/deployments/syncope.war/WEB-INF/lib/syncope-core-persistence-jpa-${syncope.version}.jar; vfs:${project.build.directory}/cargo/configurations/wildfly39x/deployments/syncope.war/WEB-INF/lib/syncope-core-self-keymaster-starter-${syncope.version}.jar, Resources=##orm##)
persistence.metaDataFactory=jpa(URLs=vfs:${project.build.directory}/cargo/configurations/wildfly40x/deployments/syncope.war/WEB-INF/lib/syncope-core-persistence-jpa-${syncope.version}.jar; vfs:${project.build.directory}/cargo/configurations/wildfly40x/deployments/syncope.war/WEB-INF/lib/syncope-core-self-keymaster-starter-${syncope.version}.jar, Resources=##orm##)

javadocPaths=/WEB-INF/lib/syncope-common-idrepo-rest-api-${syncope.version}-javadoc.jar,\
/WEB-INF/lib/syncope-common-idm-rest-api-${syncope.version}-javadoc.jar,\
Expand Down
4 changes: 2 additions & 2 deletions fit/core-reference/src/main/resources/userWorkflow.bpmn20.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ under the License.
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.flowable.org/processdef">

<process id="userWorkflow" name="User Workflow" isExecutable="true">
<startEvent id="theStart"/>
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="create"/>
Expand Down Expand Up @@ -99,7 +99,7 @@ try {
<exclusiveGateway id="activeGw"/>
<sequenceFlow id="active2UpdateApproval" sourceRef="activeGw" targetRef="updateApproval">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${wfExecutor == user.getUsername() and task == 'update'
and (!userUR.getMemberships().isEmpty())}]]></conditionExpression>
and userUR.requiresApproval()}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="active2DeleteApproval" sourceRef="activeGw" targetRef="deleteApproval">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${wfExecutor == user.getUsername() and task == 'delete'}]]></conditionExpression>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.ws.rs.HttpMethod;
Expand Down Expand Up @@ -1550,6 +1551,8 @@ public void issueSYNCOPE1750() {

@Test
public void issueSYNCOPE1793() {
assumeTrue(IS_FLOWABLE_ENABLED);

RoleTO role = new RoleTO();
role.setKey("syncope1793" + getUUIDString());
role.getRealms().add(SyncopeConstants.ROOT_REALM);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -807,4 +807,87 @@ public void issueSYNCOPE373() {
UserTO userTO = ADMIN_CLIENT.self().user();
assertEquals(ADMIN_UNAME, userTO.getUsername());
}

@Test
public void issueSYNCOPE1983Default() {
assumeFalse(IS_FLOWABLE_ENABLED);

// 1. create
UserCR userCR = UserITCase.getUniqueSample("issueSYNCOPE1983@syncope.apache.org");
userCR.getRoles().add("User manager");

SyncopeClientException e = assertThrows(
SyncopeClientException.class,
() -> ANONYMOUS_CLIENT.getService(UserSelfService.class).create(userCR));
assertEquals(ClientExceptionType.Workflow, e.getType());

// remove roles so self registration can proceed
userCR.getRoles().clear();

UserTO user = ANONYMOUS_CLIENT.getService(UserSelfService.class).
create(userCR).
readEntity(new GenericType<ProvisioningResult<UserTO>>() {
}).getEntity();
assertNotNull(user);

// 2. update
UserUR userUR = new UserUR.Builder(user.getKey()).
username(new StringReplacePatchItem.Builder().value(user.getUsername() + getUUIDString()).build()).
role(new StringPatchItem.Builder().value("User manager").build()).
build();

SyncopeClient authClient = CLIENT_FACTORY.create(user.getUsername(), "password123");
e = assertThrows(
SyncopeClientException.class,
() -> authClient.getService(UserSelfService.class).update(userUR));
assertEquals(ClientExceptionType.Workflow, e.getType());

// remove roles so update can proceed
userUR.getRoles().clear();

user = authClient.getService(UserSelfService.class).
update(userUR).
readEntity(new GenericType<ProvisioningResult<UserTO>>() {
}).getEntity();
assertNotNull(user);
}

@Test
public void issueSYNCOPE1983Flowable() {
assumeTrue(IS_FLOWABLE_ENABLED);

// 1. create
UserCR userCR = UserITCase.getUniqueSample("issueSYNCOPE1983@syncope.apache.org");
userCR.getRoles().add("User manager");

UserTO user = ANONYMOUS_CLIENT.getService(UserSelfService.class).
create(userCR).
readEntity(new GenericType<ProvisioningResult<UserTO>>() {
}).getEntity();
assertNotNull(user);
assertEquals("createApproval", user.getStatus());

// approve
UserRequestForm form = USER_REQUEST_SERVICE.listForms(
new UserRequestQuery.Builder().user(user.getKey()).build()).getResult().getFirst();
form = USER_REQUEST_SERVICE.claimForm(form.getTaskId());
form.getProperty("approveCreate").get().setValue(Boolean.TRUE.toString());
user = USER_REQUEST_SERVICE.submitForm(form).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
}).getEntity();
assertNotNull(user);
assertEquals("active", user.getStatus());

// 2. update
UserUR userUR = new UserUR.Builder(user.getKey()).
username(new StringReplacePatchItem.Builder().value(user.getUsername() + getUUIDString()).build()).
role(new StringPatchItem.Builder().value("User manager").build()).
build();

SyncopeClient authClient = CLIENT_FACTORY.create(user.getUsername(), "password123");
user = authClient.getService(UserSelfService.class).
update(userUR).
readEntity(new GenericType<ProvisioningResult<UserTO>>() {
}).getEntity();
assertEquals("updateApproval", user.getStatus());
}
}
Binary file modified src/main/asciidoc/images/userWorkflow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ or to completely disable connector connection testing.
[NOTE]
====
This parameter is useful to avoid waiting for the default resource timeout, by setting a shorter value;
or to completely disable resource connection testing.
or to completely disable resource connection testing;
====

* `default.workflow.requires.approval` - when using the default <<workflow,user workflow adapter>> (e.g. not
<<flowable-user-workflow-adapter,Flowable>>), this option controls whether an exception shall be raised when any
requested change requires admin approval.

Besides this default set, new configuration parameters can be defined to support <<customization,custom>> code.
Loading