8000 Workflows: don't throw at runtime when a role condition references a missing role by Inf1mum · Pull Request #54779 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Workflows: don't throw at runtime when a role condition references a missing role - #54779

Open
Inf1mum wants to merge 2 commits into
keycloak:mainfrom
Inf1mum:fix/54759-workflow-role-condition
Open

Inf1mum wants to merge 2 commits into
keycloak:mainfrom
Inf1mum:fix/54759-workflow-role-condition

Conversation

@Inf1mum
@Inf1mum Inf1mum commented Nov 6, 2026
Copy link
Copy Markdown
Contributor

Fixes #54759

Problem

A workflow with a has-role(...) condition throws WorkflowInvalidStateException: workflowConditionRoleNotFound at runtime when the referenced role is deleted (or otherwise does not exist anymore). The scheduled runner catches and only logs the error, so the workflow silently stops working for the whole realm. The issue also mentions is-member-of behaves the same way.

Root cause

RoleWorkflowConditionProvider.validate() was called at the start of both runtime paths, toPredicate() and evaluate(), even though both methods already handle a missing role correctly (cb.disjunction() for the query predicate, role != null for in-memory evaluation). validate() throws WorkflowInvalidStateException when the role is missing, so the pre-existing, correct handling of a missing role was never reached.

Fix

Removed the validate() calls from the runtime paths (toPredicate() / evaluate()) of:

  • RoleWorkflowConditionProvider (has-role, the main fix for this issue)
  • GroupMembershipWorkflowConditionProvider (is-member-of, reported in the issue as also broken)
  • IdentityProviderWorkflowConditionProvider (has-idp-link, same pattern in evaluate())

The validate() methods themselves are kept as part of the WorkflowConditionProvider interface. Note that workflow create/update validation (WorkflowValidator.validateWorkflow) does not currently call provider validate() — it only checks that the referenced condition provider factories exist — so workflow create/update behavior is unchanged by this PR.

With this change, a condition that references a deleted entity no longer breaks the workflow at runtime: the condition simply matches no resources.

Tests

Added integration tests covering both runtime paths with a missing role/group (workflow creation, resource resolution through activateForAllEligibleResources → toPredicate, and activation through activate → evaluate):

  • RoleWorkflowConditionTest#testConditionForNonExistingRole
  • GroupWorkflowConditionTest#testConditionForNonExistingGroup

Both fail with WorkflowInvalidStateException before the fix and pass after it.

Verification

  • mvn compile -pl model/jpa passes.

keycloak#54759)

Signed-off-by: Inf1mum <Inf1mum@users.noreply.github.com>
@Inf1mum
Inf1mum requested a review from a team as a code owner November 6, 2026 16:41
Copilot AI balanced review requested due to automatic review settings November 6, 2026 16:41
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The missing-role and missing-group tests can pass despite evaluation exceptions because activation catches and logs them, so both need direct assertions that evaluate() returns false.

1 open issue
What changed in this PR

This PR addresses #54759 by allowing workflow conditions referencing missing entities to return a non-match instead of throwing at runtime.

Changes:

  • Removes runtime validation calls from role, group-membership, and identity-provider conditions.
  • Adds a null-group guard to in-memory membership evaluation.
  • Adds integration tests exercising bulk and individual activation with missing roles and groups.
File Description
tests/​base/​src/​test/​java/​org/​keycloak/​tests/​workflow/​condition/​RoleWorkflowConditionTest.java Adds missing-role activation regression coverage.
tests/​base/​src/​test/​java/​org/​keycloak/​tests/​workflow/​condition/​GroupWorkflowConditionTest.java Adds missing-group activation regression coverage.
model/​jpa/​src/​main/​java/​org/​keycloak/​models/​workflow/​conditions/​RoleWorkflowConditionProvider.java Allows runtime paths to reach existing missing-role handling.
model/​jpa/​src/​main/​java/​org/​keycloak/​models/​workflow/​conditions/​IdentityProviderWorkflowConditionProvider.java Removes validation from runtime evaluation.
model/​jpa/​src/​main/​java/​org/​keycloak/​models/​workflow/​conditions/​GroupMembershipWorkflowConditionProvider.java Removes runtime validation and guards against missing groups.

Review effort: Balanced

RealmModel realm = session.getContext().getRealm();
UserModel user = session.users().getUserByUsername(realm, "user-missing-role");
assertNotNull(user);
provider.activate(workflow, ResourceType.USERS, user.getId());
Signed-off-by: Inf1mum <Inf1mum@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings November 6, 2026 19:04
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The identity-provider condition can incorrectly match users linked to a nonexistent provider and execute workflows because it does not check whether the provider exists.

1 open issue
2 previously missed issues

In code that hasn't changed since last review

Medium severity Reject federated identities linked to missing providers

model/​jpa/​src/​main/​java/​org/​keycloak/​models/​workflow/​conditions/​IdentityProviderWorkflowConditionProvider.java:39

Removing validate() alone does not make a missing identity provider return false: UserResource.addFederatedIdentity() permits links to unknown aliases, and getFederatedIdentitiesStream() returns those links without checking provider existence. Such users can now match and execute the workflow despite the missing provider; return false when getByAlias(expectedAlias) is null.

Low severity Assert missing-group evaluation returns false directly

tests/​base/​src/​test/​java/​org/​keycloak/​tests/​workflow/​condition/​GroupWorkflowConditionTest.java:184

activate() catches condition-evaluation exceptions in DefaultWorkflowProvider.processEvent(), so the empty-state assertion also passes if the missing-group condition still throws. Assert directly that evaluate() returns false outside that catch boundary to regression-test the in-memory fix.

Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
0