- Rework RsAuthComponent into a card that shows the app, its device, an access summary and the permission status - Group connected apps by app and device, so repeated authorizations for the same app and device (e.g. after a browser update) are shown together, each with its own revoke action - Add RsAuthDetailsModalComponent with the full scope list, device, connected tokens and permission expiry, plus Launch and Revoke actions (built on the existing ModalComponent) - Add RemoteStorageAuthorization#device_name (parsed from the user agent) and #expired? Part of #254
102 lines
3.3 KiB
Ruby
102 lines
3.3 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe "Storage connected apps", type: :feature do
|
|
let(:user) { create :user }
|
|
|
|
let(:firefox_ua) do
|
|
"Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0"
|
|
end
|
|
|
|
let(:chrome_ua) do
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " \
|
|
"(KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
|
|
end
|
|
|
|
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)
|
|
allow_any_instance_of(User).to receive(:ldap_entry).and_return(
|
|
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
|
services_enabled: ["remotestorage"]
|
|
)
|
|
login_as user, scope: :user
|
|
end
|
|
|
|
def create_auth(client:, device:, permissions: %w(documents:rw))
|
|
user.remote_storage_authorizations.create!(
|
|
permissions: permissions,
|
|
client_id: client,
|
|
redirect_uri: "https://#{client}",
|
|
expire_at: 1.month.from_now,
|
|
user_agent: device
|
|
)
|
|
end
|
|
|
|
context "with a connected app" do
|
|
before do
|
|
AppCatalog::WebApp.create!(url: "https://notes.example.com", name: "Notes", metadata: {})
|
|
end
|
|
|
|
let!(:auth) do
|
|
create_auth(client: "notes.example.com", device: firefox_ua,
|
|
permissions: %w(documents:rw photos:r))
|
|
end
|
|
|
|
after { auth.destroy }
|
|
|
|
it "shows the app, its device and permission status" do
|
|
visit apps_services_storage_path
|
|
|
|
expect(page).to have_content("Notes")
|
|
expect(page).to have_content("notes.example.com")
|
|
expect(page).to have_content("Firefox on Linux")
|
|
expect(page).to have_content("2 folders")
|
|
expect(page).to have_content("Expires")
|
|
end
|
|
|
|
it "renders a details modal with scopes and actions" do
|
|
visit apps_services_storage_path
|
|
|
|
expect(page).to have_selector("li", text: "documents (read/write)", visible: false)
|
|
expect(page).to have_selector("li", text: "photos (read only)", visible: false)
|
|
expect(page).to have_link("Launch app", visible: false)
|
|
expect(page).to have_selector("a", text: "Revoke access", visible: false, count: 1)
|
|
end
|
|
end
|
|
|
|
context "with duplicates" do
|
|
let!(:first) { create_auth(client: "notes.example.com", device: firefox_ua) }
|
|
let!(:second) { create_auth(client: "notes.example.com", device: firefox_ua) }
|
|
let!(:other) { create_auth(client: "notes.example.com", device: chrome_ua) }
|
|
|
|
after do
|
|
first.destroy
|
|
second.destroy
|
|
other.destroy
|
|
end
|
|
|
|
it "groups authorizations for the same app and device into one card" do
|
|
visit apps_services_storage_path
|
|
|
|
expect(page).to have_selector(".rs-auth-card", count: 2)
|
|
expect(page).to have_content("Firefox on Linux")
|
|
expect(page).to have_content("Chrome on macOS")
|
|
expect(page).to have_content("Tokens")
|
|
end
|
|
|
|
it "lists every authorization in the group with its own revoke action" do
|
|
visit apps_services_storage_path
|
|
|
|
expect(page).to have_selector("a", text: "Revoke access", visible: false, count: 3)
|
|
end
|
|
end
|
|
|
|
context "without connected apps" do
|
|
it "shows the empty state" do
|
|
visit apps_services_storage_path
|
|
|
|
expect(page).to have_content("No apps connected")
|
|
end
|
|
end
|
|
end
|