Migrate tests from org/keycloak/tests/admin/authentication to new framework (#37003)

* Migrate tests from org/keycloak/tests/admin/authentication to new framework

Part of #34494

Signed-off-by: stianst <stianst@gmail.com>

* Update tests/MIGRATING_TESTS.md

Signed-off-by: Stian Thorgersen <stian@redhat.com>

---------

Signed-off-by: stianst <stianst@gmail.com>
Signed-off-by: Stian Thorgersen <stian@redhat.com>
This commit is contained in:
Stian Thorgersen 2025-02-06 10:24:50 +01:00 committed by GitHub
parent b9442b1690
commit 19cabe8238
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 2064 additions and 340 deletions

View File

@ -342,7 +342,7 @@ jobs:
run: ./mvnw install -e -pl testsuite/integration-arquillian/servers/auth-server/quarkus
- name: Run new base tests
run: ./mvnw test -f tests/pom.xml -Dtest=JDKTestSuite
run: ./mvnw package -f tests/pom.xml -Dtest=JDKTestSuite
- name: Run base tests
run: |
@ -681,7 +681,7 @@ jobs:
- name: Run new base tests
run: |
KC_TEST_DATABASE=${{ matrix.db }} KC_TEST_DATABASE_REUSE=true TESTCONTAINERS_REUSE_ENABLE=true ./mvnw test -f tests/pom.xml -Dtest=DatabaseTestSuite
KC_TEST_DATABASE=${{ matrix.db }} KC_TEST_DATABASE_REUSE=true TESTCONTAINERS_REUSE_ENABLE=true ./mvnw package -f tests/pom.xml -Dtest=DatabaseTestSuite
- name: Database container port
run: |
@ -1083,7 +1083,7 @@ jobs:
uses: ./.github/actions/integration-test-setup
- name: Run tests
run: ./mvnw test -f tests/pom.xml
run: ./mvnw package -f tests/pom.xml
check:
name: Status Check - Keycloak CI

View File

@ -77,7 +77,14 @@ public abstract class AbstractEvents<R> {
}
public void skipAll() {
try {
Thread.sleep(1); // Wait 1 ms to make sure time passes
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
testStarted = getCurrentTime();
lastFetch = -1;
events.clear();
}
public void clear() {
@ -101,7 +108,7 @@ public abstract class AbstractEvents<R> {
protected abstract Logger getLogger();
protected long getCurrentTime() {
public long getCurrentTime() {
return System.currentTimeMillis();
}

View File

@ -39,6 +39,21 @@ public class AdminEventAssertion {
return new AdminEventAssertion(event, false).assertValidOperationType();
}
public static AdminEventAssertion assertEvent(AdminEventRepresentation event, OperationType operationType, String resourcePath, Object representation, ResourceType resourceType) {
return assertSuccess(event)
.operationType(operationType)
.resourcePath(resourcePath)
.representation(representation)
.resourceType(resourceType);
}
public static AdminEventAssertion assertEvent(AdminEventRepresentation event, OperationType operationType, String resourcePath, ResourceType resourceType) {
return assertSuccess(event)
.operationType(operationType)
.resourcePath(resourcePath)
.resourceType(resourceType);
}
public AdminEventAssertion operationType(OperationType operationType) {
Assertions.assertEquals(operationType.name(), getOperationType());
return this;

69
tests/MIGRATING_TESTS.md Normal file
View File

@ -0,0 +1,69 @@
### Basics
When migrating tests use the remote server mode as this will make it much quicker to run tests than having to start/stop
Keycloak when you run the test from the IDE.
Add `@KeycloakIntegrationTest` to the class.
Change `import org.junit.Test;` to `import org.junit.jupiter.api.Test;`
Remove extends `AbstractKeycloakTest` as the new test framework provides injection of resources needed by the test there
is no need for the `AbstractKeycloakTest` and tests should instead inject what they need.
One thing your test is most likely going to need is a realm, this is now done with:
```
@InjectRealm
ManagedRealm realm;
```
### Changed packages/classes
| Old | New |
|---------------------------------------------|------------------------------------------------|
| org.junit.Assert | org.junit.jupiter.api.Assertions |
| org.junit.Test | org.junit.jupiter.api.Test |
| org.keycloak.testsuite.admin.ApiUtil | org.keycloak.tests.utils.admin.ApiUtil |
| org.keycloak.testsuite.util.AdminEventPaths | org.keycloak.tests.utils.admin.AdminEventPaths |
### Assertions
Change `import org.junit.Assert;` to `import org.junit.jupiter.api.Assertions;`, and replace `Assert.` with `Assertions.` throughout.
If the assert passes a message (for example `Assert.assertEquals("Message", expected, actual)`) the message in `Assertions`
is now the last parameter (for example `Assertions.assertEquals(expected, actual, "Message")`).
### Admin events
Admin events are handled slightly differently in the new test framework.
An example for the old testsuite:
```
@Rule
public AssertAdminEvents assertAdminEvents = new AssertAdminEvents(this);
public void myTest() {
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, ...);
}
```
Converted to the new test framework:
```
@InjectAdminEvents
public AdminEvents adminEvents;
public void myTest() {
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, ...);
}
```
Notice that there is no need to pass `realmId` when asserting an event, that is because the `AdminEvents` will only
receive events for the current realm.
For better readability `AdminEventAssertion` provides a method chaining approach to assert various fields in the event
(the example above could be change to `AdminEventAssertion.assertSuccess(adminEvents.poll()).operationType(OperationType.CREATE)...`).
There is also improved support for skipping events using skip methods, that allows skipping one event (`.skip()`),
multiple events (`.skip(5)`), or skipping all previous events (`.skipAll()`).

View File

@ -86,6 +86,11 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak.tests</groupId>
<artifactId>keycloak-tests-custom-providers</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>

View File

@ -0,0 +1,233 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.tests.admin.authentication;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.testframework.annotations.InjectAdminEvents;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.events.AdminEvents;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import org.keycloak.tests.utils.admin.ApiUtil;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public abstract class AbstractAuthenticationTest {
static final String REQUIRED = "REQUIRED";
static final String CONDITIONAL = "CONDITIONAL";
static final String DISABLED = "DISABLED";
static final String ALTERNATIVE = "ALTERNATIVE";
@InjectRealm
ManagedRealm managedRealm;
RealmResource realmResource;
AuthenticationManagementResource authMgmtResource;
protected String testRealmId;
@InjectAdminEvents
public AdminEvents adminEvents;
@BeforeEach
public void before() {
realmResource = managedRealm.admin();
authMgmtResource = realmResource.flows();
testRealmId = managedRealm.getId();
}
public static AuthenticationExecutionInfoRepresentation findExecutionByProvider(String provider, List<AuthenticationExecutionInfoRepresentation> executions) {
for (AuthenticationExecutionInfoRepresentation exec : executions) {
if (provider.equals(exec.getProviderId())) {
return exec;
}
}
return null;
}
/**
* Searches for an execution located before the provided execution on the same level of
* an authentication flow.
*
* @param execution execution to find a neighbor for
* @param executions list of executions to search in
* @return execution, or null if not found
*/
public static AuthenticationExecutionInfoRepresentation findPreviousExecution(AuthenticationExecutionInfoRepresentation execution, List<AuthenticationExecutionInfoRepresentation> executions) {
for (AuthenticationExecutionInfoRepresentation exec : executions) {
if (exec.getLevel() != execution.getLevel()) {
continue;
}
if (exec.getIndex() == execution.getIndex() - 1) {
return exec;
}
}
return null;
}
public static AuthenticationFlowRepresentation findFlowByAlias(String alias, List<AuthenticationFlowRepresentation> flows) {
for (AuthenticationFlowRepresentation flow : flows) {
if (alias.equals(flow.getAlias())) {
return flow;
}
}
return null;
}
void compareExecution(AuthenticationExecutionInfoRepresentation expected, AuthenticationExecutionInfoRepresentation actual) {
Assertions.assertEquals(expected.getRequirement(), actual.getRequirement(), "Execution requirement - " + actual.getProviderId());
Assertions.assertEquals(expected.getDisplayName(), actual.getDisplayName(), "Execution display name - " + actual.getProviderId());
Assertions.assertEquals(expected.getConfigurable(), actual.getConfigurable(), "Execution configurable - " + actual.getProviderId());
Assertions.assertEquals(expected.getProviderId(), actual.getProviderId(), "Execution provider id - " + actual.getProviderId());
Assertions.assertEquals(expected.getLevel(), actual.getLevel(), "Execution level - " + actual.getProviderId());
Assertions.assertEquals(expected.getIndex(), actual.getIndex(), "Execution index - " + actual.getProviderId());
Assertions.assertEquals(expected.getPriority(), actual.getPriority(), "Execution priority - " + actual.getProviderId());
Assertions.assertEquals(expected.getAuthenticationFlow(), actual.getAuthenticationFlow(), "Execution authentication flow - " + actual.getProviderId());
Assertions.assertEquals(expected.getRequirementChoices(), actual.getRequirementChoices(), "Execution requirement choices - " + actual.getProviderId());
}
void compareExecution(AuthenticationExecutionExportRepresentation expected, AuthenticationExecutionExportRepresentation actual) {
Assertions.assertEquals(expected.getFlowAlias(), actual.getFlowAlias(), "Execution flowAlias - " + actual.getFlowAlias());
Assertions.assertEquals(expected.getAuthenticator(), actual.getAuthenticator(), "Execution authenticator - " + actual.getAuthenticator());
Assertions.assertEquals(expected.isUserSetupAllowed(), actual.isUserSetupAllowed(), "Execution userSetupAllowed - " + actual.getAuthenticator());
Assertions.assertEquals(expected.isAuthenticatorFlow(), actual.isAuthenticatorFlow(), "Execution authenticatorFlow - " + actual.getAuthenticator());
Assertions.assertEquals(expected.getAuthenticatorConfig(), actual.getAuthenticatorConfig(), "Execution authenticatorConfig - " + actual.getAuthenticatorConfig());
Assertions.assertEquals(expected.getPriority(), actual.getPriority(), "Execution priority - " + actual.getAuthenticator());
Assertions.assertEquals(expected.getRequirement(), actual.getRequirement(), "Execution requirement - " + actual.getAuthenticator());
}
void compareExecutions(List<AuthenticationExecutionExportRepresentation> expected, List<AuthenticationExecutionExportRepresentation> actual) {
Assertions.assertNotNull(actual, "Executions should not be null");
Assertions.assertEquals(expected.size(), actual.size(), "Size");
for (int i = 0; i < expected.size(); i++) {
compareExecution(expected.get(i), actual.get(i));
}
}
void compareFlows(AuthenticationFlowRepresentation expected, AuthenticationFlowRepresentation actual) {
Assertions.assertEquals(expected.getAlias(), actual.getAlias(), "Flow alias");
Assertions.assertEquals(expected.getDescription(), actual.getDescription(), "Flow description");
Assertions.assertEquals(expected.getProviderId(), actual.getProviderId(), "Flow providerId");
Assertions.assertEquals(expected.isTopLevel(), actual.isTopLevel(), "Flow top level");
Assertions.assertEquals(expected.isBuiltIn(), actual.isBuiltIn(), "Flow built-in");
List<AuthenticationExecutionExportRepresentation> expectedExecs = expected.getAuthenticationExecutions();
List<AuthenticationExecutionExportRepresentation> actualExecs = actual.getAuthenticationExecutions();
if (expectedExecs == null) {
Assertions.assertTrue(actualExecs == null || actualExecs.size() == 0, "Executions should be null or empty");
} else {
compareExecutions(expectedExecs, actualExecs);
}
}
AuthenticationFlowRepresentation newFlow(String alias, String description,
String providerId, boolean topLevel, boolean builtIn) {
AuthenticationFlowRepresentation flow = new AuthenticationFlowRepresentation();
flow.setAlias(alias);
flow.setDescription(description);
flow.setProviderId(providerId);
flow.setTopLevel(topLevel);
flow.setBuiltIn(builtIn);
return flow;
}
AuthenticationExecutionInfoRepresentation newExecInfo(String displayName, String providerId, Boolean configurable,
int level, int index, String requirement, Boolean authFlow, String[] choices,
int priority) {
AuthenticationExecutionInfoRepresentation execution = new AuthenticationExecutionInfoRepresentation();
execution.setRequirement(requirement);
execution.setDisplayName(displayName);
execution.setConfigurable(configurable);
execution.setProviderId(providerId);
execution.setLevel(level);
execution.setIndex(index);
execution.setAuthenticationFlow(authFlow);
execution.setPriority(priority);
if (choices != null) {
execution.setRequirementChoices(Arrays.asList(choices));
}
return execution;
}
void addExecInfo(List<AuthenticationExecutionInfoRepresentation> target, String displayName, String providerId, Boolean configurable,
int level, int index, String requirement, Boolean authFlow, String[] choices, int priority) {
AuthenticationExecutionInfoRepresentation exec = newExecInfo(displayName, providerId, configurable, level, index, requirement, authFlow, choices, priority);
target.add(exec);
}
AuthenticatorConfigRepresentation newConfig(String alias, String[] keyvalues) {
AuthenticatorConfigRepresentation config = new AuthenticatorConfigRepresentation();
config.setAlias(alias);
if (keyvalues == null) {
throw new IllegalArgumentException("keyvalues == null");
}
if (keyvalues.length % 2 != 0) {
throw new IllegalArgumentException("keyvalues should have even number of elements");
}
LinkedHashMap<String, String> params = new LinkedHashMap<>();
for (int i = 0; i < keyvalues.length; i += 2) {
params.put(keyvalues[i], keyvalues[i + 1]);
}
config.setConfig(params);
return config;
}
String createFlow(AuthenticationFlowRepresentation flowRep) {
return createFlow(flowRep, true);
}
String createFlow(AuthenticationFlowRepresentation flowRep, boolean autoDelete) {
Response response = authMgmtResource.createFlow(flowRep);
Assertions.assertEquals(201, response.getStatus());
response.close();
String flowId = ApiUtil.getCreatedId(response);
if (autoDelete) {
managedRealm.cleanup().add(r -> r.flows().deleteFlow(flowId));
}
AdminEventAssertion.assertSuccess(adminEvents.poll())
.operationType(OperationType.CREATE)
.resourcePath(AdminEventPaths.authFlowPath(flowId))
.representation(flowRep)
.resourceType(ResourceType.AUTH_FLOW);
return flowId;
}
}

View File

@ -15,10 +15,13 @@
* limitations under the License.
*/
package org.keycloak.testsuite.admin.authentication;
package org.keycloak.tests.admin.authentication;
import org.junit.Assert;
import org.junit.Test;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.authentication.AuthenticationFlow;
import org.keycloak.authentication.authenticators.browser.UsernameFormFactory;
import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorFactory;
@ -29,23 +32,22 @@ import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentatio
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.AssertAdminEvents;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.tests.utils.admin.ApiUtil;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItems;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@KeycloakIntegrationTest
public class ExecutionTest extends AbstractAuthenticationTest {
// KEYCLOAK-7975
@ -55,9 +57,9 @@ public class ExecutionTest extends AbstractAuthenticationTest {
HashMap<String, Object> params = new HashMap<>();
params.put("newName", "new-browser-flow");
Response response = authMgmtResource.copy("browser", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
try {
Assert.assertEquals("Copy flow", 201, response.getStatus());
Assertions.assertEquals(201, response.getStatus(), "Copy flow");
} finally {
response.close();
}
@ -65,7 +67,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// create Conditional OTP Form execution
params.put("provider", "auth-conditional-otp-form");
authMgmtResource.addExecution("new-browser-flow", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("new-browser-flow"), params, ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionPath("new-browser-flow"), params, ResourceType.AUTH_EXECUTION);
List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions("new-browser-flow");
AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider("auth-conditional-otp-form", executionReps);
@ -108,7 +110,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
params.put("provider", "idp-review-profile");
try {
authMgmtResource.addExecution("browser", params);
Assert.fail("add execution to built-in flow should fail");
Assertions.fail("add execution to built-in flow should fail");
} catch (BadRequestException expected) {
// Expected
}
@ -116,7 +118,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// try add execution to not-existent flow
try {
authMgmtResource.addExecution("not-existent", params);
Assert.fail("add execution to not-existent flow should fail");
Assertions.fail("add execution to not-existent flow should fail");
} catch (BadRequestException expected) {
// Expected
}
@ -124,9 +126,9 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// copy built-in flow so we get a new editable flow
params.put("newName", "Copy-of-browser");
Response response = authMgmtResource.copy("browser", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
try {
Assert.assertEquals("Copy flow", 201, response.getStatus());
Assertions.assertEquals(201, response.getStatus(), "Copy flow");
} finally {
response.close();
}
@ -135,7 +137,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
params.put("provider", "test-execution");
try {
authMgmtResource.addExecution("CopyOfBrowser", params);
Assert.fail("add execution with inexistent provider should fail");
Assertions.fail("add execution with inexistent provider should fail");
} catch(BadRequestException expected) {
// Expected
}
@ -143,34 +145,34 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// add execution - should succeed
params.put("provider", "idp-review-profile");
authMgmtResource.addExecution("Copy-of-browser", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("Copy-of-browser"), params, ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionPath("Copy-of-browser"), params, ResourceType.AUTH_EXECUTION);
// check execution was added
List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions("Copy-of-browser");
AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider("idp-review-profile", executionReps);
Assert.assertNotNull("idp-review-profile added", exec);
Assertions.assertNotNull(exec, "idp-review-profile added");
// we'll need auth-cookie later
AuthenticationExecutionInfoRepresentation authCookieExec = findExecutionByProvider("auth-cookie", executionReps);
AuthenticationExecutionInfoRepresentation previousExecution = findPreviousExecution(exec, executionReps);
Assert.assertNotNull(previousExecution);
Assertions.assertNotNull(previousExecution);
compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 5, DISABLED, null, new String[]{REQUIRED, ALTERNATIVE,DISABLED}, previousExecution.getPriority() + 1), exec);
// remove execution
authMgmtResource.removeExecution(exec.getId());
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authExecutionPath(exec.getId()), ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.authExecutionPath(exec.getId()), ResourceType.AUTH_EXECUTION);
// check execution was removed
executionReps = authMgmtResource.getExecutions("Copy-of-browser");
exec = findExecutionByProvider("idp-review-profile", executionReps);
Assert.assertNull("idp-review-profile removed", exec);
Assertions.assertNull(exec, "idp-review-profile removed");
// now add the execution again using a different method and representation
// delete auth-cookie
authMgmtResource.removeExecution(authCookieExec.getId());
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authExecutionPath(authCookieExec.getId()), ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.authExecutionPath(authCookieExec.getId()), ResourceType.AUTH_EXECUTION);
AuthenticationExecutionRepresentation rep = new AuthenticationExecutionRepresentation();
rep.setPriority(10);
@ -180,7 +182,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// Should fail - missing parent flow
response = authMgmtResource.addExecution(rep);
try {
Assert.assertEquals("added execution missing parent flow", 400, response.getStatus());
Assertions.assertEquals(400, response.getStatus(), "added execution missing parent flow");
} finally {
response.close();
}
@ -189,7 +191,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
rep.setParentFlow("not-existent-id");
response = authMgmtResource.addExecution(rep);
try {
Assert.assertEquals("added execution missing parent flow", 400, response.getStatus());
Assertions.assertEquals(400, response.getStatus(), "added execution missing parent flow");
} finally {
response.close();
}
@ -199,7 +201,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
rep.setParentFlow(browserFlow.getId());
response = authMgmtResource.addExecution(rep);
try {
Assert.assertEquals("added execution to builtin flow", 400, response.getStatus());
Assertions.assertEquals(400, response.getStatus(), "added execution to builtin flow");
} finally {
response.close();
}
@ -210,18 +212,17 @@ public class ExecutionTest extends AbstractAuthenticationTest {
rep.setParentFlow(flow.getId());
// add execution - should succeed
response = authMgmtResource.addExecution(rep);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AssertAdminEvents.isExpectedPrefixFollowedByUuid(AdminEventPaths.authMgmtBasePath() + "/executions"), rep, ResourceType.AUTH_EXECUTION);
try {
Assert.assertEquals("added execution", 201, response.getStatus());
} finally {
response.close();
}
String createdId = ApiUtil.getCreatedId(authMgmtResource.addExecution(rep));
AdminEventAssertion.assertSuccess(adminEvents.poll())
.operationType(OperationType.CREATE)
.resourcePath(AdminEventPaths.authMgmtBasePath(), "executions", createdId)
.representation(rep)
.resourceType(ResourceType.AUTH_EXECUTION);
// check execution was added
List<AuthenticationExecutionInfoRepresentation> executions = authMgmtResource.getExecutions("Copy-of-browser");
exec = findExecutionByProvider("auth-cookie", executions);
Assert.assertNotNull("auth-cookie added", exec);
Assertions.assertNotNull(exec, "auth-cookie added");
// Note: there is no checking in addExecution if requirement is one of requirementChoices
// Thus we can have OPTIONAL which is neither ALTERNATIVE, nor DISABLED
@ -235,14 +236,14 @@ public class ExecutionTest extends AbstractAuthenticationTest {
List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions("browser");
AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider("auth-cookie", executionReps);
Assert.assertEquals("auth-cookie set to ALTERNATIVE", ALTERNATIVE, exec.getRequirement());
Assert.assertEquals("auth-cookie is first in the flow", exec.getIndex(), 0);
Assertions.assertEquals(ALTERNATIVE, exec.getRequirement(), "auth-cookie set to ALTERNATIVE");
Assertions.assertEquals(exec.getIndex(), 0, "auth-cookie is first in the flow");
// switch from DISABLED to ALTERNATIVE
exec.setRequirement(DISABLED);
exec.setPriority(Integer.MAX_VALUE);
authMgmtResource.updateExecutions("browser", exec);
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authUpdateExecutionPath("browser"), exec, ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authUpdateExecutionPath("browser"), exec, ResourceType.AUTH_EXECUTION);
// make sure the change is visible
executionReps = authMgmtResource.getExecutions("browser");
@ -269,12 +270,12 @@ public class ExecutionTest extends AbstractAuthenticationTest {
Map<String, Object> executionData = new HashMap<>();
executionData.put("provider", ClientIdAndSecretAuthenticator.PROVIDER_ID);
authMgmtResource.addExecution("new-client-flow", executionData);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("new-client-flow"), executionData, ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionPath("new-client-flow"), executionData, ResourceType.AUTH_EXECUTION);
// Check executions of not-existent flow - SHOULD FAIL
try {
authMgmtResource.getExecutions("not-existent");
Assert.fail("Not expected to find executions");
Assertions.fail("Not expected to find executions");
} catch (NotFoundException nfe) {
// Expected
}
@ -282,12 +283,12 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// Check existent executions
List<AuthenticationExecutionInfoRepresentation> executions = authMgmtResource.getExecutions("new-client-flow");
AuthenticationExecutionInfoRepresentation executionRep = findExecutionByProvider(ClientIdAndSecretAuthenticator.PROVIDER_ID, executions);
Assert.assertNotNull(executionRep);
Assertions.assertNotNull(executionRep);
// Update execution with not-existent flow - SHOULD FAIL
try {
authMgmtResource.updateExecutions("not-existent", executionRep);
Assert.fail("Not expected to update execution with not-existent flow");
Assertions.fail("Not expected to update execution with not-existent flow");
} catch (NotFoundException nfe) {
// Expected
}
@ -297,7 +298,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
executionRep2.setId("not-existent");
try {
authMgmtResource.updateExecutions("new-client-flow", executionRep2);
Assert.fail("Not expected to update not-existent execution");
Assertions.fail("Not expected to update not-existent execution");
} catch (NotFoundException nfe) {
// Expected
}
@ -305,27 +306,23 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// Update success
executionRep.setRequirement(ALTERNATIVE);
authMgmtResource.updateExecutions("new-client-flow", executionRep);
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authUpdateExecutionPath("new-client-flow"), executionRep, ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authUpdateExecutionPath("new-client-flow"), executionRep, ResourceType.AUTH_EXECUTION);
// Check updated
executionRep = findExecutionByProvider(ClientIdAndSecretAuthenticator.PROVIDER_ID, authMgmtResource.getExecutions("new-client-flow"));
Assert.assertEquals(ALTERNATIVE, executionRep.getRequirement());
Assertions.assertEquals(ALTERNATIVE, executionRep.getRequirement());
// Remove execution with not-existent ID
try {
authMgmtResource.removeExecution("not-existent");
Assert.fail("Didn't expect to find execution");
Assertions.fail("Didn't expect to find execution");
} catch (NotFoundException nfe) {
// Expected
}
// Successfuly remove execution and flow
authMgmtResource.removeExecution(executionRep.getId());
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authExecutionPath(executionRep.getId()), ResourceType.AUTH_EXECUTION);
AuthenticationFlowRepresentation rep = findFlowByAlias("new-client-flow", authMgmtResource.getFlows());
authMgmtResource.deleteFlow(rep.getId());
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authFlowPath(rep.getId()), ResourceType.AUTH_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.authExecutionPath(executionRep.getId()), ResourceType.AUTH_EXECUTION);
}
@Test
@ -335,31 +332,31 @@ public class ExecutionTest extends AbstractAuthenticationTest {
params.put("newName", newBrowserFlow);
try (Response response = authMgmtResource.copy("browser", params)) {
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
Assert.assertEquals("Copy flow", 201, response.getStatus());
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
Assertions.assertEquals(201, response.getStatus(), "Copy flow");
}
addExecutionCheckReq(newBrowserFlow, UsernameFormFactory.PROVIDER_ID, params, REQUIRED);
addExecutionCheckReq(newBrowserFlow, WebAuthnAuthenticatorFactory.PROVIDER_ID, params, DISABLED);
AuthenticationFlowRepresentation rep = findFlowByAlias(newBrowserFlow, authMgmtResource.getFlows());
Assert.assertNotNull(rep);
Assertions.assertNotNull(rep);
authMgmtResource.deleteFlow(rep.getId());
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authFlowPath(rep.getId()), ResourceType.AUTH_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.authFlowPath(rep.getId()), ResourceType.AUTH_FLOW);
}
private void addExecutionCheckReq(String flow, String providerID, HashMap<String, Object> params, String expectedRequirement) {
params.put("provider", providerID);
authMgmtResource.addExecution(flow, params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionPath(flow), params, ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionPath(flow), params, ResourceType.AUTH_EXECUTION);
List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions(flow);
AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider(providerID, executionReps);
Assert.assertNotNull(exec);
Assert.assertEquals(expectedRequirement, exec.getRequirement());
Assertions.assertNotNull(exec);
Assertions.assertEquals(expectedRequirement, exec.getRequirement());
authMgmtResource.removeExecution(exec.getId());
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authExecutionPath(exec.getId()), ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.authExecutionPath(exec.getId()), ResourceType.AUTH_EXECUTION);
}
}

