OPScopeFiltering

Overview

By default, only the scope values in the OIDC authentication request that are registered in the client's metadata are considered as allowed scope values. Other values are filtered out before they can be referred to in OIDCScope policy rule or included in the response messages.

This functionality can be customised by specifying a function shibboleth.oidc.AllowedScopeStrategy of type Function<ProfileRequestContext, Scope>. The scope returned by this function is used as the value of allowed scope: ie. all these values can be requested by the RPs without being filtered out.

The class ScopeUtil provides helper-methods for dealing with Scope values together with IdP attributes.

Example

The following snippet may be included for instance in conf/global.xml. It specifies the function shibboleth.oidc.AllowedScopeStrategy, in this case together with a HelperPair to be able to exploit the default function and attribute resolver service:

<bean id="shibboleth.oidc.AllowedScopeStrategy.HelperPair" class="net.shibboleth.utilities.java.support.collection.Pair" p:first-ref="shibboleth.AttributeResolverService" p:second-ref="shibboleth.oidc.DefaultAllowedScopeStrategy" /> <bean id="shibboleth.oidc.AllowedScopeStrategy" parent="shibboleth.Functions.Scripted" p:customObject-ref="shibboleth.oidc.AllowedScopeStrategy.HelperPair" factory-method="inlineScript"> <constructor-arg name="scriptSource"> <value> <![CDATA[ var logger = Java.type("org.slf4j.LoggerFactory").getLogger("net.shibboleth.idp.plugin.oidc.op.profile.AllowedScopeStrategy"); authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext"); usernameLookupStrategyClass = Java.type("net.shibboleth.idp.session.context.navigate.CanonicalUsernameLookupStrategy"); usernameLookupStrategy = new usernameLookupStrategyClass(); username = usernameLookupStrategy.apply(input); result = custom.getSecond().apply(input); if (username != null) { resCtx = input.getSubcontext("net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext", true); resCtx.setPrincipal(username); resCtx.resolveAttributes(custom.getFirst()); attribute = resCtx.getResolvedIdPAttributes().get("allowedScope"); if (attribute != null && attribute.getValues().size() > 0) { logger.debug("Setting new scope value from allowedScope attribute"); result = Java.type("net.shibboleth.idp.plugin.oidc.op.profile.ScopeUtil").buildScope(attribute); } else { logger.debug("Keeping the existing scope value"); } input.removeSubcontext(resCtx); // cleanup } logger.debug("Scope to be returned: " + result); result; ]]> </value> </constructor-arg> </bean>

The function checks if username can be resolved and if yes, resolves user’s attribute via resolver service. If an attribute allowedScope is resolved, its value will be used as the allowed scope for this user. If the username cannot be resolved (for instance with the backend flows) or if the attribute allowedScope is not resolved, the value returned by the default function shibboleth.oidc.DefaultAllowedScopeStrategy is returned. It corresponds to the scope specified in the RP metadata.

The attribute-resolver snippet below shows one example for specifying the allowedScope attribute:

<AttributeDefinition id="allowedScope" xsi:type="ScriptedAttribute"> <InputDataConnector ref="oAuth2ScopeConnector" attributeNames="defaultAllowedScope"/> <InputDataConnector ref="staticAttributes" attributeNames="phone_number_verified"/> <Script><![CDATA[ logger = Java.type("org.slf4j.LoggerFactory").getLogger("net.shibboleth.attribute.allowedSope"); ScopeUtil = Java.type("net.shibboleth.idp.plugin.oidc.op.profile.ScopeUtil"); var phoneVerified = ""; if (typeof phone_number_verified != "undefined" && phone_number_verified.getValues().size() > 0) { phoneVerified = phone_number_verified.getValues().get(0); } if (phoneVerified.equals("true")) { logger.debug("Keeping scope as it was"); ScopeUtil.populateScriptedAttribute(allowedScope, defaultAllowedScope); } else { logger.debug("Removing 'phone' from the scopes"); newScope = ScopeUtil.buildScope(defaultAllowedScope); ScopeUtil.removeValue(newScope, "phone"); newAttribute = ScopeUtil.populateScriptedAttribute(allowedScope, newScope); } ]]></Script> </AttributeDefinition> <DataConnector id="oAuth2ScopeConnector" xsi:type="ScriptedDataConnector" customObjectRef="shibboleth.oidc.DefaultAllowedScopeStrategy"> <Script><![CDATA[ defaultAllowedScope = custom.apply(profileContext); scopeAsAttribute = Java.type("net.shibboleth.idp.plugin.oidc.op.profile.ScopeUtil").buildAttribute("defaultAllowedScope", defaultAllowedScope); connectorResults.add(scopeAsAttribute); ]]></Script> </DataConnector>

In this example, the allowedScope attribute is defined so that phone value is removed from the default scope, if the attribute phone_number_verified is not set to true.

Obviously this specific example could have been implemented solely inside single shibboleth.oidc.AllowedScopeStrategy function, but the attribute-resolver snippet was included to demonstrate how its features may be exploited too in this context.