CDLUC3 / dmptool

DMPTool version of the DMPRoadmap codebase
https://dmptool.org
MIT License
57 stars 13 forks source link

Enabling SSO #471

Closed rwvaldivia closed 4 months ago

rwvaldivia commented 1 year ago

Hi,

We are trying to enable login via SSO to work on our Federated Network (CAFE Network) and I have already used several tutorials on the Internet without success, such as:

but without success.

Any idea how to perform this integration?

Does dmptool have a native integration or an descritive documentation about this question?

briri commented 1 year ago

Hi @rwvaldivia it really depends on how you will be interacting with the CAFE Network. Are they the only SSO your system will use?

The DMPTool supports SSO integration for hundreds of institutions (including CAFE), so we use another system to handle all of the SAML exchange.

This other service we use is a Shibboleth Service Provider(SP). We have it installed on a server that the DMPTool code is able to reach.

The Shibboleth SP is registered with the InCommon Federation as a "trusted service". The InCommon federation provides our Shibboleth SP with a list of all the"trusted" SSO identity providers (IdPs) that are registered with the federation (there are many).

When a user wants to login with SSO, the DMPTool redirects them to the Shibboleth SP with the entityID for their institution. The Shibboleth SP then packages a SAML message and redirects the user to their SSO sign in page. After the user signs in, their SSO sends our Shibboleth SP a SAML response. Our Shibboleth SP verifies the SAML response and transforms it into an omniauth hash that it sends back to the DMPTool. The hash contains their name, email and their unique identifier within the SSO (also called an eppn).

If you are only going to integrate with the CAFE network and want to work with SAML directly, see if this saml_authenticatable plugin for the Devise gem will work for you. It says that it allows you to connect directly to an IdP using SAML.

When you make the changes to your config/initializers/devise.rb, you can comment out the shibboleth settings in that file. You will probably also need to modify the app/controllers/users/omniauth_*_controller.rb files

rwvaldivia commented 1 year ago

Hi @Briri

Thanks for the previous information.

Over the past few weeks, I've been trying to do the procedures via the saml_authenticatable plugin for the Devise gem, but there are some coding issues I haven't been able to resolve. For example when changing the model/users.rb source code, I noticed that the tutorial has many differences in relation to the current DMPTool code. In the plugin example the class extends ActiveRecord::Base while in DMPTool the Users class extends ApplicationRecord.

Anyway... there are many differences.

So we are inclined to perform the integration procedures through the Shibboleth Service Provider(SP), but we still have several doubts.

Since DMPTool is already integrated with the Shibboleth Service Provider(SP), is it enough to parameterize the information in the current code (we are in version 4.09) or is it necessary to modify the source code for the integration to really work?

I found Shibboleth references in the following files:

Will it be necessary to change others for seamless integration with Shibboleth Service Provider(SP)?

We intend to install the Shibboleth Service Provider(SP) version from this tutorial (_we are using Ubuntu 20.04 and the tutorial from shibboleth.atlassian.net is for RHEL 6/7. CentOS 6/7 environment_). If you can make any observations or precautions to take, it would be of great help:

briri commented 1 year ago

Hey @rwvaldivia,

If you do want to setup your own shibboleth SP you may need to verify/modify the Devise config settings to work for the institutions you wish to allow to use SSO (I think you will not need to change this though since many of the Brazilian institutions had been using SSO with the DMPTool before).

The big question is where you will install the shibboleth SP and how you have your infrastructure deployed. The DMPTool runs on several servers behind a load balancer. Each of those servers has the DMPTool Rails application running along with a Shibboleth SP (shibd) and apache httpd. Apache has some rules defined that directs certain traffic to Shibboleth and then lets all other traffic go to the DMPTool application.

