# 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's 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:

- Enable SAML Authentication (SSO): on
- Authentication service URL: https://auth.example.com/saml/singleSignOn
- Single Logout URL: https://auth.example.com/saml/singleLogout
- Single logout return URL: https://auth.example.com/saml/singleLogoutReturn
- Entity identification: http://alfresco.myecm.org:8080/share
- User ID mapping: Subject/NameID

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

- DevCon 2012: Unlocking the Secrets of Alfresco Authentication, Mehdi Belmekki
- Setting up Alfresco SAML authentication with LemonLDAP::NG