Alfresco

Presentation

Alfresco is an ECM/BPM software.

Since 4.0 release, it offers an easy way to configure SSO thanks to authentication subsystems.

Authentication against LL::NG can be done trough:

  • HTTP headers (LL::NG Handler)
  • SAML 2 (LL::NG as SAML2 IDP)
Alfresco now recommends SAML2 method

HTTP headers

Alfresco

The official documentation can be found here: http://docs.alfresco.com/4.0/tasks/auth-alfrescoexternal-sso.html

You need to find the following files in your Alfresco installation:

  • alfresco-global.properties (ex: tomcat/shared/classes/alfresco-global.properties)
  • share-config-custom.xml (ex: tomcat/shared/classes/alfresco/web-extension/share-config-custom.xml)

The first will allow one to configure SSO for the alfresco webapp, and the other for the share webapp.

Edit first alfresco-global.properties and add the following:

### SSO ###
authentication.chain=external1:external
external.authentication.enabled=true
external.authentication.defaultAdministratorUserNames=
external.authentication.proxyUserName=
external.authentication.proxyHeader=Auth-User
external.authentication.userIdPattern=

Edit then share-config-custom.xml and uncomment the last part. In the <endpoint>, change <connector-id> value to alfrescoHeader and change the <userHeader> value to Auth-User:

   <config evaluator="string-compare" condition="Remote">
      <remote>
          <keystore>
             <path>alfresco/web-extension/alfresco-system.p12</path>
             <type>pkcs12</type>
             <password>alfresco-system</password>
         </keystore>
 
         <connector>
            <id>alfrescoCookie</id>
            <name>Alfresco Connector</name>
            <description>Connects to an Alfresco instance using cookie-based authentication</description>
            <class>org.alfresco.web.site.servlet.SlingshotAlfrescoConnector</class>
         </connector>
 
         <connector>
            <id>alfrescoHeader</id>
            <name>Alfresco Connector</name>
            <description>Connects to an Alfresco instance using header and cookie-based authentication</description>
            <class>org.alfresco.web.site.servlet.SlingshotAlfrescoConnector</class>
            <userHeader>Auth-User</userHeader>
         </connector>
 
         <endpoint>
            <id>alfresco</id>
            <name>Alfresco - user access</name>
            <description>Access to Alfresco Repository WebScripts that require user authentication</description>
            <connector-id>alfrescoHeader</connector-id>
            <endpoint-url>http://localhost:8080/alfresco/wcs</endpoint-url>
            <identity>user</identity>
            <external-auth>true</external-auth>
         </endpoint>
      </remote>
   </config>

You need to restart Tomcat to apply changes.

Now you can log in with a simple HTTP header. You need to restrict access to Alfresco to LL::NG.

LL::NG

Headers

Just set the Auth-User header with the attribute that carries the user login, for example $uid.

Rules

Set the default rule to what you need.

Other rules:

  • Unprotect access to some resources: ^/share/res ⇒ unprotect
  • Catch logout: ^/share/page/dologout ⇒ logout_app_sso

SAML2

Alfresco

Install SAML Alfresco module package:

cp alfresco-saml-repo-1.0.1.amp <ALFRESCO_HOME>/amps
cp alfresco-saml-share-1.0.1.amp <ALFRESCO_HOME>/amps_share
./bin/apply_amp.sh

Generate SAML certificate:

keytool -genkeypair -alias my-saml-key -keypass change-me -storepass change-me -keystore my-saml.keystore -storetype JCEKS

Export the keystore:

mv my-saml.keystore alf_data/keystore
cat <<EOT > alf_data/keystore/my-saml.keystore-metadata.properties
aliases=my-saml-key
keystore.password=change-me
my-saml-key.password=change-me
EOT
cat <<EOT >> tomcat/shared/classes/alfresco-global.properties

saml.keystore.location=\${dir.keystore}/my-saml.keystore
saml.keystore.keyMetaData.location=\${dir.keystore}/my-saml.keystore-metadata.properties
EOT

