For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

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

57 Comments