2025-06-12 07:09:03 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
|
|
require_relative '../application_system_test_case'
|
|
|
|
|
require 'oauth2'
|
2025-06-16 21:32:45 +00:00
|
|
|
require 'rack'
|
|
|
|
|
require 'puma'
|
2025-06-12 07:09:03 +00:00
|
|
|
|
|
|
|
|
class OauthProviderSystemTest < ApplicationSystemTestCase
|
|
|
|
|
fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
|
|
|
|
|
:trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues,
|
|
|
|
|
:enumerations, :custom_fields, :custom_values, :custom_fields_trackers,
|
|
|
|
|
:watchers, :journals, :journal_details, :versions,
|
|
|
|
|
:workflows
|
|
|
|
|
|
|
|
|
|
test 'application creation and authorization' do
|
|
|
|
|
#
|
|
|
|
|
# admin creates the application, granting permissions and generating a uuid
|
|
|
|
|
# and secret.
|
|
|
|
|
#
|
|
|
|
|
log_user 'admin', 'admin'
|
|
|
|
|
with_settings rest_api_enabled: 1 do
|
|
|
|
|
visit '/admin'
|
|
|
|
|
within 'div#admin-menu ul' do
|
|
|
|
|
click_link 'Applications'
|
|
|
|
|
end
|
|
|
|
|
click_link 'New Application'
|
|
|
|
|
fill_in 'Name', with: 'Oauth Test'
|
|
|
|
|
|
|
|
|
|
# as per https://tools.ietf.org/html/rfc8252#section-7.3, the port can be
|
|
|
|
|
# anything when the redirect URI's host is 127.0.0.1.
|
|
|
|
|
fill_in 'Redirect URI', with: 'http://127.0.0.1'
|
|
|
|
|
|
|
|
|
|
check 'View Issues'
|
|
|
|
|
click_button 'Create'
|
|
|
|
|
|
2025-06-16 21:21:24 +00:00
|
|
|
assert_text "Application created."
|
|
|
|
|
end
|
2025-06-12 07:09:03 +00:00
|
|
|
|
|
|
|
|
assert app = Doorkeeper::Application.find_by_name('Oauth Test')
|
|
|
|
|
|
|
|
|
|
find 'h2', visible: true, text: /Oauth Test/
|
|
|
|
|
find 'p code', visible: true, text: app.uid
|
|
|
|
|
find 'p strong', visible: true, text: /will not be shown again/
|
|
|
|
|
find 'p code', visible: true, text: /View Issues/
|
|
|
|
|
|
|
|
|
|
# scrape the clear text secret from the page
|
|
|
|
|
app_secret = all(:css, 'p code')[1].text
|
|
|
|
|
|
|
|
|
|
click_link 'Sign out'
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# regular user authorizes the application
|
|
|
|
|
#
|
|
|
|
|
client = OAuth2::Client.new(app.uid, app_secret, site: "http://127.0.0.1:#{test_port}/")
|
|
|
|
|
|
|
|
|
|
# set up a dummy http listener to handle the redirect
|
|
|
|
|
port = rand 10000..20000
|
|
|
|
|
redirect_uri = "http://127.0.0.1:#{port}"
|
|
|
|
|
# the request handler below will set this to the auth token
|
|
|
|
|
token = nil
|
|
|
|
|
|
|
|
|
|
# launches webrick, listening for the redirect with the auth code.
|
|
|
|
|
launch_client_app(port: port) do |req, res|
|
|
|
|
|
# get access code from code url param
|
2025-06-16 21:32:45 +00:00
|
|
|
if code = req.params['code'].presence
|
2025-06-12 07:09:03 +00:00
|
|
|
# exchange it for token
|
|
|
|
|
token = client.auth_code.get_token(code, redirect_uri: redirect_uri)
|
2025-06-16 21:32:45 +00:00
|
|
|
res.body = ["<html><body><p>Authorization succeeded, you may close this window now.</p></body></html>"]
|
2025-06-12 07:09:03 +00:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
log_user 'jsmith', 'jsmith'
|
|
|
|
|
with_settings rest_api_enabled: 1 do
|
|
|
|
|
visit '/my/account'
|
|
|
|
|
click_link 'Authorized applications'
|
|
|
|
|
find 'p.nodata', visible: true
|
|
|
|
|
|
|
|
|
|
# an oauth client would send the user to this url to request permission
|
|
|
|
|
url = client.auth_code.authorize_url redirect_uri: redirect_uri, scope: 'view_issues view_project'
|
|
|
|
|
uri = URI.parse url
|
|
|
|
|
visit uri.path + '?' + uri.query
|
|
|
|
|
|
|
|
|
|
find 'h2', visible: true, text: 'Authorization required'
|
|
|
|
|
find 'p', visible: true, text: /Authorize Oauth Test/
|
|
|
|
|
find '.oauth-permissions', visible: true, text: /View Issues/
|
|
|
|
|
find '.oauth-permissions', visible: true, text: /View project/
|
|
|
|
|
|
|
|
|
|
click_button 'Authorize'
|
|
|
|
|
|
|
|
|
|
assert grant = app.access_grants.last
|
|
|
|
|
assert_equal 'view_issues view_project', grant.scopes.to_s
|
|
|
|
|
|
|
|
|
|
# check for output defined above in the request handler
|
|
|
|
|
find 'p', visible: true, text: /Authorization succeeded/
|
|
|
|
|
assert token.present?
|
|
|
|
|
|
|
|
|
|
visit '/my/account'
|
|
|
|
|
click_link 'Authorized applications'
|
|
|
|
|
find 'td', visible: true, text: /Oauth Test/
|
|
|
|
|
click_link 'Sign out'
|
|
|
|
|
|
|
|
|
|
# Now, use the token for some API requests
|
|
|
|
|
assert_raise(RestClient::Unauthorized) do
|
|
|
|
|
RestClient.get "http://localhost:#{test_port}/projects/onlinestore/issues.json"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
headers = { 'Authorization' => "Bearer #{token.token}" }
|
|
|
|
|
r = RestClient.get "http://localhost:#{test_port}/projects/onlinestore/issues.json", headers
|
|
|
|
|
issues = JSON.parse(r.body)['issues']
|
|
|
|
|
assert issues.any?
|
|
|
|
|
|
|
|
|
|
# time entries access is not part of the granted scopes
|
|
|
|
|
assert_raise(RestClient::Forbidden) do
|
|
|
|
|
RestClient.get "http://localhost:#{test_port}/projects/onlinestore/time_entries.json", headers
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
|
|
def launch_client_app(port: 12345, path: '/', &block)
|
2025-06-16 21:32:45 +00:00
|
|
|
app = ->(env) do
|
|
|
|
|
req = Rack::Request.new(env)
|
|
|
|
|
res = Rack::Response.new
|
|
|
|
|
yield(req, res)
|
|
|
|
|
res.finish
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
server = Puma::Server.new app
|
|
|
|
|
server.add_tcp_listener '127.0.0.1', port
|
|
|
|
|
Thread.new { server.run }
|
2025-06-12 07:09:03 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def test_port
|
|
|
|
|
Capybara.current_session.server.port
|
|
|
|
|
end
|
|
|
|
|
end
|