Index: openacs-4/packages/acs-tcl/tcl/security-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/security-procs.tcl,v diff -u -r1.78.2.9 -r1.78.2.10 --- openacs-4/packages/acs-tcl/tcl/security-procs.tcl 22 May 2016 19:20:06 -0000 1.78.2.9 +++ openacs-4/packages/acs-tcl/tcl/security-procs.tcl 23 May 2016 10:01:20 -0000 1.78.2.10 @@ -1796,104 +1796,143 @@ return $locations } -namespace eval security::csrf {} +namespace eval ::security::csrf { -ad_proc -public security::csrf::new {{-tokenname __csrf_token} -user_id} { + # + # CSRF protection. + # + # High Level commands: + # + # security::csrf::new + # security::csrf::validate + + ad_proc -public ::security::csrf::new {{-tokenname __csrf_token} -user_id} { - Create a security token to protect against CSRF (Cross-Site - Request Forgery). The token is set (and cached) in a global - per-thread variable an can be included in forms e.g. via the - following command. + Create a security token to protect against CSRF (Cross-Site + Request Forgery). The token is set (and cached) in a global + per-thread variable an can be included in forms e.g. via the + following command. - + - The token is automatically cleared together with other global - variables at the end of the processing of every request. + The token is automatically cleared together with other global + variables at the end of the processing of every request. - @return csrf token -} { - set cached_var_name ::__csrf_token - if {[info exists $cached_var_name]} { - return [set $cached_var_name] + @return csrf token + } { + set cached_var_name ::$tokenname + if {[info exists $cached_var_name]} { + return [set $cached_var_name] + } + + set token [token -tokenname $tokenname] + return [set $cached_var_name $token] } - if {![info exists user_id]} { - set user_id [ad_conn user_id] - } - if {$user_id == 0} { - # - # Anonymous request, use a peer address as session_id - # - set session_id [ad_conn peeraddr] - } else { - # - # User is logged in, use a session token. - # - set session_id [ad_conn session_id] - } - - if {[info commands ::crypto::hmac] ne ""} { - set secret [ns_config "ns/server/[ns_info server]/acs" parametersecret ""] - set signature [::crypto::hmac string $secret $session_id] - } else { - set signature [ns_sha1 $session_id] - } - return [set $cached_var_name $signature] -} + # + # validate + # + ad_proc -public ::security::csrf::validate {{-tokenname __csrf_token} {-allowempty false}} { + + Validate a CSRF token and call security::csrf::fail the request if + invalid. - -ad_proc -public security::csrf::validate {{-tokenname __csrf_token} {-allowempty false}} { + @return nothing + } { + if {![info exists ::$tokenname]} { + # + # If there is no global csrf token, we assume that the + # csrf token generation is deactivated, we accept everything. + # + return + } + + set oldToken [ns_queryget $tokenname] + if {$oldToken eq ""} { + # + # There is not token in the query/form parameters, we + # can't validate, since there is no token. + # + if {$allowempty} { + return + } + fail + } + + set token [token -tokenname $tokenname] + if {$oldToken ne $token} { + fail + } + } - Validate a CSRF token and call security::csrf::fail the request if - invalid. + # + # Compute a session id or the best equivalent + # + ad_proc -private ::security::csrf::session_id { } { - @return nothing -} { - set oldToken [ns_queryget $tokenname] - if {$oldToken eq ""} { - # We can't validate, since there is no token. - if {$allowempty} { - return "" + Return an ID for the current session for CSRF protection + + @return session ID + } { + if {[ad_conn untrusted_user_id] == 0} { + # + # Anonymous request, use a peer address as session_id + # + set session_id [ad_conn peeraddr] + } else { + # + # User is logged in, use a session token. + # + set session_id [ad_conn session_id] } - security::csrf::fail + return $session_id } - if {![info exists user_id]} { - set user_id [ad_conn user_id] - } - if {$user_id == 0} { - set session_id [ad_conn peeraddr] - } else { - set session_id [ad_conn session_id] - } + # + # Generate CSRF token + # + ad_proc -private ::security::csrf::token { {-tokenname __csrf_token} } { - if {[info commands ::crypto::hmac] ne ""} { - set secret [ns_config "ns/server/[ns_info server]/acs" parametersecret ""] - set signature [::crypto::hmac string $secret $session_id] - } else { - set signature [ns_sha1 $session_id] + Generate a CSRF token and return it + + @return CSRF token + } { + # + # We compute the token only once per requests. If it was already + # computed, and we can pick it up and return it. Otherwise, + # we compute it new. + # + set globalTokenName ::$tokenname + if {[info exists $globalTokenName]} { + set token [set $globalTokenName] + } elseif {[info commands ::crypto::hmac] ne ""} { + set secret [ns_config "ns/server/[ns_info server]/acs" parametersecret ""] + set token [::crypto::hmac string $secret [session_id]] + } else { + set token [ns_sha1 [session_id]] + } + return $token } - if {$oldToken ne $signature} { - security::csrf::fail - } -} -ad_proc -private security::csrf::fail {} { + # + # Failure handling + # + ad_proc -private ::security::csrf::fail {} { - This function is called, when a csrf validation fails. Unless the - current user is swa, it aborts the current request. - -} { - ad_log Warning "CSRF failure" - if {[acs_user::site_wide_admin_p]} { - ns_log notice "would abort if not swa: [ns_conn request]" - } else { - ad_page_contract_handle_datasource_error "Invalid form token (potential Cross-Site Request Forgery)" - ad_script_abort + This function is called, when a csrf validation fails. Unless the + current user is swa, it aborts the current request. + + } { + ad_log Warning "CSRF failure" + if {[acs_user::site_wide_admin_p]} { + ns_log notice "would abort if not swa: [ns_conn request]" + } else { + ad_page_contract_handle_datasource_error "Invalid request token (potential Cross-Site Request Forgery)" + ad_script_abort + } } } - # # Local variables: # mode: tcl