angular_rails_csrf 4.2.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +25 -4
- data/Rakefile +1 -2
- data/lib/angular_rails_csrf/concern.rb +13 -24
- data/lib/angular_rails_csrf/version.rb +1 -1
- data/test/angular_rails_csrf_skip_test.rb +14 -0
- data/test/angular_rails_csrf_test.rb +64 -53
- data/test/dummy/app/controllers/api_controller.rb +7 -0
- data/test/dummy/config/routes.rb +2 -0
- data/test/dummy/config.ru +1 -1
- data/test/dummy/log/test.log +72 -787
- metadata +22 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 777a87b5a6709b3c193f4205d7327790efa17a1a1d3686a8e49fc69b03e62e1b
|
4
|
+
data.tar.gz: b693aa26b63bc1772a8b14728414d3922e9a2c2a33be088f4f123ca06885c58a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e0a0afe2adc0f5dd08d95c7776f219101a659e3ba4ad1ba4abc4392a5a8f6e70cb5c957174c443266665348be35d9a33936ec2b1aefa2abb6bf0cd7558933e72
|
7
|
+
data.tar.gz: 5bba0256727b1dd432178d5fe4405ae11d4591e2be1fee0d13ac9c4bdfe29108040b2247a3f7fa80d3f0d6c43a7e426ab47490db521b1f1ec659625ef397a18c
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
## AngularJS-style CSRF Protection for Rails
|
2
2
|
|
3
|
-
|
4
|
-
[![Build Status](https://travis-ci.
|
3
|
+
![Gem](https://img.shields.io/gem/v/angular_rails_csrf)
|
4
|
+
[![Build Status](https://travis-ci.com/jsanders/angular_rails_csrf.svg?branch=master)](https://travis-ci.com/jsanders/angular_rails_csrf)
|
5
5
|
[![Test Coverage](https://codecov.io/gh/jsanders/angular_rails_csrf/graph/badge.svg)](https://codecov.io/gh/jsanders/angular_rails_csrf)
|
6
|
+
![Downloads total](https://img.shields.io/gem/dt/angular_rails_csrf)
|
6
7
|
|
7
8
|
The AngularJS [ng.$http](http://docs.angularjs.org/api/ng.$http) service has built-in CSRF protection. By default, it looks for a cookie named `XSRF-TOKEN` and, if found, writes its value into an `X-XSRF-TOKEN` header, which the server compares with the CSRF token saved in the user's session.
|
8
9
|
|
@@ -16,11 +17,15 @@ Check [version compatibility](https://github.com/jsanders/angular_rails_csrf/wik
|
|
16
17
|
|
17
18
|
Add this line to your application's *Gemfile*:
|
18
19
|
|
19
|
-
|
20
|
+
```ruby
|
21
|
+
gem 'angular_rails_csrf'
|
22
|
+
```
|
20
23
|
|
21
24
|
And then execute:
|
22
25
|
|
23
|
-
|
26
|
+
```console
|
27
|
+
$ bundle
|
28
|
+
```
|
24
29
|
|
25
30
|
That's it!
|
26
31
|
|
@@ -80,6 +85,22 @@ end
|
|
80
85
|
|
81
86
|
**NOTE**: When using `config.angular_rails_csrf_same_site = :none`, this gem automatically sets the cookie to `Secure` (`config.angular_rails_csrf_secure = true`) to comply with [the specifications](https://tools.ietf.org/html/draft-west-cookie-incrementalism-00).
|
82
87
|
|
88
|
+
Please note that [Safari is known to have issues](https://bugs.webkit.org/show_bug.cgi?id=198181) with SameSite attribute set to `:none`.
|
89
|
+
|
90
|
+
### HttpOnly Cookie
|
91
|
+
|
92
|
+
To set the ["httponly" flag](https://owasp.org/www-community/HttpOnly) for your cookie, set the `angular_rails_csrf_httponly` option to `true`:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
# application.rb
|
96
|
+
class Application < Rails::Application
|
97
|
+
#...
|
98
|
+
config.angular_rails_csrf_httponly = true
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
`angular_rails_csrf_httponly` defaults to `false`.
|
103
|
+
|
83
104
|
### Exclusions
|
84
105
|
|
85
106
|
Sometimes you will want to skip setting the XSRF token for certain controllers (for example, when using SSE or ActionCable, as discussed [here](https://github.com/jsanders/angular_rails_csrf/issues/7)):
|
data/Rakefile
CHANGED
@@ -9,48 +9,37 @@ module AngularRailsCsrf
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def set_xsrf_token_cookie
|
12
|
-
return unless protect_against_forgery? && !respond_to?(:__exclude_xsrf_token_cookie?)
|
12
|
+
return unless defined?(protect_against_forgery?) && protect_against_forgery? && !respond_to?(:__exclude_xsrf_token_cookie?)
|
13
13
|
|
14
14
|
config = Rails.application.config
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
secure = option_from config, :angular_rails_csrf_secure
|
17
|
+
same_site = option_from config, :angular_rails_csrf_same_site, :lax
|
18
18
|
|
19
19
|
cookie_options = {
|
20
20
|
value: form_authenticity_token,
|
21
|
-
domain:
|
21
|
+
domain: option_from(config, :angular_rails_csrf_domain),
|
22
22
|
same_site: same_site,
|
23
|
+
httponly: option_from(config, :angular_rails_csrf_httponly, false),
|
23
24
|
secure: same_site.eql?(:none) || secure
|
24
25
|
}
|
25
26
|
|
26
|
-
cookie_name =
|
27
|
+
cookie_name = option_from(config,
|
28
|
+
:angular_rails_csrf_cookie_name,
|
29
|
+
'XSRF-TOKEN')
|
27
30
|
cookies[cookie_name] = cookie_options
|
28
31
|
end
|
29
32
|
|
30
33
|
def verified_request?
|
31
|
-
|
32
|
-
super || valid_authenticity_token?(session, request.headers['X-XSRF-TOKEN'])
|
33
|
-
else
|
34
|
-
super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
|
35
|
-
end
|
34
|
+
super || valid_authenticity_token?(session, request.headers['X-XSRF-TOKEN'])
|
36
35
|
end
|
37
36
|
|
38
37
|
private
|
39
38
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
def secure_from(config)
|
45
|
-
config.angular_rails_csrf_secure if config.respond_to?(:angular_rails_csrf_secure)
|
46
|
-
end
|
47
|
-
|
48
|
-
def domain_from(config)
|
49
|
-
config.respond_to?(:angular_rails_csrf_domain) ? config.angular_rails_csrf_domain : nil
|
50
|
-
end
|
51
|
-
|
52
|
-
def cookie_name_from(config)
|
53
|
-
config.respond_to?(:angular_rails_csrf_cookie_name) ? config.angular_rails_csrf_cookie_name : 'XSRF-TOKEN'
|
39
|
+
# Fetches the given option from config
|
40
|
+
# If the option is not set, return a default value
|
41
|
+
def option_from(config, option, default = nil)
|
42
|
+
config.respond_to?(option) ? config.send(option) : default
|
54
43
|
end
|
55
44
|
|
56
45
|
module ClassMethods
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class AngularRailsCsrfSkipTest < ActionController::TestCase
|
6
|
+
tests ApiController
|
7
|
+
|
8
|
+
test 'csrf-cookie is not set and no error if protect_against_forgery? is not defined' do
|
9
|
+
refute @controller.respond_to?(:protect_against_forgery?)
|
10
|
+
get :index
|
11
|
+
assert_nil cookies['XSRF-TOKEN']
|
12
|
+
assert_response :success
|
13
|
+
end
|
14
|
+
end
|
@@ -31,48 +31,65 @@ class AngularRailsCsrfTest < ActionController::TestCase
|
|
31
31
|
assert_response :success
|
32
32
|
end
|
33
33
|
|
34
|
-
test '
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
34
|
+
test 'csrf-cookie is not set if exclusion is enabled' do
|
35
|
+
refute @controller.respond_to?(:__exclude_xsrf_token_cookie?)
|
36
|
+
@controller.class_eval { exclude_xsrf_token_cookie }
|
37
|
+
get :index
|
38
|
+
assert_valid_cookie present: false
|
39
|
+
assert @controller.__exclude_xsrf_token_cookie?
|
40
|
+
assert_response :success
|
41
|
+
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
ensure
|
46
|
-
config.instance_eval('undef :angular_rails_csrf_domain', __FILE__, __LINE__)
|
43
|
+
test 'the domain is used if present' do
|
44
|
+
config = Rails.application.config
|
45
|
+
def config.angular_rails_csrf_domain
|
46
|
+
:all
|
47
47
|
end
|
48
|
+
|
49
|
+
get :index
|
50
|
+
assert @response.headers['Set-Cookie'].include?('.test.host')
|
51
|
+
assert_valid_cookie
|
52
|
+
assert_response :success
|
53
|
+
ensure
|
54
|
+
config.instance_eval('undef :angular_rails_csrf_domain', __FILE__, __LINE__)
|
48
55
|
end
|
49
56
|
|
50
57
|
test 'the secure flag is set if configured' do
|
51
|
-
|
52
|
-
@request.headers['HTTPS'] = 'on'
|
58
|
+
@request.headers['HTTPS'] = 'on'
|
53
59
|
|
54
|
-
|
55
|
-
|
60
|
+
config = Rails.application.config
|
61
|
+
config.define_singleton_method(:angular_rails_csrf_secure) { true }
|
56
62
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
63
|
+
get :index
|
64
|
+
assert @response.headers['Set-Cookie'].include?('secure')
|
65
|
+
assert_valid_cookie
|
66
|
+
assert_response :success
|
67
|
+
ensure
|
68
|
+
@request.headers['HTTPS'] = nil
|
69
|
+
config.instance_eval('undef :angular_rails_csrf_secure', __FILE__, __LINE__)
|
65
70
|
end
|
66
71
|
|
67
72
|
test 'a custom name is used if present' do
|
68
73
|
use_custom_cookie_name do
|
69
74
|
get :index
|
70
75
|
assert @response.headers['Set-Cookie'].include?('CUSTOM-COOKIE-NAME')
|
71
|
-
assert_valid_cookie
|
76
|
+
assert_valid_cookie name: 'CUSTOM-COOKIE-NAME'
|
72
77
|
assert_response :success
|
73
78
|
end
|
74
79
|
end
|
75
80
|
|
81
|
+
test 'the httponly flag is set if configured' do
|
82
|
+
config = Rails.application.config
|
83
|
+
config.define_singleton_method(:angular_rails_csrf_httponly) { true }
|
84
|
+
|
85
|
+
get :index
|
86
|
+
assert @response.headers['Set-Cookie'].include?('HttpOnly')
|
87
|
+
assert_valid_cookie
|
88
|
+
assert_response :success
|
89
|
+
ensure
|
90
|
+
config.instance_eval('undef :angular_rails_csrf_httponly', __FILE__, __LINE__)
|
91
|
+
end
|
92
|
+
|
76
93
|
test 'same_site is set to Lax by default' do
|
77
94
|
get :index
|
78
95
|
assert @response.headers['Set-Cookie'].include?('SameSite=Lax')
|
@@ -81,34 +98,30 @@ class AngularRailsCsrfTest < ActionController::TestCase
|
|
81
98
|
end
|
82
99
|
|
83
100
|
test 'same_site can be configured' do
|
84
|
-
|
85
|
-
|
86
|
-
config.define_singleton_method(:angular_rails_csrf_same_site) { :strict }
|
101
|
+
config = Rails.application.config
|
102
|
+
config.define_singleton_method(:angular_rails_csrf_same_site) { :strict }
|
87
103
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
end
|
104
|
+
get :index
|
105
|
+
assert @response.headers['Set-Cookie'].include?('SameSite=Strict')
|
106
|
+
assert_valid_cookie
|
107
|
+
assert_response :success
|
108
|
+
ensure
|
109
|
+
config.instance_eval('undef :angular_rails_csrf_same_site', __FILE__, __LINE__)
|
95
110
|
end
|
96
111
|
|
97
112
|
test 'secure is set automatically when same_site is set to none' do
|
98
|
-
|
99
|
-
@request.headers['HTTPS'] = 'on'
|
113
|
+
@request.headers['HTTPS'] = 'on'
|
100
114
|
|
101
|
-
|
102
|
-
|
115
|
+
config = Rails.application.config
|
116
|
+
config.define_singleton_method(:angular_rails_csrf_same_site) { :none }
|
103
117
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
118
|
+
get :index
|
119
|
+
assert @response.headers['Set-Cookie'].include?('SameSite=None')
|
120
|
+
assert @response.headers['Set-Cookie'].include?('secure')
|
121
|
+
assert_valid_cookie
|
122
|
+
assert_response :success
|
123
|
+
ensure
|
124
|
+
config.instance_eval('undef :angular_rails_csrf_same_site', __FILE__, __LINE__)
|
112
125
|
end
|
113
126
|
|
114
127
|
private
|
@@ -119,12 +132,10 @@ class AngularRailsCsrfTest < ActionController::TestCase
|
|
119
132
|
@request.headers['X-XSRF-TOKEN'] = value
|
120
133
|
end
|
121
134
|
|
122
|
-
def assert_valid_cookie(name
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
assert_equal @controller.send(:form_authenticity_token), cookies['XSRF-TOKEN']
|
127
|
-
end
|
135
|
+
def assert_valid_cookie(name: 'XSRF-TOKEN', present: true)
|
136
|
+
cookie_valid = @controller.send(:valid_authenticity_token?, session, cookies[name])
|
137
|
+
cookie_valid = !cookie_valid unless present
|
138
|
+
assert cookie_valid
|
128
139
|
end
|
129
140
|
|
130
141
|
def use_custom_cookie_name
|
data/test/dummy/config/routes.rb
CHANGED
data/test/dummy/config.ru
CHANGED