ms-ofba
2 TopicsAPM 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.52.3KViews0likes11CommentsAPM 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.54.9KViews1like57Comments