Add test for user invite registration and fix minor bug with registration link generation and email templating

Signed-off-by: Alice W <105500542+alice-wondered@users.noreply.github.com>
This commit is contained in:
Alice W 2024-05-01 15:02:19 -04:00 committed by Pedro Igor
parent e0bdb42d41
commit 18356761db
4 changed files with 71 additions and 87 deletions

View file

@ -60,6 +60,7 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
protected FreeMarkerProvider freeMarker; protected FreeMarkerProvider freeMarker;
protected RealmModel realm; protected RealmModel realm;
protected UserModel user; protected UserModel user;
protected String userEmail;
protected final Map<String, Object> attributes = new HashMap<>(); protected final Map<String, Object> attributes = new HashMap<>();
public FreeMarkerEmailTemplateProvider(KeycloakSession session) { public FreeMarkerEmailTemplateProvider(KeycloakSession session) {

View file

@ -63,6 +63,7 @@ import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.UserResource; import org.keycloak.services.resources.admin.UserResource;
import org.keycloak.services.resources.admin.UsersResource; import org.keycloak.services.resources.admin.UsersResource;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.storage.adapter.InMemoryUserAdapter;
import org.keycloak.utils.StringUtil; import org.keycloak.utils.StringUtil;
@Provider @Provider
@ -125,9 +126,6 @@ public class OrganizationMemberResource {
UserModel user = session.users().getUserByEmail(realm, rep.getEmail()); UserModel user = session.users().getUserByEmail(realm, rep.getEmail());
InviteOrgActionToken token = null; InviteOrgActionToken token = null;
// TODO not sure if this client id is right or if we should get one from the user somehow...
// TODO not really sure if the token is getting signed so we need to figure out where that's happening... maybe in the serialize method?
// TODO the expiration is set to a day in seconds but we should probably get this from configuration instead
String link = null; String link = null;
int tokenExpiration = Time.currentTime() + realm.getActionTokenGeneratedByAdminLifespan(); int tokenExpiration = Time.currentTime() + realm.getActionTokenGeneratedByAdminLifespan();
if (user != null) { if (user != null) {
@ -138,13 +136,18 @@ public class OrganizationMemberResource {
.build(realm.getName()).toString(); .build(realm.getName()).toString();
} else { } else {
// this path lets us invite a user that doesn't exist yet, letting them register into the organization // this path lets us invite a user that doesn't exist yet, letting them register into the organization
token = new InviteOrgActionToken(null, tokenExpiration, rep.getEmail(), session.getContext().getClient().getClientId()); token = new InviteOrgActionToken(null, tokenExpiration, rep.getEmail(), Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
token.setOrgId(organization.getId()); token.setOrgId(organization.getId());
link = LoginActionsService.registrationFormProcessor(session.getContext().getUri()) link = LoginActionsService.registrationFormProcessor(session.getContext().getUri())
.queryParam(Constants.ORG_TOKEN, token.serialize(session, realm, session.getContext().getUri())) .queryParam(Constants.ORG_TOKEN, token.serialize(session, realm, session.getContext().getUri()))
.build(realm.getName()).toString(); .build(realm.getName()).toString();
} }
if (user == null ) {
user = new InMemoryUserAdapter(session, realm, null);
user.setEmail(rep.getEmail());
}
try { try {
session session
.getProvider(EmailTemplateProvider.class) .getProvider(EmailTemplateProvider.class)

View file

@ -19,9 +19,12 @@ package org.keycloak.testsuite.organization.admin;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -31,13 +34,23 @@ import org.jboss.arquillian.graphene.page.Page;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.OrganizationResource; import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.authentication.requiredactions.TermsAndConditions;
import org.keycloak.common.Profile.Feature; import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.Time;
import org.keycloak.common.util.UriUtils;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.InfoPage; import org.keycloak.testsuite.pages.InfoPage;
import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.util.GreenMailRule; import org.keycloak.testsuite.util.GreenMailRule;
import org.keycloak.testsuite.util.MailUtils; import org.keycloak.testsuite.util.MailUtils;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
@ -45,12 +58,18 @@ import org.keycloak.testsuite.util.UserBuilder;
@EnableFeature(Feature.ORGANIZATION) @EnableFeature(Feature.ORGANIZATION)
public class OrganizationInvitationLinkTest extends AbstractOrganizationTest { public class OrganizationInvitationLinkTest extends AbstractOrganizationTest {
@Rule
public AssertEvents events = new AssertEvents(this);
@Rule @Rule
public GreenMailRule greenMail = new GreenMailRule(); public GreenMailRule greenMail = new GreenMailRule();
@Page @Page
protected InfoPage infoPage; protected InfoPage infoPage;
@Page
protected RegisterPage registerPage;
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
Map<String, String> smtpConfig = testRealm.getSmtpServer(); Map<String, String> smtpConfig = testRealm.getSmtpServer();
@ -84,4 +103,48 @@ public class OrganizationInvitationLinkTest extends AbstractOrganizationTest {
assertThat(infoPage.getInfo(), containsString("Your account has been updated.")); assertThat(infoPage.getInfo(), containsString("Your account has been updated."));
Assert.assertTrue(organization.members().getAll().stream().anyMatch(actual -> user.getId().equals(actual.getId()))); Assert.assertTrue(organization.members().getAll().stream().anyMatch(actual -> user.getId().equals(actual.getId())));
} }
@Test
public void testInviteNewUserRegistration() throws IOException {
UserRepresentation user = UserBuilder.create()
.username("invitedUser")
.email("inviteduser@email")
.enabled(true)
.build();
// User isn't created when we send the invite
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
organization.members().inviteMember(user).close();
MimeMessage message = greenMail.getLastReceivedMessage();
Assert.assertNotNull(message);
String link = MailUtils.getPasswordResetEmailLink(message);
String orgToken = UriUtils.parseQueryParameters(link, false).values().stream().map(strings -> strings.get(0)).findFirst().orElse(null);
Assert.assertNotNull(orgToken);
driver.manage().timeouts().pageLoadTimeout(1, TimeUnit.DAYS);
driver.navigate().to(link.trim());
Assert.assertFalse(organization.members().getAll().stream().anyMatch(actual -> user.getId().equals(actual.getId())));
registerPage.assertCurrent();
registerPage.register("firstName", "lastName", "inviteduser@myemail",
"invitedUser", "password", "password", null, true, null);
assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
String userId = events.expectRegister("invitedUser", "inviteduser@email")
.assertEvent().getUserId();
UserRepresentation registeredUser = assertUserRegistered(userId, "invitedUser");
Assert.assertTrue(organization.members().getAll().stream().anyMatch(actual -> registeredUser.getId().equals(actual.getId())));
}
private UserRepresentation assertUserRegistered(String userId, String username) {
events.expectLogin().detail("username", username.toLowerCase()).user(userId).assertEvent();
UserRepresentation user = testRealm().users().get(userId).toRepresentation();
org.junit.Assert.assertNotNull(user);
org.junit.Assert.assertNotNull(user.getCreatedTimestamp());
return user;
}
} }

View file

@ -33,7 +33,6 @@ import static org.keycloak.models.OrganizationModel.ORGANIZATION_ATTRIBUTE;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
@ -296,86 +295,4 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
assertFalse(testRealm().groups().groups().stream().anyMatch(group -> group.getName().startsWith("kc.org."))); assertFalse(testRealm().groups().groups().stream().anyMatch(group -> group.getName().startsWith("kc.org.")));
} }
@Test
public void testSearchMembers() {
// create test users, ordered by username (e-mail).
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
List<UserRepresentation> expected = new ArrayList<>();
expected.add(addMember(organization, "batwoman@neworg.org", "Katherine", "Kane"));
expected.add(addMember(organization, "brucewayne@neworg.org", "Bruce", "Wayne"));
expected.add(addMember(organization, "harveydent@neworg.org", "Harvey", "Dent"));
expected.add(addMember(organization, "marthaw@neworg.org", "Martha", "Wayne"));
expected.add(addMember(organization, "thejoker@neworg.org", "Jack", "White"));
// exact search - username/e-mail, first name, last name.
List<UserRepresentation> existing = organization.members().search("brucewayne@neworg.org", true, null, null);
assertThat(existing, hasSize(1));
assertThat(existing.get(0).getUsername(), is(equalTo("brucewayne@neworg.org")));
assertThat(existing.get(0).getEmail(), is(equalTo("brucewayne@neworg.org")));
assertThat(existing.get(0).getFirstName(), is(equalTo("Bruce")));
assertThat(existing.get(0).getLastName(), is(equalTo("Wayne")));
existing = organization.members().search("Harvey", true, null, null);
assertThat(existing, hasSize(1));
assertThat(existing.get(0).getUsername(), is(equalTo("harveydent@neworg.org")));
assertThat(existing.get(0).getEmail(), is(equalTo("harveydent@neworg.org")));
assertThat(existing.get(0).getFirstName(), is(equalTo("Harvey")));
assertThat(existing.get(0).getLastName(), is(equalTo("Dent")));
existing = organization.members().search("Wayne", true, null, null);
assertThat(existing, hasSize(2));
assertThat(existing.get(0).getUsername(), is(equalTo("brucewayne@neworg.org")));
assertThat(existing.get(1).getUsername(), is(equalTo("marthaw@neworg.org")));
existing = organization.members().search("Gordon", true, null, null);
assertThat(existing, is(empty()));
// partial search - partial e-mail should match all users.
existing = organization.members().search("neworg", false, null, null);
assertThat(existing, hasSize(5));
for (int i = 0; i < 5; i++) { // returned entries should also be ordered.
assertThat(expected.get(i).getId(), is(equalTo(expected.get(i).getId())));
assertThat(expected.get(i).getUsername(), is(equalTo(expected.get(i).getUsername())));
assertThat(expected.get(i).getEmail(), is(equalTo(expected.get(i).getEmail())));
assertThat(expected.get(i).getFirstName(), is(equalTo(expected.get(i).getFirstName())));
assertThat(expected.get(i).getLastName(), is(equalTo(expected.get(i).getLastName())));
}
// partial search using 'th' search string - should match 'Katherine' by name, 'Jack' by username/e-mail
// and 'Martha' either by username or first name.
existing = organization.members().search("th", false, null, null);
assertThat(existing, hasSize(3));
assertThat(existing.get(0).getUsername(), is(equalTo("batwoman@neworg.org")));
assertThat(existing.get(0).getFirstName(), is(equalTo("Katherine")));
assertThat(existing.get(1).getUsername(), is(equalTo("marthaw@neworg.org")));
assertThat(existing.get(1).getFirstName(), is(equalTo("Martha")));
assertThat(existing.get(2).getUsername(), is(equalTo("thejoker@neworg.org")));
assertThat(existing.get(2).getFirstName(), is(equalTo("Jack")));
// partial search using 'way' - should match both 'Bruce' (either by username or last name) and 'Martha' by last name.
existing = organization.members().search("way", false, null, null);
assertThat(existing, hasSize(2));
assertThat(existing.get(0).getUsername(), is(equalTo("brucewayne@neworg.org")));
assertThat(existing.get(0).getFirstName(), is(equalTo("Bruce")));
assertThat(existing.get(1).getUsername(), is(equalTo("marthaw@neworg.org")));
assertThat(existing.get(1).getFirstName(), is(equalTo("Martha")));
// partial search using with no match - e.g. 'nonexistent'.
existing = organization.members().search("nonexistent", false, null, null);
assertThat(existing, is(empty()));
// paginated search - try to fetch 3 users per page.
existing = organization.members().search("", false, 0, 3);
assertThat(existing, hasSize(3));
assertThat(existing.get(0).getUsername(), is(equalTo("batwoman@neworg.org")));
assertThat(existing.get(1).getUsername(), is(equalTo("brucewayne@neworg.org")));
assertThat(existing.get(2).getUsername(), is(equalTo("harveydent@neworg.org")));
existing = organization.members().search("", false, 3, 3);
assertThat(existing, hasSize(2));
assertThat(existing.get(0).getUsername(), is(equalTo("marthaw@neworg.org")));
assertThat(existing.get(1).getUsername(), is(equalTo("thejoker@neworg.org")));
}
} }