[java-identity-provider] branch master updated: IDP-1211 - Interceptor for controlled impersonation to services

Scott Cantor cantor.2 at osu.edu
Wed Aug 15 17:10:39 EDT 2018


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch master
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=fca608c2ef61bbe6171bd5e1050072b3a5a148cd

The following commit(s) were added to refs/heads/master by this push:
       new  fca608c   IDP-1211 - Interceptor for controlled impersonation to services
fca608c is described below

commit fca608c2ef61bbe6171bd5e1050072b3a5a148cd
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Aug 15 17:10:37 2018 -0400

    IDP-1211 - Interceptor for controlled impersonation to services
    
    https://issues.shibboleth.net/jira/browse/IDP-1211
    
    Redesigned flow.
---
 .../idp/authn/context/SubjectContext.java          | 33 +++++++++++++++++
 ...ontextImpersonatingPrincipalLookupFunction.java | 40 +++++++++++++++++++++
 .../intercept/impersonate-intercept-config.xml     | 14 ++++----
 .../system/flows/intercept/impersonate-beans.xml   | 41 +++++++++++++++-------
 .../system/flows/intercept/impersonate-flow.xml    | 40 ++++++++++++---------
 .../resources/system/messages/messages.properties  |  5 ++-
 .../main/resources/views/intercept/impersonate.vm  | 41 ++++++----------------
 .../net/shibboleth/idp/profile/IdPAuditFields.java |  2 +-
 8 files changed, 150 insertions(+), 66 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/SubjectContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/SubjectContext.java
index cc4c2e3..4c1b73a 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/SubjectContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/SubjectContext.java
@@ -45,11 +45,18 @@ import com.google.common.collect.ImmutableList;
  * 
  * <p>This is the ultimate product of a successful authentication process.</p>
  * 
