]> git.openstreetmap.org Git - rails.git/blob - test/integration/oauth2_test.rb
Be paranoid when sending password reset emails
[rails.git] / test / integration / oauth2_test.rb
1 require "test_helper"
2 require "jwt"
3
4 class OAuth2Test < ActionDispatch::IntegrationTest
5   def test_oauth2
6     user = create(:user)
7     client = create(:oauth_application, :redirect_uri => "https://some.web.app.example.org/callback", :scopes => "read_prefs write_api read_gpx")
8     state = SecureRandom.urlsafe_base64(16)
9
10     authorize_client(user, client, :state => state)
11     assert_response :redirect
12     code = validate_redirect(client, state)
13
14     token = request_token(client, code)
15
16     assert_equal "read_prefs", token["scope"]
17     test_token(token["access_token"], user, client)
18   end
19
20   def test_oauth2_oob
21     user = create(:user)
22     client = create(:oauth_application, :redirect_uri => "urn:ietf:wg:oauth:2.0:oob", :scopes => "read_prefs write_api read_gpx")
23
24     authorize_client(user, client)
25     assert_response :redirect
26     follow_redirect!
27     assert_response :success
28     assert_template "oauth2_authorizations/show"
29     m = response.body.match(%r{<code id="authorization_code">([A-Za-z0-9_-]+)</code>})
30     assert_not_nil m
31     code = m[1]
32
33     token = request_token(client, code)
34
35     assert_equal "read_prefs", token["scope"]
36     test_token(token["access_token"], user, client)
37   end
38
39   def test_oauth2_pkce_plain
40     user = create(:user)
41     client = create(:oauth_application, :redirect_uri => "https://some.web.app.example.org/callback", :scopes => "read_prefs write_api read_gpx")
42     state = SecureRandom.urlsafe_base64(16)
43     verifier = SecureRandom.urlsafe_base64(48)
44     challenge = verifier
45
46     authorize_client(user, client, :state => state, :code_challenge => challenge, :code_challenge_method => "plain")
47     assert_response :redirect
48     code = validate_redirect(client, state)
49
50     token = request_token(client, code, verifier)
51
52     assert_equal "read_prefs", token["scope"]
53     test_token(token["access_token"], user, client)
54   end
55
56   def test_oauth2_pkce_s256
57     user = create(:user)
58     client = create(:oauth_application, :redirect_uri => "https://some.web.app.example.org/callback", :scopes => "read_prefs write_api read_gpx")
59     state = SecureRandom.urlsafe_base64(16)
60     verifier = SecureRandom.urlsafe_base64(48)
61     challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), :padding => false)
62
63     authorize_client(user, client, :state => state, :code_challenge => challenge, :code_challenge_method => "S256")
64     assert_response :redirect
65     code = validate_redirect(client, state)
66
67     token = request_token(client, code, verifier)
68
69     assert_equal "read_prefs", token["scope"]
70     test_token(token["access_token"], user, client)
71   end
72
73   def test_openid_connect
74     user = create(:user)
75     client = create(:oauth_application, :redirect_uri => "https://some.web.app.example.org/callback", :scopes => "openid read_prefs")
76     state = SecureRandom.urlsafe_base64(16)
77     verifier = SecureRandom.urlsafe_base64(48)
78     challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), :padding => false)
79
80     authorize_client(user, client, :state => state, :code_challenge => challenge, :code_challenge_method => "S256", :scope => "openid read_prefs")
81     assert_response :redirect
82     code = validate_redirect(client, state)
83
84     token = request_token(client, code, verifier)
85
86     assert_equal "openid read_prefs", token["scope"]
87
88     access_token = token["access_token"]
89     assert_not_nil access_token
90
91     id_token = token["id_token"]
92     assert_not_nil id_token
93
94     data, _headers = JWT.decode id_token, nil, true, {
95       :algorithm => [Doorkeeper::OpenidConnect.signing_algorithm.to_s],
96       :verify_iss => true,
97       :iss => "#{Settings.server_protocol}://#{Settings.server_url}",
98       :verify_sub => true,
99       :sub => user.id,
100       :verify_aud => true,
101       :aud => client.uid
102     } do |headers, _payload|
103       kid = headers["kid"]
104       get oauth_discovery_keys_path
105       keys = response.parsed_body["keys"]
106       jwk = keys&.detect { |e| e["kid"] == kid }
107       jwk && JWT::JWK::RSA.import(jwk).public_key
108     end
109
110     assert_equal user.id.to_s, data["sub"]
111     assert_not data.key?("preferred_username")
112
113     get oauth_userinfo_path
114     assert_response :unauthorized
115
116     auth_header = bearer_authorization_header(access_token)
117     get oauth_userinfo_path, :headers => auth_header
118     assert_response :success
119
120     userinfo = response.parsed_body
121
122     assert_not_nil userinfo
123     assert_equal user.id.to_s, userinfo["sub"]
124     assert_equal user.display_name, userinfo["preferred_username"]
125   end
126
127   def test_openid_discovery
128     get oauth_discovery_provider_path
129     assert_response :success
130     openid_config = response.parsed_body
131
132     assert_equal "#{Settings.server_protocol}://#{Settings.server_url}", openid_config["issuer"]
133
134     assert_equal oauth_authorization_path, URI(openid_config["authorization_endpoint"]).path
135     assert_equal oauth_token_path, URI(openid_config["token_endpoint"]).path
136     assert_equal oauth_userinfo_path, URI(openid_config["userinfo_endpoint"]).path
137     assert_equal oauth_discovery_keys_path, URI(openid_config["jwks_uri"]).path
138   end
139
140   def test_openid_key
141     get oauth_discovery_keys_path
142     assert_response :success
143     key_info = response.parsed_body
144     assert key_info.key?("keys")
145     assert_equal 1, key_info["keys"].size
146     assert_equal Doorkeeper::OpenidConnect.signing_key.kid, key_info["keys"][0]["kid"]
147   end
148
149   private
150
151   def authorize_client(user, client, options = {})
152     options = {
153       :client_id => client.uid,
154       :redirect_uri => client.redirect_uri,
155       :response_type => "code",
156       :scope => "read_prefs"
157     }.merge(options)
158
159     get oauth_authorization_path(options)
160     assert_response :redirect
161     assert_redirected_to login_path(:referer => request.fullpath)
162
163     post login_path(:username => user.email, :password => "test")
164     follow_redirect!
165     assert_response :success
166
167     get oauth_authorization_path(options)
168     assert_response :success
169     assert_template "oauth2_authorizations/new"
170
171     delete oauth_authorization_path(options)
172
173     validate_deny(client, options)
174
175     post oauth_authorization_path(options)
176   end
177
178   def validate_deny(client, options)
179     if client.redirect_uri == "urn:ietf:wg:oauth:2.0:oob"
180       assert_response :bad_request
181     else
182       assert_response :redirect
183       location = URI.parse(response.location)
184       assert_match(/^#{Regexp.escape(client.redirect_uri)}/, location.to_s)
185       query = Rack::Utils.parse_query(location.query)
186       assert_equal "access_denied", query["error"]
187       assert_equal "The resource owner or authorization server denied the request.", query["error_description"]
188       assert_equal options[:state], query["state"]
189     end
190   end
191
192   def validate_redirect(client, state)
193     location = URI.parse(response.location)
194     assert_match(/^#{Regexp.escape(client.redirect_uri)}/, location.to_s)
195     query = Rack::Utils.parse_query(location.query)
196     assert_equal state, query["state"]
197
198     query["code"]
199   end
200
201   def request_token(client, code, verifier = nil)
202     options = {
203       :client_id => client.uid,
204       :client_secret => client.plaintext_secret,
205       :code => code,
206       :grant_type => "authorization_code",
207       :redirect_uri => client.redirect_uri
208     }
209
210     if verifier
211       post oauth_token_path(options)
212       assert_response :bad_request
213
214       options = options.merge(:code_verifier => verifier)
215     end
216
217     post oauth_token_path(options)
218     assert_response :success
219     token = response.parsed_body
220     assert_equal "Bearer", token["token_type"]
221
222     token
223   end
224
225   def test_token(token, user, client)
226     get user_preferences_path
227     assert_response :unauthorized
228
229     auth_header = bearer_authorization_header(token)
230
231     get user_preferences_path, :headers => auth_header
232     assert_response :success
233
234     get user_preferences_path(:access_token => token)
235     assert_response :unauthorized
236
237     get user_preferences_path(:bearer_token => token)
238     assert_response :unauthorized
239
240     get api_trace_path(:id => 2), :headers => auth_header
241     assert_response :forbidden
242
243     user.suspend!
244
245     get user_preferences_path, :headers => auth_header
246     assert_response :forbidden
247
248     user.hide!
249
250     get user_preferences_path, :headers => auth_header
251     assert_response :forbidden
252
253     user.unhide!
254
255     get user_preferences_path, :headers => auth_header
256     assert_response :success
257
258     post oauth_revoke_path(:token => token)
259     assert_response :forbidden
260
261     post oauth_revoke_path(:token => token,
262                            :client_id => client.uid,
263                            :client_secret => client.plaintext_secret)
264     assert_response :success
265
266     get user_preferences_path, :headers => auth_header
267     assert_response :unauthorized
268   end
269 end