Skip to content

Commit

Permalink
Merge pull request #895 from alphagov/sso-feature
Browse files Browse the repository at this point in the history
Login to moderation portal using SAML SSO
  • Loading branch information
pixeltrix authored Feb 23, 2024
2 parents a2e5f34 + 94034ab commit 66364e3
Show file tree
Hide file tree
Showing 95 changed files with 989 additions and 1,972 deletions.
5 changes: 4 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ gem 'rails', '6.1.7.7'

gem 'rake'
gem 'pg', '< 1.5'
gem 'authlogic'
gem 'devise'
gem 'will_paginate'
gem 'json'
gem 'delayed_job_active_record'
Expand Down Expand Up @@ -36,6 +36,9 @@ gem 'redcarpet'
gem 'scrypt'
gem 'webrick'
gem 'nokogiri'
gem 'omniauth'
gem 'omniauth-rails_csrf_protection'
gem 'omniauth-saml'

gem 'aws-sdk-codedeploy'
gem 'aws-sdk-cloudwatchlogs'
Expand Down
40 changes: 34 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,6 @@ GEM
public_suffix (>= 2.0.2, < 6.0)
appsignal (3.4.13)
rack
authlogic (6.4.3)
activemodel (>= 5.2, < 7.2)
activerecord (>= 5.2, < 7.2)
activesupport (>= 5.2, < 7.2)
request_store (~> 1.0)
aws-eventstream (1.3.0)
aws-partitions (1.888.0)
aws-sdk-cloudwatchlogs (1.79.0)
Expand Down Expand Up @@ -142,6 +137,14 @@ GEM
delayed_job_active_record (4.1.8)
activerecord (>= 3.0, < 8.0)
delayed_job (>= 3.0, < 5)
devise (4.9.3)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-encryptable (0.2.0)
devise (>= 2.1.0)
diff-lcs (1.5.1)
docile (1.3.1)
dotenv (2.7.6)
Expand Down Expand Up @@ -194,6 +197,7 @@ GEM
globalid (1.2.1)
activesupport (>= 6.1)
hashdiff (1.1.0)
hashie (5.0.0)
htmlentities (4.3.4)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
Expand Down Expand Up @@ -257,6 +261,17 @@ GEM
racc (~> 1.4)
nokogiri (1.16.2-x86_64-linux)
racc (~> 1.4)
omniauth (2.1.1)
hashie (>= 3.4.6)
rack (>= 2.2.3)
rack-protection
omniauth-rails_csrf_protection (1.0.1)
actionpack (>= 4.2)
omniauth (~> 2.0)
omniauth-saml (2.1.0)
omniauth (~> 2.0)
ruby-saml (~> 1.12)
orm_adapter (0.5.0)
pg (1.4.6)
pry (0.14.2)
coderay (~> 1.1)
Expand All @@ -266,6 +281,8 @@ GEM
nio4r (~> 2.0)
racc (1.7.3)
rack (2.2.8)
rack-protection (3.1.0)
rack (~> 2.2, >= 2.2.4)
rack-test (2.1.0)
rack (>= 1.3)
rails (6.1.7.7)
Expand Down Expand Up @@ -308,6 +325,9 @@ GEM
regexp_parser (2.8.2)
request_store (1.5.0)
rack (>= 1.4)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.6)
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
Expand All @@ -326,6 +346,9 @@ GEM
rspec-mocks (~> 3.12)
rspec-support (~> 3.12)
rspec-support (3.13.0)
ruby-saml (1.16.0)
nokogiri (>= 1.13.10)
rexml
ruby-vips (2.1.4)
ffi (~> 1.12)
ruby2_keywords (0.0.5)
Expand Down Expand Up @@ -371,6 +394,8 @@ GEM
concurrent-ruby (~> 1.0)
uglifier (4.1.8)
execjs (>= 0.3.0, < 3)
warden (1.2.9)
rack (>= 2.0.9)
webmock (3.20.0)
addressable (>= 2.8.0)
crack (>= 0.3.2)
Expand All @@ -395,7 +420,6 @@ PLATFORMS

