mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-26 13:50:48 +00:00
Disabled organization should not execute invitations
Closes #45760 Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
@@ -49,6 +49,7 @@ public interface Errors {
|
||||
String EMAIL_IN_USE = "email_in_use";
|
||||
String EMAIL_ALREADY_VERIFIED = "email_already_verified";
|
||||
String ORG_NOT_FOUND = "org_not_found";
|
||||
String ORG_DISABLED = "org_disabled";
|
||||
String USER_ORG_MEMBER_ALREADY = "user_org_member_already";
|
||||
|
||||
String INVALID_REDIRECT_URI = "invalid_redirect_uri";
|
||||
|
||||
+25
@@ -84,6 +84,10 @@ public class InviteOrgActionTokenHandler extends AbstractActionTokenHandler<Invi
|
||||
return invalidOrganizationResponse(tokenContext, token);
|
||||
}
|
||||
|
||||
if (!organization.isEnabled()) {
|
||||
return disabledOrganizationResponse(tokenContext, token);
|
||||
}
|
||||
|
||||
InvitationManager invitationManager = orgProvider.getInvitationManager();
|
||||
OrganizationInvitationModel invitation = invitationManager.getById(token.getId());
|
||||
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
|
||||
@@ -121,6 +125,10 @@ public class InviteOrgActionTokenHandler extends AbstractActionTokenHandler<Invi
|
||||
return invalidOrganizationResponse(tokenContext, token);
|
||||
}
|
||||
|
||||
if (!organization.isEnabled()) {
|
||||
return disabledOrganizationResponse(tokenContext, token);
|
||||
}
|
||||
|
||||
if (organization.isMember(user)) {
|
||||
return alreadyMemberResponse(organization, user, tokenContext, token);
|
||||
}
|
||||
@@ -206,6 +214,23 @@ public class InviteOrgActionTokenHandler extends AbstractActionTokenHandler<Invi
|
||||
.createInfoPage();
|
||||
}
|
||||
|
||||
private Response disabledOrganizationResponse(ActionTokenContext<InviteOrgActionToken> tokenContext, InviteOrgActionToken token) {
|
||||
EventBuilder event = tokenContext.getEvent();
|
||||
KeycloakSession session = tokenContext.getSession();
|
||||
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
|
||||
|
||||
event.detail(Details.TOKEN_ID, token.getId())
|
||||
.detail(Details.EMAIL, token.getEmail())
|
||||
.detail(Details.ORG_ID, token.getOrgId())
|
||||
.error(Errors.ORG_DISABLED);
|
||||
return session.getProvider(LoginFormsProvider.class)
|
||||
.setStatus(Status.BAD_REQUEST)
|
||||
.setAuthenticationSession(authSession)
|
||||
.setAttribute("messageHeader", Messages.STALE_INVITE_ORG_LINK)
|
||||
.setInfo(Messages.ORG_DISABLED)
|
||||
.createInfoPage();
|
||||
}
|
||||
|
||||
private Response alreadyMemberResponse(OrganizationModel organization, UserModel user, ActionTokenContext<InviteOrgActionToken> tokenContext, InviteOrgActionToken token) {
|
||||
EventBuilder event = tokenContext.getEvent();
|
||||
KeycloakSession session = tokenContext.getSession();
|
||||
|
||||
@@ -319,6 +319,11 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!organization.isEnabled()) {
|
||||
error.accept(List.of(new FormMessage("The organization is not available at this time.")));
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure the organization is set to the session so that UP org-related validators can run
|
||||
session.getContext().setOrganization(organization);
|
||||
session.setAttribute(InviteOrgActionToken.class.getName(), token);
|
||||
|
||||
+12
@@ -94,6 +94,10 @@ public class OrganizationInvitationResource {
|
||||
}
|
||||
|
||||
public Response inviteUser(String email, String firstName, String lastName) {
|
||||
if (!organization.isEnabled()) {
|
||||
throw ErrorResponse.error("Organization is disabled", Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (StringUtil.isBlank(email)) {
|
||||
throw ErrorResponse.error("Email is required to invite a member", Status.BAD_REQUEST);
|
||||
}
|
||||
@@ -138,6 +142,10 @@ public class OrganizationInvitationResource {
|
||||
}
|
||||
|
||||
public Response inviteExistingUser(String id) {
|
||||
if (!organization.isEnabled()) {
|
||||
throw ErrorResponse.error("Organization is disabled", Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (StringUtil.isBlank(id)) {
|
||||
throw new BadRequestException("To invite a member you need to provide the user id");
|
||||
}
|
||||
@@ -317,6 +325,10 @@ public class OrganizationInvitationResource {
|
||||
@APIResponse(responseCode = "404", description = "Not Found")
|
||||
})
|
||||
public Response resendInvitation(@PathParam("id") String id) {
|
||||
if (!organization.isEnabled()) {
|
||||
throw ErrorResponse.error("Organization is disabled", Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
||||
InvitationManager invitationManager = provider.getInvitationManager();
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ public class Messages {
|
||||
|
||||
public static final String ORG_NOT_FOUND = "orgNotFoundMessage";
|
||||
|
||||
public static final String ORG_DISABLED = "orgDisabledMessage";
|
||||
|
||||
public static final String ORG_MEMBER_ALREADY = "orgMemberAlready";
|
||||
|
||||
public static final String INVALID_ORG_INVITE = "invalidOrgInviteMessage";
|
||||
|
||||
+5
@@ -29,4 +29,9 @@ public class OrganizationAttributeUpdater extends ServerResourceUpdater<Organiza
|
||||
this.rep.setRedirectUrl(redirectUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrganizationAttributeUpdater setEnabled(boolean enabled) {
|
||||
this.rep.setEnabled(enabled);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
+42
@@ -545,6 +545,48 @@ public class OrganizationInvitationLinkTest extends AbstractOrganizationTest {
|
||||
Assert.assertNotNull(organization.members().member(user.getId()).toRepresentation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptInvitationLinkAfterOrgDisabled() throws IOException, MessagingException {
|
||||
UserRepresentation user = createUser("invited", "invited@myemail.com");
|
||||
|
||||
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||
|
||||
organization.members().inviteExistingUser(user.getId()).close();
|
||||
|
||||
String link = getInvitationLinkFromEmail(user.getFirstName(), user.getLastName());
|
||||
|
||||
// Disable the organization after the invitation was sent
|
||||
try (OrganizationAttributeUpdater oau = new OrganizationAttributeUpdater(organization).setEnabled(false).update()) {
|
||||
driver.navigate().to(link);
|
||||
assertThat(infoPage.isCurrent(), is(true));
|
||||
assertThat(infoPage.getInfo(), containsString("The organization is not available at this time and cannot accept new members."));
|
||||
|
||||
// User should not be added to organization
|
||||
List<MemberRepresentation> members = organization.members().search(user.getEmail(), Boolean.TRUE, null, null);
|
||||
assertThat(members, empty());
|
||||
}
|
||||
|
||||
// After re-enabling, the link should work again
|
||||
acceptInvitation(organization, user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewUserRegistrationAfterOrgDisabled() throws IOException, MessagingException {
|
||||
String email = "inviteduser@email";
|
||||
|
||||
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||
organization.members().inviteUser(email, "Homer", "Simpson").close();
|
||||
|
||||
String link = getInvitationLinkFromEmail();
|
||||
|
||||
// Disable the organization after the invitation was sent
|
||||
try (OrganizationAttributeUpdater oau = new OrganizationAttributeUpdater(organization).setEnabled(false).update()) {
|
||||
driver.navigate().to(link);
|
||||
// Registration page should show error about disabled org
|
||||
assertThat(driver.getPageSource(), containsString("The organization is not available at this time and cannot accept new members."));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvitationLinkAfterInvitationDeleted() throws IOException, MessagingException {
|
||||
String email = "inviteduser@email";
|
||||
|
||||
+42
@@ -28,6 +28,7 @@ import org.keycloak.admin.client.resource.OrganizationResource;
|
||||
import org.keycloak.representations.idm.OrganizationInvitationRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.updaters.OrganizationAttributeUpdater;
|
||||
import org.keycloak.testsuite.util.MailServer;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -382,6 +383,47 @@ public class OrganizationInvitationManagementTest extends AbstractOrganizationTe
|
||||
assertThat(org2Invitations.get(0).getOrganizationId(), equalTo(org2Rep.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendInvitationToDisabledOrganization() throws Exception {
|
||||
try (OrganizationAttributeUpdater oau = new OrganizationAttributeUpdater(organization).setEnabled(false).update()) {
|
||||
try (Response response = organization.members().inviteUser("user@test-org.com", "John", "Doe")) {
|
||||
assertThat(response.getStatus(), equalTo(400));
|
||||
assertThat(response.readEntity(String.class), containsString("Organization is disabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResendInvitationToDisabledOrganization() throws Exception {
|
||||
sendInvitation("user@test-org.com", "John", "Doe");
|
||||
|
||||
List<OrganizationInvitationRepresentation> invitations = organization.invitations().list();
|
||||
assertThat(invitations, hasSize(1));
|
||||
String invitationId = invitations.get(0).getId();
|
||||
|
||||
try (OrganizationAttributeUpdater oau = new OrganizationAttributeUpdater(organization).setEnabled(false).update()) {
|
||||
try (Response response = organization.invitations().resend(invitationId)) {
|
||||
assertThat(response.getStatus(), equalTo(400));
|
||||
assertThat(response.readEntity(String.class), containsString("Organization is disabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvitationWorksAfterReEnablingOrganization() throws Exception {
|
||||
try (OrganizationAttributeUpdater oau = new OrganizationAttributeUpdater(organization).setEnabled(false).update()) {
|
||||
try (Response response = organization.members().inviteUser("user@test-org.com", "John", "Doe")) {
|
||||
assertThat(response.getStatus(), equalTo(400));
|
||||
}
|
||||
}
|
||||
|
||||
// After re-enabling (OrganizationAttributeUpdater restores original state), invitation should work
|
||||
sendInvitation("user@test-org.com", "John", "Doe");
|
||||
List<OrganizationInvitationRepresentation> invitations = organization.invitations().list();
|
||||
assertThat(invitations, hasSize(1));
|
||||
assertThat(invitations.get(0).getEmail(), equalTo("user@test-org.com"));
|
||||
}
|
||||
|
||||
private void sendInvitation(String email, String firstName, String lastName) {
|
||||
sendInvitationToOrganization(organization, email, firstName, lastName);
|
||||
}
|
||||
|
||||
@@ -566,4 +566,5 @@ notMemberOfOrganization=User is not a member of the organization {0}
|
||||
notMemberOfAnyOrganization=User is not a member of any organization
|
||||
emailVerificationPending=A verification email was sent to {0}. You can submit without changes to resend the verification email, or enter a different email address.
|
||||
orgMemberAlready=You are already a member of the {1} organization.
|
||||
orgDisabledMessage=The organization is not available at this time and cannot accept new members.
|
||||
staleInviteOrgLink=The link you clicked is no longer valid. It may have expired or already been used.
|
||||
|
||||
Reference in New Issue
Block a user