Revoking a device redirected away from the storage dashboard, which closed the details modal and reloaded the page. Refreshing the affected parts instead keeps the modal open and shows the change where it happened. - Extract the card and the modal's access list into their own components, so either can be re-rendered without touching the modal's open state, and share the group summaries between the components - Derive the app's DOM ids from its client_id (the grouping key) and the device row ids from the authorization, through one helper used by both the templates and the controller - Revoke one of several devices as a Turbo Stream: remove the row and refresh the card behind the modal and the access list - Revoke an app's last device with a redirect, so the dashboard re-renders itself and grouping, sorting and the empty state stay in one place - Fade the removed row out, deferring Turbo's own removal until the animation has ended Part of #254
165 lines
5.5 KiB
Ruby
165 lines
5.5 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe Services::RsAuthsController, type: :controller do
|
|
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)
|
|
allow_any_instance_of(LdapService).to receive(:fetch_users).and_return([
|
|
{ services_enabled: ["remotestorage"] }
|
|
])
|
|
end
|
|
|
|
describe "GET /services/storage/apps/:client_id/launch (storage-first)" do
|
|
context "when user is signed in" do
|
|
before do
|
|
sign_in user
|
|
end
|
|
|
|
context "authorization exists" do
|
|
before do
|
|
@auth = user.remote_storage_authorizations.create!(
|
|
permissions: %w(documents), client_id: "app.example.com",
|
|
redirect_uri: "https://app.example.com",
|
|
expire_at: 2.days.from_now
|
|
)
|
|
|
|
get :launch_app, params: { client_id: @auth.client_id }
|
|
end
|
|
|
|
after do
|
|
@auth.destroy
|
|
end
|
|
|
|
it "hands the app the user address in the URL fragment" do
|
|
launch_url = "https://app.example.com#remotestorage=#{user.address}"
|
|
expect(response).to redirect_to(launch_url)
|
|
end
|
|
|
|
it "does not hand over an access token" do
|
|
expect(response.location).not_to include("access_token")
|
|
end
|
|
end
|
|
|
|
context "when remoteStorage is not enabled for the user" do
|
|
before do
|
|
allow_any_instance_of(LdapService).to receive(:fetch_users).and_return([
|
|
{ services_enabled: [] }
|
|
])
|
|
|
|
get :launch_app, params: { client_id: "app.example.com" }
|
|
end
|
|
|
|
it "responds with forbidden" do
|
|
expect(response).to have_http_status(:forbidden)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "GET /services/storage/apps/:client_id/launch for an unknown app" do
|
|
before { sign_in user }
|
|
|
|
it "returns 404 instead of raising" do
|
|
get :launch_app, params: { client_id: "nonexistent.example.com" }
|
|
|
|
expect(response).to have_http_status(:not_found)
|
|
end
|
|
end
|
|
|
|
describe "DELETE /services/storage/rs_auths/:id" do
|
|
context "when user is signed in" do
|
|
before { sign_in user }
|
|
|
|
it "revokes the authorization" do
|
|
auth = user.remote_storage_authorizations.create!(
|
|
permissions: %w(documents), client_id: "app.example.com",
|
|
redirect_uri: "https://app.example.com", expire_at: 2.days.from_now
|
|
)
|
|
|
|
expect {
|
|
delete :destroy, params: { id: auth.id }
|
|
}.to change(RemoteStorageAuthorization, :count).by(-1)
|
|
|
|
expect(response).to redirect_to(apps_services_storage_url)
|
|
end
|
|
|
|
it "returns 404 for an unknown authorization" do
|
|
delete :destroy, params: { id: 0 }
|
|
|
|
expect(response).to have_http_status(:not_found)
|
|
end
|
|
|
|
it "returns 404 for another user's authorization" do
|
|
other = create :user, id: 99, cn: "otheruser", email: "other@example.com"
|
|
auth = other.remote_storage_authorizations.create!(
|
|
permissions: %w(documents), client_id: "app.example.com",
|
|
redirect_uri: "https://app.example.com"
|
|
)
|
|
|
|
delete :destroy, params: { id: auth.id }
|
|
|
|
expect(response).to have_http_status(:not_found)
|
|
expect(RemoteStorageAuthorization.exists?(auth.id)).to be(true)
|
|
end
|
|
|
|
context "when another device remains connected to the app" do
|
|
let!(:revoked) do
|
|
user.remote_storage_authorizations.create!(
|
|
permissions: %w(documents), client_id: "app.example.com",
|
|
redirect_uri: "https://app.example.com", expire_at: 2.days.from_now
|
|
)
|
|
end
|
|
|
|
let!(:remaining) do
|
|
user.remote_storage_authorizations.create!(
|
|
permissions: %w(documents photos), client_id: "app.example.com",
|
|
redirect_uri: "https://app.example.com", expire_at: 2.days.from_now
|
|
)
|
|
end
|
|
|
|
after do
|
|
revoked.destroy
|
|
remaining.destroy
|
|
end
|
|
|
|
it "streams the removed device row and the refreshed card and access list" do
|
|
delete :destroy, params: { id: revoked.id }, format: :turbo_stream
|
|
|
|
expect(response.media_type).to eq("text/vnd.turbo-stream.html")
|
|
expect(response.body).to include(
|
|
%(action="remove" target="#{ActionView::RecordIdentifier.dom_id(revoked, :device)}")
|
|
)
|
|
expect(response.body).to include(
|
|
%(action="replace" target="#{controller.helpers.rs_app_dom_id('app.example.com', :card)}")
|
|
)
|
|
expect(response.body).to include(
|
|
%(action="replace" target="#{controller.helpers.rs_app_dom_id('app.example.com', :access)}")
|
|
)
|
|
end
|
|
|
|
it "leaves the other device connected" do
|
|
delete :destroy, params: { id: revoked.id }, format: :turbo_stream
|
|
|
|
expect(RemoteStorageAuthorization.exists?(remaining.id)).to be(true)
|
|
end
|
|
end
|
|
|
|
context "when the app's last device is revoked" do
|
|
it "redirects so that the dashboard re-renders itself" do
|
|
auth = user.remote_storage_authorizations.create!(
|
|
permissions: %w(documents), client_id: "app.example.com",
|
|
redirect_uri: "https://app.example.com", expire_at: 2.days.from_now
|
|
)
|
|
|
|
delete :destroy, params: { id: auth.id }, format: :turbo_stream
|
|
|
|
expect(response).to redirect_to(apps_services_storage_url)
|
|
expect(flash[:success]).to eq("App authorization revoked")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|