discourse-oauth2-basic/spec/plugin_spec.rb

399 lines
14 KiB
Ruby

# frozen_string_literal: true
require "rails_helper"
describe OAuth2BasicAuthenticator do
describe "after_authenticate" do
before { SiteSetting.oauth2_user_json_url = "https://provider.com/user" }
let(:user) { Fabricate(:user) }
let(:authenticator) { OAuth2BasicAuthenticator.new }
let(:auth) do
OmniAuth::AuthHash.new(
"provider" => "oauth2_basic",
"credentials" => {
token: "token",
},
"uid" => "123456789",
"info" => {
id: "id",
},
"extra" => {
},
)
end
before(:each) { SiteSetting.oauth2_email_verified = true }
it "finds user by email" do
authenticator.expects(:fetch_user_details).returns(email: user.email)
result = authenticator.after_authenticate(auth)
expect(result.user).to eq(user)
end
it "validates user email if provider has verified" do
SiteSetting.oauth2_email_verified = false
authenticator.stubs(:fetch_user_details).returns(email: user.email, email_verified: true)
result = authenticator.after_authenticate(auth)
expect(result.email_valid).to eq(true)
end
it "doesn't validate user email if provider hasn't verified" do
SiteSetting.oauth2_email_verified = false
authenticator.stubs(:fetch_user_details).returns(email: user.email, email_verified: nil)
result = authenticator.after_authenticate(auth)
expect(result.email_valid).to eq(false)
end
it "doesn't affect the site setting" do
SiteSetting.oauth2_email_verified = true
authenticator.stubs(:fetch_user_details).returns(email: user.email, email_verified: false)
result = authenticator.after_authenticate(auth)
expect(result.email_valid).to eq(true)
end
it "handles true/false strings from identity provider" do
SiteSetting.oauth2_email_verified = false
authenticator.stubs(:fetch_user_details).returns(email: user.email, email_verified: "true")
result = authenticator.after_authenticate(auth)
expect(result.email_valid).to eq(true)
authenticator.stubs(:fetch_user_details).returns(email: user.email, email_verified: "false")
result = authenticator.after_authenticate(auth)
expect(result.email_valid).to eq(false)
end
describe "fetch_user_details" do
before(:each) do
SiteSetting.oauth2_fetch_user_details = true
SiteSetting.oauth2_user_json_url = "https://provider.com/user"
SiteSetting.oauth2_user_json_url_method = "GET"
SiteSetting.oauth2_json_email_path = "account.email"
end
let(:success_response) do
{ status: 200, body: '{"account":{"email":"newemail@example.com"}}' }
end
let(:fail_response) { { status: 403 } }
it "works" do
stub_request(:get, SiteSetting.oauth2_user_json_url).to_return(success_response)
result = authenticator.after_authenticate(auth)
expect(result.email).to eq("newemail@example.com")
SiteSetting.oauth2_user_json_url_method = "POST"
stub_request(:post, SiteSetting.oauth2_user_json_url).to_return(success_response)
result = authenticator.after_authenticate(auth)
expect(result.email).to eq("newemail@example.com")
end
it "returns an standardised result if the http request fails" do
stub_request(:get, SiteSetting.oauth2_user_json_url).to_return(fail_response)
result = authenticator.after_authenticate(auth)
expect(result.failed).to eq(true)
SiteSetting.oauth2_user_json_url_method = "POST"
stub_request(:post, SiteSetting.oauth2_user_json_url).to_return(fail_response)
result = authenticator.after_authenticate(auth)
expect(result.failed).to eq(true)
end
describe "fetch custom attributes" do
after { DiscoursePluginRegistry.reset_register!(:oauth2_basic_additional_json_paths) }
let(:response) do
{
status: 200,
body: '{"account":{"email":"newemail@example.com","custom_attr":"received"}}',
}
end
it "stores custom attributes in the user associated account" do
custom_path = "account.custom_attr"
DiscoursePluginRegistry.register_oauth2_basic_additional_json_path(
custom_path,
Plugin::Instance.new,
)
stub_request(:get, SiteSetting.oauth2_user_json_url).to_return(response)
result = authenticator.after_authenticate(auth)
associated_account = UserAssociatedAccount.last
expect(associated_account.extra[custom_path]).to eq("received")
end
end
describe "required attributes" do
after { DiscoursePluginRegistry.reset_register!(:oauth2_basic_required_json_paths) }
it "'authenticates' successfully if required json path is fulfilled" do
DiscoursePluginRegistry.register_oauth2_basic_additional_json_path(
"account.is_legit",
Plugin::Instance.new,
)
DiscoursePluginRegistry.register_oauth2_basic_required_json_path(
{ path: "extra:account.is_legit", required_value: true },
Plugin::Instance.new,
)
response = {
status: 200,
body: '{"account":{"email":"newemail@example.com","is_legit":true}}',
}
stub_request(:get, SiteSetting.oauth2_user_json_url).to_return(response)
result = authenticator.after_authenticate(auth)
expect(result.failed).to eq(false)
end
it "fails 'authentication' if required json path is unfulfilled" do
DiscoursePluginRegistry.register_oauth2_basic_additional_json_path(
"account.is_legit",
Plugin::Instance.new,
)
DiscoursePluginRegistry.register_oauth2_basic_required_json_path(
{
path: "extra:account.is_legit",
required_value: true,
error_message: "You're not legit",
},
Plugin::Instance.new,
)
response = {
status: 200,
body: '{"account":{"email":"newemail@example.com","is_legit":false}}',
}
stub_request(:get, SiteSetting.oauth2_user_json_url).to_return(response)
result = authenticator.after_authenticate(auth)
expect(result.failed).to eq(true)
expect(result.failed_reason).to eq("You're not legit")
end
end
end
describe "avatar downloading" do
before do
Jobs.run_later!
SiteSetting.oauth2_fetch_user_details = true
SiteSetting.oauth2_email_verified = true
end
let(:job_klass) { Jobs::DownloadAvatarFromUrl }
before do
png =
Base64.decode64(
"R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==",
)
stub_request(:get, "http://avatar.example.com/avatar.png").to_return(
body: png,
headers: {
"Content-Type" => "image/png",
},
)
end
it "enqueues a download_avatar_from_url job for existing user" do
authenticator.expects(:fetch_user_details).returns(
email: user.email,
avatar: "http://avatar.example.com/avatar.png",
)
expect { authenticator.after_authenticate(auth) }.to change { job_klass.jobs.count }.by(1)
job_args = job_klass.jobs.last["args"].first
expect(job_args["url"]).to eq("http://avatar.example.com/avatar.png")
expect(job_args["user_id"]).to eq(user.id)
expect(job_args["override_gravatar"]).to eq(false)
end
it "enqueues a download_avatar_from_url job for new user" do
authenticator.expects(:fetch_user_details).returns(
email: "unknown@user.com",
avatar: "http://avatar.example.com/avatar.png",
)
auth_result = nil
expect { auth_result = authenticator.after_authenticate(auth) }.not_to change {
job_klass.jobs.count
}
expect { authenticator.after_create_account(user, auth_result) }.to change {
job_klass.jobs.count
}.by(1)
job_args = job_klass.jobs.last["args"].first
expect(job_args["url"]).to eq("http://avatar.example.com/avatar.png")
expect(job_args["user_id"]).to eq(user.id)
expect(job_args["override_gravatar"]).to eq(false)
end
end
end
it "can walk json" do
authenticator = OAuth2BasicAuthenticator.new
json_string = '{"user":{"id":1234,"email":{"address":"test@example.com"}}}'
SiteSetting.oauth2_json_email_path = "user.email.address"
result = authenticator.json_walk({}, JSON.parse(json_string), :email)
expect(result).to eq "test@example.com"
end
it "allows keys containing dots, if wrapped in quotes" do
authenticator = OAuth2BasicAuthenticator.new
json_string = '{"www.example.com/uid": "myuid"}'
SiteSetting.oauth2_json_user_id_path = '"www.example.com/uid"'
result = authenticator.json_walk({}, JSON.parse(json_string), :user_id)
expect(result).to eq "myuid"
end
it "allows keys containing dots, if escaped" do
authenticator = OAuth2BasicAuthenticator.new
json_string = '{"www.example.com/uid": "myuid"}'
SiteSetting.oauth2_json_user_id_path = 'www\.example\.com/uid'
result = authenticator.json_walk({}, JSON.parse(json_string), :user_id)
expect(result).to eq "myuid"
end
it "allows keys containing literal backslashes, if escaped" do
authenticator = OAuth2BasicAuthenticator.new
# This 'single quoted heredoc' syntax means we don't have to escape backslashes in Ruby
# What you see is exactly what the user would enter in the site settings
json_string = <<~'_'.chomp
{"www.example.com/uid\\": "myuid"}
_
SiteSetting.oauth2_json_user_id_path = <<~'_'.chomp
www\.example\.com/uid\\
_
result = authenticator.json_walk({}, JSON.parse(json_string), :user_id)
expect(result).to eq "myuid"
end
it "can walk json that contains an array" do
authenticator = OAuth2BasicAuthenticator.new
json_string =
'{"email":"test@example.com","identities":[{"user_id":"123456789","provider":"auth0","isSocial":false}]}'
SiteSetting.oauth2_json_user_id_path = "identities.[].user_id"
result = authenticator.json_walk({}, JSON.parse(json_string), :user_id)
expect(result).to eq "123456789"
end
it "can walk json and handle an empty array" do
authenticator = OAuth2BasicAuthenticator.new
json_string = '{"email":"test@example.com","identities":[]}'
SiteSetting.oauth2_json_user_id_path = "identities.[].user_id"
result = authenticator.json_walk({}, JSON.parse(json_string), :user_id)
expect(result).to eq nil
end
it "can walk json and find values by index in an array" do
authenticator = OAuth2BasicAuthenticator.new
json_string = '{"emails":[{"value":"test@example.com"},{"value":"test2@example.com"}]}'
SiteSetting.oauth2_json_email_path = "emails[1].value"
result = authenticator.json_walk({}, JSON.parse(json_string), :email)
expect(result).to eq "test2@example.com"
end
it "can walk json and download avatar" do
authenticator = OAuth2BasicAuthenticator.new
json_string = '{"user":{"avatar":"http://example.com/1.png"}}'
SiteSetting.oauth2_json_avatar_path = "user.avatar"
result = authenticator.json_walk({}, JSON.parse(json_string), :avatar)
expect(result).to eq "http://example.com/1.png"
end
it "can walk json and appropriately assign a `false`" do
authenticator = OAuth2BasicAuthenticator.new
json_string = '{"user":{"id":1234, "data": {"address":"test@example.com", "is_cat": false}}}'
SiteSetting.oauth2_json_email_verified_path = "user.data.is_cat"
result =
authenticator.json_walk(
{},
JSON.parse(json_string),
"extra:user.data.is_cat",
custom_path: "user.data.is_cat",
)
expect(result).to eq false
end
describe "token_callback" do
let(:user) { Fabricate(:user) }
let(:strategy) { OmniAuth::Strategies::Oauth2Basic.new({}) }
let(:authenticator) { OAuth2BasicAuthenticator.new }
let(:auth) do
OmniAuth::AuthHash.new(
"provider" => "oauth2_basic",
"credentials" => {
"token" => "token",
},
"uid" => "e028b1b918853eca7fba208a9d7e9d29a6e93c57",
"info" => {
"name" => "Sammy the Shark",
"email" => "sammy@digitalocean.com",
},
"extra" => {
},
)
end
let(:access_token) do
{
"params" => {
"info" => {
"name" => "Sammy the Shark",
"email" => "sammy@digitalocean.com",
"uuid" => "e028b1b918853eca7fba208a9d7e9d29a6e93c57",
},
},
}
end
before(:each) do
SiteSetting.oauth2_callback_user_id_path = "params.info.uuid"
SiteSetting.oauth2_callback_user_info_paths = "name:params.info.name|email:params.info.email"
end
it "can retrieve user id from access token callback" do
strategy.stubs(:access_token).returns(access_token)
expect(strategy.uid).to eq "e028b1b918853eca7fba208a9d7e9d29a6e93c57"
end
it "can retrieve user properties from access token callback" do
strategy.stubs(:access_token).returns(access_token)
expect(strategy.info["name"]).to eq "Sammy the Shark"
expect(strategy.info["email"]).to eq "sammy@digitalocean.com"
end
it "does apply user properties from access token callback in after_authenticate" do
SiteSetting.oauth2_fetch_user_details = true
authenticator.stubs(:fetch_user_details).returns(email: "sammy@digitalocean.com")
result = authenticator.after_authenticate(auth)
expect(result.extra_data[:uid]).to eq "e028b1b918853eca7fba208a9d7e9d29a6e93c57"
expect(result.name).to eq "Sammy the Shark"
expect(result.email).to eq "sammy@digitalocean.com"
end
it "does work if user details are not fetched" do
SiteSetting.oauth2_fetch_user_details = false
result = authenticator.after_authenticate(auth)
expect(result.extra_data[:uid]).to eq "e028b1b918853eca7fba208a9d7e9d29a6e93c57"
expect(result.name).to eq "Sammy the Shark"
expect(result.email).to eq "sammy@digitalocean.com"
end
end
end