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 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 user_auth_keys = redis_rs.keys("authorizations:#{user.cn}:*") expect(user_auth_keys.length).to eq(1) authorizations = redis_rs.smembers(user_auth_keys.first) 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