APM Sharepoint authentication

Problem this snippet solves:

Updated version to support Webdav with windows explorer after Nicolas's comment.

APM is a great authentication service but it does it only with forms.

The default behavior is to redirect user to /my.policy to process VPE. this redirect is only supported for GET method.

Sharepoint provide 3 different access types:

  • browsing web site with a browser
  • Editing documents with Office
  • browser folder with webdav client (or editing documents with libreoffice through webdav protocol)

This irule display best authentication method for each of these access types:

  • browsers authenticate with default authentication method (form based authentication)
  • Microsoft office authenticate with Form based authentication (with support of MS-OFBA protocol)
  • Libreoffice and webdav clients authenticate with 401 basic authentication

Form based authentication (browser and Microsoft office) is compatible (validated for one customer) with SAML authentication

Editing documents is managed with a persistent cookie expiring after 5 minutes. to be shared between IE and Office, it requires :

  • cookie is persistent (expiration date instead of deleted at the end of session)
  • web site defined as "trusted sites" in IE.

How to use this snippet:

install this irule and enable it on the VS.

Code :

when RULE_INIT {
    array set static::MSOFBA {
        ReqHeader "X-FORMS_BASED_AUTH_REQUIRED"
        ReqVal "/sp-ofba-form"
        ReturnHeader "X-FORMS_BASED_AUTH_RETURN_URL"
        ReturnVal "/sp-ofba-completed"
        SizeHeader "X-FORMS_BASED_AUTH_DIALOG_SIZE"
        SizeVal "800x600"
    }
   set static::ckname "MRHSession_SP"
   set static::Basic_Realm_Text "SharePoint Authentication"
}