+ * <p>A second field is available to store an identity that is impersonating the effective
+ * subject identity. Profiles should operate on the effective subject unless they need to
+ * be aware of both identities.</p>
+ * 
  * @parent {@link org.opensaml.profile.context.ProfileRequestContext}
  * @added After the subject of a request is determined
  */
 public class SubjectContext extends BaseContext {
 
+    /** Canonical principal name of an impersonating identity. */
+    @Nullable private String impersonatingPrincipalName;
+    
     /** Canonical principal name of subject. */
     @Nullable private String principalName;
 
@@ -84,6 +91,32 @@ public class SubjectContext extends BaseContext {
     }
 
     /**
+     * Get the canonical principal name of an identity that is impersonating the subject.
+     * 
+     * @return the canonical principal name of an impersonating identity
+     * 
+     * @since 3.4.0
+     */
+    @Nullable public String getImpersonatingPrincipalName() {
+        return impersonatingPrincipalName;
+    }
+
+    /**
+     * Set the canonical principal name of an identity that is impersonating the subject.
+     * 
+     * @param name the canonical principal name of an impersonating identity
+     * 
+     * @return this context
+     * 
+     * @since 3.4.0
+     */
+    @Nonnull public SubjectContext setImpersonatingPrincipalName(@Nullable final String name) {
+        impersonatingPrincipalName = name;
+        
+        return this;
+    }
+    
+    /**
      * Get a mutable map of authentication flow IDs to authentication results.
      * 
      * @return  mutable map of authentication flow IDs to authentication results
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/navigate/SubjectContextImpersonatingPrincipalLookupFunction.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/navigate/SubjectContextImpersonatingPrincipalLookupFunction.java
new file mode 100644
index 0000000..d089071
--- /dev/null
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/navigate/SubjectContextImpersonatingPrincipalLookupFunction.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.authn.context.navigate;
+
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.authn.context.SubjectContext;
+
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+
+/** A function that returns the impersonating principal name from a {@link SubjectContext}. */
+public class SubjectContextImpersonatingPrincipalLookupFunction
+        implements ContextDataLookupFunction<SubjectContext,String> {
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable public String apply(@Nullable final SubjectContext input) {
+        
+        if (input != null) {
+            return input.getImpersonatingPrincipalName();
+        }
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/idp-conf/src/main/resources/conf/intercept/impersonate-intercept-config.xml b/idp-conf/src/main/resources/conf/intercept/impersonate-intercept-config.xml
index 90f3b49..7dfda2b 100644
--- a/idp-conf/src/main/resources/conf/intercept/impersonate-intercept-config.xml
+++ b/idp-conf/src/main/resources/conf/intercept/impersonate-intercept-config.xml
@@ -12,12 +12,14 @@
        default-init-method="initialize"
        default-destroy-method="destroy">
 
-    <!-- Function returns the list of principal names the user can impersonate, defaults to null. -->
+    <!--
+    Names of access control policies defined in access-control.xml to control impersonation.
+    The general policy runs first and determines whether to offer the impersonation option.
+    The specific policy runs second and determines whether to allow the requested impersonation.
+    -->
 
-    <bean id="shibboleth.impersonate.PrincipalFunction" class="com.google.common.base.Functions" factory-method="constant">
-        <constructor-arg>
-            <null/>
-        </constructor-arg>
-    </bean>
+    <bean id="shibboleth.impersonate.GeneralPolicy" class="java.lang.String" c:_0="GeneralImpersonationPolicy" />
+
+    <bean id="shibboleth.impersonate.SpecificPolicy" class="java.lang.String" c:_0="SpecificImpersonationPolicy" />
     
 </beans>
diff --git a/idp-conf/src/main/resources/system/flows/intercept/impersonate-beans.xml b/idp-conf/src/main/resources/system/flows/intercept/impersonate-beans.xml
index 00441a9..7328135 100644
--- a/idp-conf/src/main/resources/system/flows/intercept/impersonate-beans.xml
+++ b/idp-conf/src/main/resources/system/flows/intercept/impersonate-beans.xml
@@ -20,24 +20,34 @@
 
     <import resource="../../../conf/intercept/impersonate-intercept-config.xml" />
 
-    <!-- Simplifies flow definition expressions. -->
-    <alias name="shibboleth.impersonate.PrincipalFunction" alias="PrincipalFunction"/>
+    <bean id="CheckGeneralAccess"
+        class="org.opensaml.profile.action.impl.CheckAccess" scope="prototype"
+        p:httpServletRequest-ref="shibboleth.HttpServletRequest"
+        p:accessControlService-ref="shibboleth.AccessControlService"
+        p:policyName-ref="shibboleth.impersonate.GeneralPolicy"
+        p:operation="impersonate" />
+
+    <bean id="CheckSpecificAccess"
+        class="org.opensaml.profile.action.impl.CheckAccess" scope="prototype"
+        p:httpServletRequest-ref="shibboleth.HttpServletRequest"
+        p:accessControlService-ref="shibboleth.AccessControlService"
+        p:policyName-ref="shibboleth.impersonate.SpecificPolicy"
+        p:operation="impersonate"
+        p:resourceLookupStrategy-ref="FlowScopePrincipalLookup" />
+
+    <bean id="FlowScopePrincipalLookup" parent="shibboleth.ContextFunctions.Expression"
+        c:outputType="#{T(java.lang.String)}"
+        c:expression="#input.getSubcontext(T(net.shibboleth.idp.profile.context.SpringRequestContext)).getRequestContext().getRequestScope().get('principalToSpoof')" />
 
     <bean id="ResolveAttributes" class="net.shibboleth.idp.profile.impl.ResolveAttributes" scope="prototype"
         c:resolverService-ref="shibboleth.AttributeResolverService"
         p:resolutionLabel="intercept/impersonate"
-        p:maskFailures="%{idp.service.attribute.resolver.maskFailures:true}"
-        p:principalNameLookupStrategy-ref="FlowScopePrincipalLookup" />
+        p:maskFailures="%{idp.service.attribute.resolver.maskFailures:true}" />
 
     <bean id="FilterAttributes" class="net.shibboleth.idp.profile.impl.FilterAttributes" scope="prototype"
         c:filterService-ref="shibboleth.AttributeFilterService"
         p:maskFailures="%{idp.service.attribute.filter.maskFailures:true}"
-        p:metadataResolver-ref="shibboleth.MetadataResolver"
-        p:principalNameLookupStrategy-ref="FlowScopePrincipalLookup" />
-
-    <bean id="FlowScopePrincipalLookup" parent="shibboleth.ContextFunctions.Expression"
-        c:outputType="#{T(java.lang.String)}"
-        c:expression="#input.getSubcontext(T(net.shibboleth.idp.profile.context.SpringRequestContext)).getRequestContext().getRequestScope().get('principalToSpoof')" />
+        p:metadataResolver-ref="shibboleth.MetadataResolver" />
         
     <import resource="../../conf/audit-system.xml" />
     
@@ -46,9 +56,16 @@
 		    <map>
 		        <entry>
 		            <key>
-		                <util:constant static-field="net.shibboleth.idp.profile.IdPAuditFields.IMPERSONATED_USERNAME"/>
+		                <util:constant static-field="net.shibboleth.idp.profile.IdPAuditFields.IMPERSONATING_USERNAME"/>
 		            </key>
-		            <ref bean="FlowScopePrincipalLookup" />
+                    <bean class="com.google.common.base.Functions" factory-method="compose">
+                        <constructor-arg name="g">
+                            <bean class="net.shibboleth.idp.authn.context.navigate.SubjectContextImpersonatingPrincipalLookupFunction" />
+                        </constructor-arg>
+                        <constructor-arg name="f">
+                            <ref bean="shibboleth.ChildLookup.SubjectContext" />
+                        </constructor-arg>    
+                    </bean>
 		        </entry>
 		    </map>
         </property>
diff --git a/idp-conf/src/main/resources/system/flows/intercept/impersonate-flow.xml b/idp-conf/src/main/resources/system/flows/intercept/impersonate-flow.xml
index 9e87437..05a2916 100644
--- a/idp-conf/src/main/resources/system/flows/intercept/impersonate-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/intercept/impersonate-flow.xml
@@ -5,15 +5,18 @@
 
     <!-- Rudimentary impediment to direct execution of subflow. -->
     <input name="calledAsSubflow" type="boolean" required="true" />
-
+    
     <on-start>
-        <evaluate expression="PrincipalFunction.apply(opensamlProfileRequestContext)" result="flowScope.principalCollection" />
+        <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.SubjectContext))" result="flowScope.subjectContext" />
     </on-start>
 
-    <decision-state id="CanImpersonate">
-        <if test="principalCollection != null and !principalCollection.isEmpty()"
-            then="ImpersonateView" else="proceed" />
-    </decision-state>
+    <action-state id="CheckGeneralAccess">
+        <evaluate expression="CheckGeneralAccess" />
+        <evaluate expression="'proceed'" />
+        
+        <transition on="proceed" to="ImpersonateView" />
+        <transition to="proceed" />
+    </action-state>
 
     <view-state id="ImpersonateView" view="#{flowRequestContext.activeFlow.id}">
         <on-render>
@@ -26,7 +29,8 @@
             <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().containsBean('shibboleth.CustomViewContext') ? flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CustomViewContext') : null" result="viewScope.custom" />
         </on-render>
         
-        <transition on="proceed" to="CheckForImpersonation" />
+        <transition on="impersonate" to="CheckForImpersonation" />
+        <transition to="proceed" />
     </view-state>
     
     <decision-state id="CheckForImpersonation">
@@ -34,13 +38,16 @@
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest().getParameter('principal')" result="requestScope.principalToSpoof" />
         </on-entry>
         <if test="principalToSpoof == null or principalToSpoof.length() == 0"
-            then="proceed" else="CheckAllowed" />
+            then="proceed" else="CheckSpecificAccess" />
     </decision-state>
     
-    <decision-state id="CheckAllowed">
-        <if test="principalCollection.contains(principalToSpoof)"
-            then="DoImpersonate" else="ImpersonationViolation" />
-    </decision-state>
+    <action-state id="CheckSpecificAccess">
+        <evaluate expression="CheckSpecificAccess" />
+        <evaluate expression="'proceed'" />
+        
+        <transition on="proceed" to="DoImpersonate" />
+        <transition to="ImpersonationViolation" />
+    </action-state>
     
     <action-state id="DoImpersonate">
         <on-entry>
@@ -48,8 +55,10 @@
             <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.profile.context.RelyingPartyContext)).removeSubcontext(T(net.shibboleth.idp.attribute.context.AttributeContext))" />
             <!-- Log the impersonation. -->
             <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.profile.context.RelyingPartyContext)).getRelyingPartyId()" result="requestScope.relyingPartyId" />
