Skip to content

Commit

Permalink
Extract the user authorisation logic into a separate class
Browse files Browse the repository at this point in the history
- All the logic that checks the current_user permissions has been extracted to a separate class, because it will need to be re-used from different parts of the code.
  • Loading branch information
Alex Avlonitis committed Apr 29, 2024
1 parent 40afea5 commit 2fa18d8
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 1 deletion.
1 change: 1 addition & 0 deletions lib/gds-sso.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module SSO
autoload :ControllerMethods, "gds-sso/controller_methods"
autoload :User, "gds-sso/user"
autoload :ApiAccess, "gds-sso/api_access"
autoload :AuthoriseUser, "gds-sso/authorise_user"
autoload :AuthorisedUserConstraint, "gds-sso/authorised_user_constraint"
autoload :PermissionDeniedError, "gds-sso/controller_methods"

Expand Down
49 changes: 49 additions & 0 deletions lib/gds-sso/authorise_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module GDS
module SSO
class AuthoriseUser
def self.call(...) = new(...).call

def initialize(current_user, permissions)
@current_user = current_user
@permissions = permissions
end

def call
case permissions
when String
unless current_user.has_permission?(permissions)
raise GDS::SSO::PermissionDeniedError, "Sorry, you don't seem to have the #{permissions} permission for this app."
end
when Hash
raise ArgumentError, "Must be either `any_of` or `all_of`" unless permissions.keys.size == 1

if permissions[:any_of]
authorise_user_with_at_least_one_of_permissions!(permissions[:any_of])
elsif permissions[:all_of]
authorise_user_with_all_permissions!(permissions[:all_of])
else
raise ArgumentError, "Must be either `any_of` or `all_of`"
end
end
end

private

attr_reader :current_user, :permissions

def authorise_user_with_at_least_one_of_permissions!(permissions)
if permissions.none? { |permission| current_user.has_permission?(permission) }
raise GDS::SSO::PermissionDeniedError,
"Sorry, you don't seem to have any of the permissions: #{permissions.to_sentence} for this app."
end
end

def authorise_user_with_all_permissions!(permissions)
unless permissions.all? { |permission| current_user.has_permission?(permission) }
raise GDS::SSO::PermissionDeniedError,
"Sorry, you don't seem to have all of the permissions: #{permissions.to_sentence} for this app."
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/gds-sso/authorised_user_constraint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def matches?(request)
warden = request.env["warden"]
warden.authenticate! if !warden.authenticated? || warden.user.remotely_signed_out?

AuthoriseUser.call(warden.user, permissions)
GDS::SSO::AuthoriseUser.call(warden.user, permissions)
true
end

Expand Down
69 changes: 69 additions & 0 deletions spec/unit/authorise_user_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
require "spec_helper"
require "gds-sso/authorise_user"

describe GDS::SSO::AuthoriseUser do
describe "#call" do
let(:current_user) { double }

context "with a single string permission argument" do
let(:permissions) { "admin" }
let(:expected_error) { GDS::SSO::PermissionDeniedError }

it "permits users with the required permission" do
allow(current_user).to receive(:has_permission?).with("admin").and_return(true)

expect { described_class.call(current_user, permissions) }.not_to raise_error
end

it "does not permit the users without the required permission" do
allow(current_user).to receive(:has_permission?).with("admin").and_return(false)

expect { described_class.call(current_user, permissions) }.to raise_error(expected_error)
end
end

context "with the `all_of` option" do
let(:permissions) { { all_of: %w[admin editor] } }
let(:expected_error) { GDS::SSO::PermissionDeniedError }

it "permits users with all of the required permissions" do
allow(current_user).to receive(:has_permission?).with("admin").and_return(true)
allow(current_user).to receive(:has_permission?).with("editor").and_return(true)

expect { described_class.call(current_user, permissions) }.not_to raise_error
end

it "does not permit users without all of the required permissions" do
allow(current_user).to receive(:has_permission?).with("admin").and_return(false)
allow(current_user).to receive(:has_permission?).with("editor").and_return(true)

expect { described_class.call(current_user, permissions) }.to raise_error(expected_error)
end
end

context "with the `any_of` option" do
let(:permissions) { { any_of: %w[admin editor] } }
let(:expected_error) { GDS::SSO::PermissionDeniedError }

it "permits users with any of the required permissions" do
allow(current_user).to receive(:has_permission?).with("admin").and_return(true)
allow(current_user).to receive(:has_permission?).with("editor").and_return(false)

expect { described_class.call(current_user, permissions) }.not_to raise_error
end

it "does not permit users without any of the required permissions" do
allow(current_user).to receive(:has_permission?).and_return(false)

expect { described_class.call(current_user, permissions) }.to raise_error(expected_error)
end
end

context "with none of `any_of` or `all_of`" do
it "raises an `ArgumentError`" do
expect { described_class.call(current_user, { admin: true }) }
.to raise_error(ArgumentError)
end
end
end
end

0 comments on commit 2fa18d8

Please sign in to comment.