APM Sharepoint authentication v2

Problem this snippet solves:

This new version of irule supports NTLM auth (mandatory for Onedrive Apps)

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
  • connect to One Drive on premise from PC and mobiles
  • 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 (NTLM and Basic)
  • Form based authentication (browser and Microsoft office) is compatible (validated for one customer) with SAML authentication
  • NTLM auth for Onedrive mobile applications

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.

In the first HTTP_REQUEST event, configure authentication mode list by setting the AUTHENTICATION_MODE variable

Set authentication mode list supported. possible values are :

  • form :default Form based authentication
  • msofba : Microsoft Office Form Based Authentication for Office and Onedrive apps
  • persist : Add persistent cookie to recover closed session. this function is only supported by form and msofba authentications.

    • --> persist word must be set after authentication mode : ex : {form persist} or {msofba persist}
  • basic : Basic Authentication

  • ntlm : NTLM Authentication

  • negotiate : Kerberos / SPNEGO authentication : Not supported yet by this irule

    • --> basic, ntm and negotiate can be set together. ex: {negotiate ntlm basic} {ntlm basic}
  • deny : send a 403 response code to deny the request

  • disable : disable APM authentication

Code :

when RULE_INIT {
         #If NTLM Auth is defined below, define the ECA_METADATA_ARG with your NTLM profile and enable eca profile in virtual server configuration with tmsh command
         # modify ltm virtual [virtual name] profile add {eca}
         set static::ECA_METADATA_ARG "select_ntlm:/Common/NTLM-Auth"
         set static::session_restore_aes_key "AES 256 affeaffeaffeaffeaffeaffeaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe"  ;# AES Key to protect and validate recovery data
         # Define required APM variables stored in the restore cookie to create new session with same security level.
         set static::session_restore_variables {
            session.ui.lang
            session.logon.last.username
            session.logon.last.logonname
            session.logon.last.krbdomain
            session.logon.last.domain
            session.krbsso.last.domain
            session.krbsso.last.username
            session.assigned.acls
            session.logon.last.domain
            session.sso.token.last.username
            session.user.sessiontype
         }
         # Cookie expire in 2 hours for the test...
         set static::session_restore_timeout 172800 ;### 7200 / 172800
      }
      
      when CLIENT_ACCEPTED {
         set last_ua_agent "init"
         # Set addtional HTTP headers in HTTP authentication responses
         set ADDITIONAL_AUTH_HEADERS "MicrosoftSharePointTeamServices 15.0.0.4763"
      }
      
      when HTTP_REQUEST {
         #################################### Authentication method selection #####################################################
         # Set authentication mode list supported. possible values are
         #   form :default Form based authentication
         #   msofba : Microsoft Office Form Based Authentication for Office and Onedrive apps
         #   persist : Add persistent cookie to recover closed session. this function is only supported by form and msofba authentications.
         #   --> persist word must be set after authentication mode : ex : {form persist} or {msofba persist}
         #   basic : Basic Authentication
         #   ntlm : NTLM Authentication
         #   negotiate : Kerberos / SPNEGO authentication : Not supported yet by this irule
         #   --> basic, ntm and negotiate can be set together. ex: {negotiate ntlm basic} {ntlm basic}
         #   deny : send a 403 response code to deny the request
         #   disable : disable APM authentication
         # Disable Authentication for Internal networks
         #if { [IP::addr [IP::client_addr]/25 equals 1.1.1.0] or [IP::addr [IP::client_addr]/25 equals 2.2.2.0] } {
         #     set AUTHENTICATION_MODE {disable}
         #     ASM::disable
         #     return
         #}
         #log local0. [HTTP::header "User-Agent"]
          if { $last_ua_agent equals [set last_ua_agent [HTTP::header value "User-Agent"]] } {
              # Do nothing, keep previous request authschema value
          } elseif {[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 AUTHENTICATION_MODE {msofba}
          } else {
              switch -glob -- [string tolower [HTTP::header "User-Agent"]] {
                  "*microsoft office *ios*" -
                  "*onedriveiosapp*" -
                  "onedrive/*darwin*" {
                     # NTLM is the only one supported for onedrive mobile
                     set AUTHENTICATION_MODE {ntlm basic}
                     #set AUTHENTICATION_MODE {deny}
                  }
                  "*microsoft office onedrive*" -
                  "*microsoft onedrive*" -
                  "*microsoft office skydrive*" -
                  "*microsoft office syncproc*" -
                  "*microsoft office upload center*" -
                  "*office protocol discovery*" -
                  "*microsoft office*" -
                  "*microsoft data access internet publishing provider*" -
                  "*non-browser*" -
                  "msoffice 12*" -
                  "*microsoft-webdav-miniredir*" -
                  "*ms frontpage 1[23456789]*" {
                      # Implicit MSOFBA support detected.   
                      set AUTHENTICATION_MODE {msofba}
                      #set AUTHENTICATION_MODE {ntlm basic}
                  }
                  "*ms frontpage*" {
                      # Legacy client detected
                      set AUTHENTICATION_MODE {ntlm basic}
                      #set AUTHENTICATION_MODE {deny}
                  }
                  "*mozilla*" -
                  "*opera*" {
                      # Regular web browser detected.  
                      set AUTHENTICATION_MODE {form}
                  }
                  default { 
                      set AUTHENTICATION_MODE {ntlm basic}
                      #set AUTHENTICATION_MODE {msofba}
                  }
              }
              #log local0. "[IP::remote_addr] : [string tolower [HTTP::header "User-Agent"]] : $AUTHENTICATION_MODE : [HTTP::cookie value MRHSession] : [HTTP::cookie value MRHSession_R]"
         }
         #################################### end of Authentication method selection ##############################################
      }
 
 
      priority 900
      
      when RULE_INIT {
        set static::AUTH_POLICY_FAILED                     "policy_failed"
        set static::AUTH_POLICY_SUCCEED                    "policy_succeed"
        set static::AUTH_POLICY_DONE_WAIT_SEC              5
 
        set static::AUTH_FIRST_BIG_POST_CONTENT_LEN        640000
        set static::AUTH_POLICY_RESULT_POLL_INTERVAL       100
        set static::AUTH_POLICY_RESULT_POLL_MAXRETRYCYCLE  100
        set static::AUTH_ACCESS_USERKEY_TBLNAME            "auth_access_userkey"
        set static::AUTH_ACCESS_LOG_PREFIX                 "01490000:7:"
 
        set static::AUTH_ACCESS_DEL_COOKIE_HDR_VAL         "MRHSession=deleted; \
                                                       expires=Thu, 01-Jan-1970 00:00:01 GMT;\
                                                       path=/"
 
    }
      
      when CLIENT_ACCEPTED {
         set clientless_mode 0
         set inject_session_cookie ""
         set inject_recover_cookie 0
         if { ! [ info exists ADDITIONAL_AUTH_HEADERS ] } {
            set ADDITIONAL_AUTH_HEADERS     ""
         }
      }
      
      when HTTP_REQUEST {
         set inject_session_cookie ""
         if { ! [ info exists f_ntlm_auth_succeed ] } {
            set f_ntlm_auth_succeed         0
         }
         if { ! [ info exists sid_cache ] } {
            set sid_cache                         ""
         }
        if { ! [ info exists PROFILE_POLICY_TIMEOUT ] } { 
            set PROFILE_POLICY_TIMEOUT            [PROFILE::access access_policy_timeout]
        }
        if { ! [ info exists PROFILE_MAX_SESS_TIMEOUT ] } {
            set PROFILE_MAX_SESS_TIMEOUT          [PROFILE::access max_session_timeout]
        }
         if { ! [ info exists src_ip ] } {
            set src_ip                            [IP::remote_addr]
         }
          if { ! [ info exists PROFILE_RESTRICT_SINGLE_IP ] } {
              set PROFILE_RESTRICT_SINGLE_IP        [PROFILE::access restrict_to_single_client_ip]
          }
                 
        set persisted_session_timeout       [PROFILE::access inactivity_timeout]         
        set http_method                     [HTTP::method]
        set http_uri                        [HTTP::uri]
        set http_content_len                [HTTP::header Content-Length]
        set MRHSession_cookie               [HTTP::cookie value MRHSession]
          if { ! [ info exists AUTHENTICATION_MODE ] } {
              set AUTHENTICATION_MODE {form}
          }
 
        
         switch -- [lindex $AUTHENTICATION_MODE 0] {
            "disable" {
         #################################### APM Disable #########################################################################
         # disable APM and leave irule if mode is set to disable
               ACCESS::disable
               return
            }
            "form" {
         #################################### Form based authentication ###########################################################
         # Leave irule if authentication mode is set to default form base authentication
               if {[lindex $AUTHENTICATION_MODE 1] equals "persist"} {
                  set inject_recover_cookie 1} else {set inject_recover_cookie 0}
               set apm_sessionid [HTTP::cookie value "MRHSession"]
               if {![HTTP::cookie exists MRHSession] || !($inject_recover_cookie && [HTTP::cookie exists MRHSession_R])} {return}
            }
            "deny" {
         #################################### Deny Request ########################################################################
         # Respond with 403 response code and "Access Denied" content if mode is set to deny
               HTTP::respond 403 -version "1.1" \
                  content {Access Denied.} \
                  noserver \
                  "Content-Type" "text/html" \
                  "Set-Cookie" "MRHSession=deleted;path=/;secure" \
                  "Set-Cookie" "LastMRH_Session=deleted;path=/;secure" \
                  "Set-Cookie" "MRHSession=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure" \
                  "Set-Cookie" "LastMRH_Session=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure"
               return
            }
            default {
         #################################### All other authentication methods #####################################################
         # provision response Headers for specific authentication
               set httpheaders "$ADDITIONAL_AUTH_HEADERS Set-Cookie \"MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/\" Connection close"
               set httpcontent "ForbidenAuthentication\\ Required"
               set httpcode 401
               foreach authvalue $AUTHENTICATION_MODE {
                  switch $authvalue {
                     basic {
                        append httpheaders " WWW-Authenticate \"Basic realm=\\\"[HTTP::host]\\\"\""
                     }
                     ntlm {
                        append httpheaders " WWW-Authenticate NTLM"
                     }
                     negotiate {
                        # Authentication method negociate not managed by this irule yet
                        append httpheaders " WWW-Authenticate Negotiate"
                     }
                     msofba {
                           #log local0. [HTTP::cookie value MRHSession_SP]
                        if {[lindex $AUTHENTICATION_MODE 1] equals "persist" && [HTTP::cookie exists MRHSession_R]} {
                           set persisted_session_timeout 300
                        } elseif {[HTTP::path] ne "/vdesk/ms-ofba-form"} {
                           append httpheaders " \"Content-Type\"\ \"text/html\""
                           append httpheaders " X-FORMS_BASED_AUTH_REQUIRED"\ "https://[getfield [HTTP::host] ":" 1]/vdesk/ms-ofba-form?mode=[lindex $AUTHENTICATION_MODE 1]"
                           append httpheaders " X-FORMS_BASED_AUTH_RETURN_URL"\ "https://[getfield [HTTP::host] ":" 1]/vdesk/ms-ofba-completed"                           
                           append httpheaders " X-FORMS_BASED_AUTH_DIALOG_SIZE"\ "800x600"
                           set httpcontent "ForbidenAccess\\ Denied.\\ Make\\ sure\\ that\\ your\\ client\\ is\\ correctly\\ configured.\\ See\\ https://support.microsoft.com/en-us/kb/932118\\ for\\ further\\ information."
                           set httpcode 403
                        }
                     }
                  }
               }
            }
         }
 
         #################################### NTLM already authenticated connection ###############################################
         if {$f_ntlm_auth_succeed} {
            # enable ECA profile for already NTLM authenticated connections and ignore this irule event
            ECA::enable
            ECA::select $static::ECA_METADATA_ARG
            return
         }
         #################################### Valid session request : MHRSession Cookie ###########################################
         if { ( [set apm_sessionid [HTTP::cookie value "MRHSession"]] ne "" ) and ( [ACCESS::session exists -state_allow $apm_sessionid] ) } then {
             # Allow the successfully pre authenticated request to pass
             return
         #################################### Valid session request : MHRSession_R Cookie ########################################
         ### This cokie is inserted to allow a user to log with recorded data stored on a encrypted cookie
         } elseif {([lindex $AUTHENTICATION_MODE 0] ne "form") and ( [set apm_sessionid [HTTP::cookie value "MRHSession_SP"]] ne "" ) and ( [ACCESS::session exists -state_allow $apm_sessionid] ) } then {
         #################################### Valid session request : MHRSession_SP Cookie ########################################
            # Check if persistent APM session cookie is present and valid
            # Restore APM session cookie value
            HTTP::cookie insert name "MRHSession" value $apm_sessionid
            set inject_session_cookie $apm_sessionid 
            # Allow the successfully pre authenticated request to pass
            return
         } elseif { [lindex $AUTHENTICATION_MODE 1] equals "persist" && [HTTP::cookie exists MRHSession_R]
         and ( [set session_restore_data [AES::decrypt $static::session_restore_aes_key [b64decode [HTTP::cookie value MRHSession_R]]]]  ne "" )} {
    array set restore_data $session_restore_data
    if {$restore_data(timeout) > [clock seconds] } {
               set user_key "persist.[PROFILE::access name].$restore_data(session.logon.last.username)"
               if {[set apm_sessionid [table lookup -subtable  APMSessionRestore $user_key]] != "" &&  ( [ACCESS::session exists -state_allow $apm_sessionid] ) } {
                  #log local0. "session recover by table"
               } elseif { ([ llength [set cookie_list [ ACCESS::user getsid $user_key ] ] ] != 0) and [set apm_sessionid [ ACCESS::user getkey [ lindex $cookie_list 0 ] ] ] ne ""} {
                     #log local0. "session recover by cookie list"
              } else {
               #log local0. "new session"
               if { [HTTP::header value "Accept-Language"] eq "" } then {
    # A "Accept-language" header is not present. Injecting language code = none
    HTTP::header insert "Accept-Language" "none"
}
set apm_sessionid [ACCESS::session create -timeout $persisted_session_timeout]
ACCESS::session data set -sid $apm_sessionid "session.policy.result" "allow"
foreach session_variable [lsearch -all -inline -not -exact [array names restore_data] timeout] {
    ACCESS::session data set -sid $apm_sessionid $session_variable $restore_data($session_variable)
    #log local0.debug "Adding $session_variable = $restore_data($session_variable)"
}
ACCESS::session data set -sid $apm_sessionid ".session.assigned.uuid" "tmm.uuid.$user_key"
                ACCESS::session data set -sid $apm_sessionid "session.user.uuid" $user_key
       }
            HTTP::cookie insert name "MRHSession" value $apm_sessionid
    set inject_session_cookie $apm_sessionid
            table set -subtable "APMSessionRestore"  $user_key $apm_sessionid $persisted_session_timeout indef
            unset restore_data user_key
            return
         }
         }
         #################################### Request with valid Authorization Header ############################################
         ### convert authentication header to be managed by APM
          if { [ llength [set auth_data [split [HTTP::header Authorization] " "]] ] == 2 } {
               if {[lsearch -exact $AUTHENTICATION_MODE [set authvalue [string tolower [ lindex $auth_data 0]]]] ne -1} {
                  switch $authvalue {
                      "ntlm" {
                          ECA::enable
                          ECA::select $static::ECA_METADATA_ARG
                      }
                      "basic" {
                          set clientless(insert_mode) 1
                          set clientless(src_ip)      [IP::remote_addr]
                          set clientless(username)    [ string tolower [HTTP::username] ]
                          set clientless(password)    [HTTP::password]
                          if { $PROFILE_RESTRICT_SINGLE_IP == 0 } {
                              binary scan [md5 "$clientless(password)"] H* clientless(hash)
                          } else {
                              binary scan [md5 "$clientless(password)$clientless(src_ip)"] 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)
                             set clientless_mode 1
                         }
                         unset clientless
                      }
                      "negotiate" {
                        #log local0.  [getfield [HTTP::header Authorization] " " 2]
                        #set clientless(insert_mode) 1
                        #set clientless(authparam) [getfield [HTTP::header Authorization] " " 2]
                        #log local0. $clientless(authparam)
                        #log local0. [string length $clientless(authparam)]
                        #set clientless(decode) [b64decode $clientless(authparam)]
                        ##log local0. [sha256 "$clientless(decode)"]
                        #binary scan [sha256 "$clientless(decode)"] H* clientless(hash)
                        #log local0. $clientless(hash)
                        #set user_key "$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
                        #     set clientless_mode 1
                        # }
                        # unset clientless
                      }
                      default {
                          #other authentication methodes are not managed by this irule yet
                      }
                  }
               }
         #################################### Redirect unmanaged requests with supported authentication method  ##################               
           } else {
                eval HTTP::respond $httpcode -version 1.1 content $httpcontent noserver $httpheaders
               return
           }
      }
      
      
      when HTTP_RESPONSE {
          # Insert persistent cookie for html content type and private session
          if { [HTTP::header "Content-Type" ] contains "text/html" && [info exists "apm_sessionid"]} {
              HTTP::cookie remove MRHSession_SP
              HTTP::cookie insert name MRHSession_SP value $apm_sessionid path "/"
              HTTP::cookie expires MRHSession_SP 300 relative
              HTTP::cookie secure MRHSession_SP enable
          }
          # Insert session cookie if session was recovered from persistent cookie
          if { ([info exists "inject_session_cookie"]) && ($inject_session_cookie ne "") } {
              HTTP::cookie insert name MRHSession value $inject_session_cookie path "/"
              HTTP::cookie secure MRHSession enable
              #log local0. "injected $inject_session_cookie"
          }
         if { $inject_recover_cookie } then {
                  # Insert APM recover session cookie into HTTP response.
                  set session_restore_data "[clock seconds] [ACCESS::session data get "session.logon.last.username"]"
                  foreach session_variable $static::session_restore_variables {
                      if { [set session_variable_value [ACCESS::session data get $session_variable]] ne "" } then {
                          lappend session_restore_data "$session_variable=$session_variable_value"
                      }
                  }
                  HTTP::header insert "Set-Cookie" "MRHSession_R=[b64encode [AES::encrypt $static::session_restore_aes_key $session_restore_data]];Path=/;Secure;HttpOnly"
                  set inject_recover_cookie 0
         }
        if {[HTTP::header exists "Transfer-Encoding"]} {
            HTTP::payload rechunk
        }
      }
      
      when ACCESS_SESSION_STARTED {
          if {[set landinguri [ACCESS::session data get session.server.landinguri]] equals "/vdesk/ms-ofba-form?mode=persist" } {
            ACCESS::session data set session.server.landinguri "/vdesk/AddPersistentCookie?url=[b64encode "/vdesk/ms-ofba-completed"]"
          } elseif {([info exists "inject_recover_cookie"])} {
              ACCESS::session data set session.server.landinguri "/vdesk/AddPersistentCookie?url=[b64encode [ACCESS::session data get session.server.landinguri]]"
              ACCESS::session data set session.inactivity_timeout 300
          }
          if {([info exists "clientless_mode"])} {
              ACCESS::session data set session.clientless $clientless_mode
              if {$clientless_mode} {
               ACCESS::session data set session.inactivity_timeout 300
               if {[HTTP::header Authorization] starts_with "Negotiate"}{
                  ACCESS::session data set session.logon.last.authtype "Negotiate"         
                  ACCESS::session data set session.logon.last.authparam [getfield [HTTP::header Authorization] " " 2]
               }
              }
          }
          if { [ info exists user_key ] } {
              ACCESS::session data set "session.user.uuid" $user_key
          }
      }
      
      when ACCESS_POLICY_COMPLETED {
         if { ! [ info exists user_key ] } {
            return
        }
        
        set user_key_value ""
        set f_delete_session 0
        set policy_result [ACCESS::policy result]
        set sid [ ACCESS::session sid ]
 
        switch $policy_result {
        "allow" {
            set user_key_value          $sid
            set sid_cache               $user_key_value
            log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX Result: Allow: $user_key"
            log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX sid = $sid"
 
        }
        "deny" {
            eval ACCESS::respond 401 -version 1.1 content {ForbidenAuthentication\ Required} noserver $httpheaders
            set f_delete_session  1
        }
        default {
            ACCESS::respond 503 content  $static::actsync_503_http_body Connection Close
            log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX Got unsupported policy result for $user_key ($sid)"
            set f_delete_session  1
        }
        }
 
        if { $f_ntlm_auth_succeed == 1 } {
            if { $user_key_value != "" } {
                log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX Setting $user_key => $static::AUTH_POLICY_SUCCEED"
                table set -subtable  $static::AUTH_ACCESS_USERKEY_TBLNAME $user_key $static::AUTH_POLICY_SUCCEED
            } else {
                log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX Setting $user_key => $static::AUTH_POLICY_FAILED  $static::AUTH_POLICY_DONE_WAIT_SEC $static::AUTH_POLICY_DONE_WAIT_SEC in table $static::AUTH_ACCESS_USERKEY_TBLNAME"
                table set -subtable  $static::AUTH_ACCESS_USERKEY_TBLNAME $user_key $static::AUTH_POLICY_FAILED  $static::AUTH_POLICY_DONE_WAIT_SEC $static::AUTH_POLICY_DONE_WAIT_SEC
            }
        }
 
        if { $f_delete_session == 1 } {
            ACCESS::session remove
            set f_delete_session 0
            log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX Removing the session for $user_key."
        }
      }
      
      when ACCESS_ACL_ALLOWED {
          switch -glob -- [string tolower [HTTP::path]] {
               "/vdesk/addpersistentcookie" {
                  set cookie_expire_absolute [expr {[clock seconds] + $static::session_restore_timeout}]
                  set cookie_expire_date  [clock format $cookie_expire_absolute -format "%a, %d-%b-%Y %H:%M:%S GMT" -gmt true]
                  set session_restore_data "timeout $cookie_expire_absolute"
                  foreach session_variable $static::session_restore_variables {
                      if { [set session_variable_value [ACCESS::session data get $session_variable]] ne "" } then {
                          lappend session_restore_data $session_variable $session_variable_value
                      }
                  }
                  set cookie [format "MRHSession_R=%s; path=/; expires=%s;Secure; HttpOnly" [b64encode [AES::encrypt $static::session_restore_aes_key $session_restore_data]] $cookie_expire_date]
                  ACCESS::respond 302 noserver Location [b64decode [URI::query [HTTP::uri] url]] "Set-Cookie" $cookie
               }
              "/vdesk/ms-ofba-form" {
                     ACCESS::respond 302 noserver Location "/vdesk/ms-ofba-completed"
              }
              "/vdesk/ms-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" "Set-Cookie" "MRHSession_R=deleted;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure"
                  event disable
                  TCP::close
                  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 "/" "Set-Cookie" "MRHSession_R=deleted;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure"
                      event disable
                      TCP::close
                      return
                  }
              }
              default {
                  # No Actions
              }
          }
      }
 
    when ECA_REQUEST_ALLOWED {
        set f_ntlm_auth_succeed                 1
 
        if { $MRHSession_cookie == "" } {
            # Retrieve from SID cache
            set MRHSession_cookie   $sid_cache
            HTTP::cookie insert name MRHSession value $sid_cache
        }
 
        if { $MRHSession_cookie != "" } {
            # Destroy session ID cache. This client should not need session ID cache 
            if { ($sid_cache != "") && ($sid_cache != $MRHSession_cookie) } {
                set sid_cache   ""
            }
            if { [ ACCESS::session exists -state_allow $MRHSession_cookie ] } {
                log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX HTTP *VALID* MRHSession cookie: $MRHSession_cookie"
                # Default profile access setting is false
                if { $PROFILE_RESTRICT_SINGLE_IP == 0 } {
                    log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX Release the request"
                    return
                }
                elseif { [ IP::addr $src_ip equals [ ACCESS::session data get -sid $MRHSession_cookie "session.user.clientip" ] ] } {
                    log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX source IP matched. Release the request"
                    return
                }
                else {
                    log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX source IP does not matched"
                }
            } else {
                log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX HTTP *INVALID* MRHSession cookie: $MRHSession_cookie"
            }
        }
 
        set MRHSession  ""
        set sid_cache   ""
        HTTP::cookie remove MRHSession
 
        # Build user_key
        set    user_key                 {}
        append user_key                 [string tolower [ECA::username]] "@" [ string tolower [ECA::domainname] ]
        if { $PROFILE_RESTRICT_SINGLE_IP == 0 } {
            append user_key             ":" $src_ip
        }
        append user_key                 ":" [ECA::client_machine_name]
 
        set apm_cookie_list             [ ACCESS::user getsid $user_key ]
        if { [ llength $apm_cookie_list ] != 0 } {
            set MRHSession_cookie [ ACCESS::user getkey [ lindex $apm_cookie_list 0 ] ]
            if { $MRHSession_cookie != "" } {
                set sid_cache           $MRHSession_cookie
                HTTP::cookie insert name MRHSession value $MRHSession_cookie
                log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX APM Cookie found: $sid_cache"
                return
            }
        }
        unset apm_cookie_list
 
        set try                         1
        set start_policy_str            $src_ip
        append start_policy_str         [TCP::client_port]
 
        while { $try <=  $static::AUTH_POLICY_RESULT_POLL_MAXRETRYCYCLE } {
 
            log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX NO APM Cookie found"
            log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX Trying #$try for $http_method $http_uri $http_content_len"
 
            if { $http_content_len > $static::AUTH_FIRST_BIG_POST_CONTENT_LEN } {
                # Wait at below
            } else {
                log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX EXEC: table set -notouch -subtable  $static::AUTH_ACCESS_USERKEY_TBLNAME -excl $user_key $start_policy_str $PROFILE_POLICY_TIMEOUT $PROFILE_MAX_SESS_TIMEOUT"
                set policy_status [table set -notouch -subtable  $static::AUTH_ACCESS_USERKEY_TBLNAME -excl $user_key $start_policy_str $PROFILE_POLICY_TIMEOUT $PROFILE_MAX_SESS_TIMEOUT]
                log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX DONE: table set -notouch -subtable  $static::AUTH_ACCESS_USERKEY_TBLNAME -excl $user_key $start_policy_str $PROFILE_POLICY_TIMEOUT $PROFILE_MAX_SESS_TIMEOUT"
                if { $policy_status == $start_policy_str } {
                    # ACCESS Policy has not started. Start one
                    HTTP::header insert "clientless-mode"    1
                    set clientless_mode 1
                    break
                } elseif { $policy_status == $static::AUTH_POLICY_SUCCEED } {
                    log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX table is out-of-sync retry"
                    table delete -subtable  $static::AUTH_ACCESS_USERKEY_TBLNAME $user_key
                    continue
                } elseif { $policy_status == $static::AUTH_POLICY_FAILED } {
                    ACCESS::disable
                    TCP::close
                    return
                }
                # Wait at below
            }
 
            log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX Waiting  $static::AUTH_POLICY_RESULT_POLL_INTERVAL ms for $http_method $http_uri"
            # Touch the entry table
            table lookup -subtable  $static::AUTH_ACCESS_USERKEY_TBLNAME $user_key
            after  $static::AUTH_POLICY_RESULT_POLL_INTERVAL
 
            set apm_cookie_list             [ ACCESS::user getsid $user_key ]
            if { [ llength $apm_cookie_list ] != 0 } {
                set MRHSession_cookie [ ACCESS::user getkey [ lindex $apm_cookie_list 0 ] ]
                if { $MRHSession_cookie != "" } {
                    set sid_cache           $MRHSession_cookie
                    HTTP::cookie insert name MRHSession value $MRHSession_cookie
                    log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX APM Cookie found: $sid_cache"
                    return
                }
            }
 
            incr try
        }
 
        if { $try >  $static::AUTH_POLICY_RESULT_POLL_MAXRETRYCYCLE } {
            log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX Policy did not finish in [ expr { $static::AUTH_POLICY_RESULT_POLL_MAXRETRYCYCLE * $static::AUTH_POLICY_RESULT_POLL_INTERVAL } ] ms. Close connection for $http_method $http_uri"
            table delete -subtable  $static::AUTH_ACCESS_USERKEY_TBLNAME $user_key
            ACCESS::disable
            TCP::close
            return
        }
 
        log -noname accesscontrol.local1.debug "$static::AUTH_ACCESS_LOG_PREFIX Releasing request $http_method $http_uri"
 
        unset try
        unset start_policy_str
    }
 
      when ECA_REQUEST_DENIED {
         log local0. "User [ECA::username]@[ECA::domainname], Client Machine [ECA::client_machine_name], Auth Status [ECA::status]"
         set f_ntlm_auth_succeed                 0
      }

Tested this on version:

11.5
Published May 24, 2017
Version 1.0
  • This code was developed for Office 2016 clients and seems to get some issues with office 2019 clients.