1 # frozen_string_literal: true
6 class OAuth2Test < ActionDispatch::IntegrationTest
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)
12 authorize_client(user, client, :state => state)
13 assert_response :redirect
14 code = validate_redirect(client, state)
16 token = request_token(client, code)
18 assert_equal "read_prefs", token["scope"]
19 test_token(token["access_token"], user, client)
24 client = create(:oauth_application, :redirect_uri => "urn:ietf:wg:oauth:2.0:oob", :scopes => "read_prefs write_api read_gpx")
26 authorize_client(user, client)
27 assert_response :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>})
35 token = request_token(client, code)
37 assert_equal "read_prefs", token["scope"]
38 test_token(token["access_token"], user, client)
41 def test_oauth2_pkce_plain
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)
48 authorize_client(user, client, :state => state, :code_challenge => challenge, :code_challenge_method => "plain")
49 assert_response :redirect
50 code = validate_redirect(client, state)
52 token = request_token(client, code, verifier)
54 assert_equal "read_prefs", token["scope"]
55 test_token(token["access_token"], user, client)
58 def test_oauth2_pkce_s256
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)
65 authorize_client(user, client, :state => state, :code_challenge => challenge, :code_challenge_method => "S256")
66 assert_response :redirect
67 code = validate_redirect(client, state)
69 token = request_token(client, code, verifier)
71 assert_equal "read_prefs", token["scope"]
72 test_token(token["access_token"], user, client)
75 def test_openid_connect
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)
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)
86 token = request_token(client, code, verifier)
88 assert_equal "openid read_prefs", token["scope"]
90 access_token = token["access_token"]
91 assert_not_nil access_token
93 id_token = token["id_token"]
94 assert_not_nil id_token
96 data, _headers = JWT.decode id_token, nil, true, {
97 :algorithm => [Doorkeeper::OpenidConnect.signing_algorithm.to_s],
99 :iss => "#{Settings.server_protocol}://#{Settings.server_url}",
104 } do |headers, _payload|
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
112 assert_equal user.id.to_s, data["sub"]
113 assert_not data.key?("preferred_username")
115 get oauth_userinfo_path
116 assert_response :unauthorized
118 auth_header = bearer_authorization_header(access_token)
119 get oauth_userinfo_path, :headers => auth_header
120 assert_response :success
122 userinfo = response.parsed_body
124 assert_not_nil userinfo
125 assert_equal user.id.to_s, userinfo["sub"]
126 assert_equal user.display_name, userinfo["preferred_username"]
129 def test_openid_discovery
130 get oauth_discovery_provider_path
131 assert_response :success
132 openid_config = response.parsed_body
134 assert_equal "#{Settings.server_protocol}://#{Settings.server_url}", openid_config["issuer"]
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
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"]
153 def authorize_client(user, client, options = {})
155 :client_id => client.uid,
156 :redirect_uri => client.redirect_uri,
157 :response_type => "code",
158 :scope => "read_prefs"
161 get oauth_authorization_path(options)
162 assert_redirected_to login_path(:referer => request.fullpath)
164 post login_path(:username => user.email, :password => "test")
166 assert_response :success
168 get oauth_authorization_path(options)
169 assert_response :success
170 assert_template "oauth2_authorizations/new"
172 delete oauth_authorization_path(options)
174 validate_deny(client, options)
176 post oauth_authorization_path(options)
179 def validate_deny(client, options)
180 if client.redirect_uri == "urn:ietf:wg:oauth:2.0:oob"
181 assert_response :bad_request
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"]
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"]
202 def request_token(client, code, verifier = nil)
204 :client_id => client.uid,
205 :client_secret => client.plaintext_secret,
207 :grant_type => "authorization_code",
208 :redirect_uri => client.redirect_uri
212 post oauth_token_path(options)
213 assert_response :bad_request
215 options = options.merge(:code_verifier => verifier)
218 post oauth_token_path(options)
219 assert_response :success
220 token = response.parsed_body
221 assert_equal "Bearer", token["token_type"]
226 def test_token(token, user, client)
227 get api_user_preferences_path
228 assert_response :unauthorized
230 auth_header = bearer_authorization_header(token)
232 get api_user_preferences_path, :headers => auth_header
233 assert_response :success
235 get api_user_preferences_path(:access_token => token)
236 assert_response :unauthorized
238 get api_user_preferences_path(:bearer_token => token)
239 assert_response :unauthorized
241 get api_trace_path(:id => 2), :headers => auth_header
242 assert_response :forbidden
246 get api_user_preferences_path, :headers => auth_header
247 assert_response :forbidden
251 get api_user_preferences_path, :headers => auth_header
252 assert_response :forbidden
256 get api_user_preferences_path, :headers => auth_header
257 assert_response :success
259 post oauth_revoke_path(:token => token)
260 assert_response :forbidden
262 post oauth_revoke_path(:token => token,
263 :client_id => client.uid,
264 :client_secret => client.plaintext_secret)
265 assert_response :success
267 get api_user_preferences_path, :headers => auth_header
268 assert_response :unauthorized