Edit then share-config-custom.xml:

    ...
        <config evaluator="string-compare" condition="CSRFPolicy" replace="true">
 
 
 
        <!--
            If using https make a CSRFPolicy with replace="true" and override the properties section.
            Note, localhost is there to allow local checks to succeed.
 
 
 
            I.e.
            <properties>
                <token>Alfresco-CSRFToken</token>
                <referer>https://your-domain.com/.*|http://localhost:8080/.*</referer>
                <origin>https://your-domain.com|http://localhost:8080</origin>
            </properties>
        -->
 
 
 
            <filter>
 
 
 
                <!-- SAML SPECIFIC CONFIG -  START -->
 
 
 
                <!--
                 Since we have added the CSRF filter with filter-mapping of "/*" we will catch all public GET to avoid them
                 having to pass through the remaining rules.
                 -->
                <rule>
                    <request>
                        <method>GET</method>
                        <path>/res/.*</path>
                    </request>
                </rule>
 
 
 
                <!-- Incoming posts from IDPs do not require a token -->
                <rule>
                    <request>
                        <method>POST</method>
                        <path>/page/saml-authnresponse|/page/saml-logoutresponse|/page/saml-logoutrequest</path>
                    </request>
                </rule>
 
 
 
                <!-- SAML SPECIFIC CONFIG -  STOP -->
 
 
 
                <!-- EVERYTHING BELOW FROM HERE IS COPIED FROM share-security-config.xml -->
 
 
 
                <!--
                 Certain webscripts shall not be allowed to be accessed directly form the browser.
                 Make sure to throw an error if they are used.
                 -->
                <rule>
                    <request>
                        <path>/proxy/alfresco/remoteadm/.*</path>
                    </request>
                    <action name="throwError">
                        <param name="message">It is not allowed to access this url from your browser</param>
                    </action>
                </rule>
 
 
 
                <!--
                 Certain Repo webscripts should be allowed to pass without a token since they have no Share knowledge.
                 TODO: Refactor the publishing code so that form that is posted to this URL is a Share webscript with the right tokens.
                 -->
                <rule>
                    <request>
                        <method>POST</method>
                        <path>/proxy/alfresco/api/publishing/channels/.+</path>
                    </request>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                </rule>
 
 
 
                <!--
                 Certain Surf POST requests from the WebScript console must be allowed to pass without a token since
                 the Surf WebScript console code can't be dependent on a Share specific filter.
                 -->
                <rule>
                    <request>
                        <method>POST</method>
                        <path>/page/caches/dependency/clear|/page/index|/page/surfBugStatus|/page/modules/deploy|/page/modules/module|/page/api/javascript/debugger|/page/console</path>
                    </request>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                </rule>
 
 
 
                <!-- Certain Share POST requests does NOT require a token -->
                <rule>
                    <request>
                        <method>POST</method>
                        <path>/page/dologin(\?.+)?|/page/site/[^/]+/start-workflow|/page/start-workflow|/page/context/[^/]+/start-workflow</path>
                    </request>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                </rule>
 
 
 
                <!-- Assert logout is done from a valid domain, if so clear the token when logging out -->
                <rule>
                    <request>
                        <method>POST</method>
                        <path>/page/dologout(\?.+)?</path>
                    </request>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                    <action name="clearToken">
                        <param name="session">{token}</param>
                        <param name="cookie">{token}</param>
                    </action>
                </rule>
 
 
 
                <!-- Make sure the first token is generated -->
                <rule>
                    <request>
                        <session>
                            <attribute name="_alf_USER_ID">.+</attribute>
                            <attribute name="{token}"/>
                            <!-- empty attribute element indicates null, meaning the token has not yet been set -->
                        </session>
                    </request>
                    <action name="generateToken">
                        <param name="session">{token}</param>
                        <param name="cookie">{token}</param>
                    </action>
                </rule>
 
 
 
                <!-- Refresh token on new "page" visit when a user is logged in -->
                <rule>
                    <request>
                        <method>GET</method>
                        <path>/page/.*</path>
                        <session>
                            <attribute name="_alf_USER_ID">.+</attribute>
                            <attribute name="{token}">.+</attribute>
                        </session>
                    </request>
                    <action name="generateToken">
                        <param name="session">{token}</param>
                        <param name="cookie">{token}</param>
                    </action>
                </rule>
 
 
 
                <!--
                 Verify multipart requests from logged in users contain the token as a parameter
                 and also correct referer & origin header if available
                 -->
                <rule>
                    <request>
                        <method>POST</method>
                        <header name="Content-Type">multipart/.+</header>
                        <session>
                            <attribute name="_alf_USER_ID">.+</attribute>
                        </session>
                    </request>
                    <action name="assertToken">
                        <param name="session">{token}</param>
                        <param name="parameter">{token}</param>
                    </action>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                </rule>
 
 
 
                <!--
                 Verify that all remaining state changing requests from logged in users' requests contains a token in the
                 header and correct referer & origin headers if available. We "catch" all content types since just setting it to
                 "application/json.*" since a webscript that doesn't require a json request body otherwise would be
                 successfully executed using i.e."text/plain".
                 -->
                <rule>
                    <request>
                        <method>POST|PUT|DELETE</method>
                        <session>
                            <attribute name="_alf_USER_ID">.+</attribute>
                        </session>
                    </request>
                    <action name="assertToken">
                        <param name="session">{token}</param>
                        <param name="header">{token}</param>
                    </action>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                </rule>
            </filter>
        </config>
    ...

Configure SAML service provider using the Alfresco admin console (/alfresco/s/enterprise/admin/admin-saml).

Set the following parameters:

To finish with Alfresco configuration, tick the “Enable SAML authentication (SSO)” box.

LL::NG

Configure SAML service and set a certificate as signature public key in metadata.

Export Alfresco SAML Metadata from admin console and import them in LL::NG.

In the authentication response option, set:

  • Default NameID Format: Unspecified
  • Force NameID session key: uid

And you can define these exported attributes:

  • GivenName
  • Surname
  • Email

Other resources