399 lines
14 KiB
Ruby
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
|