SSO support for Aikau apps

Alfresco Share webapp supports Kerberos SSO since many years ago. In order to enable SSO in a standalone Aikau application some of these SSO Alfresco Share resources have to be copied to the web application.

Starting from the default aikau-sample web app, following steps are including these resources to enable SSO support.

Creating a new Aikau client

From the command line a new client project can be created:

mvn archetype:generate -DarchetypeCatalog=https://artifacts.alfresco.com/nexus/content/groups/public/archetype-catalog.xml \
-DarchetypeGroupId=org.alfresco -DarchetypeArtifactId=aikau-sample-archetype -DarchetypeVersion=RELEASE

A new label has been added to identify the user logged in the web app at home page.

{
    name: "alfresco/html/Label",
       config: {
          label: "Current user: " + user.fullName,
          additionalCssClasses: "bold {additionalCssClasses}"
    }
}

Including Maven dependencies

As SSO filter requires some classes which are part of share artifact, pom.xml has to be modified

<dependency>
    <groupId>org.alfresco</groupId>
    <artifactId>share</artifactId>
    <version>5.1.g</version>
    <classifier>classes</classifier>
</dependency>

Declaring the filter in web application descriptor

Once our SSO classes are available, we adapt our web.xml to filter the requests.

A. Filter definition

<filter>
  <description>Share SSO authentication support filter.</description>
  <filter-name>SSOAuthenticationFilter</filter-name>
  <filter-class>org.alfresco.web.site.servlet.SSOAuthenticationFilter</filter-class>
  <init-param>
     <param-name>endpoint</param-name>
     <param-value>alfresco</param-value>
  </init-param>
</filter>

B. Filter URL patterns

<filter-mapping>
  <filter-name>SSOAuthenticationFilter</filter-name>
  <url-pattern>/page/*</url-pattern>
</filter-mapping>

<filter-mapping>
  <filter-name>SSOAuthenticationFilter</filter-name>
  <url-pattern>/p/*</url-pattern>
</filter-mapping>

<filter-mapping>
  <filter-name>SSOAuthenticationFilter</filter-name>
  <url-pattern>/proxy/*</url-pattern>
</filter-mapping>

C. Spring Listener

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/config/web-application-config.xml</param-value>
</context-param>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>   

Including Kerberos properties in Surf configuration file

In Alfresco Share, Surf configuration can be included at share-config-custom.xml. This configuration can be copied to surf.xml in our Aikau web app.

<!-- KERBEROS SETTINGS -->
<config evaluator="string-compare" condition="Kerberos" replace="true">
  <Kerberos>
    <kerberos>
       <password>password</password>
       <realm>KEENSOFT.LOCAL</realm>
       <endpoint-spn>http/dms.keensoft.local@KEENSOFT.LOCAL</endpoint-spn>
       <config-entry>ShareHTTP</config-entry>
    </kerberos>
  </Kerberos>
</config>

<config evaluator="string-compare" condition="Remote">
  <remote>
     <endpoint>
        <id>alfresco-noauth</id>
        <name>Alfresco - unauthenticated access</name>
        <description>Access to Alfresco Repository WebScripts that do not require authentication</description>
        <connector-id>alfresco</connector-id>
        <endpoint-url>http://localhost:8080/alfresco/s</endpoint-url>
        <identity>none</identity>
     </endpoint>

     <endpoint>
        <id>alfresco</id>
        <name>Alfresco - user access</name>
        <description>Access to Alfresco Repository WebScripts that require user authentication</description>
        <connector-id>alfresco</connector-id>
        <endpoint-url>http://localhost:8080/alfresco/s</endpoint-url>
        <identity>user</identity>
     </endpoint>

  </remote>
</config>

<config evaluator="string-compare" condition="Remote">
  <remote>

     <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>

     <endpoint>
        <id>alfresco</id>
        <name>Alfresco - user access</name>
        <description>Access to Alfresco Repository WebScripts that require user authentication</description>
        <connector-id>alfrescoCookie</connector-id>
        <endpoint-url>http://localhost:8080/alfresco/wcs</endpoint-url>
        <identity>user</identity>
        <external-auth>true</external-auth>
     </endpoint>

  </remote>
</config>

Caution: Check password, realm, endpoint-spn and config-entry to include your own data.

Fixing a broken link

Every SSO resource has been copied to Aikau web app by now, however a final touch has to be made. AikauLoginController.java is generated by Aikau archetype to work with the client, however the implementation is not compatible with SSO filter. A minor modification for extension is required.

public class AikauLoginController extends org.alfresco.web.site.servlet.SlingshotLoginController {

From now your Aikau web application is accepting SSO Kerberos request for authentication.

Final words

  • The source code for this blog post is available at https://github.com/angelborroy/aikau-kerberos-sso
  • More grained Maven dependencies can be configured, instead of full Share artifact, in order to save some JAR files from packaged WAR
  • Every Alfresco Share SSO resource has been copied without modification, only a minor change has been made to Aikau auto-generated source code

Alfresco: extending Aikau widgets without “id”

One of the most used mechanisms to extend Aikau pages is to find a page widget with widgetUtils.findObject function and to inject new services and new widgets in order to build an extended Aikau page.

// Find widget by id
var siteConfig = widgetUtils.findObject(model.jsonModel, "id", "HEADER_SITE_CONFIGURATION_DROPDOWN");

if (siteConfig != null) {
    
    // Inject new service    
    model.jsonModel.services.push("sitehomepage/SiteHomePageService");
        
    // Inject new widgets at "HEADER_SITE_CONFIGURATION_DROPDOWN"
    siteConfig.config.widgets.push({
        //...
    }
}

However, what about if our targeted widget has no ID field?

I was extending Manage Sites aikau page (manage-sites.get.js) to add a new option (Restore Home Page) for every Site at actions column.

alfresco-sites-manager-extension

However, there is no ID to find on this widgets at Alfresco source code.

name: "alfresco/documentlibrary/views/layouts/Cell",
config: {
   additionalCssClasses: "actions smallpad",
   widgets: [
      {
         name: "alfresco/menus/AlfMenuBar",
         align: "left",
         config: {
            widgets: [
               {
                  name: "alfresco/menus/AlfMenuBarPopup",
                  config: {
                     label: msg.get("message.actions-header-label"),
                     widgets: [
                        {
                           name: "alfresco/menus/AlfMenuGroup",
                           config: {
                              additionalCssClasses: "unmargined",
                              widgets: [
                              ...

So, I’ve been learning about widgetUtils.findObject behavior at spring surf script class ScriptSiteData.java and I’ve found that in fact it can be used to find by every object field. In my case there is just one AlfMenuGroup, so I’ve extended this page by using following approach.

// Find widget by NAME (!)
var dropDownMenuConfig = widgetUtils.findObject(model.jsonModel, "name", "alfresco/menus/AlfMenuGroup");

if (dropDownMenuConfig != null) {
	
    model.jsonModel.services.push("sitehomepage/RestoreSiteHomePageService");
	
	dropDownMenuConfig.config.widgets.push({
        name: "alfresco/menus/AlfMenuItem",
        config: {
           label: msg.get("button.site.home.page.restore"),
           iconClass: "alf-fullscreen-icon",
           publishTopic: "ALF_RESTORE_SITE_HOME_PAGE",
           publishPayloadType: "BUILD",
           publishPayload: {
              site: {
                 alfType: "item",
                 alfProperty: "shortName"
              }
           }
        }
    });
}

Obviously this is not the cleaner way to solve this problem, but it can help in the meanwhile.

Remember that all this code is available as an Alfresco SDK 2.1 project at https://github.com/angelborroy/alfresco-site-home-page