Add regex support in 'Condition - User attribute' execution

Closes #265
This commit is contained in:
Konstantinos Georgilakis 2023-09-12 12:36:14 +03:00 committed by Marek Posolda
parent 48ab2b1688
commit 0044472f87
3 changed files with 48 additions and 3 deletions

View file

@ -27,6 +27,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Pattern;
public class ConditionalUserAttributeValue implements ConditionalAuthenticator { public class ConditionalUserAttributeValue implements ConditionalAuthenticator {
@ -40,15 +41,16 @@ public class ConditionalUserAttributeValue implements ConditionalAuthenticator {
String attributeValue = config.get(ConditionalUserAttributeValueFactory.CONF_ATTRIBUTE_EXPECTED_VALUE); String attributeValue = config.get(ConditionalUserAttributeValueFactory.CONF_ATTRIBUTE_EXPECTED_VALUE);
boolean includeGroupAttributes = Boolean.parseBoolean(config.get(ConditionalUserAttributeValueFactory.CONF_INCLUDE_GROUP_ATTRIBUTES)); boolean includeGroupAttributes = Boolean.parseBoolean(config.get(ConditionalUserAttributeValueFactory.CONF_INCLUDE_GROUP_ATTRIBUTES));
boolean negateOutput = Boolean.parseBoolean(config.get(ConditionalUserAttributeValueFactory.CONF_NOT)); boolean negateOutput = Boolean.parseBoolean(config.get(ConditionalUserAttributeValueFactory.CONF_NOT));
boolean regexOutput = Boolean.parseBoolean(config.get(ConditionalUserAttributeValueFactory.REGEX));
UserModel user = context.getUser(); UserModel user = context.getUser();
if (user == null) { if (user == null) {
throw new AuthenticationFlowException("Cannot find user for obtaining particular user attributes. Authenticator: " + ConditionalUserAttributeValueFactory.PROVIDER_ID, AuthenticationFlowError.UNKNOWN_USER); throw new AuthenticationFlowException("Cannot find user for obtaining particular user attributes. Authenticator: " + ConditionalUserAttributeValueFactory.PROVIDER_ID, AuthenticationFlowError.UNKNOWN_USER);
} }
boolean result = user.getAttributeStream(attributeName).anyMatch(attr -> Objects.equals(attr, attributeValue)); boolean result = user.getAttributeStream(attributeName).anyMatch(attr -> regexOutput ? Pattern.compile(attributeValue).matcher(attr).matches() : Objects.equals(attr, attributeValue));
if (!result && includeGroupAttributes) { if (!result && includeGroupAttributes) {
result = KeycloakModelUtils.resolveAttribute(user, attributeName, true).stream().anyMatch(attr -> Objects.equals(attr, attributeValue)); result = KeycloakModelUtils.resolveAttribute(user, attributeName, true).stream().anyMatch(attr -> regexOutput ? Pattern.compile(attributeValue).matcher(attr).matches() : Objects.equals(attr, attributeValue));
} }
return negateOutput != result; return negateOutput != result;
} }

View file

@ -33,6 +33,7 @@ public class ConditionalUserAttributeValueFactory implements ConditionalAuthenti
public static final String CONF_ATTRIBUTE_EXPECTED_VALUE = "attribute_expected_value"; public static final String CONF_ATTRIBUTE_EXPECTED_VALUE = "attribute_expected_value";
public static final String CONF_INCLUDE_GROUP_ATTRIBUTES = "include_group_attributes"; public static final String CONF_INCLUDE_GROUP_ATTRIBUTES = "include_group_attributes";
public static final String CONF_NOT = "not"; public static final String CONF_NOT = "not";
public static final String REGEX = "regex";
private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED, AuthenticationExecutionModel.Requirement.DISABLED AuthenticationExecutionModel.Requirement.REQUIRED, AuthenticationExecutionModel.Requirement.DISABLED
@ -109,7 +110,13 @@ public class ConditionalUserAttributeValueFactory implements ConditionalAuthenti
negateOutput.setLabel("Negate output"); negateOutput.setLabel("Negate output");
negateOutput.setHelpText("Apply a not to the check result"); negateOutput.setHelpText("Apply a not to the check result");
return Arrays.asList(authNoteName, authNoteExpectedValue, includeGroupAttributes, negateOutput); ProviderConfigProperty regexOutput = new ProviderConfigProperty();
regexOutput.setType(ProviderConfigProperty.BOOLEAN_TYPE);
regexOutput.setName(REGEX);
regexOutput.setLabel(REGEX);
regexOutput.setHelpText("Check equality with regex");
return Arrays.asList(authNoteName, authNoteExpectedValue, includeGroupAttributes, negateOutput, regexOutput);
} }
@Override @Override

View file

@ -146,6 +146,42 @@ public class AllowDenyAuthenticatorTest extends AbstractTestRealmKeycloakTest {
} }
} }
@Test
public void testDenyAccessWithRegexUserAttributeCondition() {
final String flowAlias = "browser - user attribute condition";
final String userWithoutAttribute = "test-user@localhost";
final String errorMessage = "You don't have necessary attribute.";
Map<String, String> attributeConfigMap = new HashMap<>();
attributeConfigMap.put(ConditionalUserAttributeValueFactory.CONF_ATTRIBUTE_NAME, "firstName");
attributeConfigMap.put(ConditionalUserAttributeValueFactory.CONF_ATTRIBUTE_EXPECTED_VALUE, "T(.*)");
attributeConfigMap.put(ConditionalUserAttributeValueFactory.REGEX, "true");
Map<String, String> denyAccessConfigMap = new HashMap<>();
denyAccessConfigMap.put(DenyAccessAuthenticatorFactory.ERROR_MESSAGE, errorMessage);
configureBrowserFlowWithDenyAccessInConditionalFlow(flowAlias, ConditionalUserAttributeValueFactory.PROVIDER_ID, attributeConfigMap, denyAccessConfigMap);
try {
loginUsernameOnlyPage.open();
loginUsernameOnlyPage.assertCurrent();
loginUsernameOnlyPage.login(userWithoutAttribute);
errorPage.assertCurrent();
assertThat(errorPage.getError(), is(errorMessage));
events.expectLogin()
.user((String) null)
.session((String) null)
.error(Errors.ACCESS_DENIED)
.detail(Details.USERNAME, userWithoutAttribute)
.removeDetail(Details.CONSENT)
.assertEvent();
} finally {
revertFlows(testRealm(), flowAlias);
}
}
/** /**
* Deny access, if user has defined the role and print error message. * Deny access, if user has defined the role and print error message.
*/ */