Completed
Push — master ( e81144...a25954 )
by Robbie
15s queued 15s
created

BackupCode::getTokensForMember()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Firesphere\BootstrapMFA\Models;
4
5
use Firesphere\BootstrapMFA\Generators\CodeGenerator;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Control\Director;
8
use SilverStripe\Control\Email\Email;
9
use SilverStripe\Core\Config\Config;
10
use SilverStripe\Core\Injector\Injector;
11
use SilverStripe\ORM\DataList;
12
use SilverStripe\ORM\DataObject;
13
use SilverStripe\Security\Member;
14
use SilverStripe\Security\Security;
15
16
/**
17
 * Class BackupCode
18
 *
19
 * @property string $Code
20
 * @property boolean $Used
21
 * @property int $MemberID
22
 * @method Member Member()
23
 */
24
class BackupCode extends DataObject
25
{
26
    /**
27
     * @var string
28
     */
29
    private static $table_name = 'BackupCode';
0 ignored issues
show
introduced by
The private property $table_name is not used, and could be removed.
Loading history...
30
31
    /**
32
     * @var array
33
     */
34
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
35
        'Code' => 'Varchar(255)',
36
        'Used' => 'Boolean(false)'
37
    ];
38
39
    /**
40
     * @var array
41
     */
42
    private static $has_one = [
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
43
        'Member' => Member::class
44
    ];
45
46
    /**
47
     * @var array
48
     */
49
    private static $indexes = [
0 ignored issues
show
introduced by
The private property $indexes is not used, and could be removed.
Loading history...
50
        'Code' => [
51
            'type'    => 'unique',
52
            'columns' => ['MemberID', 'Code'],
53
        ],
54
    ];
55
56
    /**
57
     * @param Member $member
58
     * @return DataList|static[]
59
     */
60
    public static function getTokensForMember($member)
61
    {
62
        return static::get()->filter(['MemberID' => $member->ID]);
63
    }
64
65
    /**
66
     * @param Member $member
67
     * @throws \SilverStripe\ORM\ValidationException
68
     */
69
    public static function generateTokensForMember($member)
70
    {
71
        if (Security::getCurrentUser() && (int)Security::getCurrentUser()->ID !== $member->ID) {
72
            self::sendWarningEmail($member);
73
        } else {
74
            $message = _t(
75
                self::class . '.SESSIONMESSAGE_START',
76
                '<p>Here are your tokens, please store them securily. ' .
77
                'They are stored hashed and can not be recovered, only reset.</p><p>'
78
            );
79
            $session = Controller::curr()->getRequest()->getSession();
80
            $limit = static::config()->get('token_limit');
81
            for ($i = 0; $i < $limit; ++$i) {
82
                $token = self::createCode($member);
83
                $message .= sprintf('%s<br />', $token);
84
            }
85
            $message .= '</p>';
86
            $session->set('tokens', $message);
87
        }
88
    }
89
90
    /**
91
     * @param $member
92
     */
93
    public static function sendWarningEmail($member)
94
    {
95
        /** @var Email $mail */
96
        $mail = Email::create();
97
        $mail->setTo($member->Email);
98
        $mail->setFrom(Config::inst()->get(Email::class, 'admin_email'));
99
        $mail->setSubject(_t(self::class . '.REGENERATIONMAIL', 'Your backup tokens need to be regenerated'));
100
        $mail->setBody(
101
            _t(
102
                self::class . '.REGENERATIONREQUIRED',
103
                "<p>Your backup codes for multi factor authentication have been requested to regenerate by someone "
104
                . "that is not you. \n"
105
                . "Please visit the <a href='{url}/{segment}'>website to regenerate your backupcodes</a></p>",
106
                [
107
                    'url'     => Director::absoluteBaseURL(),
108
                    'segment' => Security::config()->get('lost_password_url')
109
                ]
110
            )
111
        );
112
        $mail->send();
113
    }
114
115
    /**
116
     * @param $member
117
     * @return string
118
     * @throws \SilverStripe\ORM\ValidationException
119
     */
120
    private static function createCode($member)
121
    {
122
        $code = static::create([
123
            'MemberID' => $member->ID
124
        ]);
125
        $token = $code->Code;
126
        $code->write();
127
        $code->destroy();
128
129
        return $token;
130
    }
131
132
    /**
133
     * @return DataObject
134
     * @throws \Psr\Container\NotFoundExceptionInterface
135
     */
136
    public function populateDefaults()
137
    {
138
        $this->Code = $this->generateToken();
139
140
        return parent::populateDefaults();
141
    }
142
143
    /**
144
     * @return mixed
145
     * @throws \Psr\Container\NotFoundExceptionInterface
146
     */
147
    protected function generateToken()
148
    {
149
        $config = Config::inst()->get(CodeGenerator::class);
150
        /** @var CodeGenerator $generator */
151
        $generator = Injector::inst()->get(CodeGenerator::class)
152
            ->setLength($config['length']);
153
        switch ($config['type']) {
154
            case 'mixed':
155
                $generator->alphanumeric();
156
                break;
157
            case 'numeric':
158
                $generator->numbersonly();
159
                break;
160
            case 'characters':
161
                $generator->charactersonly();
162
                break;
163
            default:
164
                $generator->numbersonly();
165
        }
166
        switch ($config['case']) {
167
            case 'upper':
168
                $generator->uppercase();
169
                break;
170
            case 'lower':
171
                $generator->lowercase();
172
                break;
173
            case 'mixed':
174
                $generator->mixedcase();
175
                break;
176
            default:
177
                $generator->mixedcase();
178
        }
179
180
        return $generator->generate();
181
    }
182
183
    /**
184
     * @throws \SilverStripe\Security\PasswordEncryptor_NotFoundException
185
     */
186
    public function onBeforeWrite()
187
    {
188
        parent::onBeforeWrite();
189
        // Encrypt a new temporary key before writing to the database
190
        if (!$this->Used) {
191
            $hashingMethod = Security::config()->get('password_encryption_algorithm');
192
            $hashed = Security::encrypt_password($this->Code, $this->Member()->BackupCodeSalt, $hashingMethod);
193
            $this->Code = $hashed['password'];
194
        }
195
    }
196
197
    /**
198
     * @return $this
199
     * @throws \SilverStripe\ORM\ValidationException
200
     */
201
    public function expire()
202
    {
203
        $this->Used = true;
204
        $this->write();
205
206
        return $this;
207
    }
208
209
    /**
210
     * @param null|Member $member
211
     * @return bool
212
     */
213
    public function canEdit($member = null)
214
    {
215
        return false;
216
    }
217
}
218