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