Skip to main content
Last updated: 15 Aug 2024

signon: Access and permissions

In addition to providing a single sign on solution for the GOV.UK publishing app suite, Signon can be used to manage users' access to apps and the permissions they have for each app. The permissions can then be used within specific non-Signon apps to dictate behaviour. This document provides a primer on key parts of how the management of access and permissions is implemented.

Section notes

What can you do?

Each "What can you do?" section provides a breakdown of the actions a given user (a 'granter') can take to manage access to and permissions for an app for a given user (a 'grantee').

The granter is either:

  • a GOV.UK admin, meaning a user with the role "Superadmin" or "Admin"; or
  • a publishing manager, meaning a user with the role "Super organisation admin" or "Organisation admin"

Each row represents an app with certain permissions set as delegatable, as indicated by the "Delegatable permissions" column. The "Grant access" column indicates whether a granter can give a grantee the signin permission, and the "Revoke access" column relates to removing the signin permission.

Dependencies by route

Each "Dependencies by route" section provides a breakdown of which actions are determined by dependencies and a tree or trees of relevant dependencies. The aim of this section isn't to go all the way down every node of each dependency tree, but rather to provide enough context on what determines what you can do, and to make it easier to identify shared dependencies. There's a particular focus on the different policies that are hit by each route, since the policies are both fundamental to how everything works and the source of a lot of complexity.

For the current user (self)

In this section, the granter and grantee are the same user: this is about managing your own access and permissions.

What can you do?

As a GOV.UK admin

Delegatable permissions Grant access Revoke access Edit permissions View permissions
None
signin
Another permission

As a publishing manager

Delegatable permissions Grant access Revoke access Edit permissions View permissions
None
signin
Another permission ✅*

* only delegatable non-signin permissions

Dependencies by route

Account applications show

These dependencies determine whether a user can:

  • be redirected to the index