-            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.SubjectContext)).getPrincipalName()" result="requestScope.principalSpoofer" />
-            <evaluate expression="T(org.slf4j.LoggerFactory).getLogger('net.shibboleth.idp.profile.interceptor').info('Impersonation by principal ''{}'' as ''{}'' to relying party ''{}''', new Object[]{principalSpoofer, principalToSpoof, relyingPartyId})" />
+            <evaluate expression="T(org.slf4j.LoggerFactory).getLogger('net.shibboleth.idp.profile.interceptor').info('Impersonation by principal ''{}'' as ''{}'' to relying party ''{}''', new Object[]{subjectContext.getPrincipalName(), principalToSpoof, relyingPartyId})" />
+            <!-- Swap identities. -->
+            <evaluate expression="subjectContext.setImpersonatingPrincipalName(subjectContext.getPrincipalName())" />
+            <evaluate expression="subjectContext.setPrincipalName(principalToSpoof)" />
         </on-entry>
         
         <evaluate expression="PopulateAuditContext" />
@@ -64,8 +73,7 @@
         <on-entry>
             <!-- Log violation. -->
             <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.profile.context.RelyingPartyContext)).getRelyingPartyId()" result="requestScope.relyingPartyId" />
-            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.SubjectContext)).getPrincipalName()" result="requestScope.principalSpoofer" />
-            <evaluate expression="T(org.slf4j.LoggerFactory).getLogger('net.shibboleth.idp.profile.interceptor').warn('Unauthorized impersonation attempt by principal ''{}'' as ''{}'' to relying party ''{}''', new Object[]{principalSpoofer, principalToSpoof, relyingPartyId})" />
+            <evaluate expression="T(org.slf4j.LoggerFactory).getLogger('net.shibboleth.idp.profile.interceptor').warn('Unauthorized impersonation attempt by principal ''{}'' as ''{}'' to relying party ''{}''', new Object[]{subjectContext.getPrincipalName(), principalToSpoof, relyingPartyId})" />
         </on-entry>
     </end-state>
 
