Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Note

The examples in this page reflect certain approaches required by IdP V5. They are not entirely compatible with earlier versions, though the differences are fairly minor relative the point of the example.

Table of Contents
minLevel1
maxLevel2
outlinefalse
typelist
printablefalse

Overview

This is a companion example to the more more general Grouper Integration Example. It demonstrates an approach to leveraging the IdP’s impersonation feature, while using a particular design pattern in Grouper to model and manage the authorization decisions that are required by that feature. It is not by any means the only way to manage impersonation, nor the only way to use Grouper to do that, as the feature is extremely generic and merely requires that a pair of access control policies exist to supply answers to the basic questions “can this user impersonate anybody to this service?” and “can this user impersonate this subject to this service?”.

...

Expand
titlegrouperImpersonation.js
Code Block
importClass(Packages.java.util.ArrayList);
importClass(Packages.net.shibboleth.shared.httpclient.HttpClientSupport);
importPackage(Packages.net.shibboleth.idp.attribute);

var body = HttpClientSupport.toString(response.getEntity(), "UTF-8", 65536);
var result = JSON.parse(body);

if (result != null
        && result.WsGetGroupsLiteResult != null
        && result.WsGetGroupsLiteResult.resultMetadata != null
        && result.WsGetGroupsLiteResult.resultMetadata.resultCode == "SUCCESS") {
        var groups = result.WsGetGroupsLiteResult.wsGroups;
        if (groups != null) {
                var values = new ArrayList();
                for (var i=0; i<groups.length; i++) {

                        // Format is OSU:WebLoginService:SHA1(entityID):impersonate:username
                        var ctx= custom.get("servletRequestSupplier").get().getAttribute("opensamlProfileRequestContext").getSubcontext("net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext");
                        if (ctx != null && ctx.getAttributeRecipientID() != null) {
                            var prefix = "OSU:WebLoginService:" + custom.get("digester").apply(ctx.getAttributeRecipientID()) + ":impersonate:";
                            if (groups[i].name.indexOf(prefix) != 0) {
                                values.clear();
                                break;
                            }
                        }

                        var username = groups[i].name.substring(73);
                        values.add(new StringAttributeValue(username));
                }

                if (!values.isEmpty()) {
                        var usernames = new IdPAttribute("impersonatableUsernames");
                        usernames.setValues(values);
                        connectorResults.add(usernames);
                }
        }
}

Most of this is just parsing the Grouper result, but Grouper has a critical, unfixed bug that causes a request for a non-existent stem to return every group membership the requesting account can see. To prevent this, the code checks that the stem prefix is as expected, and if not, that the data should be cleared and nothing returned.

Apart from that, the username suffix of each group membership is stripped off and populated as a value of an array list. Provided at least one exists, the array list is then converted into values of a resolver attribute named “impersonatableUsernames“.

Impersonation Policies

TBD - the With all this machinery in place, the connection back to the impersonation process involves definiing the to access control policies used by the flow to control the feature based on the resolved attributethat the feature relies on.

The “general” policy which controls whether the impersonation flow runs at all is simply a check for whether the “impersonatableUsernames” IdPAttribute computed via the script above based on Grouper data has any values at all. That is, if it has a value, then at least one impersonatable identity was authorized for the user, so they have the option to select one to use or proceed as themselves.

The “specific” policy which controls whether the specific identity chosen is acceptable or not is then evaluated against the values present in the IdPAttribute indirectly using a special condition defined for this use case. In English, what this does is define a function to run to obtain the value to check for in the values of the IdPAttribute. The function in this case is a Spring Expression that extracts the “resource” the access control policy is being run against, which for impersonation checking is the actual identity to impersonate.

Expand
titleaccess-control.xml
Code Block
        <entry key="GeneralImpersonationPolicy">
            <bean parent="shibboleth.PredicateAccessControl">
                <constructor-arg>
                    <bean class="net.shibboleth.idp.profile.logic.SimpleAttributePredicate"
                            p:useUnfilteredAttributes="true">
                        <property name="attributeValueMap">
                            <map>
                                <entry key="impersonatableUsernames">
                                    <list>
                                        <value>*</value>
                                    </list>
                                </entry>
                            </map>
                        </property>
                    </bean>
                </constructor-arg>
            </bean>
        </entry>

        <entry key="SpecificImpersonationPolicy">
            <bean parent="shibboleth.PredicateAccessControl">
                <constructor-arg>
                    <bean parent="shibboleth.Conditions.DynamicAttribute">
                        <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>
            </bean>
        </entry>