mirror of
https://github.com/keycloak/keycloak.git
synced 2026-02-03 20:39:33 -05:00
Merge 7f7599c1e3 into 1f0fceb867
This commit is contained in:
commit
f9fc461ba3
3 changed files with 34 additions and 16 deletions
|
|
@ -75,22 +75,14 @@ public enum OrganizationScope {
|
|||
*/
|
||||
SINGLE(StringUtil::isNotBlank,
|
||||
(user, scopes, session) -> {
|
||||
OrganizationModel organization = parseScopeParameter(session, scopes)
|
||||
List<OrganizationModel> organizations = parseScopeParameter(session, scopes)
|
||||
.map((String scope) -> parseScopeValue(session, scope))
|
||||
.map(alias -> getProvider(session).getByAlias(alias))
|
||||
.filter(Objects::nonNull)
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
.filter(org -> user == null || org.isMember(user))
|
||||
.toList();
|
||||
|
||||
if (organization == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
if (user == null || organization.isMember(user)) {
|
||||
return Stream.of(organization);
|
||||
}
|
||||
|
||||
return Stream.empty();
|
||||
return organizations.stream();
|
||||
},
|
||||
(organizations) -> organizations.findAny().isPresent(),
|
||||
(session, current, previous) -> {
|
||||
|
|
|
|||
|
|
@ -723,10 +723,21 @@ public class TokenManager {
|
|||
|
||||
Collection<String> rawScopes = TokenManager.parseScopeParameter(scopes).collect(Collectors.toSet());
|
||||
|
||||
// detect multiple organization scopes
|
||||
// validate organization scopes - allow multiple specific organization scopes, but reject mixed types
|
||||
if (Profile.isFeatureEnabled(Feature.ORGANIZATION)) {
|
||||
if (rawScopes.stream().filter(scope -> scope.startsWith(ORGANIZATION)).count() > 1) {
|
||||
return false;
|
||||
List<String> orgScopes = rawScopes.stream()
|
||||
.filter(scope -> scope.equals(ORGANIZATION) || scope.startsWith(ORGANIZATION + ":"))
|
||||
.toList();
|
||||
|
||||
if (orgScopes.size() > 1) {
|
||||
// multiple organization scopes - only allow if all are specific organizations (not ANY or ALL)
|
||||
boolean hasAnyScope = orgScopes.stream().anyMatch(ORGANIZATION::equals);
|
||||
boolean hasAllScope = orgScopes.stream().anyMatch(s -> s.equals(ORGANIZATION + ":*"));
|
||||
|
||||
if (hasAnyScope || hasAllScope) {
|
||||
// mixing ANY (organization) or ALL (organization:*) with other organization scopes is not allowed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMultipleOrganizationScopes() throws Exception {
|
||||
OrganizationResource orga = testRealm().organizations().get(createOrganization("org-a").getId());
|
||||
OrganizationResource orgb = testRealm().organizations().get(createOrganization("org-b").getId());
|
||||
|
|
@ -129,14 +130,28 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
Assert.assertTrue(orgb.members().list(-1, -1).stream().map(UserRepresentation::getId).anyMatch(member.getId()::equals));
|
||||
|
||||
oauth.clientId("test-app");
|
||||
oauth.scope("openid organization organization:org-a");
|
||||
|
||||
// Test multiple specific organization scopes - should return both organizations
|
||||
oauth.scope("openid organization:org-a organization:org-b");
|
||||
AccessTokenResponse response = oauth.doPasswordGrantRequest(memberEmail, memberPassword);
|
||||
Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode());
|
||||
AccessToken accessToken = TokenVerifier.create(response.getAccessToken(), AccessToken.class).getToken();
|
||||
List<String> organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
Assert.assertNotNull(organizations);
|
||||
Assert.assertTrue(organizations.contains("org-a"));
|
||||
Assert.assertTrue(organizations.contains("org-b"));
|
||||
|
||||
// Test organization + specific organization scope - should still fail (mixing ANY with SINGLE)
|
||||
oauth.scope("openid organization organization:org-a");
|
||||
response = oauth.doPasswordGrantRequest(memberEmail, memberPassword);
|
||||
Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
|
||||
|
||||
// Test organization + wildcard scope - should still fail (mixing ANY with ALL)
|
||||
oauth.scope("openid organization organization:*");
|
||||
response = oauth.doPasswordGrantRequest(memberEmail, memberPassword);
|
||||
Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
|
||||
|
||||
// Test specific organization + wildcard scope - should still fail (mixing SINGLE with ALL)
|
||||
oauth.scope("openid organization:org-a organization:*");
|
||||
response = oauth.doPasswordGrantRequest(memberEmail, memberPassword);
|
||||
Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
|
||||
|
|
|
|||
Loading…
Reference in a new issue