demarches-normaliennes/app/models/super_admin.rb
2024-08-22 09:26:48 +02:00

118 lines
4.4 KiB
Ruby

# frozen_string_literal: true
class SuperAdmin < ApplicationRecord
include PasswordComplexityConcern
devise :rememberable, :trackable, :validatable, :lockable, :recoverable
if SUPER_ADMIN_OTP_ENABLED
devise :two_factor_authenticatable, otp_secret_encryption_key: Rails.application.secrets.otp_secret_key, sign_in_after_reset_password: false
else
devise :database_authenticatable
end
def enable_otp!
self.otp_secret = SuperAdmin.generate_otp_secret
self.otp_required_for_login = true
save!
end
def disable_otp!
self.assign_attributes(
{
otp_secret: nil,
encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil,
consumed_timestep: nil,
otp_required_for_login: false
}
)
save!
end
def invite_admin(email)
user = User.create_or_promote_to_administrateur(email, SecureRandom.hex)
if user.valid?
user.invite_administrateur!
Procedure.create_initiation_procedure(user.administrateur)
end
user
end
def send_devise_notification(notification, *args)
devise_mailer.send(notification, self, *args).deliver_later
end
private
# From https://github.com/tinfoil/devise-two-factor/blob/main/UPGRADING.md
# Remove me after super admin have been migrated to the new OTP system.
# Decrypt and return the `encrypted_otp_secret` attribute which was used in
# prior versions of devise-two-factor
# @return [String] The decrypted OTP secret
def legacy_otp_secret
return nil unless self[:encrypted_otp_secret]
return nil unless self.class.otp_secret_encryption_key
hmac_iterations = 2000 # a default set by the Encryptor gem
key = self.class.otp_secret_encryption_key
salt = Base64.decode64(encrypted_otp_secret_salt)
iv = Base64.decode64(encrypted_otp_secret_iv)
raw_cipher_text = Base64.decode64(encrypted_otp_secret)
# The last 16 bytes of the ciphertext are the authentication tag - we use
# Galois Counter Mode which is an authenticated encryption mode
cipher_text = raw_cipher_text[0..-17]
auth_tag = raw_cipher_text[-16..-1]
# this alrorithm lifted from
# https://github.com/attr-encrypted/encryptor/blob/master/lib/encryptor.rb#L54
# create an OpenSSL object which will decrypt the AES cipher with 256 bit
# keys in Galois Counter Mode (GCM). See
# https://ruby.github.io/openssl/OpenSSL/Cipher.html
cipher = OpenSSL::Cipher.new('aes-256-gcm')
# tell the cipher we want to decrypt. Symmetric algorithms use a very
# similar process for encryption and decryption, hence the same object can
# do both.
cipher.decrypt
# Use a Password-Based Key Derivation Function to generate the key actually
# used for encryptoin from the key we got as input.
cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(key, salt, hmac_iterations, cipher.key_len)
# set the Initialization Vector (IV)
cipher.iv = iv
# The tag must be set after calling Cipher#decrypt, Cipher#key= and
# Cipher#iv=, but before calling Cipher#final. After all decryption is
# performed, the tag is verified automatically in the call to Cipher#final.
#
# If the auth_tag does not verify, then #final will raise OpenSSL::Cipher::CipherError
cipher.auth_tag = auth_tag
# auth_data must be set after auth_tag has been set when decrypting See
# http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html#method-i-auth_data-3D
# we are not adding any authenticated data but OpenSSL docs say this should
# still be called.
cipher.auth_data = ''
# #update is (somewhat confusingly named) the method which actually
# performs the decryption on the given chunk of data. Our OTP secret is
# short so we only need to call it once.
#
# It is very important that we call #final because:
#
# 1. The authentication tag is checked during the call to #final
# 2. Block based cipher modes (e.g. CBC) work on fixed size chunks. We need
# to call #final to get it to process the last chunk properly. The output
# of #final should be appended to the decrypted value. This isn't
# required for streaming cipher modes but including it is a best practice
# so that your code will continue to function correctly even if you later
# change to a block cipher mode.
cipher.update(cipher_text) + cipher.final
end
end