4 class OAuth2Test < ActionDispatch::IntegrationTest
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)
10 authorize_client(user, client, :state => state)
11 assert_response :redirect
12 code = validate_redirect(client, state)
14 token = request_token(client, code)
16 assert_equal "read_prefs", token["scope"]
17 test_token(token["access_token"], user, client)
22 client = create(:oauth_application, :redirect_uri => "urn:ietf:wg:oauth:2.0:oob", :scopes => "read_prefs write_api read_gpx")
24 authorize_client(user, client)
25 assert_response :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>})
33 token = request_token(client, code)
35 assert_equal "read_prefs", token["scope"]
36 test_token(token["access_token"], user, client)
39 def test_oauth2_pkce_plain
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)
46 authorize_client(user, client, :state => state, :code_challenge => challenge, :code_challenge_method => "plain")
47 assert_response :redirect
48 code = validate_redirect(client, state)
50 token = request_token(client, code, verifier)
52 assert_equal "read_prefs", token["scope"]
53 test_token(token["access_token"], user, client)
56 def test_oauth2_pkce_s256
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)
63 authorize_client(user, client, :state => state, :code_challenge => challenge, :code_challenge_method => "S256")
64 assert_response :redirect
65 code = validate_redirect(client, state)
67 token = request_token(client, code, verifier)
69 assert_equal "read_prefs", token["scope"]
70 test_token(token["access_token"], user, client)
73 def test_openid_connect
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)
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)
84 token = request_token(client, code, verifier)
86 assert_equal "openid read_prefs", token["scope"]
88 access_token = token["access_token"]
89 assert_not_nil access_token
91 id_token = token["id_token"]
92 assert_not_nil id_token
94 data, _headers = JWT.decode id_token, nil, true, {
95 :algorithm => [Doorkeeper::OpenidConnect.signing_algorithm.to_s],
97 :iss => "#{Settings.server_protocol}://#{Settings.server_url}",
102 } do |headers, _payload|
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
110 assert_equal user.id.to_s, data["sub"]
111 assert_not data.key?("preferred_username")
113 get oauth_userinfo_path
114 assert_response :unauthorized
116 auth_header = bearer_authorization_header(access_token)
117 get oauth_userinfo_path, :headers => auth_header
118 assert_response :success
120 userinfo = response.parsed_body
122 assert_not_nil userinfo
123 assert_equal user.id.to_s, userinfo["sub"]
124 assert_equal user.display_name, userinfo["preferred_username"]
127 def test_openid_discovery
128 get oauth_discovery_provider_path
129 assert_response :success
130 openid_config = response.parsed_body
132 assert_equal "#{Settings.server_protocol}://#{Settings.server_url}", openid_config["issuer"]
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
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"]
151 def authorize_client(user, client, options = {})
153 :client_id => client.uid,
154 :redirect_uri => client.redirect_uri,
155 :response_type => "code",
156 :scope => "read_prefs"
159 get oauth_authorization_path(options)
160 assert_response :redirect
161 assert_redirected_to login_path(:referer => request.fullpath)
163 post login_path(:username => user.email, :password => "test")
165 assert_response :success
167 get oauth_authorization_path(options)
168 assert_response :success
169 assert_template "oauth2_authorizations/new"
171 delete oauth_authorization_path(options)
173 validate_deny(client, options)
175 post oauth_authorization_path(options)
178 def validate_deny(client, options)
179 if client.redirect_uri == "urn:ietf:wg:oauth:2.0:oob"
180 assert_response :bad_request
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"]
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"]
201 def request_token(client, code, verifier = nil)
203 :client_id => client.uid,
204 :client_secret => client.plaintext_secret,
206 :grant_type => "authorization_code",
207 :redirect_uri => client.redirect_uri
211 post oauth_token_path(options)
212 assert_response :bad_request
214 options = options.merge(:code_verifier => verifier)
217 post oauth_token_path(options)
218 assert_response :success
219 token = response.parsed_body
220 assert_equal "Bearer", token["token_type"]
225 def test_token(token, user, client)
226 get user_preferences_path
227 assert_response :unauthorized
229 auth_header = bearer_authorization_header(token)
231 get user_preferences_path, :headers => auth_header
232 assert_response :success
234 get user_preferences_path(:access_token => token)
235 assert_response :unauthorized
237 get user_preferences_path(:bearer_token => token)
238 assert_response :unauthorized
240 get api_trace_path(:id => 2), :headers => auth_header
241 assert_response :forbidden
245 get user_preferences_path, :headers => auth_header
246 assert_response :forbidden
250 get user_preferences_path, :headers => auth_header
251 assert_response :forbidden
255 get user_preferences_path, :headers => auth_header
256 assert_response :success
258 post oauth_revoke_path(:token => token)
259 assert_response :forbidden
261 post oauth_revoke_path(:token => token,
262 :client_id => client.uid,
263 :client_secret => client.plaintext_secret)
264 assert_response :success
266 get user_preferences_path, :headers => auth_header
267 assert_response :unauthorized