"stores a token in redis" asserted an exact count of Redis keys for the user, but database cleanup only covers SQL and Redis keys accumulate across examples and spec files. It also relied on a previously-run example having created the authorization, so it failed depending on test order and leftover state. Clear the authorizations:* namespace before each example in the #create group, create the authorization under test, and assert on its specific token key instead of a global count.
315 lines
10 KiB
Ruby
315 lines
10 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe RemoteStorageAuthorization, type: :model do
|
|
include ActiveJob::TestHelper
|
|
|
|
let(:user) { create :user }
|
|
|
|
before do
|
|
allow_any_instance_of(AppCatalog::WebApp).to receive(:update_metadata).and_return(true)
|
|
allow_any_instance_of(RemoteStorageAuthorization).to receive(:remove_token_expiry_job).and_return(nil)
|
|
end
|
|
|
|
describe "#create" do
|
|
before(:each) { redis_rs_delete_keys("authorizations:*") }
|
|
after(:each) { clear_enqueued_jobs }
|
|
after(:all) { redis_rs_delete_keys("authorizations:*") }
|
|
|
|
let(:auth) do
|
|
user.remote_storage_authorizations.create!(
|
|
permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
|
|
client_id: "example.com",
|
|
redirect_uri: "https://example.com"
|
|
)
|
|
end
|
|
|
|
it "generates a token" do
|
|
expect(auth.token).to match(/[a-zA-Z0-9]+/)
|
|
end
|
|
|
|
it "stores a token in redis" do
|
|
auth
|
|
|
|
authorizations = redis_rs.smembers("authorizations:#{user.cn}:#{auth.token}")
|
|
expect(authorizations.sort).to eq(%w(documents photos contacts:rw videos:r tasks/work:r).sort)
|
|
end
|
|
|
|
context "with expiry set" do
|
|
it "enqueues an expiration job" do
|
|
auth_with_expiry = user.remote_storage_authorizations.create!(
|
|
permissions: %w(documents:rw), client_id: "example.com",
|
|
redirect_uri: "https://example.com",
|
|
expire_at: 1.month.from_now
|
|
)
|
|
job = enqueued_jobs.select{|j| j['job_class'] == "RemoteStorageExpireAuthorizationJob"}.first
|
|
expect(job['arguments'][0]).to eq(auth_with_expiry.id)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#destroy" do
|
|
after(:each) { clear_enqueued_jobs }
|
|
after(:all) { redis_rs_delete_keys("authorizations:*") }
|
|
|
|
it "removes the token from redis" do
|
|
auth = user.remote_storage_authorizations.create!(
|
|
permissions: %w(shares:rw documents pictures:r),
|
|
client_id: "sharesome.5apps.com",
|
|
redirect_uri: "https://sharesome.5apps.com"
|
|
)
|
|
auth.destroy!
|
|
|
|
expect(redis_rs.keys("authorizations:#{user.address}:*")).to be_empty
|
|
end
|
|
|
|
context "with expiry set" do
|
|
it "removes the expiration job" do
|
|
auth_with_expiry = user.remote_storage_authorizations.create!(
|
|
permissions: %w(documents:rw), client_id: "example.com",
|
|
redirect_uri: "https://example.com",
|
|
expire_at: 1.month.from_now
|
|
)
|
|
# Cannot test for removal from the actual Sidekiq::Queue, because it is
|
|
# not used in specs, but the method directly removes jobs from there
|
|
expect(auth_with_expiry).to receive(:remove_token_expiry_job)
|
|
auth_with_expiry.destroy!
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#find_or_create_web_app" do
|
|
context "with origin that looks hosted" do
|
|
after(:all) { redis_rs_delete_keys("authorizations:*") }
|
|
|
|
let(:auth) do
|
|
user.remote_storage_authorizations.create!(
|
|
permissions: %w(documents:rw),
|
|
client_id: "example.com",
|
|
redirect_uri: "https://example.com"
|
|
)
|
|
end
|
|
|
|
it "generates a web_app" do
|
|
expect(auth.web_app).to be_a(AppCatalog::WebApp)
|
|
end
|
|
end
|
|
|
|
context "when creating two authorizations for the same app" do
|
|
let(:user_2) { create :user, id: 23, cn: "michiel", email: "michiel@example.com" }
|
|
|
|
let(:auth_1) do
|
|
user.remote_storage_authorizations.create!(
|
|
permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
|
|
client_id: "example.com",
|
|
redirect_uri: "https://example.com"
|
|
)
|
|
end
|
|
|
|
let(:auth_2) do
|
|
user_2.remote_storage_authorizations.create!(
|
|
permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
|
|
client_id: "example.com",
|
|
redirect_uri: "https://example.com"
|
|
)
|
|
end
|
|
|
|
after do
|
|
auth_1.destroy
|
|
auth_2.destroy
|
|
user_2.destroy
|
|
end
|
|
|
|
it "uses the same web app for both authorizations" do
|
|
expect(auth_1.web_app).to eq(auth_2.web_app)
|
|
end
|
|
end
|
|
|
|
describe "non-production app origins" do
|
|
context "when host is not an FQDN" do
|
|
let(:auth) do
|
|
user.remote_storage_authorizations.create!(
|
|
permissions: %w(recipes),
|
|
client_id: "localhost:4200",
|
|
redirect_uri: "http://localhost:4200"
|
|
)
|
|
end
|
|
|
|
it "does not create a web app" do
|
|
expect(auth.web_app).to be_nil
|
|
expect(auth.app_name).to eq("localhost:4200")
|
|
end
|
|
end
|
|
|
|
context "when host is an IP address" do
|
|
let(:auth) do
|
|
user.remote_storage_authorizations.create!(
|
|
permissions: %w(recipes),
|
|
client_id: "192.168.0.23:3000",
|
|
redirect_uri: "http://192.168.0.23:3000"
|
|
)
|
|
end
|
|
|
|
it "does not create a web app" do
|
|
expect(auth.web_app).to be_nil
|
|
expect(auth.app_name).to eq("192.168.0.23:3000")
|
|
end
|
|
end
|
|
|
|
context "when host is an extension URL" do
|
|
let(:auth) do
|
|
user.remote_storage_authorizations.create!(
|
|
permissions: %w(bookmarks),
|
|
client_id: "123.addons.allizom.org",
|
|
redirect_uri: "123.addons.allizom.org/foo"
|
|
)
|
|
end
|
|
|
|
it "does not create a web app" do
|
|
expect(auth.web_app).to be_nil
|
|
expect(auth.app_name).to eq("123.addons.allizom.org")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#launch_url" do
|
|
after(:all) { redis_rs_delete_keys("authorizations:*") }
|
|
|
|
context "without start URL" do
|
|
before do
|
|
AppCatalog::WebApp.create!(
|
|
url: "https://webmarks.5apps.com", name: "Webmarks",
|
|
metadata: { name: "Webmarks", start_url: nil, scope: nil }
|
|
)
|
|
end
|
|
|
|
let(:auth) do
|
|
user.remote_storage_authorizations.create!(
|
|
permissions: %w(bookmarks:rw), client_id: "webmarks.5apps.com",
|
|
redirect_uri: "https://webmarks.5apps.com/connect"
|
|
)
|
|
end
|
|
|
|
it "uses the base URL (from client ID)" do
|
|
expect(auth.launch_url).to eq("https://webmarks.5apps.com")
|
|
end
|
|
end
|
|
|
|
context "with start URL" do
|
|
before do
|
|
AppCatalog::WebApp.create!(
|
|
url: "https://hyperdraft.rosano.ca", name: "Hyperdraft",
|
|
metadata: {
|
|
name: "Hyperdraft", scope: nil,
|
|
start_url: "https://hyperdraft.rosano.ca/start"
|
|
}
|
|
)
|
|
end
|
|
|
|
let(:auth) do
|
|
user.remote_storage_authorizations.create!(
|
|
permissions: %w(notes:rw), client_id: "hyperdraft.rosano.ca",
|
|
redirect_uri: "https://hyperdraft.rosano.ca/write/foo"
|
|
)
|
|
end
|
|
|
|
describe "full URL" do
|
|
it "respects the start URL" do
|
|
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start")
|
|
end
|
|
|
|
it "does not respect URLs outside of the client ID scope" do
|
|
auth.web_app.metadata[:start_url] = "https://uberdraft.rosano.ca/write"
|
|
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca")
|
|
end
|
|
end
|
|
|
|
describe "relative paths" do
|
|
it "includes the path relative from the base URL" do
|
|
auth.web_app.metadata[:start_url] = "start.html"
|
|
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start.html")
|
|
|
|
auth.web_app.metadata[:start_url] = "./start.html"
|
|
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start.html")
|
|
|
|
auth.web_app.metadata[:start_url] = "../start.html"
|
|
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start.html")
|
|
end
|
|
end
|
|
|
|
describe "absolute path" do
|
|
it "includes the path relative from the base URL" do
|
|
auth.web_app.metadata[:start_url] = "/write"
|
|
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/write")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "notifications" do
|
|
include ActiveJob::TestHelper
|
|
|
|
after(:each) { clear_enqueued_jobs }
|
|
after(:all) { redis_rs_delete_keys("authorizations:*") }
|
|
|
|
before { allow(user).to receive(:display_name).and_return("Jimmy") }
|
|
|
|
context "with notifications disabled" do
|
|
before do
|
|
user.preferences.merge!({ remotestorage_notify_auth_created: "off" })
|
|
user.save!
|
|
user.remote_storage_authorizations.create!(
|
|
:permissions => %w(photos), :client_id => "app.example.com",
|
|
:redirect_uri => "https://app.example.com"
|
|
)
|
|
end
|
|
|
|
it "does not notify the user via email about new RS app" do
|
|
expect(enqueued_jobs.size).to eq(0)
|
|
end
|
|
end
|
|
|
|
context "with email notifications enabled" do
|
|
before do
|
|
user.preferences.merge!({ remotestorage_notify_auth_created: "email" })
|
|
user.save!
|
|
user.remote_storage_authorizations.create!(
|
|
:permissions => %w(photos), :client_id => "app.example.com",
|
|
:redirect_uri => "https://app.example.com"
|
|
)
|
|
end
|
|
|
|
it "notifies the user via email" do
|
|
expect(enqueued_jobs.size).to eq(1)
|
|
job = enqueued_jobs.select{|j| j['job_class'] == "ActionMailer::MailDeliveryJob"}.first
|
|
expect(job['arguments'][0]).to eq('NotificationMailer')
|
|
expect(job['arguments'][1]).to eq('remotestorage_auth_created')
|
|
expect(job['arguments'][3]['params']['user']['_aj_globalid']).to eq('gid://akkounts/User/1')
|
|
expect(job['arguments'][3]['params']['auth']['_aj_globalid']).to eq('gid://akkounts/RemoteStorageAuthorization/1')
|
|
end
|
|
end
|
|
|
|
context "with XMPP notifications enabled" do
|
|
before do
|
|
Setting.xmpp_notifications_from_address = "botka@kosmos.org"
|
|
user.preferences.merge!({ remotestorage_notify_auth_created: "xmpp" })
|
|
user.save!
|
|
user.remote_storage_authorizations.create!(
|
|
:permissions => %w(photos), :client_id => "app.example.com",
|
|
:redirect_uri => "https://app.example.com"
|
|
)
|
|
end
|
|
|
|
it "sends an XMPP message to the account owner's JID" do
|
|
expect(enqueued_jobs.size).to eq(1)
|
|
expect(enqueued_jobs.first["job_class"]).to eq("XmppSendMessageJob")
|
|
msg = enqueued_jobs.first["arguments"].first
|
|
expect(msg["type"]).to eq("normal")
|
|
expect(msg["from"]).to eq("botka@kosmos.org")
|
|
expect(msg["to"]).to eq(user.address)
|
|
expect(msg["body"]).to match(/granted 'app\.example\.com' access to your Kosmos Storage/)
|
|
end
|
|
end
|
|
end
|
|
end
|