DEPENDENCIES
appsignal
authlogic
aws-sdk-cloudwatchlogs
aws-sdk-codedeploy
aws-sdk-s3
Expand All @@ -411,6 +435,7 @@ DEPENDENCIES
database_cleaner
delayed-web
delayed_job_active_record
devise
dotenv-rails
email_spec
factory_bot_rails
Expand All @@ -427,6 +452,9 @@ DEPENDENCIES
maxminddb
net-http-persistent
nokogiri
omniauth
omniauth-rails_csrf_protection
omniauth-saml
pg (< 1.5)
pry
puma (< 6)
Expand Down
63 changes: 57 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ We recommend using [Docker Desktop][2] to get setup quickly. If you'd prefer not
docker compose run --rm web rake db:setup
```

### Create an admin user

```
docker compose run --rm web rake epets:add_sysadmin_user
```

### Load the country list

```
Expand Down Expand Up @@ -82,8 +76,65 @@ Similarly, individual cucumber features can be run using the following command:
docker compose run --rm web cucumber features/suzie_views_a_petition.feature
```

## Moderation Portal SSO

The moderation portal is authenticated using the [OmniAuth][6] gem and implements
a light wrapper around strategies so that multiple configurations of a strategy can be
supported, e.g. two or more SAML identity providers.

The `config/sso.yml` has a configuration of the `Developer` strategy for local development
which should not be used in production. The test configuration in the file shows how a
typical SAML IdP would be configured.

There are four key attributes that need to be returned in the OmniAuth `auth_info` hash,
these being `first_name`, `last_name`, `email` and `groups`. The `email` attribute acts
as the uid for the user and the `groups` attribute controls what role they get assigned.

The configuration attributes are:

- **name**

This is a required attribute and must be unique. It also must be suitable for use
in a url as it forms part of the callback url for OmniAuth.

- **strategy**

This is the OmniAuth strategy to use as the parent class for the identity provider.

- **domains**

The list of email domains to use with this identity provider, e.g.

``` yaml
domains:
- "example.com"
```
- **roles**
Controls the mapping of the `groups` attribute to the assigned role, e.g.

``` yaml
roles:
sysadmin:
- "System Administrators"
moderator:
- "Petition Moderators"
reviewer:
- "Petition Reviewers"
```

The default for any of the three roles is an empty set so if an identity provider is
only being used for one of the roles then there's no need to configure the others.

- **config**

This is the configuration that is passed to the OmniAuth strategy and should
be a hash of the documented options supported by the strategy.

[1]: https://petition.parliament.uk
[2]: https://www.docker.com/products/docker-desktop
[3]: http://localhost:3000/
[4]: http://localhost:3000/admin
[5]: http://localhost:1080/
[6]: https://github.com/omniauth/omniauth
1 change: 1 addition & 0 deletions app/assets/stylesheets/petitions/admin/_header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
float: right;
}
li {
color: $white;
display: inline;
margin-left: $gutter-one-third;
a {
Expand Down
8 changes: 8 additions & 0 deletions app/controllers/admin/admin_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@ def set_appsignal_namespace
def set_current_user
Admin::Current.user = current_user
end

def after_sign_in_path_for(resource)
admin_root_url
end

def after_sign_out_path_for(resource)
admin_login_url
end
end
60 changes: 0 additions & 60 deletions app/controllers/admin/admin_users_controller.rb
Original file line number Diff line number Diff line change
@@ -1,67 +1,7 @@
class Admin::AdminUsersController < Admin::AdminController
before_action :require_sysadmin
before_action :find_user, only: %i[edit update destroy]

rescue_from AdminUser::CannotDeleteCurrentUser do
redirect_to admin_admin_users_url, alert: :user_is_current_user
end

rescue_from AdminUser::MustBeAtLeastOneAdminUser do
redirect_to admin_admin_users_url, alert: :user_count_is_too_low
end

rescue_from ActiveRecord::DeleteRestrictionError do
redirect_to admin_admin_users_url, alert: :user_has_moderated_petitions
end

def index
@users = AdminUser.by_name.paginate(page: params[:page], per_page: 50)
end

def new
@user = AdminUser.new
end

def create
@user = AdminUser.new(admin_user_params)

if @user.save
redirect_to admin_admin_users_url, notice: :user_created
else
render :new
end
end

def edit
end

def update
if @user.update(admin_user_params)
redirect_to admin_admin_users_url, notice: :user_updated
else
render :edit
end
end

def destroy
if @user.destroy(current_user: current_user)
redirect_to admin_admin_users_url, notice: :user_deleted
else
redirect_to admin_admin_users_url, alert: :user_not_deleted
end
end

protected

def find_user
@user = AdminUser.find(params[:id])
end

def admin_user_params
params.
require(:admin_user).
permit(:password, :password_confirmation, :first_name,
:last_name, :role, :email, :force_password_reset,
:account_disabled)
end
end
45 changes: 45 additions & 0 deletions app/controllers/admin/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
class Admin::OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_action :require_admin
skip_before_action :verify_authenticity_token, only: %i[saml]

rescue_from ActiveRecord::RecordNotFound do
redirect_to admin_login_url, alert: :login_failed
end

def saml
@user = AdminUser.find_or_create_from!(provider, auth_data)

if @user.present?
sign_in @user, event: :authentication

set_flash_message(:notice, :signed_in)
set_refresh_header

render "admin/admin/index"
else
redirect_to admin_login_url, alert: :invalid_login
end
end

def failure
redirect_to admin_login_url, alert: :login_failed
end

private

def after_omniauth_failure_path_for(scope)
admin_login_url
end

def auth_data
request.env["omniauth.auth"]
end

def provider
IdentityProvider.find_by!(name: auth_data.provider)
end

def set_refresh_header
headers['Refresh'] = "0; url=#{admin_root_url}"
end
end
20 changes: 0 additions & 20 deletions app/controllers/admin/profile_controller.rb

This file was deleted.

Loading

0 comments on commit 66364e3

Please sign in to comment.