This feature is available in V3.4 and later of the software.
Current File(s): conf/intercept/impersonate-intercept-config.xml, views/intercept/impersonate.vm, conf/access-control.xml
Format: Native Spring
The "impersonate" interceptor flow is an example of how to use an interceptor to implement a testing capability that allows a user to represent themselves as a different user to a downstream service subject to appropriate authorization. It does not affect the user's overall session and has no persistent effect on access to other services within the same session; it is a per-request feature that changes the set of attributes resolved by re-running the attribute resolver as though the user had a different identity.
All interceptors are enabled or disabled on a per-relying-party basis using properties in the profile bean(s) you want to enable the flow for. See the ProfileInterceptConfiguration topic for an example.
The primary configuration involved with this flow is to define AccessControl policies to control who can impersonate whom. And of course, one can customize the view template displayed.
The beans named shibboleth.impersonate.GeneralPolicy and shibboleth.impersonate.SpecificPolicy in intercept/impersonate-intercept-config.xml must be defined by you with the named AccessControl policies you want to apply.
The "general" policy runs first up front and determines whether to actually offer the impersonation option. This is logically equivalent to attaching an activation condition to the flow itself, but the AccessControl service is reloadable, so using a policy is more flexible.
The "specific" policy runs after an account name is selected to decide whether to allow it.
Since retrieving a policy by name that doesn't exist results in denial of access, the system is safe out of the box. Enabling the flow won't actually cause it to run due to the lack of a matching policy defined to allow access to it.
The view to display is contained in views/intercept/impersonate.vm, and it provides an example allowing the user to enter the account to impersonate using a form field, or to bypass the impersonation and just continue normally.
If the policy named by the shibboleth.impersonate.GeneralPolicy bean grants access, the view is rendered. Assuming a username is entered and the impersonation is attempted, the flow runs the policy named by the shibboleth.impersonate.SpecificPolicy bean. This policy is evaluated with its "resource" input set to the intended account name to impersonate, allowing the policy to react accordingly.
If the policy denies access, the failed attempt is logged and an event named "ImpersonationViolation" terminates the entire request. This is an event you can configure whatever error handling you prefer in response to (see ErrorHandlingConfiguration), but for new installs it's defined to be a "local error". Upgraded systems will need to add that event to the map of local events (compare your copy of conf/errors.xml to the one in the dist directory).
If the policy allows access, the flow will:
For most configurations, the only user-specific element of the resolution process will be derived from the
principalName property of the AttributeResolutionContext provided to the resolver, which will be set by this flow to the impersonated name instead of the original one.
However, you should thoroughly examine and test your configuration to be sure that you're getting the output you expect. In particular, if you have behavior (likely via scripts or the advanced SubjectDerivedAttributeAttributeDefinition) that depends on content internal to the user's authentication state, this will not be effectively overridden by this flow.
To facilitate attaching conditions or adding script logic that can be made dependent on whether impersonation is happening, a new property has been added to the AttributeResolutionContext, the
resolutionLabel, and it will be set to "intercept/impersonate" (the flow ID) when this flow is doing the resolution (it will be empty in the traditional case).
|shibboleth.impersonate.GeneralPolicy||String||Named AccessControl policy to run to determine whether to run this flow|
|shibboleth.impersonate.SpecificPolicy||String||Named AccessControl policy to run to determine whether to allow the requested impersonation|
Aside from the UI, all of the flow's configuration is actually just defining policies, either in conf/access-control.xml or an included file. In practice, a "real world" implementation of such policies would likely rely on some kind of directory or database of rules controlling which users can impersonate which users to which services, perhaps through group memberships resolved during initial attribute resolution.
For illustrative purposes, consider a rule that the flow is authorized only to users possessing a particular entitlement value, and then are allowed to impersonate any users named by a second custom attribute to services named by a third. This fully devolves the decision making to data that could be supplied from any directory or database, instead of internal rules hardcoded into the configuration.
The "general" policy is a simple test of an entitlement attribute for a specific value that is intended to mean "allow this logged-in user to at least attempt impersonation", and uses the SimpleAttributePredicate Java class. This class is often used to implement authorization rules in the context-check interceptor.
The "specific" policy could be implemented by a somewhat-involved script, but the example below relies on a new Java class added to V3.4 that checks the values of a resolved IdPAttribute against a dynamically computed candidate value (or values) produced by functions, which can themselves be scripts or expressions. This DynamicAttributePredicate class relies on an injected map of attribute names to functions, instead of attribute names to strings. The mapped function objects return the data values that have to be found in the named attribute at runtime to be a valid match. It's indirect instead static/direct.
The example uses this trick to derive the values to look for from the actual impersonation attempt. The ID of the service and the ID of the impersonated user come from functions and those functions are what the example is putting in the map that the Java class calls.
Instead of a rule saying "can impersonate jdoe to service xyz", which would be static and hard to manage, the rule is saying "allow the impersonation of jdoe to xyz if jdoe is one of the values of the impersonatableUsernames IdPAttribute and xyz is one of the values of the impersonatableServices IdPAttribute".
Once you understand the idea, it's not a lot of configuration to maintain and supports a plausible real-world approach to authorizing the impersonation feature.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd" default-init-method="initialize" default-destroy-method="destroy"> <util:map id="shibboleth.AccessControlPolicies"> <!-- Limits who can impersonate based on entitlement. --> <entry key="GeneralImpersonationPolicy"> <bean parent="shibboleth.PredicateAccessControl"> <constructor-arg> <bean class="net.shibboleth.idp.profile.logic.SimpleAttributePredicate"> <property name="attributeValueMap"> <map> <entry key="eduPersonEntitlement"> <list> <value>https://example.org/entitlement/impersonation</value> </list> </entry> </map> </property> </bean> </constructor-arg> </bean> </entry> <!-- Controls the impersonation scenarios to allow. --> <entry key="SpecificImpersonationPolicy"> <bean parent="shibboleth.PredicateAccessControl"> <constructor-arg> <bean parent="shibboleth.Conditions.AND"> <constructor-arg> <bean class="net.shibboleth.idp.profile.logic.DynamicAttributePredicate"> <property name="attributeFunctionMap"> <map> <entry key="impersonatableUsernames"> <list> <bean parent="shibboleth.ContextFunctions.Expression" c:expression="#input.getSubcontext(T(org.opensaml.profile.context.AccessControlContext)).getResource()" /> </list> </entry> </map> </property> </bean> </constructor-arg> <constructor-arg> <bean class="net.shibboleth.idp.profile.logic.DynamicAttributePredicate"> <property name="attributeFunctionMap"> <map> <entry key="impersonatableServices"> <list> <bean parent="shibboleth.RelyingPartyIdLookup.Simple" /> </list> </entry> </map> </property> </bean> </constructor-arg> </bean> </constructor-arg> </bean> </entry> </util:map> </beans>