Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docs/documentation/server_admin/topics/threat/scope.adoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@

=== Limiting scope

By default, new client applications have unlimited `role scope mappings`. Every access token for that client contains all permissions that the user has. If an attacker compromises the client and obtains the client's access tokens, each system that the user can access is compromised.
==== Scope availability

By default, new client applications have unlimited `role scope mappings`. Every access token for that client contains all permissions that the user has. If an attacker compromises the client and obtains the client's access tokens, each system that the user can access is compromised.

Limit the roles of an access token by using the <<_role_scope_mappings, Scope menu>> for each client. Alternatively, you can set role scope mappings at the Client Scope level and assign Client Scopes to your client by using the <<_client_scopes_linking, Client Scope menu>>.

Removing the offline scope for a client also removes the ability to issue long-lived offline tokens for a client and offers better control over sessions by users.

==== Scope visibility

By default, all scopes are included in the OpenID Connect discovery endpoint.
To reduce the discoverability and OSINT-exposure, you can configure each scope to be excluded.
Original file line number Diff line number Diff line change
Expand Up @@ -3542,6 +3542,8 @@ oid4vciEnabled=Enable OID4VCI
oid4vciEnabledHelp=Enable this option to allow the client to request verifiable credentials from Keycloak's OID4VCI credential endpoint.
noAccessPolicies=No access policies
noAccessPoliciesInstructions=There haven't been configured any access policies yet. Click the button below to configure the first policy.
includeInOpenIdProviderMetadata=Include in OpenID Provider Metadata
includeInOpenIdProviderMetadataHelp=If on, this client scope will be included in OpenID Provider Metadata.
# standard error responses OAuth
invalid_request=Invalid request
unauthorized_client=Unauthorized client
Expand Down Expand Up @@ -3604,4 +3606,4 @@ changeStatusTooltip=Enable or disable this workflow
workflowEnabled=Workflow enabled
workflowDisabled=Workflow disabled
workflowUpdated=Workflow updated successfully
workflowUpdateError=Could not update the workflow\: {{error}}
workflowUpdateError=Could not update the workflow\: {{error}}
12 changes: 12 additions & 0 deletions js/apps/admin-ui/src/client-scopes/details/ScopeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {

const isOid4vcProtocol = selectedProtocol === OID4VC_PROTOCOL;
const isOid4vcEnabled = isFeatureEnabled(Feature.OpenId4VCI);
const isNotSaml = selectedProtocol != "saml";

const setDynamicRegex = (value: string, append: boolean) =>
setValue(
Expand Down Expand Up @@ -190,6 +191,17 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
labelIcon={t("includeInTokenScopeHelp")}
stringify
/>
{isNotSaml && (
<DefaultSwitchControl
name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.include.in.openid.provider.metadata",
)}
defaultValue="true"
label={t("includeInOpenIdProviderMetadata")}
labelIcon={t("includeInOpenIdProviderMetadataHelp")}
stringify
/>
)}
<TextControl
name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.gui.order",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,16 @@ public String getDynamicScopeRegexp() {
return getDelegate().getDynamicScopeRegexp();
}

@Override
public boolean isIncludeInOpenIDProviderMetadata() {
return getDelegate().isIncludeInOpenIDProviderMetadata();
}

@Override
public void setIncludeInOpenIDProviderMetadata(boolean includeInOpenIDProviderMetadata) {
getDelegate().setIncludeInOpenIDProviderMetadata(includeInOpenIDProviderMetadata);
}