flowchart LR
    A(Account::ApplicationsController#show) --authorize [:account, Doorkeeper::Application]--> B(Account::ApplicationPolicy#show?)

Account applications index

These dependencies determine whether a user can:

  • access the page
  • see a link to grant access to an given app
  • see a link to revoke access to an given app
  • see a link to edit or view permissions for a given app
flowchart TD
    A(Account::ApplicationsController#index) --authorize [:account, Doorkeeper::Application]--> B(Account::ApplicationPolicy#index?)
    C(app/views/account/applications/index.html.erb) --wrap_links_in_actions_markup--> D(ApplicationTableHelper#wrap_links_in_actions_markup)
    C --account_applications_permissions_links--> E(ApplicationTableHelper#account_applications_permissions_links)
    E --policy([:account, application]).view_permissions?--> F(Account::ApplicationPolicy#view_permissions?)
    E --policy([:account, application]).edit_permissions?--> G(Account::ApplicationPolicy#edit_permissions?)
    C --account_applications_remove_access_link--> H(ApplicationTableHelper#account_applications_remove_access_link)
    H --policy([:account, application]).remove_signin_permission?--> I(Account::ApplicationPolicy#remove_signin_permission?)
    C --account_applications_grant_access_link--> J(ApplicationTableHelper#account_applications_grant_access_link)
    J --policy([:account, Doorkeeper::Application]).grant_signin_permission?--> K(Account::ApplicationPolicy#grant_signin_permission?)

Account permissions show

These dependencies determine whether a user can:

  • access the page
  • see a list of (all) permissions
flowchart TD
    A(Account::PermissionsController#show) --authorize [:account, @application], :view_permissions?--> B(Account::ApplicationPolicy#view_permissions?)
    A --@application.sorted_supported_permissions_grantable_from_ui--> C(Doorkeeper::Application#sorted_supported_permissions_grantable_from_ui)

Account permissions edit

These dependencies determine whether a user can:

  • access the page
  • manage (non-signin) permissions
flowchart TD
    A(Account::PermissionsController#edit) --@application.sorted_supported_permissions_grantable_from_ui( [...] )--> B(Doorkeeper::Application#sorted_supported_permissions_grantable_from_ui)
    A --authorize [:account, @application], :edit_permissions?--> C(Account::ApplicationPolicy#edit_permissions?)

Account permissions update

These dependencies determine whether a user can:

  • complete the controller action
  • update certain permissions
flowchart TD
    A(Account::PermissionsController#update) --@application.sorted_supported_permissions_grantable_from_ui( [...] )--> B(Doorkeeper::Application#sorted_supported_permissions_grantable_from_ui)
    A --authorize [:account, @application], :edit_permissions?--> C(Account::ApplicationPolicy#edit_permissions?)
    A --UserUpdatePermissionBuilder.new( [...] ).build--> D(UserUpdatePermissionBuilder#build)
    A --UserUpdate.new(current_user, { supported_permission_ids: }, current_user, user_ip_address).call--> E(UserUpdate#call)
    E --SupportedPermissionParameterFilter.new(current_user, user, user_params) [...] .filtered_supported_permission_ids--> F(SupportedPermissionParameterFilter#filtered_supported_permission_ids)
    D --Pundit.policy_scope(current_user, SupportedPermission)--> G("SupportedPermissionPolicy (scope)")

Account signin permissions create

These dependencies determine whether a user can:

  • complete the controller action
  • grant themself access to an app
flowchart TD
    A(Account::SigninPermissionsController#create)
    --authorize [:account, Doorkeeper::Application], :grant_signin_permission?
    --> B(Account::ApplicationPolicy#grant_signin_permission?)

    A --"UserUpdate.new(current_user, { supported_permission_ids: }, current_user, user_ip_address).call"
    --> C(UserUpdate#call)

    C --SupportedPermissionParameterFilter.new(current_user, user, user_params) [...] .filtered_supported_permission_ids
    --> D(SupportedPermissionParameterFilter#filtered_supported_permission_ids)
    D --Pundit.policy_scope(current_user, SupportedPermission)--> E("SupportedPermissionPolicy (scope)")

Account signin permissions delete

These dependencies determine whether a user can:

  • view the confirmation screen for revoking access to a given app
flowchart TD
    A(Account::SigninPermissionsController#delete)
    --authorize [:account, application], :remove_signin_permission?
    --> B(Account::ApplicationPolicy#remove_signin_permission?)

Account signin permissions destroy

These dependencies determine whether a user can:

  • complete the controller action
  • revoke access to a given app
flowchart TD
    A(Account::SigninPermissionsController#destroy)
    --authorize [:account, Doorkeeper::Application], :remove_signin_permission?
    --> B(Account::ApplicationPolicy#remove_signin_permission?)

    A --"UserUpdate.new(current_user, { supported_permission_ids: }, current_user, user_ip_address).call"
    --> C(UserUpdate#call)

    C --SupportedPermissionParameterFilter.new(current_user, user, user_params) [...] .filtered_supported_permission_ids
    --> D(SupportedPermissionParameterFilter#filtered_supported_permission_ids)
    D --Pundit.policy_scope(current_user, SupportedPermission)--> E("SupportedPermissionPolicy (scope)")

For another existing user

In this section, the granter and grantee are different users: this is about managing another user's access and permissions.

What can you do?

As a GOV.UK admin

With or without access to the app
Delegatable permissions Grant access Revoke access Edit permissions View permissions
None
signin
Another permission

As a publishing manager

With access to the app
Delegatable permissions Grant access Revoke access Edit permissions View permissions
None
signin
Another permission ✅*

* only delegatable non-signin permissions

Without access to the app
Delegatable permissions Grant access Revoke access Edit permissions View permissions
None
signin
Another permission

Dependencies by route

Users applications show

These dependencies determine whether a user can:

  • be redirected to the index
flowchart LR
    A(Users::ApplicationsController#show) --authorize [{ user: }], policy_class: Users::ApplicationPolicy--> B(Users::ApplicationPolicy#show?)
    B --Pundit.policy(current_user, user).edit?--> C(UserPolicy#edit?)

Users applications index

These dependencies determine whether a user can:

  • access the page
  • see a link to grant access to an given app
  • see a link to revoke access to an given app
  • see a link to edit or view permissions for a given app
flowchart TD
    A(Users::ApplicationsController#index) --authorize [{ user: }], policy_class: Users::ApplicationPolicy--> B(Users::ApplicationPolicy#index?)
    B --Pundit.policy(current_user, user).edit?--> C(UserPolicy#edit?)
    D(app/views/users/applications/index.html.erb) --wrap_links_in_actions_markup--> E(ApplicationTableHelper#wrap_links_in_actions_markup)
    D --users_applications_permissions_links--> F(ApplicationTableHelper#users_applications_permissions_links)
    F --Users::ApplicationPolicy.new(current_user, { application:, user: }).view_permissions?--> G(Users::ApplicationPolicy#view_permissions?)
    G --Pundit.policy(current_user, user).edit?--> H(UserPolicy#edit?)
    F --Users::ApplicationPolicy.new(current_user, { application:, user: }).edit_permissions?--> I(Users::ApplicationPolicy#edit_permissions?)
    I --Pundit.policy(current_user, user).edit?--> H
    D --users_applications_remove_access_link--> J(ApplicationTableHelper#users_applications_remove_access_link)
    J --Users::ApplicationPolicy.new(current_user, { application:, user: }).remove_signin_permission?--> K(Users::ApplicationPolicy#remove_signin_permission?)
    K --Pundit.policy(current_user, user).edit?--> H
    D --users_applications_grant_access_link--> L(ApplicationTableHelper#users_applications_grant_access_link)
    L --Users::ApplicationPolicy.new(current_user, { application:, user: }).grant_signin_permission?--> M(Users::ApplicationPolicy#grant_signin_permission?)
    M --Pundit.policy(current_user, user).edit?--> H

Users permissions show

These dependencies determine whether a user can:

  • access the page
  • see a list of (all) permissions
flowchart TD
    A(Users::PermissionsController#show) --authorize [{ application: @application, user: @user }], :view_permissions?, policy_class: Users::ApplicationPolicy--> B(Users::ApplicationPolicy#view_permissions?)
    B --Pundit.policy(current_user, user).edit?--> C(UserPolicy#edit?)
    A --@application.sorted_supported_permissions_grantable_from_ui--> D(Doorkeeper::Application#sorted_supported_permissions_grantable_from_ui)

Users permissions edit

These dependencies determine whether a user can:

  • access the page
  • manage (non-signin) permissions
flowchart TD
    A(Users::PermissionsController#edit) --@application.sorted_supported_permissions_grantable_from_ui( [...] )--> B(Doorkeeper::Application#sorted_supported_permissions_grantable_from_ui)
    A --authorize [{ application: @application, user: @user }], :edit_permissions?, policy_class: Users::ApplicationPolicy--> C(Users::ApplicationPolicy#edit_permissions?)
    C --Pundit.policy(current_user, user).edit?--> D(UserPolicy#edit?)

Users permissions update

These dependencies determine whether a user can:

  • complete the controller action
  • update certain permissions
flowchart TD
    A(Users::PermissionsController#update) --@application.sorted_supported_permissions_grantable_from_ui( [...] )--> B(Doorkeeper::Application#sorted_supported_permissions_grantable_from_ui)
    A --authorize [{ application: @application, user: @user }], :edit_permissions?, policy_class: Users::ApplicationPolicy--> C(Users::ApplicationPolicy#edit_permissions?)
    C --Pundit.policy(current_user, user).edit?--> D(UserPolicy#edit?)
    A --UserUpdatePermissionBuilder.new( [...] ).build--> E(UserUpdatePermissionBuilder#build)
    A --UserUpdate.new(@user, { supported_permission_ids: }, current_user, user_ip_address).call--> F(UserUpdate#call)
    F --SupportedPermissionParameterFilter.new(current_user, user, user_params) [...] .filtered_supported_permission_ids--> G(SupportedPermissionParameterFilter#filtered_supported_permission_ids)
    G --Pundit.policy_scope(current_user, SupportedPermission)--> H("SupportedPermissionPolicy (scope)")

Users signin permissions create

These dependencies determine whether a user can:

  • complete the controller action
  • grant a given user access to an app
flowchart TD
    A(Users::SigninPermissionsController#create)
    --authorize [application, user: @user], :grant_signin_permission?, policy_class: Users::ApplicationPolicy
    --> B(Users::ApplicationPolicy#grant_signin_permission?)

    A --"UserUpdate.new(@user, { supported_permission_ids: }, current_user, user_ip_address).call"
    --> C(UserUpdate#call)

    C --SupportedPermissionParameterFilter.new(current_user, user, user_params) [...] .filtered_supported_permission_ids
    --> D(SupportedPermissionParameterFilter#filtered_supported_permission_ids)
    D --Pundit.policy_scope(current_user, SupportedPermission)--> E("SupportedPermissionPolicy (scope)")

Users signin permissions delete

These dependencies determine whether a user can:

  • view the confirmation screen for revoking access to a given app
flowchart TD
    A(Users::SigninPermissionsController#delete)
    --authorize [application: @application, user: @user], :remove_signin_permission?, policy_class: Users::ApplicationPolicy
    --> B(Users::ApplicationPolicy#remove_signin_permission?)

Users signin permissions destroy

These dependencies determine whether a user can:

  • complete the controller action
  • revoke access to a given app
flowchart TD
    A(Users::SigninPermissionsController#destroy)
    --authorize [application, user: @user], :remove_signin_permission?, policy_class: Users::ApplicationPolicy
    --> B(Users::ApplicationPolicy#remove_signin_permission?)

    A --"UserUpdate.new(@user, { supported_permission_ids: }, current_user, user_ip_address).call"
    --> C(UserUpdate#call)

    C --SupportedPermissionParameterFilter.new(current_user, user, user_params) [...] .filtered_supported_permission_ids
    --> D(SupportedPermissionParameterFilter#filtered_supported_permission_ids)
    D --Pundit.policy_scope(current_user, SupportedPermission)--> E("SupportedPermissionPolicy (scope)")

For a new user

What can you do?

The following actions are taken in the process of inviting a new user - once the invitation is sent, refer to the For another existing user section.

As a GOV.UK admin

Send invitation Grant access Edit permissions

As a publishing manager

Send invitation Grant access Edit permissions

Dependencies by route

Invitations new

These dependencies determine whether a user can:

  • access the page
  • see certain apps to which to grant the new user access
  • see certain permissions for those apps to grant the new user
flowchart TD
    A(InvitationsController#new) --authorize User--> B(UserPolicy#new?)
    C(app/views/devise/invitations/new.html.erb) --"options_for_permission_option_select(application:, user: f.object)"--> D(UsersHelper#options_for_permission_option_select)
    D --application.sorted_supported_permissions_grantable_from_ui--> E(Doorkeeper::Application#sorted_supported_permissions_grantable_from_ui)

Invitations create

These dependencies determine whether a user can:

  • complete the controller action
flowchart TD
    A(InvitationsController#create) --authorize User--> B(UserPolicy#create?)

Important files

Models

Class Relevant associations
Doorkeeper::Application Has many supported permissions
SupportedPermission Belongs to an app
UserApplicationPermission Joins users with supported permissions (and apps), determining what individual users can do

Controllers

Class Responsibility
Account::ApplicationsController Rendering index and individual pages for own app permissions
Account::PermissionsController Managing own permissions
Account::SigninPermissionsController Managing own access to apps
InvitationsController Giving users access to apps and permissions while creating their account
Users::ApplicationsController Rendering index and individual pages for other users' app permissions
Users::PermissionsController Managing other users' permissions
Users::SigninPermissionsController Managing other users' access to apps

Policies

Class Responsibility
Account::ApplicationPolicy Determining whether granters can see and update their own access and permissions
SupportedPermissionPolicy Determining which permissions can be updated by a given granter
UserPolicy Determining whether a granter can update a grantee's access and permissions*, and whether they can invite a new user and grant them access and permissions in the process
Users::ApplicationPolicy Determining whether a granter can see and update a grantee's access and permissions*

* the responsibility of these two policies is hard to distinguish in this context, but as seen in the dependency trees for existing users, the Users::ApplicationPolicy depends on the UserPolicy, and in reality the latter is larger in scope. The InvitationsController depends on different parts of the UserPolicy, for instance.

Others

Class Responsibility
ApplicationTableHelper Generating links to view/manage access and permissions dependent on policy-based authorisation
SupportedPermissionParameterFilter Ensuring granters can only change permissions they're authorised to manage
UserUpdate Updating a user's permissions
UserUpdatePermissionBuilder Generating a list of updated permissions
UsersHelper Generating a list of permissions that can be granted when creating a new user