diff --git a/idp-conf/src/main/resources/system/messages/messages.properties b/idp-conf/src/main/resources/system/messages/messages.properties
index 29eae36..3f71609 100644
--- a/idp-conf/src/main/resources/system/messages/messages.properties
+++ b/idp-conf/src/main/resources/system/messages/messages.properties
@@ -22,6 +22,7 @@ MessageReplay = stale
 MessageExpired = stale
 UnableToDecode = stale
 AccountError = authn
+AccountLocked = authn
 AuthenticationException = authn
 InvalidCredentials = authn
 NoCredentials = authn
@@ -174,8 +175,10 @@ idp.terms-of-use.required   = Please check this box if you want to proceed.
 # General messages related to impersonation interceptor
 
 idp.impersonate.header = Account Impersonation
+idp.impersonate.text = Enter an account name to impersonate to this service or continue normally.
 idp.impersonate.login-as = Login as
-idp.impersonate.proceed = Proceed
+idp.impersonate.impersonate = Impersonate
+idp.impersonate.continue = Continue Normally
 
 # General messages related to key-unlocking admin flow
 
diff --git a/idp-conf/src/main/resources/views/intercept/impersonate.vm b/idp-conf/src/main/resources/views/intercept/impersonate.vm
index 4fa4e93..4895677 100644
--- a/idp-conf/src/main/resources/views/intercept/impersonate.vm
+++ b/idp-conf/src/main/resources/views/intercept/impersonate.vm
@@ -12,10 +12,8 @@
 ## response - HttpServletResponse
 ## environment - Spring Environment object for property resolution
 ## custom - arbitrary object injected by deployer
