Skip to content

Commit

Permalink
Merge pull request #202 from daniel-frak/feat_keycloak_26
Browse files Browse the repository at this point in the history
feat: Update to Keycloak 26
  • Loading branch information
daniel-frak authored Nov 7, 2024
2 parents 68868f8 + 66f487f commit 6d6bd34
Show file tree
Hide file tree
Showing 11 changed files with 60 additions and 23 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ When updating the code to work with newer versions of Keycloak, remember to upda

To check if the plugin works correctly after the upgrade:
1) Run `mvn clean package` in the project's root directory to run unit tests and build the plugin
2) Run `docker-compose up -d` in `./docker` to create the dependencies necessary for end-to-end testing
2) Run `docker compose up -d` in `./docker` to create the dependencies necessary for end-to-end testing
3) If this is the first time you're doing this, run `npm install` in `./docker/e2e` to install Cypress
4) Run `npx cypress run` in `./docker/e2e` to run end-to-end tests

Expand Down
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ https://codesoapbox.dev/keycloak-user-migration
*(`SNAPSHOT` means that the version is not yet released)*

| Keycloak Version | Version/Commit |
|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| 25.X | [5.0.0](https://github.com/daniel-frak/keycloak-user-migration/releases/tag/5.0.0)
|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------
| 26.X | SNAPSHOT |
| 25.X | [5.0.0](https://github.com/daniel-frak/keycloak-user-migration/releases/tag/5.0.0)
| 24.X | [4.0.0](https://github.com/daniel-frak/keycloak-user-migration/releases/tag/4.0.0) |
| 23.X | [3.0.0](https://github.com/daniel-frak/keycloak-user-migration/releases/tag/3.0.0) |
| 22.X | [2.0.0](https://github.com/daniel-frak/keycloak-user-migration/releases/tag/2.0.0) |
Expand All @@ -39,7 +40,10 @@ https://codesoapbox.dev/keycloak-user-migration

### Note about compatibility with JBoss Keycloak distributions

Using this plugin with legacy JBoss distributions of Keycloak might result in a `java.lang.NoClassDefFoundError: org/apache/commons/codec/binary/Base64` error. It seems that [adding the maven-shade-plugin](https://github.com/daniel-frak/keycloak-user-migration/issues/72) as a dependency fixes this issue.
Using this plugin with legacy JBoss distributions of Keycloak might result in a
`java.lang.NoClassDefFoundError: org/apache/commons/codec/binary/Base64` error. It seems
that [adding the maven-shade-plugin](https://github.com/daniel-frak/keycloak-user-migration/issues/72) as a dependency
fixes this issue.

## Prerequisites - REST endpoints in the legacy system

Expand Down Expand Up @@ -318,7 +322,9 @@ This switch can be toggled to decide whether groups which are not defined in the
migrated anyway or simply ignored.

## Totp

This module supports the migration of totp devices. The totp configuration block could look like this:

```json
{
"name": "Totp Device 1",
Expand All @@ -329,6 +335,8 @@ This module supports the migration of totp devices. The totp configuration block
"encoding": "BASE32"
}
```
`name` should be the name of the totp device, while `secret` is the secret, that could be encoded in "BASE32" or as UTF-8 plaintext.

`name` should be the name of the totp device, while `secret` is the secret, that could be encoded in "BASE32" or as
UTF-8 plaintext.
For the utf8 bytes just set the `encoding` attribute to null.
Possible `algorithm`s are: HmacSHA1, HmacSHA256, HmacSHA512
2 changes: 1 addition & 1 deletion docker/.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
COMPOSE_PROJECT_NAME=keycloak_migration_demo
KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:25.0.0
KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:26.0.5
MAVEN_IMAGE=maven:3.9.7-eclipse-temurin-21
OPENJDK_IMAGE=openjdk:21-jdk-slim

Expand Down
4 changes: 2 additions & 2 deletions docker/e2e/cypress/e2e/pages/forgotPasswordPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class ForgotPasswordPage {

elements = {
userNameInput: () => cy.get('#username'),
submitBtn: () => cy.get('input[type=submit]')
form: () => cy.get('#kc-reset-password-form')
}

visit() {
Expand All @@ -11,7 +11,7 @@ class ForgotPasswordPage {

triggerPasswordReset(userEmail) {
this.elements.userNameInput().clear().type(userEmail);
this.elements.submitBtn().click();
this.elements.form().submit();
cy.get('body').should('contain.text',
'You should receive an email shortly with further instructions.');
cy.mhGetMailsBySubject('Reset password')
Expand Down
4 changes: 2 additions & 2 deletions docker/e2e/cypress/e2e/pages/resetPasswordPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ class ResetPasswordPage {
elements = {
passwordInput: () => cy.get('#password-new'),
confirmPasswordInput: () => cy.get('#password-confirm'),
submitBtn: () => cy.get('input[type=submit]')
form: () => cy.get('#kc-passwd-update-form'),
}

chooseNewPassword(newPassword) {
this.elements.passwordInput().type(newPassword);
this.elements.confirmPasswordInput().type(newPassword);
this.elements.submitBtn().click();
this.elements.form().submit();
}
}

Expand Down
4 changes: 2 additions & 2 deletions docker/e2e/cypress/e2e/pages/updatePasswordPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ class UpdatePasswordPage {
elements = {
passwordInput: () => cy.get('#password-new'),
confirmPasswordInput: () => cy.get('#password-confirm'),
submitBtn: () => cy.get('input[type=submit]')
form: () => cy.get('#kc-passwd-update-form'),
}

chooseNewPassword(newPassword) {
this.elements.passwordInput().type(newPassword);
this.elements.confirmPasswordInput().type(newPassword);
this.elements.submitBtn().click();
this.elements.form().submit()
}
}

Expand Down
14 changes: 10 additions & 4 deletions docker/e2e/cypress/e2e/pages/userFederationPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@ class UserFederationPage {
}

visit() {
cy.intercept('/admin/realms/master/components*')
.as("components")
cy.intercept('/admin/realms/master/components*&type=org.keycloak.storage.UserStorageProvider*')
.as("getUserStorageProviders")
cy.visit('/admin/master/console/#/master/user-federation');
cy.wait('@components');
cy.wait(2000); // The initial page will always claim there are no components, so we must wait to make sure.
cy.wait('@getUserStorageProviders');

/*
The initial page will always claim there are no configured User Federation Providers,
and there is no way to check whether it has updated the DOM using the @getUserStorageProviders request.
Therefore, an artificial wait is introduced to make sure the DOM has updated.
*/
cy.wait(5000);
}

removePluginIfExists() {
Expand Down
14 changes: 13 additions & 1 deletion docker/e2e/cypress/e2e/pages/userMigrationProviderPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ class UserMigrationProviderPage {
header: () => cy.get("h1"),
uiDisplayName: () => cy.getByTestId('name'),
restClientUri: () => cy.getByTestId('URI'),
actionDropdown: () => cy.getByTestId('action-dropdown'),
actionDropdownRemoveImportedBtn: () => cy.get('button').contains('Remove imported'),
modalConfirmButton: () => cy.getByTestId('confirm'),
saveBtn: () => cy.get('button').contains('Save')
}

Expand All @@ -15,7 +18,7 @@ class UserMigrationProviderPage {
userFederationPage.goToUserMigrationPluginPage(data.providerName, data.pluginName);
}

addPlugin(legacySystemUrl) {
configurePlugin(legacySystemUrl) {
this.elements.uiDisplayName()
.invoke('val', '') // clear() doesn't seem to work here for some reason
.type(data.pluginName);
Expand All @@ -27,6 +30,15 @@ class UserMigrationProviderPage {
this.elements.saveBtn().click()
cy.wait('@savePlugin').its('response.statusCode').should('be.oneOf', [201, 204]);
}

removeImportedUsers() {
this.elements.actionDropdown().click();
this.elements.actionDropdownRemoveImportedBtn().click();
cy.intercept('POST', '/admin/realms/master/user-storage/*/remove-imported-users')
.as('removeImportedUsers')
this.elements.modalConfirmButton().click();
cy.wait('@removeImportedUsers');
}
}

module.exports = new UserMigrationProviderPage();
8 changes: 4 additions & 4 deletions docker/e2e/cypress/e2e/pages/usersPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ class UsersPage {
}

visit() {
cy.intercept('/admin/realms/master/components*')
.as("components")
cy.intercept('admin/realms/master/ui-ext/brute-force-user*')
.as("userList")
cy.visit('/admin/master/console/#/master/users');
cy.wait('@components');
cy.wait('@userList');
}

goToUserDetails(userName) {
Expand Down Expand Up @@ -68,7 +68,7 @@ class UsersPage {

assertUserWasDeleted(userName) {
return this.findByName(userName)
.then((() => this.elements.foundUserTable().should('not.exist')))
.then((() => this.elements.foundUserTable(userName).should('not.exist')))
.then((() => this.elements.emptySearchResultsText().should('exist')));
}
}
Expand Down
13 changes: 12 additions & 1 deletion docker/e2e/cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Cypress.Commands.add("setupKeycloak", () => {
userFederationPage.visit();
userFederationPage.removePluginIfExists();
userFederationPage.goToUserMigrationPluginPage();
userMigrationProviderPage.addPlugin(data.legacySystem.url);
userMigrationProviderPage.configurePlugin(data.legacySystem.url);
}

/**
Expand Down Expand Up @@ -88,10 +88,21 @@ Cypress.Commands.add("resetState", () => {
}

function deleteTestUserIfExists() {
removeImportedUsers();
usersPage.visit();
return usersPage.deleteUserIfExists(data.legacyUser.username);
}

/*
Users which still have a federation link (e.g. they have been imported but have not successfully logged in yet)
cannot be removed using normal means. The "Remove imported users" option on the User Federation Plugin page
removes those users (but will not remove users for whom the federation link has already been severed).
*/
function removeImportedUsers() {
userMigrationProviderPage.visit();
userMigrationProviderPage.removeImportedUsers();
}

function deletePasswordPoliciesIfExist() {
passwordPoliciesPage.visit();
return passwordPoliciesPage.deleteEveryPasswordPolicy();
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<version>5.1.1-SNAPSHOT</version>
<properties>
<java.version>21</java.version>
<keycloak.version>25.0.0</keycloak.version>
<keycloak.version>26.0.5</keycloak.version>
<mockito.version>5.14.2</mockito.version>

<maven.compiler.target>${java.version}</maven.compiler.target>
Expand Down

0 comments on commit 6d6bd34

Please sign in to comment.