@Override
public Stream<RoleModel> getScopeMappingsStream() {
return getDelegate().getScopeMappingsStream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ interface ClientScopeCreatedEvent extends ProviderEvent {
String INCLUDE_IN_TOKEN_SCOPE = "include.in.token.scope";
String IS_DYNAMIC_SCOPE = "is.dynamic.scope";
String DYNAMIC_SCOPE_REGEXP = "dynamic.scope.regexp";
String INCLUDE_IN_OPENID_PROVIDER_METADATA = "include.in.openid.provider.metadata";

default boolean isDisplayOnConsentScreen() {
String displayVal = getAttribute(DISPLAY_ON_CONSENT_SCREEN);
Expand Down Expand Up @@ -125,4 +126,13 @@ default void setIsDynamicScope(boolean isDynamicScope) {
default String getDynamicScopeRegexp() {
return getAttribute(DYNAMIC_SCOPE_REGEXP);
}

default boolean isIncludeInOpenIDProviderMetadata() {
String includeInOpenIDProviderMetadata = getAttribute(INCLUDE_IN_OPENID_PROVIDER_METADATA);
return includeInOpenIDProviderMetadata == null ? true : Boolean.parseBoolean(includeInOpenIDProviderMetadata);
}

default void setIncludeInOpenIDProviderMetadata(boolean includeInOpenIDProviderMetadata) {
setAttribute(INCLUDE_IN_OPENID_PROVIDER_METADATA, String.valueOf(includeInOpenIDProviderMetadata));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public Object getConfig() {
// Include client scopes can be disabled in the environments with thousands of client scopes to avoid potentially expensive iteration over client scopes
if (includeClientScopes) {
List<String> scopeNames = realm.getClientScopesStream()
.filter(clientScope -> Objects.equals(OIDCLoginProtocol.LOGIN_PROTOCOL, clientScope.getProtocol()))
.filter(clientScope -> Objects.equals(OIDCLoginProtocol.LOGIN_PROTOCOL, clientScope.getProtocol()) && clientScope.isIncludeInOpenIDProviderMetadata())
.map(ClientScopeModel::getName)
.collect(Collectors.toList());
if (!scopeNames.contains(OAuth2Constants.SCOPE_OPENID)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@
import org.keycloak.http.simple.SimpleHttpResponse;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.Constants;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.representations.MTLSEndpointAliases;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.clientregistration.ClientRegistrationService;
import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
Expand All @@ -51,6 +54,7 @@
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AbstractAdminTest;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.broker.util.SimpleHttpDefault;
import org.keycloak.testsuite.forms.BrowserFlowTest;
import org.keycloak.testsuite.forms.LevelOfAssuranceFlowTest;
Expand All @@ -60,13 +64,16 @@
import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse;
import org.keycloak.testsuite.util.oauth.OAuthClient;
import org.keycloak.testsuite.util.TokenSignatureUtil;
import org.keycloak.testsuite.wellknown.CustomOIDCWellKnownProviderFactory;
import org.keycloak.util.JsonSerialization;

import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static jakarta.ws.rs.core.HttpHeaders.ACCEPT;
import static jakarta.ws.rs.core.HttpHeaders.CONTENT_TYPE;
Expand Down Expand Up @@ -388,6 +395,55 @@ public void testDpopSigningAlgValuesSupportedWithDpop() throws IOException {
}
}

@Test
public void testDefaultProviderCustomizations() throws IOException {
Client client = AdminClientUtil.createResteasyClient();
String showScopeId = null;
String hideScopeId = null;
try {
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);

// Exact names already tested in OIDC
assertScopesSupportedMatchesWithRealm(oidcConfig);

//create 2 client scope - one with hideFromOpenIDProviderMetadata equal to true
ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
clientScope.setName("show-scope");
clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
Response resp = adminClient.realm("test").clientScopes().create(clientScope);
showScopeId = ApiUtil.getCreatedId(resp);
resp.close();

ClientScopeRepresentation clientScope2 = new ClientScopeRepresentation();
clientScope2.setName("hidden-scope");
clientScope2.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
Map<String,String> attributes = new HashMap<>();
attributes.put(ClientScopeModel.INCLUDE_IN_OPENID_PROVIDER_METADATA,"false");
clientScope2.setAttributes(attributes);
Response resp2 = adminClient.realm("test").clientScopes().create(clientScope2);
hideScopeId = ApiUtil.getCreatedId(resp2);
resp2.close();

List<String> expectedScopeList = Stream.of(OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS,
OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_PHONE, OAuth2Constants.SCOPE_ADDRESS, OIDCLoginProtocolFactory.ACR_SCOPE, OIDCLoginProtocolFactory.BASIC_SCOPE,
OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE, OAuth2Constants.ORGANIZATION,
ServiceAccountConstants.SERVICE_ACCOUNT_SCOPE, "show-scope").collect(Collectors.toList());
oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
assertScopesSupportedMatchesWithRealm(oidcConfig, expectedScopeList);
} finally {
getTestingClient().testing().setSystemPropertyOnServer(CustomOIDCWellKnownProviderFactory.INCLUDE_CLIENT_SCOPES, null);
if ( showScopeId != null)
adminClient.realm("test").clientScopes().get(showScopeId).remove();
if ( hideScopeId != null)
adminClient.realm("test").clientScopes().get(hideScopeId).remove();
client.close();
}
}

private void assertScopesSupportedMatchesWithRealm(OIDCConfigurationRepresentation oidcConfig, List<String> expectedScopeList) {
Assert.assertNames(oidcConfig.getScopesSupported(), expectedScopeList.toArray(new String[expectedScopeList.size()]) );
}

protected void assertScopesSupportedMatchesWithRealm(OIDCConfigurationRepresentation oidcConfig) {
Assert.assertNames(oidcConfig.getScopesSupported(), OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS,
OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_PHONE, OAuth2Constants.SCOPE_ADDRESS, OIDCLoginProtocolFactory.ACR_SCOPE, OIDCLoginProtocolFactory.BASIC_SCOPE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,7 @@

package org.keycloak.testsuite.oidc;

import jakarta.ws.rs.client.Client;
import org.junit.Test;
import org.keycloak.protocol.oidc.OIDCWellKnownProviderFactory;
import org.keycloak.protocol.oidc.representations.MTLSEndpointAliases;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.oauth.OAuthClient;
import org.keycloak.testsuite.wellknown.CustomOIDCWellKnownProviderFactory;

import java.io.IOException;
import java.util.Map;

/**
* @author <a href="mailto:[email protected]">Marek Posolda</a>
Expand All @@ -39,33 +28,4 @@ protected String getWellKnownProviderId() {
return OIDCWellKnownProviderFactory.PROVIDER_ID;
}

@Test
public void testDefaultProviderCustomizations() throws IOException {
Client client = AdminClientUtil.createResteasyClient();
try {
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);

// Assert that CustomOIDCWellKnownProvider was used as a prioritized provider over default OIDCWellKnownProvider
MTLSEndpointAliases mtlsEndpointAliases = oidcConfig.getMtlsEndpointAliases();
Assert.assertEquals("https://placeholder-host-set-by-testsuite-provider/registration", mtlsEndpointAliases.getRegistrationEndpoint());
Assert.assertEquals("bar", oidcConfig.getOtherClaims().get("foo"));

// Assert some configuration was overriden
Assert.assertEquals("some-new-property-value", oidcConfig.getOtherClaims().get("some-new-property"));
Assert.assertEquals("nested-value", ((Map) oidcConfig.getOtherClaims().get("some-new-property-compound")).get("nested1"));
Assert.assertNames(oidcConfig.getIntrospectionEndpointAuthMethodsSupported(), "private_key_jwt", "client_secret_jwt", "tls_client_auth", "custom_nonexisting_authenticator");

// Exact names already tested in OIDC
assertScopesSupportedMatchesWithRealm(oidcConfig);

// Temporarily disable client scopes
getTestingClient().testing().setSystemPropertyOnServer(CustomOIDCWellKnownProviderFactory.INCLUDE_CLIENT_SCOPES, "false");
oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
Assert.assertNull(oidcConfig.getScopesSupported());
} finally {
getTestingClient().testing().setSystemPropertyOnServer(CustomOIDCWellKnownProviderFactory.INCLUDE_CLIENT_SCOPES, null);
client.close();
}
}

}
Loading