Our apache config uses a VirtualHost with the following rules for Shibboleth:

    #   Shibboleth
    # This is the target that the DMPTool calls when sending a user to their IdP.
    <Location /Shibboleth.sso>
      AuthType shibboleth
      require shibboleth
      SSLRequireSSL
    </Location>

   # This is the target that Shibboleth sends to the IdP so that the user is redirected back to Shibboleth after they sign in
    <Location /users/auth/shibboleth/callback>
      AuthType shibboleth
      require shibboleth
      ShibUseHeaders On
      SSLRequireSSL
    </Location>

    # This is a test URL. Ours goes out to the InCommon federation which provides the user with a select box to choose an
    # institution and then sends them to their IdP where they can login. It helps you test the Shibboleth SSO outside of the
    # DMPTool code.
    <Location /shibtest>
       AuthType shibboleth
       ShibRequestSetting requireSession 1
       require valid-user
       ShibUseHeaders On
       SSLRequireSSL
    </Location>

The omniauth_passthrus_controller.rb will show you which configuration variables you will want to change to get the code to send users to your SP.

You will also need to register your SP with the CAFE Network so that the institutional IdPs will "trust" your application. I believe that the registration process will give you an entityID to use in your shibboleth config.

You can then reference our Shibboleth config files below to setup your SP to use the SAML attributes that the DMPTool code expects. Please follow the core Shibboleth documentation though and just use these as a reference!

