Source of file TwoFactorAuthMemberExtension.php
Size: 4,840 Bytes - Last Modified: 2021-12-23T10:06:51+00:00
/var/www/docs.ssmods.com/process/src/code/extensions/TwoFactorAuthMemberExtension.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 | <?php namespace _2fa\Extensions; use Rych\OTP\TOTP; use Rych\OTP\Seed; use Endroid\QrCode\QrCode; /** * @property \Member $owner * @property bool $Has2FA * @property string $TOTPToken * * @method BackupToken BackupTokens() */ class TwoFactorAuthMemberExtension extends \DataExtension { private static $db = array( 'Has2FA' => 'Boolean', 'TOTPToken' => 'Varchar(160)', ); private static $has_many = array( 'BackupTokens' => '_2fa\BackupToken', ); private static $admins_can_disable = false; private static $validated_activation_mode = false; private static $totp_window = 2; public static function validated_activation_mode() { return \Config::inst()->get(__CLASS__, 'validated_activation_mode'); } public function validateTOTP($token) { assert(is_string($token)); if (!$this->owner->Has2FA) { return true; } $seed = $this->OTPSeed(); if (!$seed) { return true; } $window = (int) \Config::inst()->get(__CLASS__, 'totp_window'); $totp = new TOTP($seed, array('window' => $window)); $valid = $totp->validate($token); // Check backup tokens if unsuccessful if (!$valid) { $backup_tokens = $this->owner->BackupTokens()->filter('Value', $token); if ($backup_tokens->count()) { $candidate_backup_token = $backup_tokens->first(); if ($token === $candidate_backup_token->Value) { $valid = true; // Backup tokens should be unique; // ensure any duplicates are deleted when successfully used foreach ($backup_tokens as $bt) { $bt->delete(); } } } } return $valid; } public function getPrintableTOTPToken() { $seed = $this->OTPSeed(); return $seed ? $seed->getValue(Seed::FORMAT_BASE32) : ''; } public function OTPSeed() { if ($this->owner->TOTPToken) { return new Seed($this->owner->TOTPToken); } return; } /** * Allow other admins to turn off 2FA if it is set & admins_can_disable is set in the config. * 2FA in general is managed in the user's own profile. * * @param \FieldList $fields */ public function updateCMSFields(\FieldList $fields) { // Generate default token (allows scanning the QR at the moment of activation and (optionally) validate before activating 2FA) if(!$this->owner->TOTPToken && self::validated_activation_mode()) { $this->generateTOTPToken(); $this->owner->write(); } $fields->removeByName('TOTPToken'); $fields->removeByName('BackupTokens'); if (!(\Config::inst()->get(__CLASS__, 'admins_can_disable') && $this->owner->Has2FA && \Permission::check('ADMIN'))) { $fields->removeByName('Has2FA'); } } public function updateFieldLabels(&$labels) { $labels['Has2FA'] = 'Enable Two Factor Authentication'; } public function generateTOTPToken($bytes = 20) { $seed = Seed::generate($bytes); $this->owner->TOTPToken = $seed->getValue(Seed::FORMAT_HEX); } /** * Delete a member's backup tokens when deleting the member. */ public function onBeforeDelete() { foreach ($this->owner->BackupTokens() as $bt) { $bt->delete(); } parent::onBeforeDelete(); } public function onBeforeWrite() { // regenerate token if Has2FA activated and not in validated_activation_mode if ($this->owner->isChanged('Has2FA', 2) && $this->owner->Has2FA && !self::validated_activation_mode()) { $this->generateTOTPToken(); } } public function getOTPUrl() { if (class_exists('SiteConfig')) { $config = \SiteConfig::current_site_config(); $issuer = $config->Title; } else { $issuer = explode(':', $_SERVER['HTTP_HOST']); $issuer = $issuer[0]; } $label = sprintf('%s: %s', $issuer, $this->owner->Name); return sprintf( 'otpauth://totp/%s?secret=%s&issuer=%s', rawurlencode($label), $this->getPrintableTOTPToken(), rawurlencode($issuer) ); } public function generateQRCode() { $qrCode = new QrCode(); $qrCode->setText($this->getOTPUrl()); $qrCode->setSize(175); $qrCode->setPadding(10); $data = $qrCode->get(QrCode::IMAGE_TYPE_GIF); $data = base64_encode($data); return sprintf('data:image/gif;base64,%s', $data); } } |