Add ability to require 2FA for specific roles (including Everybody) (#37701)
This commit is contained in:
1 parent
3e1127d27b
commit
dfe44bcaef
23 files changed
+184
-15
No files matched your search
@@ -62,7 +62,7 @@ module Admin
|
||||
|
||||
def resource_params
|
||||
params
|
||||
.expect(user_role: [:name, :color, :highlighted, :position, permissions_as_keys: []])
|
||||
.expect(user_role: [:name, :color, :highlighted, :position, :require_2fa, permissions_as_keys: []])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -61,19 +61,25 @@ class ApplicationController < ActionController::Base
|
||||
return if request.referer.blank?
|
||||
|
||||
redirect_uri = URI(request.referer)
|
||||
return if redirect_uri.path.start_with?('/auth')
|
||||
return if redirect_uri.path.start_with?('/auth', '/settings/two_factor_authentication', '/settings/otp_authentication')
|
||||
|
||||
stored_url = redirect_uri.to_s if redirect_uri.host == request.host && redirect_uri.port == request.port
|
||||
|
||||
store_location_for(:user, stored_url)
|
||||
end
|
||||
|
||||
def mfa_setup_path(path_params = {})
|
||||
settings_two_factor_authentication_methods_path(path_params)
|
||||
end
|
||||
|
||||
def require_functional!
|
||||
return if current_user.functional?
|
||||
|
||||
respond_to do |format|
|
||||
format.any do
|
||||
if current_user.confirmed?
|
||||
if current_user.missing_2fa?
|
||||
redirect_to mfa_setup_path
|
||||
elsif current_user.confirmed?
|
||||
redirect_to edit_user_registration_path
|
||||
else
|
||||
redirect_to auth_setup_path
|
||||
@@ -85,6 +91,8 @@ class ApplicationController < ActionController::Base
|
||||
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
|
||||
elsif !current_user.approved?
|
||||
render json: { error: 'Your login is currently pending approval' }, status: 403
|
||||
elsif current_user.missing_2fa?
|
||||
render json: { error: 'Your account requires two-factor authentication' }, status: 403
|
||||
elsif !current_user.functional?
|
||||
render json: { error: 'Your login is currently disabled' }, status: 403
|
||||
end
|
||||
|
||||
@@ -42,7 +42,7 @@ module ChallengableConcern
|
||||
end
|
||||
|
||||
def render_challenge
|
||||
render 'auth/challenges/new', layout: 'auth'
|
||||
render 'auth/challenges/new', layout: params[:oauth] ? 'modal' : 'auth'
|
||||
end
|
||||
|
||||
def challenge_passed?
|
||||
|
||||
@@ -24,4 +24,8 @@ class OAuth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
||||
def truthy_param?(key)
|
||||
ActiveModel::Type::Boolean.new.cast(params[key])
|
||||
end
|
||||
|
||||
def mfa_setup_path
|
||||
super({ oauth: true })
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Settings
|
||||
module TwoFactorAuthentication
|
||||
class BaseController < ::Settings::BaseController
|
||||
layout -> { truthy_param?(:oauth) ? 'modal' : 'admin' }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,12 +4,15 @@ module Settings
|
||||
module TwoFactorAuthentication
|
||||
class ConfirmationsController < BaseController
|
||||
include ChallengableConcern
|
||||
include Devise::Controllers::StoreLocation
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :require_challenge!
|
||||
before_action :ensure_otp_secret
|
||||
|
||||
helper_method :return_to_app_url
|
||||
|
||||
def new
|
||||
prepare_two_factor_form
|
||||
end
|
||||
@@ -37,6 +40,10 @@ module Settings
|
||||
|
||||
private
|
||||
|
||||
def return_to_app_url
|
||||
stored_location_for(:user)
|
||||
end
|
||||
|
||||
def confirmation_params
|
||||
params.expect(form_two_factor_confirmation: [:otp_attempt])
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ module Settings
|
||||
def create
|
||||
session[:new_otp_secret] = User.generate_otp_secret
|
||||
|
||||
redirect_to new_settings_two_factor_authentication_confirmation_path
|
||||
redirect_to new_settings_two_factor_authentication_confirmation_path(params.permit(:oauth))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -22,7 +22,7 @@ module Settings
|
||||
private
|
||||
|
||||
def require_otp_enabled
|
||||
redirect_to settings_otp_authentication_path unless current_user.otp_enabled?
|
||||
redirect_to settings_otp_authentication_path(params.permit(:oauth)) unless current_user.otp_enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
+5
-1
@@ -226,7 +226,11 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def functional_or_moved?
|
||||
confirmed? && approved? && !disabled? && !account.unavailable? && !account.memorial?
|
||||
confirmed? && approved? && !disabled? && !account.unavailable? && !account.memorial? && !missing_2fa?
|
||||
end
|
||||
|
||||
def missing_2fa?
|
||||
!two_factor_enabled? && role.require_2fa?
|
||||
end
|
||||
|
||||
def unconfirmed_or_pending?
|
||||
|
||||
+10
-4
@@ -5,11 +5,12 @@
|
||||
# Table name: user_roles
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# name :string default(""), not null
|
||||
# color :string default(""), not null
|
||||
# position :integer default(0), not null
|
||||
# permissions :bigint(8) default(0), not null
|
||||
# highlighted :boolean default(FALSE), not null
|
||||
# name :string default(""), not null
|
||||
# permissions :bigint(8) default(0), not null
|
||||
# position :integer default(0), not null
|
||||
# require_2fa :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
@@ -160,7 +161,7 @@ class UserRole < ApplicationRecord
|
||||
@computed_permissions ||= begin
|
||||
permissions = self.class.everyone.permissions | self.permissions
|
||||
|
||||
if permissions & FLAGS[:administrator] == FLAGS[:administrator]
|
||||
if administrator?
|
||||
Flags::ALL
|
||||
else
|
||||
permissions
|
||||
@@ -172,6 +173,10 @@ class UserRole < ApplicationRecord
|
||||
name
|
||||
end
|
||||
|
||||
def administrator?
|
||||
permissions & FLAGS[:administrator] == FLAGS[:administrator]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def in_permissions?(privilege)
|
||||
@@ -189,6 +194,7 @@ class UserRole < ApplicationRecord
|
||||
|
||||
errors.add(:permissions_as_keys, :own_role) if permissions_changed?
|
||||
errors.add(:position, :own_role) if position_changed?
|
||||
errors.add(:require_2fa, :own_role) if require_2fa_changed? && !administrator?
|
||||
end
|
||||
|
||||
def validate_permissions_elevation
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
= form.input :highlighted,
|
||||
wrapper: :with_label
|
||||
|
||||
- if current_user.role.administrator? || current_user.role != form.object
|
||||
.fields-group
|
||||
= form.input :require_2fa,
|
||||
wrapper: :with_label
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
- unless current_user.role == form.object
|
||||
|
||||
@@ -21,9 +21,13 @@
|
||||
.announcements-list__item__meta
|
||||
- if role.everyone?
|
||||
= t('admin.roles.everyone_full_description_html')
|
||||
= t('admin.roles.requires_2fa') if role.require_2fa?
|
||||
- else
|
||||
= link_to t('admin.roles.assigned_users', count: role.users.count), admin_accounts_path(role_ids: role.id)
|
||||
·
|
||||
%abbr{ title: role.permissions_as_keys.map { |privilege| I18n.t("admin.roles.privileges.#{privilege}") }.join(', ') }= t('admin.roles.permissions_count', count: role.permissions_as_keys.size)
|
||||
- if role.require_2fa?
|
||||
·
|
||||
= t('admin.roles.requires_2fa')
|
||||
.announcements-list__item__actions
|
||||
= table_link_to 'edit', t('admin.accounts.edit'), edit_admin_role_path(role) if can?(:update, role)
|
||||
@@ -12,6 +12,8 @@
|
||||
%samp.qr-alternative__code= @new_otp_secret.scan(/.{4}/).join(' ')
|
||||
|
||||
.fields-group
|
||||
= hidden_field_tag :oauth, params[:oauth]
|
||||
|
||||
= f.input :otp_attempt,
|
||||
hint: t('otp_authentication.code_hint'),
|
||||
input_html: { autocomplete: 'off' },
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
- content_for :page_title do
|
||||
= t('settings.two_factor_authentication')
|
||||
|
||||
- if current_user.role.require_2fa?
|
||||
.flash-message= t('two_factor_authentication.role_requirement', domain: site_hostname)
|
||||
|
||||
.simple_form
|
||||
%p.hint= t('otp_authentication.description_html')
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
= link_to t('otp_authentication.setup'), settings_otp_authentication_path, data: { method: :post }, class: 'button button--block'
|
||||
= link_to t('otp_authentication.setup'), settings_otp_authentication_path(params.permit(:oauth)), data: { method: :post }, class: 'button button--block'
|
||||
@@ -7,3 +7,9 @@
|
||||
- @recovery_codes.each do |code|
|
||||
%li<
|
||||
%samp= code
|
||||
|
||||
- if params[:oauth]
|
||||
%hr.spacer/
|
||||
|
||||
.simple_form
|
||||
= link_to t('two_factor_authentication.resume_app_authorization'), return_to_app_url, class: 'button button--block'
|
||||
@@ -2,7 +2,10 @@
|
||||
= t('settings.two_factor_authentication')
|
||||
|
||||
- content_for :heading_actions do
|
||||
= link_to t('two_factor_authentication.disable'), disable_settings_two_factor_authentication_methods_path, class: 'button button--destructive', method: :post
|
||||
= link_to t('two_factor_authentication.disable'), disable_settings_two_factor_authentication_methods_path, class: 'button button--destructive', method: :post unless current_user.role.require_2fa?
|
||||
|
||||
- if current_user.role.require_2fa?
|
||||
.flash-message= t('two_factor_authentication.role_requirement', domain: site_hostname)
|
||||
|
||||
%p.hint
|
||||
%span.positive-hint
|
||||
|
||||
Reference in new issue
Block a user