Here is our shibboleth2.xml config (note that I've replaced some values with placeholders that you would need to replace)

<SPConfig xmlns="urn:mace:shibboleth:3.0:native:sp:config" xmlns:conf="urn:mace:shibboleth:3.0:native:sp:config" clockSkew="180">

    <OutOfProcess tranLogFormat="%u|%s|%IDP|%i|%ac|%t|%attr|%n|%b|%E|%S|%SS|%L|%UA|%a" />

    <!-- The ApplicationDefaults element is where most of Shibboleth's SAML bits are defined. -->
    <ApplicationDefaults entityID="YOUR_CAFE_NETWORK_ENTITYID" 
                                        REMOTE_USER="eppn persistent-id mail"

cipherSuites="DEFAULT:!EXP:!LOW:!aNULL:!eNULL:!DES:!IDEA:!SEED:!RC4:!3DES:!kRSA:!SSLv2:!SSLv3:!TLSv1:!TLSv1.1">
       <Sessions lifetime="28800" timeout="3600" relayState="ss:mem"
                         checkAddress="false" handlerSSL="true" cookieProps="https"
                         redirectLimit="exact">
         <SSO discoveryProtocol="SAMLDS" discoveryURL="URL_FOR_THE_CAFE_DISCOVERY_SERVICE"> SAML2 SAML1 </SSO>
         <!-- SAML and local-only logout. -->
         <Logout>SAML2 Local</Logout>

         <!-- Administrative logout. -->
         <LogoutInitiator type="Admin" Location="/Logout/Admin" acl="127.0.0.1 ::1" />

         <!-- Extension service that generates "approximate" metadata based on SP configuration. -->
         <Handler type="MetadataGenerator" Location="/Metadata" signing="false"/>

         <!-- Status reporting service. -->
          <Handler type="Status" Location="/Status" acl="127.0.0.1 ::1"/>

         <!-- Session diagnostic service. -->
         <Handler type="Session" Location="/Session" showAttributeValues="false"/>

         <!-- JSON feed of discovery information. -->
         <Handler type="DiscoveryFeed" Location="/DiscoFeed"/>
     </Sessions>

     <!-- There are ways to use a local metadata file, but I would recommend you use one from the CAFE network 
             For example: 
                 <MetadataProvider type="XML" path="my_local_metadata_file.xml"/>
     -->
     <MetadataProvider type="MDQ" id="incommon" ignoreTransport="true" cacheDirectory="inc-mdq-cache"
                                      maxCacheDuration="28800" minCacheDuration="600"
                                      baseUrl="THE CAFE NETWORK MDQ URL">
         <MetadataFilter type="Signature" certificate="CAFE_NETWORK MDQ PEM"/>
         <MetadataFilter type="RequireValidUntil" maxValidityInterval="1209600"/>
     </MetadataProvider>

     <!-- Map to extract attributes from SAML assertions. -->
     <AttributeExtractor type="XML" validate="true" reloadChanges="false" path="attribute-map.xml"/>

     <!-- Default filtering policy for recognized attributes, lets other data pass. -->
     <AttributeFilter type="XML" validate="true" path="attribute-policy.xml"/>

     <!-- File-based resolver for using a single keypair for both signing and encryption. -->
     <CredentialResolver type="File" key="YOUR_LOCAL.pem" certificate="YOUR_LOCAL.pem"/>
   </ApplicationDefaults>

    <!-- Policies that determine how to process and authenticate runtime messages. -->
    <SecurityPolicyProvider type="XML" validate="true" path="security-policy.xml"/>

    <!-- Low-level configuration about protocols and bindings available for use. -->
    <ProtocolProvider type="XML" validate="true" reloadChanges="false" path="protocols.xml"/>
</SPConfig>

and here is the attribute-policy.xml

<AttributeFilterPolicyGroup
    xmlns="urn:mace:shibboleth:2.0:afp"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <!-- Shared rule for affiliation values. -->
    <PermitValueRule id="eduPersonAffiliationValues" xsi:type="OR">
        <Rule xsi:type="Value" value="faculty"/>
        <Rule xsi:type="Value" value="student"/>
        <Rule xsi:type="Value" value="staff"/>
        <Rule xsi:type="Value" value="alum"/>
        <Rule xsi:type="Value" value="member"/>
        <Rule xsi:type="Value" value="affiliate"/>
        <Rule xsi:type="Value" value="employee"/>
        <Rule xsi:type="Value" value="library-walk-in"/>
    </PermitValueRule>
    <!--
    Shared rule for all "scoped" attributes, but you'll have to manually apply it inside
    an AttributeRule for each attribute you want to check.
    -->
    <PermitValueRule id="ScopingRules" xsi:type="AND">
        <Rule xsi:type="NOT">
            <Rule xsi:type="ValueRegex" regex="@"/>
        </Rule>
        <Rule xsi:type="ScopeMatchesShibMDScope"/>
    </PermitValueRule>
    <AttributeFilterPolicy>
        <!-- This policy is in effect in all cases. -->
        <PolicyRequirementRule xsi:type="ANY"/>
        <!-- Filter out undefined affiliations and ensure only one primary. -->
        <AttributeRule attributeID="affiliation">
            <PermitValueRule xsi:type="AND">
                <RuleReference ref="eduPersonAffiliationValues"/>
                <RuleReference ref="ScopingRules"/>
            </PermitValueRule>
        </AttributeRule>
        <AttributeRule attributeID="unscoped-affiliation">
            <PermitValueRuleReference ref="eduPersonAffiliationValues"/>
        </AttributeRule>
        <AttributeRule attributeID="primary-affiliation">
            <PermitValueRuleReference ref="eduPersonAffiliationValues"/>
        </AttributeRule>
        <AttributeRule attributeID="subject-id">
            <PermitValueRuleReference ref="ScopingRules"/>
        </AttributeRule>
        <AttributeRule attributeID="pairwise-id">
            <PermitValueRuleReference ref="ScopingRules"/>
        </AttributeRule>
        <AttributeRule attributeID="eppn">
            <PermitValueRuleReference ref="ScopingRules"/>
        </AttributeRule>
        <AttributeRule attributeID="targeted-id">
            <PermitValueRuleReference ref="ScopingRules"/>
        </AttributeRule>
        <!-- Require NameQualifier/SPNameQualifier match IdP and SP entityID respectively. -->
        <AttributeRule attributeID="persistent-id">
            <PermitValueRule xsi:type="NameIDQualifierString"/>
        </AttributeRule>
        <!-- Enforce that the values of schacHomeOrganization are a valid Scope. -->
        <AttributeRule attributeID="schacHomeOrganization">
            <PermitValueRule xsi:type="ValueMatchesShibMDScope" />
        </AttributeRule>
        <!-- Catch-all that passes everything else through unmolested. -->
        <AttributeRule attributeID="*" permitAny="true"/>
    </AttributeFilterPolicy>
</AttributeFilterPolicyGroup>

and the attribute-map.xml we are currently using:

<Attributes xmlns="urn:mace:shibboleth:2.0:attribute-map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <!--
    The mappings are a mix of SAML 1.1 and SAML 2.0 attribute names agreed to within the Shibboleth
    community. The non-OID URNs are SAML 1.1 names and most of the OIDs are SAML 2.0 names, with a
    few exceptions for newer attributes where the name is the same for both versions. You will
    usually want to uncomment or map the names for both SAML versions as a unit.
    -->
    <!-- New standard identifier attributes for SAML. -->
    <Attribute name="urn:oasis:names:tc:SAML:attribute:subject-id" id="subject-id">
        <AttributeDecoder xsi:type="ScopedAttributeDecoder" caseSensitive="false"/>
    </Attribute>
    <Attribute name="urn:oasis:names:tc:SAML:attribute:pairwise-id" id="pairwise-id">
        <AttributeDecoder xsi:type="ScopedAttributeDecoder" caseSensitive="false"/>
    </Attribute>
    <!-- The most typical eduPerson attributes. -->
    <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" id="eppn">
        <AttributeDecoder xsi:type="ScopedAttributeDecoder" caseSensitive="false"/>
    </Attribute>
    <Attribute name="urn:mace:dir:attribute-def:eduPersonPrincipalName" id="eppn">
        <AttributeDecoder xsi:type="ScopedAttributeDecoder" caseSensitive="false"/>
    </Attribute>
    <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.9" id="affiliation">
        <AttributeDecoder xsi:type="ScopedAttributeDecoder" caseSensitive="false"/>
    </Attribute>
    <Attribute name="urn:mace:dir:attribute-def:eduPersonScopedAffiliation" id="affiliation">
        <AttributeDecoder xsi:type="ScopedAttributeDecoder" caseSensitive="false"/>
    </Attribute>
    <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.7" id="entitlement"/>
    <Attribute name="urn:mace:dir:attribute-def:eduPersonEntitlement" id="entitlement"/>
    <!-- Additions specific to DMPTool -->
    <Attribute name="urn:mace:dir:attribute-def:mail" id="mail">
        <AttributeDecoder xsi:type="ScopedAttributeDecoder"/>
    </Attribute>
    <Attribute name="urn:oid:0.9.2342.19200300.100.1.3" id="mail">
        <AttributeDecoder xsi:type="ScopedAttributeDecoder" caseSensitive="false"/>
    </Attribute>
    <Attribute name="urn:mace:dir:attribute-def:displayName" id="displayName"/>
    <Attribute name="urn:oid:2.16.840.1.113730.3.1.241" id="displayName"/>
    <!--
    Adding sn and givenName here for the DMPTool
    -->
    <Attribute name="urn:mace:dir:attribute-def:sn" id="sn"/>
    <Attribute name="urn:oid:2.5.4.4" id="sn"/>
    <Attribute name="urn:mace:dir:attribute-def:givenName" id="givenName"/>
    <Attribute name="urn:oid:2.5.4.42" id="givenName"/>
    <!--
    Legacy pairwise identifier attribute / NameID format, intended to be replaced by the
    simpler pairwise-id attribute (see top of file).
    -->
    <!-- The eduPerson attribute version (note the OID-style name): -->
    <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10" id="persistent-id">
        <AttributeDecoder xsi:type="NameIDAttributeDecoder" formatter="$NameQualifier!$SPNameQualifier!$Name" defaultQualifiers="true"/>
    </Attribute>
    <!-- The SAML 2.0 NameID Format: -->
    <Attribute name="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" id="persistent-id">
        <AttributeDecoder xsi:type="NameIDAttributeDecoder" formatter="$NameQualifier!$SPNameQualifier!$Name" defaultQualifiers="true"/>
    </Attribute>
</Attributes>