when HTTP_REQUEST {
   set apmsessionid [HTTP::cookie value MRHSession]
   set persist_cookie [HTTP::cookie value $static::ckname]
   set clientless_mode 0
   set form_mode 0
   # Identify User-Agents type
   if {[HTTP::header exists "X-FORMS_BASED_AUTH_ACCEPTED"] && (([HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "t") || ([HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "f"))} {
      set clientless_mode 0; set form_mode 1
   } else {
      switch -glob [string tolower [HTTP::header "User-Agent"]] {
        "*microsoft-webdav-miniredir*" { set clientless_mode 1 }
         "*microsoft data access internet publishing provider*" -
         "*office protocol discovery*" -
         "*microsoft office*" -
         "*non-browser*" -
         "msoffice 12*" { set form_mode 1 }
         "*mozilla/4.0 (compatible; ms frontpage*" {
            if { [ string range [getfield [string tolower [HTTP::header "User-Agent"]] "MS FrontPage " 2] 0 1]  > 12 } {
               set form_mode 1
            } else {
               set clientless_mode 1
            }
         }
         "*mozilla*" -
         "*opera*" { set clientless_mode 0 }
         default { set clientless_mode 1
         }
      }
   }
   if { $clientless_mode || $form_mode } {
      if { [HTTP::cookie exists "MRHSession"] } {set apmstatus [ACCESS::session exists -state_allow $apmsessionid]} else {set apmstatus 0}
      if { !($apmstatus) && [HTTP::cookie exists $static::ckname] } {set apmpersiststatus [ACCESS::session exists -state_allow $persist_cookie]} else {set apmpersiststatus 0}
      if { ($apmpersiststatus) && !($apmstatus) } {
         # Add MRHSession cookie for non browser user-agent first request and persistent cookie present
         if { [catch {HTTP::cookie insert name "MRHSession" value $persist_cookie} ] } {log local0. "[IP::client_addr]:[TCP::client_port] : TCL error on HTTP cookie insert MRHSession : URL : [HTTP::host][HTTP::path] - Headers : [HTTP::request]"} else {return}
      }
   } else { return }      
   if { $clientless_mode && !($apmstatus)} {
      if { !([HTTP::header Authorization] == "") } {
         set clientless(insert_mode) 1
         set clientless(username)    [ string tolower [HTTP::username] ]
         set clientless(password)    [HTTP::password]
         binary scan [md5 "$clientless(password)"] H* clientless(hash)
         set user_key "$clientless(username).$clientless(hash)"
         set clientless(cookie_list)             [ ACCESS::user getsid $user_key ]
         if { [ llength $clientless(cookie_list) ] != 0 } {
            set clientless(cookie) [ ACCESS::user getkey [ lindex $clientless(cookie_list) 0 ] ]
            if { $clientless(cookie) != "" } {
               HTTP::cookie insert name MRHSession value $clientless(cookie)
               set clientless(insert_mode) 0
            }
         }
         if { $clientless(insert_mode) } {
            HTTP::header insert "clientless-mode" 1
            HTTP::header insert "username" $clientless(username)
            HTTP::header insert "password" $clientless(password)
        }
        unset clientless
      } else {
        HTTP::respond 401 WWW-Authenticate "Basic realm=\"$static::Basic_Realm_Text\"" Set-Cookie "MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/" Connection close
        return
      }
   } elseif {$form_mode && !($apmstatus) && !([HTTP::path] equals $static::MSOFBA(ReqVal))}{
      HTTP::respond 403 -version "1.1" noserver \
         $static::MSOFBA(ReqHeader) "https://[HTTP::host]$static::MSOFBA(ReqVal)" \
         $static::MSOFBA(ReturnHeader) "https://[HTTP::host]$static::MSOFBA(ReturnVal)" \
         $static::MSOFBA(SizeHeader) $static::MSOFBA(SizeVal) \
         "Connection" "Close"
      return
   }
}


when HTTP_RESPONSE {
   # Insert persistent cookie for html content type and private session
   if { [HTTP::header "Content-Type" ] contains "text/html" } {
      HTTP::cookie remove $static::ckname
      HTTP::cookie insert name $static::ckname value $apmsessionid path "/"
      HTTP::cookie expires $static::ckname 120 relative
      HTTP::cookie secure $static::ckname enable
   }
   # Insert session cookie if session was recovered from persistent cookie
   if { ([info exists "apmpersiststatus"]) && ($apmpersiststatus) } {
         HTTP::cookie insert name MRHSession value $persist_cookie path "/"
         HTTP::cookie secure MRHSession enable
   }
}

when ACCESS_SESSION_STARTED {
   if {([info exists "clientless_mode"])} {
      ACCESS::session data set session.clientless $clientless_mode
   }
   if { [ info exists user_key ] } {
      ACCESS::session data set "session.user.uuid" $user_key
   }
}

when ACCESS_POLICY_COMPLETED {
   if { ([info exists "clientless_mode"]) && ($clientless_mode) && ([ACCESS::policy result] equals "deny") } {
      ACCESS::respond 401 noserver WWW-Authenticate "Basic realm=$static::Basic_Realm_Text" Connection close
      ACCESS::session remove
   } 
}

when ACCESS_ACL_ALLOWED {
   switch -glob [string tolower [HTTP::path]] {
      "/sp-ofba-form" {
         ACCESS::respond 302 noserver Location "https://[HTTP::host]$static::MSOFBA(ReturnVal)"
      }
      "/sp-ofba-completed" {
         ACCESS::respond 200 content {
            
               Authenticated
               Good Work, you are Authenticated
            
         } noserver
      }
      "*/signout.aspx" {
         # Disconnect session and redirect to APM logout Page
         ACCESS::respond 302 noserver Location "/vdesk/hangup.php3"
         return
      }
      "/_layouts/accessdenied.aspx" {
         # Disconnect session and redirect to APM Logon Page
         if {[string tolower [URI::query [HTTP::uri] loginasanotheruser]] equals "true" } {
            ACCESS::session remove
            ACCESS::respond 302 noserver Location "/"
            return
         }
      }
      default {
         # No Actions
      }
   }
}

Tested this on version:

11.5
Published Apr 20, 2016
Version 1.0
  • Thank you for sharing, it has got me a long way to a successful configuration.

     

    I have built up a sharepoint installation using SSO with PKI to Kerberos. So the user presents there certificate because of client ssl profile. After client cert inspection and OCSP in access profile a SSO configuration does the Kerberos lookup to pass to SharePoint.

     

    Now I have added OWA on a separate VIP which also checks client certificate.

     

    The iRule takes care of switching to clientless mode when OWA connects to SharePoint VIP. I have modified it to also check for the OWA request is from a known list. I have modified the access policy to bypass client cert inspection based on above change to iRule. I also found I had to switch to clientless mode when user-agent = microsoft office protocol discovery.

     

    I don't think I have any forms based authentication so I have removed that code from the iRule.

     

    What I would be interested in is some commentary as to how the iRule works please. Are you able to share the access policy as well please? Thank you.

     

  • Hi Kai,

    Thank you for the comment.

    I rollback to the previous version. I am not able to upload new content without & and HTML header...

    the only difference with the new version is line 113. I forgot to insert quote before and after realm name.

    the line must be:

    ACCESS::respond 401 noserver WWW-Authenticate "Basic realm=\"$static::Basic_Realm_Text\"" Connection close
    
  • Hi Stanislas,

     

    Thanks for sharing this iRule to Devcentral.

     

    I'm even wondering why this iRule hasn't won the 2016 coding challenge. The ability to support MSOFBA is so much required for every SharePoint and esp. these WS-Federation based deployments. O well, but a (highly insecure) SCEP based certificate enrollment page for iPhones sounds so much cooler, isn't it? To bad that I have not found the time to vote for you... ;-)

     

    BTW: Your last code update has broken the formating of the code. It contains now a lot of & and some malformated .

     

    Cheers, Kai

     

  • Hi, we need to adapt / correct this irule for managed network drive because Windows network drive used user-agent like this : Microsoft-WebDAV-MiniRedir And it's doesn't manage authentification with form mode. So it's just need to change this : Before line 24 : "*microsoft-webdav-miniredir*" - After line 22 : "*microsoft-webdav-miniredir*" { set clientless_mode 1 } After operate this modification, Network drive it's ok. Best regards
  • Hi. Just to make sure : Would this solve the issue of editing office documents through APM (with a rewrite profile) ? I'm trying to publish Sharepoint with APM and everything seems to be working except that editing part. Many thanks
  • I reduce the persistent cookie expiration time to 20 seconds. It permits to increase security in my case. Thanks for this really efficient Irule.