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_equal user.display_name, data["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 => "s3cr3t")
 
 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