View File

@ -15,29 +15,7 @@
* limitations under the License.
*/
package org.keycloak.testsuite.admin.authentication;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.admin.client.CreatedResponseUtil;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticatorFactory;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.ContainerAssume;
package org.keycloak.tests.admin.authentication;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ClientErrorException;
@ -46,6 +24,31 @@ import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.CreatedResponseUtil;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticatorFactory;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import org.keycloak.tests.utils.admin.ApiUtil;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@ -56,7 +59,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -67,27 +69,29 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.keycloak.testsuite.util.Matchers.body;
import static org.keycloak.testsuite.util.Matchers.statusCodeIs;
import static org.keycloak.tests.utils.matchers.Matchers.body;
import static org.keycloak.tests.utils.matchers.Matchers.statusCodeIs;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@KeycloakIntegrationTest
public class FlowTest extends AbstractAuthenticationTest {
// KEYCLOAK-3681: Delete top flow doesn't delete all subflows
@Test
public void testRemoveSubflows() {
createFlow(newFlow("Foo", "Foo flow", "generic", true, false));
createFlow(newFlow("Foo", "Foo flow", "generic", true, false), false);
addFlowToParent("Foo", "child");
addFlowToParent("child", "grandchild");
List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
AuthenticationFlowRepresentation found = findFlowByAlias("Foo", flows);
authMgmtResource.deleteFlow(found.getId());
assertAdminEvents.clear();
createFlow(newFlow("Foo", "Foo flow", "generic", true, false));
adminEvents.skipAll();
createFlow(newFlow("Foo", "Foo flow", "generic", true, false), false);
addFlowToParent("Foo", "child");
// Under the old code, this would throw an error because "grandchild"
@ -109,13 +113,11 @@ public class FlowTest extends AbstractAuthenticationTest {
.filter(r -> "child".equals(r.getDisplayName()) && r.getLevel() == 0).findAny().orElse(null);
assertNotNull(childExececution);
authMgmtResource.removeExecution(childExececution.getId());
assertAdminEvents.clear();
adminEvents.skip();
// check subflows were removed and can be re-created
addFlowToParent("Foo", "child");
addFlowToParent("child", "grandchild");
authMgmtResource.deleteFlow(findFlowByAlias("Foo", authMgmtResource.getFlows()).getId());
}
private void addFlowToParent(String parentAlias, String childAlias) {
@ -129,13 +131,13 @@ public class FlowTest extends AbstractAuthenticationTest {
@Test
public void testAddFlowWithRestrictedCharInAlias() {
Response resp = authMgmtResource.createFlow(newFlow("fo]o", "Browser flow", "basic-flow", true, false));
Assert.assertEquals(400, resp.getStatus());
Assertions.assertEquals(400, resp.getStatus());
try {
CreatedResponseUtil.getCreatedId(resp);
Assert.fail("Not expected getCreatedId to success");
Assertions.fail("Not expected getCreatedId to success");
} catch (WebApplicationException wae) {
Assert.assertThat(wae.getMessage(), endsWith("Error: Character ']' not allowed."));
MatcherAssert.assertThat(wae.getMessage(), endsWith("Error: Character ']' not allowed."));
}
}
@ -145,19 +147,19 @@ public class FlowTest extends AbstractAuthenticationTest {
// test that built-in flow cannot be deleted
List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
AuthenticationFlowRepresentation builtInFlow = flows.stream().filter(AuthenticationFlowRepresentation::isBuiltIn).findAny().orElse(null);
Assert.assertNotNull("No built in flow in the realm", builtInFlow);
Assertions.assertNotNull(builtInFlow, "No built in flow in the realm");
try {
authMgmtResource.deleteFlow(builtInFlow.getId());
Assert.fail("deleteFlow should fail for built in flow");
Assertions.fail("deleteFlow should fail for built in flow");
} catch (BadRequestException e) {
OAuth2ErrorRepresentation error = e.getResponse().readEntity(OAuth2ErrorRepresentation.class);
Assert.assertEquals("Can't delete built in flow", error.getError());
Assertions.assertEquals("Can't delete built in flow", error.getError());
}
// try create new flow using alias of already existing flow
Response response = authMgmtResource.createFlow(newFlow("browser", "Browser flow", "basic-flow", true, false));
try {
Assert.assertEquals("createFlow using the alias of existing flow should fail", 409, response.getStatus());
Assertions.assertEquals(409, response.getStatus(), "createFlow using the alias of existing flow should fail");
} finally {
response.close();
}
@ -165,7 +167,7 @@ public class FlowTest extends AbstractAuthenticationTest {
// try create flow without alias
response = authMgmtResource.createFlow(newFlow(null, "Browser flow", "basic-flow", true, false));
try {
Assert.assertEquals("createFlow using the alias of existing flow should fail", 409, response.getStatus());
Assertions.assertEquals(409, response.getStatus(), "createFlow using the alias of existing flow should fail");
} finally {
response.close();
}
@ -173,26 +175,26 @@ public class FlowTest extends AbstractAuthenticationTest {
// create new flow that should succeed
AuthenticationFlowRepresentation newFlow = newFlow("browser-2", "Browser flow", "basic-flow", true, false);
createFlow(newFlow);
createFlow(newFlow, false);
// check that new flow is returned in a children list
flows = authMgmtResource.getFlows();
AuthenticationFlowRepresentation found = findFlowByAlias("browser-2", flows);
Assert.assertNotNull("created flow visible in parent", found);
Assertions.assertNotNull(found, "created flow visible in parent");
compareFlows(newFlow, found);
// check lookup flow with unexistent ID
try {
authMgmtResource.getFlow("id-123-notExistent");
Assert.fail("Not expected to find unexistent flow");
Assertions.fail("Not expected to find unexistent flow");
} catch (NotFoundException nfe) {
// Expected
}
// check that new flow is returned individually
AuthenticationFlowRepresentation found2 = authMgmtResource.getFlow(found.getId());
Assert.assertNotNull("created flow visible directly", found2);
Assertions.assertNotNull(found2, "created flow visible directly");
compareFlows(newFlow, found2);
@ -214,7 +216,7 @@ public class FlowTest extends AbstractAuthenticationTest {
// inexistent parent flow - should fail
try {
authMgmtResource.addExecutionFlow("inexistent-parent-flow-alias", data);
Assert.fail("addExecutionFlow for inexistent parent should have failed");
Assertions.fail("addExecutionFlow for inexistent parent should have failed");
} catch (Exception expected) {
// Expected
}
@ -223,7 +225,7 @@ public class FlowTest extends AbstractAuthenticationTest {
try {
data.put("alias", "browser");
authMgmtResource.addExecutionFlow("browser-2", data);
Assert.fail("addExecutionFlow should have failed as browser flow already exists");
Assertions.fail("addExecutionFlow should have failed as browser flow already exists");
} catch (Exception expected) {
// Expected
}
@ -232,17 +234,17 @@ public class FlowTest extends AbstractAuthenticationTest {
data.put("alias", "SomeFlow");
authMgmtResource.addExecutionFlow("browser-2", data);
authMgmtResource.addExecutionFlow("browser-2", data2);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("browser-2"), data, ResourceType.AUTH_EXECUTION_FLOW);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("browser-2"), data2, ResourceType.AUTH_EXECUTION_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("browser-2"), data, ResourceType.AUTH_EXECUTION_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("browser-2"), data2, ResourceType.AUTH_EXECUTION_FLOW);
// check that new flow is returned in a children list
flows = authMgmtResource.getFlows();
found2 = findFlowByAlias("browser-2", flows);
Assert.assertNotNull("created flow visible in parent", found2);
Assertions.assertNotNull(found2, "created flow visible in parent");
List<AuthenticationExecutionExportRepresentation> execs = found2.getAuthenticationExecutions();
Assert.assertNotNull(execs);
Assert.assertEquals("Size two", 2, execs.size());
Assertions.assertNotNull(execs);
Assertions.assertEquals(2, execs.size(), "Size two");
AuthenticationExecutionExportRepresentation expected = new AuthenticationExecutionExportRepresentation();
expected.setFlowAlias("SomeFlow");
@ -263,17 +265,17 @@ public class FlowTest extends AbstractAuthenticationTest {
// delete non-built-in flow
authMgmtResource.deleteFlow(found.getId());
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authFlowPath(found.getId()), ResourceType.AUTH_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.authFlowPath(found.getId()), ResourceType.AUTH_FLOW);
// check the deleted flow is no longer returned
flows = authMgmtResource.getFlows();
found = findFlowByAlias("browser-2", flows);
Assert.assertNull("flow deleted", found);
Assertions.assertNull(found, "flow deleted");
// Check deleting flow second time will fail
try {
authMgmtResource.deleteFlow("id-123-notExistent");
Assert.fail("Not expected to delete flow, which doesn't exist");
Assertions.fail("Not expected to delete flow, which doesn't exist");
} catch (NotFoundException nfe) {
// Expected
}
@ -287,10 +289,10 @@ public class FlowTest extends AbstractAuthenticationTest {
Runnable assertRemoveFail = () -> {
try {
authMgmtResource.deleteFlow(flowId);
Assert.fail("Not expected to delete flow that is in use.");
Assertions.fail("Not expected to delete flow that is in use.");
} catch (WebApplicationException e) {
OAuth2ErrorRepresentation error = e.getResponse().readEntity(OAuth2ErrorRepresentation.class);
Assert.assertEquals("For more on this error consult the server log.", error.getErrorDescription());
Assertions.assertEquals("For more on this error consult the server log.", error.getErrorDescription());
}
};
@ -347,9 +349,9 @@ public class FlowTest extends AbstractAuthenticationTest {
idp.setProviderId("oidc");
Response response = realmResource.identityProviders().create(idp);
Assert.assertNotNull(ApiUtil.getCreatedId(response));
Assertions.assertNotNull(ApiUtil.getCreatedId(response));
response.close();
getCleanup().addIdentityProviderAlias(idp.getAlias());
managedRealm.cleanup().add(r -> r.identityProviders().get(idp.getAlias()).remove());
IdentityProviderResource idpResource = realmResource.identityProviders().get("idp");
BiConsumer<Supplier<String>, Consumer<String>> assertRemoveFailByIdp =
@ -399,7 +401,7 @@ public class FlowTest extends AbstractAuthenticationTest {
// copy that should succeed
params.put("newName", "Copy of browser");
response = authMgmtResource.copy("browser", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
try {
assertThat("Copy flow", response, statusCodeIs(Status.CREATED));
} finally {
@ -411,8 +413,8 @@ public class FlowTest extends AbstractAuthenticationTest {
AuthenticationFlowRepresentation browser = findFlowByAlias("browser", flows);
AuthenticationFlowRepresentation copyOfBrowser = findFlowByAlias("Copy of browser", flows);
Assert.assertNotNull(browser);
Assert.assertNotNull(copyOfBrowser);
Assertions.assertNotNull(browser);
Assertions.assertNotNull(copyOfBrowser);
// adjust expected values before comparing
browser.setAlias("Copy of browser");
@ -423,7 +425,7 @@ public class FlowTest extends AbstractAuthenticationTest {
// get new flow directly and compare
copyOfBrowser = authMgmtResource.getFlow(copyOfBrowser.getId());
Assert.assertNotNull(copyOfBrowser);
Assertions.assertNotNull(copyOfBrowser);
compareFlows(browser, copyOfBrowser);
authMgmtResource.deleteFlow(copyOfBrowser.getId());
}
@ -434,9 +436,9 @@ public class FlowTest extends AbstractAuthenticationTest {
HashMap<String, Object> params = new HashMap<>();
params.put("newName", "parent");
Response response = authMgmtResource.copy("browser", params);
Assert.assertEquals(201, response.getStatus());
Assertions.assertEquals(201, response.getStatus());
response.close();
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
params = new HashMap<>();
params.put("alias", "child");
@ -445,7 +447,7 @@ public class FlowTest extends AbstractAuthenticationTest {
params.put("type", "basic-flow");
authMgmtResource.addExecutionFlow("parent", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("parent"), params, ResourceType.AUTH_EXECUTION_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("parent"), params, ResourceType.AUTH_EXECUTION_FLOW);
authMgmtResource.deleteFlow(findFlowByAlias("parent", authMgmtResource.getFlows()).getId());
}
@ -460,9 +462,9 @@ public class FlowTest extends AbstractAuthenticationTest {
HashMap<String, Object> params = new HashMap<>();
params.put("newName", "Copy of browser");
Response response = authMgmtResource.copy("browser", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
try {
Assert.assertEquals("Copy flow", 201, response.getStatus());
Assertions.assertEquals(201, response.getStatus(), "Copy flow");
} finally {
response.close();
}
@ -473,9 +475,9 @@ public class FlowTest extends AbstractAuthenticationTest {
//Set a new unique name. Should succeed
testFlow.setAlias("Copy of browser2");
authMgmtResource.updateFlow(testFlow.getId(), testFlow);
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authEditFlowPath(testFlow.getId()), ResourceType.AUTH_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authEditFlowPath(testFlow.getId()), ResourceType.AUTH_FLOW);
flows = authMgmtResource.getFlows();
Assert.assertEquals("Copy of browser2", findFlowByAlias("Copy of browser2", flows).getAlias());
Assertions.assertEquals("Copy of browser2", findFlowByAlias("Copy of browser2", flows).getAlias());
//Create new flow and edit the old one to have the new ones name
AuthenticationFlowRepresentation newFlow = newFlow("New Flow", "Test description", "basic-flow", true, false);
@ -484,7 +486,7 @@ public class FlowTest extends AbstractAuthenticationTest {
flows = authMgmtResource.getFlows();
AuthenticationFlowRepresentation found = findFlowByAlias("New Flow", flows);
Assert.assertNotNull("created flow visible in parent", found);
Assertions.assertNotNull(found, "created flow visible in parent");
compareFlows(newFlow, found);
//try to update old flow with alias that already exists
@ -506,15 +508,15 @@ public class FlowTest extends AbstractAuthenticationTest {
flows = authMgmtResource.getFlows();
//name should be the same for the old Flow
Assert.assertEquals("Copy of browser2", findFlowByAlias("Copy of browser2", flows).getAlias());
Assertions.assertEquals("Copy of browser2", findFlowByAlias("Copy of browser2", flows).getAlias());
//Only update the description
found.setDescription("New description");
authMgmtResource.updateFlow(found.getId(), found);
flows = authMgmtResource.getFlows();
Assert.assertEquals("New description", findFlowByAlias("New Flow", flows).getDescription());
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authEditFlowPath(found.getId()), ResourceType.AUTH_FLOW);
Assertions.assertEquals("New description", findFlowByAlias("New Flow", flows).getDescription());
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authEditFlowPath(found.getId()), ResourceType.AUTH_FLOW);
//Update name and description
found.setAlias("New Flow2");
@ -522,13 +524,12 @@ public class FlowTest extends AbstractAuthenticationTest {
authMgmtResource.updateFlow(found.getId(), found);
flows = authMgmtResource.getFlows();
Assert.assertEquals("New Flow2", findFlowByAlias("New Flow2", flows).getAlias());
Assert.assertEquals("New description2", findFlowByAlias("New Flow2", flows).getDescription());
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authEditFlowPath(found.getId()), ResourceType.AUTH_FLOW);
Assert.assertNull(findFlowByAlias("New Flow", flows));
Assertions.assertEquals("New Flow2", findFlowByAlias("New Flow2", flows).getAlias());
Assertions.assertEquals("New description2", findFlowByAlias("New Flow2", flows).getDescription());
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authEditFlowPath(found.getId()), ResourceType.AUTH_FLOW);
Assertions.assertNull(findFlowByAlias("New Flow", flows));
authMgmtResource.deleteFlow(testFlow.getId());
authMgmtResource.deleteFlow(found.getId());
}
@Test
@ -546,7 +547,7 @@ public class FlowTest extends AbstractAuthenticationTest {
params.put("type", "basic-flow");
authMgmtResource.addExecutionFlow("Parent-Flow", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("Parent-Flow"), params, ResourceType.AUTH_EXECUTION_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("Parent-Flow"), params, ResourceType.AUTH_EXECUTION_FLOW);
executionReps = authMgmtResource.getExecutions("Parent-Flow");
@ -559,7 +560,7 @@ public class FlowTest extends AbstractAuthenticationTest {
try {
authMgmtResource.addExecutionFlow("Parent-Flow", params);
Assert.fail("addExecutionFlow the alias already exist");
Assertions.fail("addExecutionFlow the alias already exist");
} catch (Exception expected) {
// Expected
}
@ -578,19 +579,19 @@ public class FlowTest extends AbstractAuthenticationTest {
found.setDescription("This is another child flow2");
authMgmtResource.updateExecutions("Parent-Flow", found);
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authUpdateExecutionPath("Parent-Flow"), ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authUpdateExecutionPath("Parent-Flow"), ResourceType.AUTH_EXECUTION);
executionReps = authMgmtResource.getExecutions("Parent-Flow");
Assert.assertEquals("Child-Flow2", executionReps.get(0).getDisplayName());
Assert.assertEquals("This is another child flow2", executionReps.get(0).getDescription());
Assertions.assertEquals("Child-Flow2", executionReps.get(0).getDisplayName());
Assertions.assertEquals("This is another child flow2", executionReps.get(0).getDescription());
//edit only description
found.setDescription("This is another child flow3");
authMgmtResource.updateExecutions("Parent-Flow", found);
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authUpdateExecutionPath("Parent-Flow"), ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authUpdateExecutionPath("Parent-Flow"), ResourceType.AUTH_EXECUTION);
executionReps = authMgmtResource.getExecutions("Parent-Flow");
Assert.assertEquals("Child-Flow2", executionReps.get(0).getDisplayName());
Assert.assertEquals("This is another child flow3", executionReps.get(0).getDescription());
Assertions.assertEquals("Child-Flow2", executionReps.get(0).getDisplayName());
Assertions.assertEquals("This is another child flow3", executionReps.get(0).getDescription());
}
@Test
@ -607,7 +608,7 @@ public class FlowTest extends AbstractAuthenticationTest {
params.put("priority", 50);
authMgmtResource.addExecutionFlow("Parent-Flow", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("Parent-Flow"), params, ResourceType.AUTH_EXECUTION_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("Parent-Flow"), params, ResourceType.AUTH_EXECUTION_FLOW);
params.clear();
params.put("alias", "Child-Flow2");
@ -617,7 +618,7 @@ public class FlowTest extends AbstractAuthenticationTest {
params.put("priority", 10);
authMgmtResource.addExecutionFlow("Parent-Flow", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("Parent-Flow"), params, ResourceType.AUTH_EXECUTION_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("Parent-Flow"), params, ResourceType.AUTH_EXECUTION_FLOW);
params.clear();
params.put("alias", "Child-Flow3");
@ -627,16 +628,16 @@ public class FlowTest extends AbstractAuthenticationTest {
params.put("priority", 20);
authMgmtResource.addExecutionFlow("Parent-Flow", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("Parent-Flow"), params, ResourceType.AUTH_EXECUTION_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("Parent-Flow"), params, ResourceType.AUTH_EXECUTION_FLOW);
List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions("Parent-Flow");
// Verify the initial order and priority value
Assert.assertEquals("Child-Flow2", executionReps.get(0).getDisplayName());
Assert.assertEquals(10, executionReps.get(0).getPriority());
Assert.assertEquals("Child-Flow3", executionReps.get(1).getDisplayName());
Assert.assertEquals(20, executionReps.get(1).getPriority());
Assert.assertEquals("Child-Flow1", executionReps.get(2).getDisplayName());
Assert.assertEquals(50, executionReps.get(2).getPriority());
Assertions.assertEquals("Child-Flow2", executionReps.get(0).getDisplayName());
Assertions.assertEquals(10, executionReps.get(0).getPriority());
Assertions.assertEquals("Child-Flow3", executionReps.get(1).getDisplayName());
Assertions.assertEquals(20, executionReps.get(1).getPriority());
Assertions.assertEquals("Child-Flow1", executionReps.get(2).getDisplayName());
Assertions.assertEquals(50, executionReps.get(2).getPriority());
// Move last execution to the beginning
AuthenticationExecutionInfoRepresentation lastToFirst = executionReps.get(2);
@ -645,24 +646,18 @@ public class FlowTest extends AbstractAuthenticationTest {
executionReps = authMgmtResource.getExecutions("Parent-Flow");
// Verify new order and priority
Assert.assertEquals("Child-Flow1", executionReps.get(0).getDisplayName());
Assert.assertEquals(5, executionReps.get(0).getPriority());
Assert.assertEquals("Child-Flow2", executionReps.get(1).getDisplayName());
Assert.assertEquals(10, executionReps.get(1).getPriority());
Assert.assertEquals("Child-Flow3", executionReps.get(2).getDisplayName());
Assert.assertEquals(20, executionReps.get(2).getPriority());
Assertions.assertEquals("Child-Flow1", executionReps.get(0).getDisplayName());
Assertions.assertEquals(5, executionReps.get(0).getPriority());
Assertions.assertEquals("Child-Flow2", executionReps.get(1).getDisplayName());
Assertions.assertEquals(10, executionReps.get(1).getPriority());
Assertions.assertEquals("Child-Flow3", executionReps.get(2).getDisplayName());
Assertions.assertEquals(20, executionReps.get(2).getPriority());
}
@Test
public void failWithLongDescription() throws IOException {
ContainerAssume.assumeAuthServerQuarkus();
AuthenticationFlowRepresentation rep = authMgmtResource.getFlows().stream()
.filter(new Predicate<AuthenticationFlowRepresentation>() {
@Override
public boolean test(AuthenticationFlowRepresentation rep) {
return "docker auth".equals(rep.getAlias());
}
}).findAny().orElse(null);
.filter(rep1 -> "docker auth".equals(rep1.getAlias())).findAny().orElse(null);
assertNotNull(rep);
@ -692,27 +687,27 @@ public class FlowTest extends AbstractAuthenticationTest {
// get a built in flow
List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
AuthenticationFlowRepresentation flow = flows.stream().filter(AuthenticationFlowRepresentation::isBuiltIn).findFirst().orElse(null);
Assert.assertNotNull("There is no builtin flow", flow);
Assertions.assertNotNull(flow, "There is no builtin flow");
// adding an execution should fail
Map<String, Object> data = new HashMap<>();
data.put("provider", "allow-access-authenticator");
BadRequestException e = Assert.assertThrows(BadRequestException.class, () -> authMgmtResource.addExecution(flow.getAlias(), data));
BadRequestException e = Assertions.assertThrows(BadRequestException.class, () -> authMgmtResource.addExecution(flow.getAlias(), data));
OAuth2ErrorRepresentation error = e.getResponse().readEntity(OAuth2ErrorRepresentation.class);
Assert.assertEquals("It is illegal to add execution to a built in flow", error.getError());
Assertions.assertEquals("It is illegal to add execution to a built in flow", error.getError());
// adding a sub-flow should fail as well
e = Assert.assertThrows(BadRequestException.class, () -> addFlowToParent(flow.getAlias(), "child"));
e = Assertions.assertThrows(BadRequestException.class, () -> addFlowToParent(flow.getAlias(), "child"));
error = e.getResponse().readEntity(OAuth2ErrorRepresentation.class);
Assert.assertEquals("It is illegal to add sub-flow to a built in flow", error.getError());
Assertions.assertEquals("It is illegal to add sub-flow to a built in flow", error.getError());
// removing any execution (execution or flow) should fail too
List<AuthenticationExecutionInfoRepresentation> executions = authMgmtResource.getExecutions(flow.getAlias());
Assert.assertNotNull("The builtin flow has no executions", executions);
Assert.assertFalse("The builtin flow has no executions", executions.isEmpty());
e = Assert.assertThrows(BadRequestException.class, () -> authMgmtResource.removeExecution(executions.get(0).getId()));
Assertions.assertNotNull(executions, "The builtin flow has no executions");
Assertions.assertFalse(executions.isEmpty(), "The builtin flow has no executions");
e = Assertions.assertThrows(BadRequestException.class, () -> authMgmtResource.removeExecution(executions.get(0).getId()));
error = e.getResponse().readEntity(OAuth2ErrorRepresentation.class);
Assert.assertEquals("It is illegal to remove execution from a built in flow", error.getError());
Assertions.assertEquals("It is illegal to remove execution from a built in flow", error.getError());
}
@Test
@ -725,7 +720,7 @@ public class FlowTest extends AbstractAuthenticationTest {
}
}
Assert.assertNotNull(existingFlow);
Assertions.assertNotNull(existingFlow);
List<AuthenticationExecutionInfoRepresentation> executions = authMgmtResource.getExecutions(existingFlow.getAlias());
AuthenticationExecutionInfoRepresentation executionWithConfig = null;
@ -736,7 +731,7 @@ public class FlowTest extends AbstractAuthenticationTest {
}
}
Assert.assertNotNull(executionWithConfig);
Assertions.assertNotNull(executionWithConfig);
AuthenticatorConfigRepresentation executionConfig = new AuthenticatorConfigRepresentation();
@ -744,14 +739,14 @@ public class FlowTest extends AbstractAuthenticationTest {
executionConfig.setConfig(Map.of("key", "value"));
try (Response response = authMgmtResource.newExecutionConfig(executionWithConfig.getId(), executionConfig)) {
getCleanup().addAuthenticationConfigId(ApiUtil.getCreatedId(response));
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionConfigPath(executionWithConfig.getId()), executionConfig, ResourceType.AUTHENTICATOR_CONFIG);
managedRealm.cleanup().add(r -> r.flows().removeAuthenticatorConfig(ApiUtil.getCreatedId(response)));
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionConfigPath(executionWithConfig.getId()), executionConfig, ResourceType.AUTHENTICATOR_CONFIG);
}
String newFlowName = "Duplicated of " + DefaultAuthenticationFlows.BROWSER_FLOW;
Map<String, Object> copyFlowParams = Map.of("newName", newFlowName);
authMgmtResource.copy(existingFlow.getAlias(), copyFlowParams).close();
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), copyFlowParams, ResourceType.AUTH_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), copyFlowParams, ResourceType.AUTH_FLOW);
AuthenticationFlowRepresentation newFlow = null;
@ -776,7 +771,7 @@ public class FlowTest extends AbstractAuthenticationTest {
assertFalse(newExecutionConfigIds.isEmpty());
for (String executionConfigId : newExecutionConfigIds) {
Assert.assertFalse("Execution config not duplicated", existingExecutionConfigIds.contains(executionConfigId));
Assertions.assertFalse(existingExecutionConfigIds.contains(executionConfigId), "Execution config not duplicated");
}
}
}

View File

@ -15,15 +15,16 @@
* limitations under the License.
*/
package org.keycloak.testsuite.admin.authentication;
package org.keycloak.tests.admin.authentication;
import org.junit.Assert;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.testsuite.util.KerberosUtils;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.tests.utils.KerberosUtils;
import java.util.ArrayList;
import java.util.Collections;
@ -35,6 +36,7 @@ import java.util.List;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@KeycloakIntegrationTest
public class InitialFlowsTest extends AbstractAuthenticationTest {
private HashMap<String, AuthenticatorConfigRepresentation> configs = new HashMap<>();
@ -85,7 +87,7 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
private void compareExecutionsInfo(List<AuthenticationExecutionInfoRepresentation> expected, List<AuthenticationExecutionInfoRepresentation> actual) {
Assert.assertEquals("Executions count", expected.size(), actual.size());
Assertions.assertEquals(expected.size(), actual.size(), "Executions count");
Iterator<AuthenticationExecutionInfoRepresentation> it1 = expected.iterator();
Iterator<AuthenticationExecutionInfoRepresentation> it2 = actual.iterator();
while (it1.hasNext()) {
@ -108,8 +110,8 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
if (cfg1 == null && cfg2 == null) {
return;
}
Assert.assertEquals("Execution configuration alias", cfg1.getAlias(), cfg2.getAlias());
Assert.assertEquals("Execution configuration params", cfg1.getConfig(), cfg2.getConfig());
Assertions.assertEquals(cfg1.getAlias(), cfg2.getAlias(), "Execution configuration alias");
Assertions.assertEquals(cfg1.getConfig(), cfg2.getConfig(), "Execution configuration params");
}
private List<FlowExecutions> orderAlphabetically(List<FlowExecutions> result) {

View File

@ -15,20 +15,20 @@
* limitations under the License.
*/
package org.keycloak.testsuite.admin.authentication;
package org.keycloak.tests.admin.authentication;
import org.junit.Test;
import jakarta.ws.rs.NotFoundException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assertions;
import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory;
import org.keycloak.authentication.forms.RegistrationRecaptcha;
import org.keycloak.authentication.forms.RegistrationRecaptchaEnterprise;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation;
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.util.KerberosUtils;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.tests.utils.Assert;
import org.keycloak.tests.utils.KerberosUtils;
import jakarta.ws.rs.NotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -37,25 +37,26 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@KeycloakIntegrationTest
public class ProvidersTest extends AbstractAuthenticationTest {
@Test
public void testFormProviders() {
List<Map<String, Object>> result = authMgmtResource.getFormProviders();
Assert.assertNotNull("null result", result);
Assert.assertEquals("size", 1, result.size());
Assertions.assertNotNull(result, "null result");
Assertions.assertEquals(1, result.size(), "size");
Map<String, Object> item = result.get(0);
Assert.assertEquals("id", "registration-page-form", item.get("id"));
Assert.assertEquals("displayName", "Registration Page", item.get("displayName"));
Assert.assertEquals("description", "This is the controller for the registration page", item.get("description"));
Assertions.assertEquals("registration-page-form", item.get("id"), "id");
Assertions.assertEquals("Registration Page", item.get("displayName"), "displayName");
Assertions.assertEquals("This is the controller for the registration page", item.get("description"), "description");
}
@Test
@ -85,12 +86,6 @@ public class ProvidersTest extends AbstractAuthenticationTest {
"Validates client based on signed JWT issued by client and signed with the Client private key", false);
addClientAuthenticatorProviderInfo(expected, "client-secret", "Client Id and Secret", "Validates client based on 'client_id' and " +
"'client_secret' sent either in request parameters or in 'Authorization: Basic' header", true);
addClientAuthenticatorProviderInfo(expected, "testsuite-client-id-required", "Signed Jwt", "Validates client based on signed JWT issued by client " +
"and signed with the Client private key", false);
addClientAuthenticatorProviderInfo(expected, "testsuite-client-passthrough", "Testsuite Dummy Client Validation", "Testsuite dummy authenticator, " +
"which automatically authenticates hardcoded client (like 'test-app' )", false);
addClientAuthenticatorProviderInfo(expected, "testsuite-client-dummy", "Testsuite ClientId Dummy",
"Dummy client authenticator, which authenticates the client with clientId only", false);
addClientAuthenticatorProviderInfo(expected, "client-x509", "X509 Certificate",
"Validates client based on a X509 Certificate", false);
addClientAuthenticatorProviderInfo(expected, "client-secret-jwt", "Signed Jwt with Client Secret",
@ -102,16 +97,10 @@ public class ProvidersTest extends AbstractAuthenticationTest {
@Test
public void testPerClientConfigDescriptions() {
Map<String, List<ConfigPropertyRepresentation>> configs = authMgmtResource.getPerClientConfigDescription();
Assert.assertTrue(configs.containsKey("client-jwt"));
Assert.assertTrue(configs.containsKey("client-secret"));
Assert.assertTrue(configs.containsKey("testsuite-client-passthrough"));
Assert.assertTrue(configs.get("client-jwt").isEmpty());
Assert.assertTrue(configs.get("client-secret").isEmpty());
List<ConfigPropertyRepresentation> cfg = configs.get("testsuite-client-passthrough");
Assert.assertProviderConfigProperty(cfg.get(0), "passthroughauth.foo", "Foo Property", null,
"Foo Property of this authenticator, which does nothing", "String");
Assert.assertProviderConfigProperty(cfg.get(1), "passthroughauth.bar", "Bar Property", null,
"Bar Property of this authenticator, which does nothing", "boolean");
Assertions.assertTrue(configs.containsKey("client-jwt"));
Assertions.assertTrue(configs.containsKey("client-secret"));
Assertions.assertTrue(configs.get("client-jwt").isEmpty());
Assertions.assertTrue(configs.get("client-secret").isEmpty());
}
@Test
@ -119,16 +108,16 @@ public class ProvidersTest extends AbstractAuthenticationTest {
// Try some not-existent provider
try {
authMgmtResource.getAuthenticatorConfigDescription("not-existent");
Assert.fail("Don't expected to find provider 'not-existent'");
Assertions.fail("Don't expected to find provider 'not-existent'");
} catch (NotFoundException nfe) {
// Expected
}
AuthenticatorConfigInfoRepresentation infoRep = authMgmtResource.getAuthenticatorConfigDescription(IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID);
Assert.assertEquals("Create User If Unique", infoRep.getName());
Assert.assertEquals(IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, infoRep.getProviderId());
Assert.assertEquals("Detect if there is existing Keycloak account with same email like identity provider. If no, create new user", infoRep.getHelpText());
Assert.assertEquals(1, infoRep.getProperties().size());
Assertions.assertEquals("Create User If Unique", infoRep.getName());
Assertions.assertEquals(IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, infoRep.getProviderId());
Assertions.assertEquals("Detect if there is existing Keycloak account with same email like identity provider. If no, create new user", infoRep.getHelpText());
Assertions.assertEquals(1, infoRep.getProperties().size());
Assert.assertProviderConfigProperty(infoRep.getProperties().get(0), "require.password.update.after.registration", "Require Password Update After Registration",
null, "If this option is true and new user is successfully imported from Identity Provider to Keycloak (there is no duplicated email or username detected in Keycloak DB), then this user is required to update his password",
"boolean");
@ -147,9 +136,6 @@ public class ProvidersTest extends AbstractAuthenticationTest {
"Validates a OTP on a separate OTP form. Only shown if required based on the configured conditions.");
addProviderInfo(result, "auth-cookie", "Cookie", "Validates the SSO cookie set by the auth server.");
addProviderInfo(result, "auth-otp-form", "OTP Form", "Validates a OTP on a separate OTP form.");
if (ProfileAssume.isFeatureEnabled(Profile.Feature.SCRIPTS)) {
addProviderInfo(result, "auth-script-based", "Script", "Script based authentication. Allows to define custom authentication logic via JavaScript.");
}
String kerberosHelpMessage = (KerberosUtils.isKerberosSupportExpected())
? "Initiates the SPNEGO protocol. Most often used with Kerberos."
: "DISABLED. Please enable Kerberos feature and make sure Kerberos available in your platform. Initiates the SPNEGO protocol. Most often used with Kerberos.";
@ -166,8 +152,6 @@ public class ProvidersTest extends AbstractAuthenticationTest {
addProviderInfo(result, "direct-grant-validate-username", "Username Validation",
"Validates the username supplied as a 'username' form parameter in direct grant request");
addProviderInfo(result, "docker-http-basic-authenticator", "Docker Authenticator", "Uses HTTP Basic authentication to validate docker users, returning a docker error token on auth failure");
addProviderInfo(result, "expected-param-authenticator", "TEST: Expected Parameter",
"You will be approved if you send query string parameter 'foo' with expected value.");
addProviderInfo(result, "http-basic-authenticator", "HTTP Basic Authentication", "Validates username and password from Authorization HTTP header");
addProviderInfo(result, "identity-provider-redirector", "Identity Provider Redirector", "Redirects to default Identity Provider or Identity Provider specified with kc_idp_hint query parameter");
addProviderInfo(result, "idp-auto-link", "Automatically set existing user", "Automatically set existing user to authentication context without any verification");
@ -182,24 +166,11 @@ public class ProvidersTest extends AbstractAuthenticationTest {
"User reviews and updates profile data retrieved from Identity Provider in the displayed form");
addProviderInfo(result, "idp-username-password-form", "Username Password Form for identity provider reauthentication",
"Validates a password from login form. Username may be already known from identity provider authentication");
addProviderInfo(result, "push-button-authenticator", "TEST: Button Login",
"Just press the button to login.");
addProviderInfo(result, "reset-credential-email", "Send Reset Email", "Send email to user and wait for response.");
addProviderInfo(result, "reset-credentials-choose-user", "Choose User", "Choose a user to reset credentials for");
addProviderInfo(result, "reset-otp", "Reset OTP", "Removes existing OTP configurations (if chosen) and sets the 'Configure OTP' required action.");
addProviderInfo(result, "reset-password", "Reset Password", "Sets the Update Password required action if execution is REQUIRED. " +
"Will also set it if execution is OPTIONAL and the password is currently configured for it.");
addProviderInfo(result, "testsuite-dummy-click-through", "Testsuite Dummy Click Thru",
"Testsuite Dummy authenticator. User needs to click through the page to continue.");
addProviderInfo(result, "testsuite-dummy-passthrough", "Testsuite Dummy Pass Thru",
"Testsuite Dummy authenticator. Just passes through and is hardcoded to a specific user");
addProviderInfo(result, "testsuite-dummy-registration", "Testsuite Dummy Pass Thru",
"Testsuite Dummy authenticator. Just passes through and is hardcoded to a specific user");
addProviderInfo(result, "set-client-note-authenticator", "Set Client Note Authenticator", "Set client note of specified name with the specified value to the authenticationSession.");
addProviderInfo(result, "testsuite-username", "Testsuite Username Only",
"Testsuite Username authenticator. Username parameter sets username");
addProviderInfo(result, "test-suite-fire-error-event", "Fire Error Event",
"Testsuite Error event firer authenticator.");
addProviderInfo(result, "webauthn-authenticator", "WebAuthn Authenticator", "Authenticator for WebAuthn. Usually used for WebAuthn two-factor authentication");
addProviderInfo(result, "webauthn-authenticator-passwordless", "WebAuthn Passwordless Authenticator", "Authenticator for Passwordless WebAuthn authentication");
@ -213,8 +184,6 @@ public class ProvidersTest extends AbstractAuthenticationTest {
"Executes the current flow only if authenticators are configured");
addProviderInfo(result, "conditional-user-attribute", "Condition - user attribute",
"Flow is executed only if the user attribute exists and has the expected value");
addProviderInfo(result, "set-attribute", "Set user attribute",
"Set a user attribute");
addProviderInfo(result, "idp-detect-existing-broker-user", "Detect existing broker user",
"Detect if there is an existing Keycloak account with same email like identity provider. If no, throw an error.");
@ -229,9 +198,6 @@ public class ProvidersTest extends AbstractAuthenticationTest {
addProviderInfo(result, "user-session-limits", "User session count limiter",
"Configures how many concurrent sessions a single user is allowed to create for this realm and/or client");
addProviderInfo(result, "custom-callback-authenticator", "Custom callback Factory",
"Used for testing purposes of Callback factory");
addProviderInfo(result, "idp-add-organization-member", "Organization Member Onboard", "Adds a federated user as a member of an organization");
addProviderInfo(result, "organization", "Organization Identity-First Login", "If organizations are enabled, automatically redirects users to the corresponding identity provider.");
addProviderInfo(result, "conditional-sub-flow-executed", "Condition - sub-flow executed", "Condition to evaluate if a sub-flow was executed successfully during the authentication process");
@ -247,7 +213,7 @@ public class ProvidersTest extends AbstractAuthenticationTest {
}
private void compareProviders(List<Map<String, Object>> expected, List<Map<String, Object>> actual) {
Assert.assertEquals("Providers count", expected.size(), actual.size());
Assertions.assertEquals(expected.size(), actual.size(), "Providers count");
// compare ignoring list and map impl types
assertThat(normalizeResults(actual), is(normalizeResults(expected)));
}

View File

@ -15,22 +15,25 @@
* limitations under the License.
*/
package org.keycloak.testsuite.admin.authentication;
package org.keycloak.tests.admin.authentication;
import org.junit.Assert;
import org.junit.Test;
import jakarta.ws.rs.BadRequestException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import jakarta.ws.rs.BadRequestException;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@KeycloakIntegrationTest
public class RegistrationFlowTest extends AbstractAuthenticationTest {
@Test
@ -46,20 +49,20 @@ public class RegistrationFlowTest extends AbstractAuthenticationTest {
data.put("description", "registrationForm2 flow");
data.put("provider", "registration-page-form");
authMgmtResource.addExecutionFlow("registration2", data);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("registration2"), data, ResourceType.AUTH_EXECUTION_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("registration2"), data, ResourceType.AUTH_EXECUTION_FLOW);
// Should fail to add execution under top level flow
Map<String, Object> data2 = new HashMap<>();
data2.put("provider", "registration-password-action");
try {
authMgmtResource.addExecution("registration2", data2);
Assert.fail("Not expected to add execution of type 'registration-password-action' under top flow");
Assertions.fail("Not expected to add execution of type 'registration-password-action' under top flow");
} catch (BadRequestException bre) {
}
// Should success to add execution under form flow
authMgmtResource.addExecution("registrationForm2", data2);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("registrationForm2"), data2, ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authAddExecutionPath("registrationForm2"), data2, ResourceType.AUTH_EXECUTION);
}
// TODO: More type-safety instead of passing generic maps

View File

@ -15,19 +15,27 @@
* limitations under the License.
*/
package org.keycloak.testsuite.admin.authentication;
package org.keycloak.tests.admin.authentication;
import org.junit.Assert;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.representations.idm.RequiredActionConfigInfoRepresentation;
import org.keycloak.representations.idm.RequiredActionConfigRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
import org.keycloak.testframework.annotations.InjectAdminEvents;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.events.AdminEvents;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.server.KeycloakServerConfig;
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
import org.keycloak.testsuite.actions.DummyConfigurableRequiredActionFactory;
import org.keycloak.testsuite.actions.DummyRequiredActionFactory;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.NotFoundException;
@ -41,13 +49,14 @@ import java.util.Map;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@KeycloakIntegrationTest(config = RequiredActionsTest.CustomProvidersServerConfig.class)
public class RequiredActionsTest extends AbstractAuthenticationTest {
@Override
protected boolean removeVerifyProfileAtImport() {
// do not remove verify profile action for this test
return false;
}
@InjectRealm
ManagedRealm managedRealm;
@InjectAdminEvents
AdminEvents adminEvents;
@Test
public void testRequiredActions() {
@ -70,22 +79,22 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
RequiredActionProviderRepresentation forUpdate = newRequiredAction("VERIFY_EMAIL", "Verify Email", false, false, null);
authMgmtResource.updateRequiredAction(forUpdate.getAlias(), forUpdate);
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(forUpdate.getAlias()), ResourceType.REQUIRED_ACTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(forUpdate.getAlias()), ResourceType.REQUIRED_ACTION);
result = authMgmtResource.getRequiredActions();
RequiredActionProviderRepresentation updated = findRequiredActionByAlias(forUpdate.getAlias(), result);
Assert.assertNotNull("Required Action still there", updated);
Assertions.assertNotNull(updated, "Required Action still there");
compareRequiredAction(forUpdate, updated);
forUpdate.setConfig(Collections.<String, String>emptyMap());
authMgmtResource.updateRequiredAction(forUpdate.getAlias(), forUpdate);
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(forUpdate.getAlias()), ResourceType.REQUIRED_ACTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(forUpdate.getAlias()), ResourceType.REQUIRED_ACTION);
result = authMgmtResource.getRequiredActions();
updated = findRequiredActionByAlias(forUpdate.getAlias(), result);
Assert.assertNotNull("Required Action still there", updated);
Assertions.assertNotNull(updated, "Required Action still there");
compareRequiredAction(forUpdate, updated);
}
@ -95,16 +104,16 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
// Dummy RequiredAction is not registered in the realm and WebAuthn actions
List<RequiredActionProviderSimpleRepresentation> result = authMgmtResource.getUnregisteredRequiredActions();
Assert.assertEquals(2, result.size());
Assertions.assertEquals(2, result.size());
RequiredActionProviderSimpleRepresentation action = result.stream().filter(
a -> a.getProviderId().equals(DummyRequiredActionFactory.PROVIDER_ID)
).findFirst().get();
Assert.assertEquals(DummyRequiredActionFactory.PROVIDER_ID, action.getProviderId());
Assert.assertEquals("Dummy Action", action.getName());
Assertions.assertEquals(DummyRequiredActionFactory.PROVIDER_ID, action.getProviderId());
Assertions.assertEquals("Dummy Action", action.getName());
// Register it
authMgmtResource.registerRequiredAction(action);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authMgmtBasePath() + "/register-required-action", action, ResourceType.REQUIRED_ACTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authMgmtBasePath() + "/register-required-action", action, ResourceType.REQUIRED_ACTION);
// Try to register 2nd time
try {
@ -119,7 +128,7 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
requiredAction.setProviderId("not-existent");
try {
authMgmtResource.registerRequiredAction(requiredAction);
Assert.fail("Didn't expect to register requiredAction with providerId: 'not-existent'");
Assertions.fail("Didn't expect to register requiredAction with providerId: 'not-existent'");
} catch (Exception ex) {
// Expected
}
@ -127,7 +136,7 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
// Try to find not-existent action - should fail
try {
authMgmtResource.getRequiredAction("not-existent");
Assert.fail("Didn't expect to find requiredAction of alias 'not-existent'");
Assertions.fail("Didn't expect to find requiredAction of alias 'not-existent'");
} catch (NotFoundException nfe) {
// Expected
}
@ -138,12 +147,12 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
true, false, Collections.<String, String>emptyMap()));
// Confirm the registered priority - should be N + 1
Assert.assertEquals(lastPriority + 1, rep.getPriority());
Assertions.assertEquals(lastPriority + 1, rep.getPriority());
// Update not-existent - should fail
try {
authMgmtResource.updateRequiredAction("not-existent", rep);
Assert.fail("Not expected to update not-existent requiredAction");
Assertions.fail("Not expected to update not-existent requiredAction");
} catch (NotFoundException nfe) {
// Expected
}
@ -151,21 +160,21 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
// Update (set it as defaultAction)
rep.setDefaultAction(true);
authMgmtResource.updateRequiredAction(DummyRequiredActionFactory.PROVIDER_ID, rep);
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(rep.getAlias()), rep, ResourceType.REQUIRED_ACTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(rep.getAlias()), rep, ResourceType.REQUIRED_ACTION);
compareRequiredAction(rep, newRequiredAction(DummyRequiredActionFactory.PROVIDER_ID, "Dummy Action",
true, true, Collections.<String, String>emptyMap()));
// Remove unexistent - should fail
try {
authMgmtResource.removeRequiredAction("not-existent");
Assert.fail("Not expected to remove not-existent requiredAction");
Assertions.fail("Not expected to remove not-existent requiredAction");
} catch (NotFoundException nfe) {
// Expected
}
// Remove success
authMgmtResource.removeRequiredAction(DummyRequiredActionFactory.PROVIDER_ID);
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authRequiredActionPath(rep.getAlias()), ResourceType.REQUIRED_ACTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.authRequiredActionPath(rep.getAlias()), ResourceType.REQUIRED_ACTION);
}
@ -176,9 +185,9 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
// query configurable properties
RequiredActionConfigInfoRepresentation requiredActionConfigDescription = authMgmtResource.getRequiredActionConfigDescription(providerId);
Assert.assertNotNull(requiredActionConfigDescription);
Assert.assertNotNull(requiredActionConfigDescription.getProperties());
Assert.assertTrue(requiredActionConfigDescription.getProperties().size() == 2);
Assertions.assertNotNull(requiredActionConfigDescription);
Assertions.assertNotNull(requiredActionConfigDescription.getProperties());
Assertions.assertTrue(requiredActionConfigDescription.getProperties().size() == 2);
}
@Test
@ -187,45 +196,45 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
// Dummy RequiredAction is not registered in the realm and WebAuthn actions
List<RequiredActionProviderSimpleRepresentation> result = authMgmtResource.getUnregisteredRequiredActions();
Assert.assertEquals(2, result.size());
Assertions.assertEquals(2, result.size());
String providerId = DummyConfigurableRequiredActionFactory.PROVIDER_ID;
RequiredActionProviderSimpleRepresentation action = result.stream().filter(
a -> providerId.equals(a.getProviderId())
).findFirst().get();
Assert.assertEquals(providerId, action.getProviderId());
Assert.assertEquals("Configurable Test Action", action.getName());
Assertions.assertEquals(providerId, action.getProviderId());
Assertions.assertEquals("Configurable Test Action", action.getName());
// Register it
authMgmtResource.registerRequiredAction(action);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authMgmtBasePath() + "/register-required-action", action, ResourceType.REQUIRED_ACTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authMgmtBasePath() + "/register-required-action", action, ResourceType.REQUIRED_ACTION);
RequiredActionConfigRepresentation requiredActionConfigRep = new RequiredActionConfigRepresentation();
Map<String, String> newActionConfig = Map.ofEntries(Map.entry("setting1", "value1"), Map.entry("setting2", "false"));
requiredActionConfigRep.setConfig(newActionConfig);
authMgmtResource.updateRequiredActionConfig(providerId, requiredActionConfigRep);
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authRequiredActionConfigPath(providerId), ResourceType.REQUIRED_ACTION_CONFIG);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authRequiredActionConfigPath(providerId), ResourceType.REQUIRED_ACTION_CONFIG);
RequiredActionConfigRepresentation savedRequiredActionConfigRep = authMgmtResource.getRequiredActionConfig(providerId);
Assert.assertNotNull(savedRequiredActionConfigRep);
Assert.assertNotNull(savedRequiredActionConfigRep.getConfig());
Assert.assertTrue(savedRequiredActionConfigRep.getConfig().entrySet().containsAll(newActionConfig.entrySet()));
Assertions.assertNotNull(savedRequiredActionConfigRep);
Assertions.assertNotNull(savedRequiredActionConfigRep.getConfig());
Assertions.assertTrue(savedRequiredActionConfigRep.getConfig().entrySet().containsAll(newActionConfig.entrySet()));
// delete config
authMgmtResource.removeRequiredActionConfig(providerId);
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authRequiredActionConfigPath(providerId), ResourceType.REQUIRED_ACTION_CONFIG);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.authRequiredActionConfigPath(providerId), ResourceType.REQUIRED_ACTION_CONFIG);
RequiredActionProviderRepresentation rep = authMgmtResource.getRequiredAction(providerId);
// Remove success
authMgmtResource.removeRequiredAction(providerId);
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authRequiredActionPath(rep.getAlias()), ResourceType.REQUIRED_ACTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.authRequiredActionPath(rep.getAlias()), ResourceType.REQUIRED_ACTION);
// Retrieval after deletion should throw a NotFound exception
try {
authMgmtResource.getRequiredActionConfig(providerId);
} catch (Exception ex) {
Assert.assertTrue(NotFoundException.class.isInstance(ex));
Assertions.assertTrue(NotFoundException.class.isInstance(ex));
}
}
@ -245,8 +254,8 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
}
private void compareRequiredActions(List<RequiredActionProviderRepresentation> expected, List<RequiredActionProviderRepresentation> actual) {
Assert.assertNotNull("Actual null", actual);
Assert.assertEquals("Required actions count", expected.size(), actual.size());
Assertions.assertNotNull(actual, "Actual null");
Assertions.assertEquals(expected.size(), actual.size(), "Required actions count");
Iterator<RequiredActionProviderRepresentation> ite = expected.iterator();
Iterator<RequiredActionProviderRepresentation> ita = actual.iterator();
@ -256,11 +265,11 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
}
private void compareRequiredAction(RequiredActionProviderRepresentation expected, RequiredActionProviderRepresentation actual) {
Assert.assertEquals("alias - " + expected.getAlias(), expected.getAlias(), actual.getAlias());
Assert.assertEquals("name - " + expected.getAlias(), expected.getName(), actual.getName());
Assert.assertEquals("enabled - " + expected.getAlias(), expected.isEnabled(), actual.isEnabled());
Assert.assertEquals("defaultAction - " + expected.getAlias(), expected.isDefaultAction(), actual.isDefaultAction());
Assert.assertEquals("config - " + expected.getAlias(), expected.getConfig() != null ? expected.getConfig() : Collections.<String, String>emptyMap(), actual.getConfig());
Assertions.assertEquals(expected.getAlias(), actual.getAlias(), "alias - " + expected.getAlias());
Assertions.assertEquals(expected.getName(), actual.getName(), "name - " + expected.getAlias());
Assertions.assertEquals(expected.isEnabled(), actual.isEnabled(), "enabled - " + expected.getAlias());
Assertions.assertEquals(expected.isDefaultAction(), actual.isDefaultAction(), "defaultAction - " + expected.getAlias());
Assertions.assertEquals(expected.getConfig() != null ? expected.getConfig() : Collections.<String, String>emptyMap(), actual.getConfig(), "config - " + expected.getAlias());
}
private void addRequiredAction(List<RequiredActionProviderRepresentation> target, String alias, String name, boolean enabled, boolean defaultAction, Map<String, String> conf) {
@ -283,4 +292,14 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
return o1.getAlias().compareTo(o2.getAlias());
}
}
public static class CustomProvidersServerConfig implements KeycloakServerConfig {
@Override
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
return config.dependency("org.keycloak.tests", "keycloak-tests-custom-providers");
}
}
}

View File

@ -15,36 +15,38 @@
* limitations under the License.
*/
package org.keycloak.testsuite.admin.authentication;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.testsuite.util.AdminEventPaths;
package org.keycloak.tests.admin.authentication;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import java.util.HashMap;
import java.util.List;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@KeycloakIntegrationTest
public class ShiftExecutionTest extends AbstractAuthenticationTest {
@Test
public void testShiftExecution() {
// copy built-in flow so we get a new editable flow
HashMap<String, Object> params = new HashMap<>();
params.put("newName", "Copy of browser");
Response response = authMgmtResource.copy("browser", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
try {
Assert.assertEquals("Copy flow", 201, response.getStatus());
Assertions.assertEquals(201, response.getStatus(), "Copy flow");
} finally {
response.close();
}
@ -58,42 +60,42 @@ public class ShiftExecutionTest extends AbstractAuthenticationTest {
// Not possible to raisePriority of not-existent flow
try {
authMgmtResource.raisePriority("not-existent");
Assert.fail("Not expected to raise priority of not existent flow");
Assertions.fail("Not expected to raise priority of not existent flow");
} catch (NotFoundException nfe) {
// Expected
}
// shift last execution up
authMgmtResource.raisePriority(last.getId());
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authRaiseExecutionPath(last.getId()), ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authRaiseExecutionPath(last.getId()), ResourceType.AUTH_EXECUTION);
List<AuthenticationExecutionInfoRepresentation> executions2 = authMgmtResource.getExecutions("Copy of browser");
AuthenticationExecutionInfoRepresentation last2 = executions2.get(executions.size() - 1);
AuthenticationExecutionInfoRepresentation oneButLast2 = executions2.get(executions.size() - 2);
Assert.assertEquals("Execution shifted up - N", last.getId(), oneButLast2.getId());
Assert.assertEquals("Execution shifted up - N-1", oneButLast.getId(), last2.getId());
Assertions.assertEquals(last.getId(), oneButLast2.getId(), "Execution shifted up - N");
Assertions.assertEquals(oneButLast.getId(), last2.getId(), "Execution shifted up - N-1");
// Not possible to lowerPriority of not-existent flow
try {
authMgmtResource.lowerPriority("not-existent");
Assert.fail("Not expected to raise priority of not existent flow");
Assertions.fail("Not expected to raise priority of not existent flow");
} catch (NotFoundException nfe) {
// Expected
}
// shift one before last down
authMgmtResource.lowerPriority(oneButLast2.getId());
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authLowerExecutionPath(oneButLast2.getId()), ResourceType.AUTH_EXECUTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authLowerExecutionPath(oneButLast2.getId()), ResourceType.AUTH_EXECUTION);
executions2 = authMgmtResource.getExecutions("Copy of browser");
last2 = executions2.get(executions.size() - 1);
oneButLast2 = executions2.get(executions.size() - 2);
Assert.assertEquals("Execution shifted down - N", last.getId(), last2.getId());
Assert.assertEquals("Execution shifted down - N-1", oneButLast.getId(), oneButLast2.getId());
Assertions.assertEquals(last.getId(), last2.getId(), "Execution shifted down - N");
Assertions.assertEquals(oneButLast.getId(), oneButLast2.getId(), "Execution shifted down - N-1");
}
@Test
@ -106,7 +108,7 @@ public class ShiftExecutionTest extends AbstractAuthenticationTest {
// Not possible to raise - It's builtin flow
try {
authMgmtResource.raisePriority(last.getId());
Assert.fail("Not expected to raise priority of builtin flow");
Assertions.fail("Not expected to raise priority of builtin flow");
} catch (BadRequestException nfe) {
// Expected
}
@ -114,7 +116,7 @@ public class ShiftExecutionTest extends AbstractAuthenticationTest {
// Not possible to lower - It's builtin flow
try {
authMgmtResource.lowerPriority(oneButLast.getId());
Assert.fail("Not expected to lower priority of builtin flow");
Assertions.fail("Not expected to lower priority of builtin flow");
} catch (BadRequestException nfe) {
// Expected
}

View File

@ -15,22 +15,24 @@
* limitations under the License.
*/
package org.keycloak.testsuite.admin.authentication;
import java.util.List;
package org.keycloak.tests.admin.authentication;
import jakarta.ws.rs.NotFoundException;
import org.junit.Assert;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import java.util.List;
/**
* @author <a href="mailto:wadahiro@gmail.com">Hiroyuki Wada</a>
*/
@KeycloakIntegrationTest
public class ShiftRequiredActionTest extends AbstractAuthenticationTest {
@Test
@ -45,41 +47,41 @@ public class ShiftRequiredActionTest extends AbstractAuthenticationTest {
// Not possible to raisePriority of not-existent required action
try {
authMgmtResource.raisePriority("not-existent");
Assert.fail("Not expected to raise priority of not existent required action");
Assertions.fail("Not expected to raise priority of not existent required action");
} catch (NotFoundException nfe) {
// Expected
}
// shift last required action up
authMgmtResource.raiseRequiredActionPriority(last.getAlias());
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authRaiseRequiredActionPath(last.getAlias()), ResourceType.REQUIRED_ACTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authRaiseRequiredActionPath(last.getAlias()), ResourceType.REQUIRED_ACTION);
List<RequiredActionProviderRepresentation> actions2 = authMgmtResource.getRequiredActions();
RequiredActionProviderRepresentation last2 = actions2.get(actions.size() - 1);
RequiredActionProviderRepresentation oneButLast2 = actions2.get(actions.size() - 2);
Assert.assertEquals("Required action shifted up - N", last.getAlias(), oneButLast2.getAlias());
Assert.assertEquals("Required action up - N-1", oneButLast.getAlias(), last2.getAlias());
Assertions.assertEquals(last.getAlias(), oneButLast2.getAlias(), "Required action shifted up - N");
Assertions.assertEquals(oneButLast.getAlias(), last2.getAlias(), "Required action up - N-1");
// Not possible to lowerPriority of not-existent required action
try {
authMgmtResource.lowerRequiredActionPriority("not-existent");
Assert.fail("Not expected to raise priority of not existent required action");
Assertions.fail("Not expected to raise priority of not existent required action");
} catch (NotFoundException nfe) {
// Expected
}
// shift one before last down
authMgmtResource.lowerRequiredActionPriority(oneButLast2.getAlias());
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authLowerRequiredActionPath(oneButLast2.getAlias()), ResourceType.REQUIRED_ACTION);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authLowerRequiredActionPath(oneButLast2.getAlias()), ResourceType.REQUIRED_ACTION);
actions2 = authMgmtResource.getRequiredActions();
last2 = actions2.get(actions.size() - 1);
oneButLast2 = actions2.get(actions.size() - 2);
Assert.assertEquals("Required action shifted down - N", last.getAlias(), last2.getAlias());
Assert.assertEquals("Required action shifted down - N-1", oneButLast.getAlias(), oneButLast2.getAlias());
Assertions.assertEquals(last.getAlias(), last2.getAlias(), "Required action shifted down - N");
Assertions.assertEquals(oneButLast.getAlias(), oneButLast2.getAlias(), "Required action shifted down - N-1");
}
}

48
tests/custom-providers/pom.xml Executable file
View File

@ -0,0 +1,48 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-tests-parent</artifactId>
<groupId>org.keycloak.tests</groupId>
<version>999.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-tests-custom-providers</artifactId>
<name>Keycloak Testsuite Custom Providers</name>
<packaging>jar</packaging>
<description>Keycloak Testsuite Custom Providers</description>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,120 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.testsuite.actions;
import org.keycloak.Config;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RequiredActionConfigModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import java.util.List;
import java.util.Map;
public class DummyConfigurableRequiredActionFactory implements RequiredActionFactory {
public static final String PROVIDER_ID = "configurable-test-action";
public static final String SETTING_1 = "setting1";
public static final String SETTING_2 = "setting2";
@Override
public String getDisplayText() {
return "Configurable Test Action";
}
@Override
public RequiredActionProvider create(KeycloakSession session) {
return new RequiredActionProvider() {
@Override
public void evaluateTriggers(RequiredActionContext context) {
}
@Override
public void requiredActionChallenge(RequiredActionContext context) {
// users can access the given Required Action configuration via RequiredActionContext#getContext()
RequiredActionConfigModel configModel = context.getConfig();
Map<String, String> config = configModel.getConfig();
String setting1Value = configModel.getConfigValue(SETTING_1);
context.success();
}
@Override
public void processAction(RequiredActionContext context) {
}
@Override
public void close() {
}
};
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = ProviderConfigurationBuilder.create() //
.property() //
.name(SETTING_1) //
.label("Setting 1") //
.helpText("Setting 1 Help Text") //
.type(ProviderConfigProperty.STRING_TYPE) //
.defaultValue("setting1Default") //
.add() //
.property() //
.name(SETTING_2) //
.label("Setting 2") //
.helpText("Setting 2 Help Text") //
.type(ProviderConfigProperty.BOOLEAN_TYPE) //
.defaultValue("true") //
.add() //
.build();
@Override
public List<ProviderConfigProperty> getConfigMetadata() {
return CONFIG_PROPERTIES;
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.testsuite.actions;
import org.keycloak.Config;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class DummyRequiredActionFactory implements RequiredActionFactory {
public static final String PROVIDER_ID = "dummy-action";
@Override
public String getDisplayText() {
return "Dummy Action";
}
@Override
public RequiredActionProvider create(KeycloakSession session) {
return new RequiredActionProvider() {
@Override
public void evaluateTriggers(RequiredActionContext context) {
}
@Override
public void requiredActionChallenge(RequiredActionContext context) {
context.success();
}
@Override
public void processAction(RequiredActionContext context) {
}
@Override
public void close() {
}
};
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
}

View File

@ -0,0 +1,19 @@
#
# Copyright 2016 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed 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.
#
org.keycloak.testsuite.actions.DummyRequiredActionFactory
org.keycloak.testsuite.actions.DummyConfigurableRequiredActionFactory

View File

@ -35,6 +35,7 @@
<modules>
<module>base</module>
<module>utils</module>
<module>custom-providers</module>
</modules>
</project>

View File

@ -0,0 +1,39 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.tests.utils;
import org.junit.jupiter.api.Assumptions;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class KerberosUtils {
// TODO Figure out if we need to be able to disable Kerberos support for some tests; if so find a better way
public static boolean isKerberosSupportExpected() {
String kerberosSupported = System.getProperty("auth.server.kerberos.supported");
// Supported by default. It is considered unsupported just if explicitly disabled
return !"false".equals(kerberosSupported);
}
public static void assumeKerberosSupportExpected() {
Assumptions.assumeTrue(isKerberosSupportExpected(), "Kerberos feature is not expected to be supported by auth server");
}
}

View File

@ -0,0 +1,491 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.tests.utils.admin;
import jakarta.ws.rs.core.UriBuilder;
import org.keycloak.admin.client.resource.AttackDetectionResource;
import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.admin.client.resource.ClientAttributeCertificateResource;
import org.keycloak.admin.client.resource.ClientInitialAccessResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientScopeResource;
import org.keycloak.admin.client.resource.ClientScopesResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.ComponentsResource;
import org.keycloak.admin.client.resource.GroupResource;
import org.keycloak.admin.client.resource.GroupsResource;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.IdentityProvidersResource;
import org.keycloak.admin.client.resource.ProtocolMappersResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleByIdResource;
import org.keycloak.admin.client.resource.RoleMappingResource;
import org.keycloak.admin.client.resource.RoleResource;
import org.keycloak.admin.client.resource.RolesResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import java.net.URI;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AdminEventPaths {
// REALM
public static String deleteSessionPath(String userSessionId) {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "deleteSession").build(userSessionId);
return uri.toString();
}
public static String defaultGroupPath(String groupId) {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "addDefaultGroup").build(groupId);
return uri.toString();
}
public static String defaultDefaultClientScopePath(String clientScopeId) {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "addDefaultDefaultClientScope").build(clientScopeId);
return uri.toString();
}
public static String defaultOptionalClientScopePath(String clientScopeId) {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "addDefaultOptionalClientScope").build(clientScopeId);
return uri.toString();
}
public static String userProfilePath() {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "users")
.path(UsersResource.class, "userProfile")
.build();
return uri.toString();
}
// CLIENT RESOURCE
public static String clientResourcePath(String clientDbId) {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "clients").path(ClientsResource.class, "get").build(clientDbId);
return uri.toString();
}
public static String clientRolesResourcePath(String clientDbId) {
URI uri = UriBuilder.fromUri(clientResourcePath(clientDbId)).path(ClientResource.class, "roles").build();
return uri.toString();
}
public static String clientRoleResourcePath(String clientDbId, String roleName) {
URI uri = UriBuilder.fromUri(clientRolesResourcePath(clientDbId)).path(RolesResource.class, "get").build(roleName);
return uri.toString();
}
public static String clientRoleResourceCompositesPath(String clientDbId, String roleName) {
URI uri = UriBuilder.fromUri(clientRoleResourcePath(clientDbId, roleName))
.path(RoleResource.class, "getRoleComposites").build();
return uri.toString();
}
public static String clientProtocolMappersPath(String clientDbId) {
URI uri = UriBuilder.fromUri(clientResourcePath(clientDbId))
.path(ClientResource.class, "getProtocolMappers")
.build();
return uri.toString();
}
public static String clientProtocolMapperPath(String clientDbId, String protocolMapperId) {
URI uri = UriBuilder.fromUri(clientProtocolMappersPath(clientDbId))
.path(ProtocolMappersResource.class, "getMapperById")
.build(protocolMapperId);
return uri.toString();
}
public static String clientPushRevocationPath(String clientDbId) {
URI uri = UriBuilder.fromUri(clientResourcePath(clientDbId)).path(ClientResource.class, "pushRevocation").build();
return uri.toString();
}
public static String clientNodesPath(String clientDbId) {
URI uri = UriBuilder.fromUri(clientResourcePath(clientDbId)).path(ClientResource.class, "registerNode").build();
return uri.toString();
}
public static String clientNodePath(String clientDbId, String node) {
URI uri = UriBuilder.fromUri(clientResourcePath(clientDbId)).path(ClientResource.class, "unregisterNode").build(node);
return uri.toString();
}
public static String clientTestNodesAvailablePath(String clientDbId) {
URI uri = UriBuilder.fromUri(clientResourcePath(clientDbId)).path(ClientResource.class, "testNodesAvailable").build();
return uri.toString();
}
public static String clientGenerateSecretPath(String clientDbId) {
URI uri = UriBuilder.fromUri(clientResourcePath(clientDbId)).path(ClientResource.class, "generateNewSecret").build();
return uri.toString();
}
public static String clientRegenerateRegistrationAccessTokenPath(String clientDbId) {
URI uri = UriBuilder.fromUri(clientResourcePath(clientDbId)).path(ClientResource.class, "regenerateRegistrationAccessToken").build();
return uri.toString();
}
public static String clientCertificateGenerateSecretPath(String clientDbId, String certificateAttribute) {
URI uri = UriBuilder.fromUri(clientResourcePath(clientDbId))
.path(ClientResource.class, "getCertficateResource")
.path(ClientAttributeCertificateResource.class, "generate")
.build(certificateAttribute);
return uri.toString();
}
public static String clientScopeMappingsRealmLevelPath(String clientDbId) {
URI uri = UriBuilder.fromUri(clientResourcePath(clientDbId)).path(ClientResource.class, "getScopeMappings")
.path(RoleMappingResource.class, "realmLevel")
.build();
return uri.toString();
}
public static String clientScopeMappingsClientLevelPath(String clientDbId, String clientOwningRoleId) {
URI uri = UriBuilder.fromUri(clientResourcePath(clientDbId)).path(ClientResource.class, "getScopeMappings")
.path(RoleMappingResource.class, "clientLevel")
.build(clientOwningRoleId);
return uri.toString();
}
// CLIENT SCOPES
public static String clientScopeResourcePath(String clientScopeId) {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "clientScopes").path(ClientScopesResource.class, "get").build(clientScopeId);
return uri.toString();
}
public static String clientScopeGenerateAudienceClientScopePath() {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "clientScopes").path(ClientScopesResource.class, "generateAudienceClientScope").build();
return uri.toString();
}
public static String clientScopeRoleMappingsRealmLevelPath(String clientScopeDbId) {
URI uri = UriBuilder.fromUri(clientScopeResourcePath(clientScopeDbId)).path(ClientScopeResource.class, "getScopeMappings")
.path(RoleMappingResource.class, "realmLevel")
.build();
return uri.toString();
}
public static String clientScopeRoleMappingsClientLevelPath(String clientScopeDbId, String clientOwningRoleId) {
URI uri = UriBuilder.fromUri(clientScopeResourcePath(clientScopeDbId)).path(ClientScopeResource.class, "getScopeMappings")
.path(RoleMappingResource.class, "clientLevel")
.build(clientOwningRoleId);
return uri.toString();
}
public static String clientScopeProtocolMappersPath(String clientScopeDbId) {
URI uri = UriBuilder.fromUri(clientScopeResourcePath(clientScopeDbId))
.path(ClientScopeResource.class, "getProtocolMappers")
.build();
return uri.toString();
}
public static String clientScopeProtocolMapperPath(String clientScopeDbId, String protocolMapperId) {
URI uri = UriBuilder.fromUri(clientScopeProtocolMappersPath(clientScopeDbId))
.path(ProtocolMappersResource.class, "getMapperById")
.build(protocolMapperId);
return uri.toString();
}
// ROLES
public static String rolesResourcePath() {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "roles").build();
return uri.toString();
}
public static String roleResourcePath(String roleName) {
URI uri = UriBuilder.fromUri(rolesResourcePath()).path(RolesResource.class, "get").build(roleName);
return uri.toString();
}
public static String roleResourceCompositesPath(String roleName) {
URI uri = UriBuilder.fromUri(roleResourcePath(roleName)).path(RoleResource.class, "getRoleComposites").build();
return uri.toString();
}
public static String rolesByIdResourcePath() {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "rolesById").build();
return uri.toString();
}
public static String roleByIdResourcePath(String roleId) {
URI uri = UriBuilder.fromUri(rolesByIdResourcePath()).path(RoleByIdResource.class, "getRole").build(roleId);
return uri.toString();
}
public static String roleByIdResourceCompositesPath(String roleId) {
URI uri = UriBuilder.fromUri(rolesByIdResourcePath()).path(RoleByIdResource.class, "getRoleComposites").build(roleId);
return uri.toString();
}
// USERS
public static String userResourcePath(String userId) {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "users").path(UsersResource.class, "get").build(userId);
return uri.toString();
}
public static String userResetPasswordPath(String userId) {
URI uri = UriBuilder.fromUri(userResourcePath(userId)).path(UserResource.class, "resetPassword").build(userId);
return uri.toString();
}
public static String userRealmRoleMappingsPath(String userId) {
URI uri = UriBuilder.fromUri(userResourcePath(userId))
.path(UserResource.class, "roles")
.path(RoleMappingResource.class, "realmLevel").build();
return uri.toString();
}
public static String userClientRoleMappingsPath(String userId, String clientDbId) {
URI uri = UriBuilder.fromUri(userResourcePath(userId))
.path(UserResource.class, "roles")
.path(RoleMappingResource.class, "clientLevel").build(clientDbId);
return uri.toString();
}
public static String userFederatedIdentityLink(String userId, String idpAlias) {
URI uri = UriBuilder.fromUri(userResourcePath(userId))
.path(UserResource.class, "addFederatedIdentity")
.build(idpAlias);
return uri.toString();
}
public static String userGroupPath(String userId, String groupId) {
URI uri = UriBuilder.fromUri(userResourcePath(userId))
.path(UserResource.class, "joinGroup")
.build(groupId);
return uri.toString();
}
// IDENTITY PROVIDERS
public static String identityProvidersPath() {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "identityProviders").build();
return uri.toString();
}
public static String identityProviderCreatePath() {
URI uri = UriBuilder.fromUri(identityProvidersPath()).path(IdentityProvidersResource.class, "create").build();
return uri.toString();
}
public static String identityProviderPath(String idpAlias) {
URI uri = UriBuilder.fromUri(identityProvidersPath()).path(IdentityProvidersResource.class, "get").build(idpAlias);
return uri.toString();
}
public static String identityProviderMapperPath(String idpAlias, String idpMapperId) {
URI uri = UriBuilder.fromUri(identityProviderPath(idpAlias)).path(IdentityProviderResource.class, "getMapperById").build(idpMapperId);
return uri.toString();
}
// COMPONENTS
public static String componentsPath() {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "components").build();
return uri.toString();
}
public static String componentPath(String componentId) {
URI uri = UriBuilder.fromUri(componentsPath()).path(ComponentsResource.class, "component").build(componentId);
return uri.toString();
}
// CLIENT INITIAL ACCESS
public static String clientInitialAccessPath(String clientInitialAccessId) {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "clientInitialAccess")
.path(ClientInitialAccessResource.class, "delete")
.build(clientInitialAccessId);
return uri.toString();
}
// GROUPS
public static String groupsPath() {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "groups")
.build();
return uri.toString();
}
public static String groupPath(String groupId) {
URI uri = UriBuilder.fromUri(groupsPath()).path(GroupsResource.class, "group")
.build(groupId);
return uri.toString();
}
public static String groupRolesPath(String groupId) {
URI uri = UriBuilder.fromUri(groupPath(groupId))
.path(GroupResource.class, "roles")
.build();
return uri.toString();
}
public static String groupRolesRealmRolesPath(String groupId) {
URI uri = UriBuilder.fromUri(groupRolesPath(groupId))
.path(RoleMappingResource.class, "realmLevel")
.build();
return uri.toString();
}
public static String groupRolesClientRolesPath(String groupId, String clientDbId) {
URI uri = UriBuilder.fromUri(groupRolesPath(groupId))
.path(RoleMappingResource.class, "clientLevel")
.build(clientDbId);
return uri.toString();
}
public static String groupSubgroupsPath(String groupId) {
URI uri = UriBuilder.fromUri(groupPath(groupId))
.path(GroupResource.class, "subGroup")
.build();
return uri.toString();
}
// AUTHENTICATION FLOWS
public static String authMgmtBasePath() {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "flows")
.build();
return uri.toString();
}
public static String authFlowsPath() {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "getFlows")
.build();
return uri.toString();
}
public static String authFlowPath(String flowId) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "getFlow")
.build(flowId);
return uri.toString();
}
public static String authCopyFlowPath(String flowAlias) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "copy")
.build(flowAlias);
return uri.toString();
}
public static String authEditFlowPath(String flowId) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "updateFlow")
.build(flowId);
return uri.toString();
}
public static String authAddExecutionFlowPath(String flowAlias) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "addExecutionFlow")
.build(flowAlias);
return uri.toString();
}
public static String authAddExecutionPath(String flowAlias) {
return authFlowPath(flowAlias) + "/executions/execution";
}
public static String authUpdateExecutionPath(String flowAlias) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "updateExecutions")
.build(flowAlias);
return uri.toString();
}
public static String authExecutionPath(String executionId) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "removeExecution")
.build(executionId);
return uri.toString();
}
public static String authAddExecutionConfigPath(String executionId) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "newExecutionConfig")
.build(executionId);
return uri.toString();
}
public static String authExecutionConfigPath(String configId) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "getAuthenticatorConfig")
.build(configId);
return uri.toString();
}
public static String authRaiseExecutionPath(String executionId) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "raisePriority")
.build(executionId);
return uri.toString();
}
public static String authLowerExecutionPath(String executionId) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "lowerPriority")
.build(executionId);
return uri.toString();
}
public static String authRequiredActionPath(String requiredActionAlias) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "getRequiredAction")
.build(requiredActionAlias);
return uri.toString();
}
public static String authRequiredActionConfigPath(String requiredActionAlias) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "getRequiredActionConfig")
.build(requiredActionAlias);
return uri.toString();
}
public static String authRaiseRequiredActionPath(String requiredActionAlias) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "raiseRequiredActionPriority")
.build(requiredActionAlias);
return uri.toString();
}
public static String authLowerRequiredActionPath(String requiredActionAlias) {
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "lowerRequiredActionPriority")
.build(requiredActionAlias);
return uri.toString();
}
// ATTACK DETECTION
public static String attackDetectionClearBruteForceForUserPath(String username) {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "attackDetection")
.path(AttackDetectionResource.class, "clearBruteForceForUser")
.build(username);
return uri.toString();
}
public static String attackDetectionClearAllBruteForcePath() {
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "attackDetection")
.path(AttackDetectionResource.class, "clearAllBruteForce")
.build();
return uri.toString();
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.tests.utils.matchers;
import jakarta.ws.rs.core.Response;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import java.io.IOException;
/**
* Matcher for matching status code of {@link Response} instance.
* @author hmlnarik
*/
public class HttpResponseBodyMatcher extends BaseMatcher<HttpResponse> {
private final Matcher<String> matcher;
private ThreadLocal<String> lastEntity = new ThreadLocal<>();
public HttpResponseBodyMatcher(Matcher<String> matcher) {
this.matcher = matcher;
}
@Override
public boolean matches(Object item) {
lastEntity.remove();
try {
lastEntity.set(EntityUtils.toString(((HttpResponse) item).getEntity()));
return (item instanceof HttpResponse) && this.matcher.matches(lastEntity.get());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void describeMismatch(Object item, Description description) {
Description d = description.appendText("was ").appendValue(item);
if (lastEntity.get() != null) {
d.appendText(" with entity ").appendText(lastEntity.get());
}
}
@Override
public void describeTo(Description description) {
description.appendText("response body matches ").appendDescriptionOf(this.matcher);
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.tests.utils.matchers;
import jakarta.ws.rs.core.Response;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import java.io.IOException;
/**
* Matcher for matching status code of {@link Response} instance.
* @author hmlnarik
*/
public class HttpResponseStatusCodeMatcher extends BaseMatcher<HttpResponse> {
private final Matcher<? extends Number> matcher;
public HttpResponseStatusCodeMatcher(Matcher<? extends Number> matcher) {
this.matcher = matcher;
}
@Override
public boolean matches(Object item) {
return (item instanceof HttpResponse) && this.matcher.matches(((HttpResponse) item).getStatusLine().getStatusCode());
}
@Override
public void describeMismatch(Object item, Description description) {
Description d = description.appendText("was ").appendValue(item)
.appendText(" with entity ");
try {
d.appendText(EntityUtils.toString(((HttpResponse) item).getEntity()));
} catch (IOException e) {
d.appendText("<Cannot decode entity: " + e.getMessage() + ">");
}
}
@Override
public void describeTo(Description description) {
description.appendText("response status code matches ").appendDescriptionOf(this.matcher);
}
}

View File

@ -0,0 +1,166 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.tests.utils.matchers;
import jakarta.ws.rs.core.Response;
import org.apache.http.HttpResponse;
import org.hamcrest.Matcher;
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import java.net.URI;
import java.util.Arrays;
import java.util.Map;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
/**
* Additional hamcrest matchers
* @author hmlnarik
*/
public class Matchers {
/**
* Matcher on HTTP status code of a {@link Response} instance.
* @param matcher
* @return
*/
public static Matcher<Response> body(Matcher<String> matcher) {
return new ResponseBodyMatcher(matcher);
}
/**
* Matcher on HTTP body of a {@link Response} instance.
* @param matcher
* @return
*/
public static Matcher<HttpResponse> bodyHC(Matcher<String> matcher) {
return new HttpResponseBodyMatcher(matcher);
}
/**
* Matcher on HTTP status code of a {@link Response} instance.
* @param matcher
* @return
*/
public static Matcher<Response> statusCode(Matcher<? extends Number> matcher) {
return new ResponseStatusCodeMatcher(matcher);
}
/**
* Matcher on HTTP status code of a {@link Response} instance (HttpClient variant).
* @param matcher
* @return
*/
public static Matcher<HttpResponse> statusCodeHC(Matcher<? extends Number> matcher) {
return new HttpResponseStatusCodeMatcher(matcher);
}
/**
* Matches when the HTTP status code of a {@link Response} instance is equal to the given code.
* @param expectedStatusCode
* @return
*/
public static Matcher<Response> statusCodeIs(Response.Status expectedStatusCode) {
return new ResponseStatusCodeMatcher(org.hamcrest.Matchers.is(expectedStatusCode.getStatusCode()));
}
/**
* Matches when the HTTP status code of a {@link Response} instance is equal to the given code (HttpClient variant).
* @param expectedStatusCode
* @return
*/
public static Matcher<HttpResponse> statusCodeIsHC(Response.Status expectedStatusCode) {
return new HttpResponseStatusCodeMatcher(org.hamcrest.Matchers.is(expectedStatusCode.getStatusCode()));
}
/**
* Matches when the HTTP status code of a {@link Response} instance is equal to the given code.
* @param expectedStatusCode
* @return
*/
public static Matcher<Response> statusCodeIs(int expectedStatusCode) {
return new ResponseStatusCodeMatcher(org.hamcrest.Matchers.is(expectedStatusCode));
}
/**
* Matches when the HTTP status code of a {@link Response} instance is equal to the given code (HttpClient variant).
* @param expectedStatusCode
* @return
*/
public static Matcher<HttpResponse> statusCodeIsHC(int expectedStatusCode) {
return new HttpResponseStatusCodeMatcher(org.hamcrest.Matchers.is(expectedStatusCode));
}
/**
* Matches when the HTTP status code of a {@link Response} instance is equal to the given code.
* @param matcher
* @return
*/
public static <T> Matcher<Response> header(Matcher<Map<String, T>> matcher) {
return new ResponseHeaderMatcher<>(matcher);
}
/**
* Matches when the SAML status code of a {@link ResponseType} instance is equal to the given code.
* @param expectedStatus
* @return
*/
public static <T> Matcher<SAML2Object> isSamlResponse(JBossSAMLURIConstants expectedStatus) {
return allOf(
instanceOf(ResponseType.class),
new SamlResponseTypeMatcher(is(expectedStatus.getUri()))
);
}
/**
* Matches when the destination of a SAML {@link LogoutRequestType} instance is equal to the given destination.
* @param destination
* @return
*/
public static <T> Matcher<SAML2Object> isSamlLogoutRequest(String destination) {
return allOf(
instanceOf(LogoutRequestType.class),
new SamlLogoutRequestTypeMatcher(URI.create(destination))
);
}
/**
* Matches when the type of a SAML object is instance of {@link AuthnRequestType}.
* @return
*/
public static <T> Matcher<SAML2Object> isSamlAuthnRequest() {
return instanceOf(AuthnRequestType.class);
}
/**
* Matches when the SAML status of a {@link StatusResponseType} instance is equal to the given code.
* @param expectedStatus
* @return
*/
public static <T> Matcher<SAML2Object> isSamlStatusResponse(JBossSAMLURIConstants... expectedStatus) {
return allOf(
instanceOf(StatusResponseType.class),
new SamlStatusResponseTypeMatcher(Arrays.stream(expectedStatus).map(JBossSAMLURIConstants::getUri).toArray(i -> new URI[i]))
);
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.tests.utils.matchers;
import jakarta.ws.rs.core.Response;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
/**
* Matcher for matching status code of {@link Response} instance.
* @author hmlnarik
*/
public class ResponseBodyMatcher extends BaseMatcher<Response> {
private final Matcher<String> matcher;
public ResponseBodyMatcher(Matcher<String> matcher) {
this.matcher = matcher;
}
@Override
public boolean matches(Object item) {
if (item instanceof Response) {
final Response rItem = (Response) item;
rItem.bufferEntity();
return this.matcher.matches(rItem.readEntity(String.class));
} else {
return false;
}
}
@Override
public void describeTo(Description description) {
description.appendText("response body matches ").appendDescriptionOf(this.matcher);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.tests.utils.matchers;
import jakarta.ws.rs.core.Response;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import java.util.Map;
/**
* Matcher for matching status code of {@link Response} instance.
* @author hmlnarik
*/
public class ResponseHeaderMatcher<T> extends BaseMatcher<Response> {
private final Matcher<Map<String, T>> matcher;
public ResponseHeaderMatcher(Matcher<Map<String, T>> matcher) {
this.matcher = matcher;
}
@Override
public boolean matches(Object item) {
return (item instanceof Response) && this.matcher.matches(((Response) item).getHeaders());
}
@Override
public void describeTo(Description description) {
description.appendText("response headers match ").appendDescriptionOf(this.matcher);
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.tests.utils.matchers;
import jakarta.ws.rs.core.Response;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
/**
* Matcher for matching status code of {@link Response} instance.
* @author hmlnarik
*/
public class ResponseStatusCodeMatcher extends BaseMatcher<Response> {
private final Matcher<? extends Number> matcher;
public ResponseStatusCodeMatcher(Matcher<? extends Number> matcher) {
this.matcher = matcher;
}
@Override
public boolean matches(Object item) {
return (item instanceof Response) && this.matcher.matches(((Response) item).getStatus());
}
@Override
public void describeTo(Description description) {
description.appendText("response status code matches ").appendDescriptionOf(this.matcher);
}
}

View File

@ -0,0 +1,48 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.keycloak.tests.utils.matchers;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
import java.net.URI;
import static org.hamcrest.Matchers.is;
/**
*
* @author hmlnarik
*/
public class SamlLogoutRequestTypeMatcher extends BaseMatcher<SAML2Object> {
private final Matcher<URI> destinationMatcher;
public SamlLogoutRequestTypeMatcher(URI destination) {
this.destinationMatcher = is(destination);
}
public SamlLogoutRequestTypeMatcher(Matcher<URI> destinationMatcher) {
this.destinationMatcher = destinationMatcher;
}
@Override
public boolean matches(Object item) {
return destinationMatcher.matches(((LogoutRequestType) item).getDestination());
}
@Override
public void describeMismatch(Object item, Description description) {
description.appendText("was ").appendValue(((LogoutRequestType) item).getDestination());
}
@Override
public void describeTo(Description description) {
description.appendText("SAML logout request destination matches ").appendDescriptionOf(this.destinationMatcher);
}
}

View File

@ -0,0 +1,51 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.keycloak.tests.utils.matchers;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import java.net.URI;
import static org.hamcrest.Matchers.is;
/**
*
* @author hmlnarik
*/
public class SamlResponseTypeMatcher extends BaseMatcher<SAML2Object> {
private final Matcher<URI> statusMatcher;
public SamlResponseTypeMatcher(JBossSAMLURIConstants expectedStatus) {
this.statusMatcher = is(expectedStatus.getUri());
}
public SamlResponseTypeMatcher(Matcher<URI> statusMatcher) {
this.statusMatcher = statusMatcher;
}
@Override
public boolean matches(Object item) {
return statusMatcher.matches(((ResponseType) item).getStatus().getStatusCode().getValue());
}
@Override
public void describeMismatch(Object item, Description description) {
description.appendText("was ").appendValue(((ResponseType) item).getStatus().getStatusCode());
}
@Override
public void describeTo(Description description) {
description.appendText("SAML response status code matches ").appendDescriptionOf(this.statusMatcher);
}
}

View File

@ -0,0 +1,69 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.keycloak.tests.utils.matchers;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.is;
/**
*
* @author hmlnarik
*/
public class SamlStatusResponseTypeMatcher extends BaseMatcher<SAML2Object> {
private final List<Matcher<URI>> statusMatchers;
public SamlStatusResponseTypeMatcher(URI... statusMatchers) {
this.statusMatchers = new ArrayList(statusMatchers.length);
for (URI uri : statusMatchers) {
this.statusMatchers.add(is(uri));
}
}
public SamlStatusResponseTypeMatcher(List<Matcher<URI>> statusMatchers) {
this.statusMatchers = statusMatchers;
}
@Override
public boolean matches(Object item) {
StatusCodeType statusCode = ((StatusResponseType) item).getStatus().getStatusCode();
for (Matcher<URI> statusMatcher : statusMatchers) {
if (! statusMatcher.matches(statusCode.getValue())) {
return false;
}
statusCode = statusCode.getStatusCode();
}
return true;
}
@Override
public void describeMismatch(Object item, Description description) {
StatusCodeType statusCode = ((StatusResponseType) item).getStatus().getStatusCode();
description.appendText("was ");
while (statusCode != null) {
description.appendText("/").appendValue(statusCode.getValue());
statusCode = statusCode.getStatusCode();
}
}
@Override
public void describeTo(Description description) {
description.appendText("SAML status response status matches ");
for (Matcher<URI> statusMatcher : statusMatchers) {
description.appendText("/").appendDescriptionOf(statusMatcher);
}
}
}