-## principalCollection - Collection of impersonateable principal names (yes, this is re-validated)
 ##
 #set ($rpContext = $profileRequestContext.getSubcontext('net.shibboleth.idp.profile.context.RelyingPartyContext'))
-#set ($subjectContext = $profileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.SubjectContext'))
 <!DOCTYPE html>
 <html>
     <head>
@@ -44,44 +42,27 @@
               </legend>
             #end
 
+              <legend>
+                #springMessageText("idp.impersonate.text", "Enter an account name to impersonate to this service or continue normally.")
+              </legend>
+
               <div class="form-element-wrapper">
                 <label for="impersonation">#springMessageText("idp.impersonate.login-as", "Login as")</label>
-                <select class="form-element form-field" id="impersonation" name="principal">
-                  <option value="">$encoder.encodeForHTML($subjectContext.getPrincipalName())</option>
-                  #foreach ($principal in $principalCollection)
-                    <option value="$encoder.encodeForHTML($principal)">$encoder.encodeForHTML($principal)</option>
-                  #end
-                </select>
+                <input class="form-element form-field" id="impersonation" name="principal" type="text">
+              </div>
+
+              <div class="form-element-wrapper">
+                <button class="form-element form-button" type="submit" name="_eventId_impersonate"
+                    >#springMessageText("idp.impersonate.impersonate", "Impersonate")</button>
               </div>
 
               <div class="form-element-wrapper">
                 <button class="form-element form-button" type="submit" name="_eventId_proceed"
-                    >#springMessageText("idp.impersonate.proceed", "Proceed")</button>
+                    >#springMessageText("idp.impersonate.continue", "Continue Normally")</button>
               </div>
                 
             </form>
 
-            #*
-              //
-              //    SP Description & Logo (optional)
-              //    These idpui lines will display added information (if available
-              //    in the metadata) about the Service Provider (SP) that requested
-              //    authentication. These idpui lines are "active" in this example
-              //    (not commented out) - this extra SP info will be displayed.
-              //    Remove or comment out these lines to stop the display of the
-              //    added SP information.
-              //
-            *#
-            #set ($logo = $rpUIContext.getLogo())
-            #if ($logo)
-              <img src= "$encoder.encodeForHTMLAttribute($logo)"
-                  alt="$encoder.encodeForHTMLAttribute($serviceName)">
-            #end
-            #set ($desc = $rpUIContext.getServiceDescription())
-            #if ($desc)
-              $encoder.encodeForHTML($desc)
-            #end
-
           </div>
         </div>
 
diff --git a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/IdPAuditFields.java b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/IdPAuditFields.java
index 4639d64..fbaa587 100644
--- a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/IdPAuditFields.java
+++ b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/IdPAuditFields.java
@@ -57,7 +57,7 @@ public final class IdPAuditFields {
     @Nonnull @NotEmpty public static final String HASHED_USERNAME = "HASHEDu";
 
     /** Impersonating username field. */
-    @Nonnull @NotEmpty public static final String IMPERSONATED_USERNAME = "IMPu";
+    @Nonnull @NotEmpty public static final String IMPERSONATING_USERNAME = "uu";
     
     /** Attributes field. */
     @Nonnull @NotEmpty public static final String ATTRIBUTES = "attr";

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list