Passed
Pull Request — master (#97)
by Robbie
02:15
created

EnforcementManager::canSkipMFA()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace SilverStripe\MFA\Service;
4
5
use SilverStripe\Core\Config\Config;
6
use SilverStripe\Core\Injector\Injectable;
7
use SilverStripe\MFA\Extension\MemberExtension;
8
use SilverStripe\MFA\Extension\SiteConfigExtension;
9
use SilverStripe\ORM\FieldType\DBDate;
10
use SilverStripe\Security\Member;
11
use SilverStripe\SiteConfig\SiteConfig;
12
13
/**
14
 * The EnforcementManager class is responsible for making decisions regarding multi factor authentication app flow,
15
 * e.g. "is MFA required", "should we redirect to the MFA section", "can the user skip MFA registration" etc.
16
 */
17
class EnforcementManager
18
{
19
    use Injectable;
20
21
    /**
22
     * Whether the current member can skip the multi factor authentication registration process.
23
     *
24
     * This is determined by a combination of:
25
     *  - Whether MFA is required or optional
26
     *  - If MFA is required, whether there is a grace period
27
     *  - If MFA is required and there is a grace period, whether we're currently within that timeframe
28
     *
29
     * @param Member&MemberExtension $member
30
     * @return bool
31
     */
32
    public function canSkipMFA(Member $member): bool
33
    {
34
        if ($this->isMFARequired()) {
35
            return false;
36
        }
37
38
        // If they've already registered MFA methods we will not allow them to skip the authentication process
39
        $registeredMethods = $member->RegisteredMFAMethods();
40
        if ($registeredMethods->exists()) {
41
            return false;
42
        }
43
44
        // MFA is optional, or is required but might be within a grace period (see isMFARequired)
45
        return true;
46
    }
47
48
    /**
49
     * Whether the authentication process should redirect the user to multi factor authentication registration or
50
     * login.
51
     *
52
     * This is determined by a combination of:
53
     *  - Whether MFA is enabled
54
     *  - Whether MFA is required or optional
55
     *  - Whether the user has registered MFA methods already
56
     *  - If the user doesn't have any registered MFA methods already, and MFA is optional, whether the user has opted
57
     *    to skip the registration process
58
     *
59
     * Note that in determining this, we ignore whether or not MFA is enabled for the site in general.
60
     *
61
     * @param Member&MemberExtension $member
62
     * @return bool
63
     */
64
    public function shouldRedirectToMFA(Member $member): bool
65
    {
66
        if (!$this->isMFAEnabled()) {
67
            return false;
68
        }
69
70
        if ($this->isMFARequired()) {
71
            return true;
72
        }
73
74
        if ($this->isGracePeriodInEffect()) {
75
            return true;
76
        }
77
78
        if (!$member->HasSkippedMFARegistration) {
79
            return true;
80
        }
81
82
        return false;
83
    }
84
85
    /**
86
     * Check if the provided member has registered the required MFA methods. This includes a "back-up" method set in
87
     * configuration plus at least one other method.
88
     * Note that this method returns true if there is no backup method registered (and they have one other method
89
     *
90
     * @param Member&MemberExtension $member
91
     * @return bool
92
     */
93
    public function hasCompletedRegistration(Member $member): bool
94
    {
95
        $methodCount = $member->RegisteredMFAMethods()->count();
96
97
        $backupMethod = Config::inst()->get(MethodRegistry::class, 'default_backup_method');
98
        if (!$backupMethod) {
99
            // Ensure they have at least one method
100
            return $methodCount > 0;
101
        }
102
103
        // Ensure they have the required backup method and at least 2 methods (the backup method plus one other)
104
        return ((bool) $member->RegisteredMFAMethods()->find('MethodClassName', $backupMethod)) && $methodCount > 1;
105
    }
106
107
    /**
108
     * Whether multi factor authentication is required for site members. This also takes into account whether a
109
     * grace period is set and whether we're currently inside the window for it.
110
     *
111
     * Note that in determining this, we ignore whether or not MFA is enabled for the site in general.
112
     *
113
     * @return bool
114
     */
115
    public function isMFARequired(): bool
116
    {
117
        $siteConfig = SiteConfig::current_site_config();
118
119
        $isRequired = $siteConfig->MFARequired;
120
        if (!$isRequired) {
121
            return false;
122
        }
123
124
        $gracePeriod = $siteConfig->MFAGracePeriodExpires;
125
        if ($isRequired && !$gracePeriod) {
126
            return true;
127
        }
128
129
        /** @var DBDate $gracePeriodDate */
130
        $gracePeriodDate = $siteConfig->dbObject('MFAGracePeriodExpires');
131
        if ($isRequired && $gracePeriodDate->InPast()) {
132
            return true;
133
        }
134
135
        // MFA is required, a grace period is set, and it's in the future
136
        return false;
137
    }
138
139
    /**
140
     * Whether multi factor authentication is enabled
141
     *
142
     * @return bool
143
     */
144
    public function isMFAEnabled(): bool
145
    {
146
        return (bool) SiteConfig::current_site_config()->MFAEnabled;
147
    }
148
149
    /**
150
     * Specifically determines whether the MFA Grace Period is currently active.
151
     *
152
     * @return bool
153
     */
154
    public function isGracePeriodInEffect(): bool
155
    {
156
        /** @var SiteConfig&SiteConfigExtension $siteConfig */
157
        $siteConfig = SiteConfig::current_site_config();
158
159
        $isRequired = $siteConfig->MFARequired;
160
        if (!$isRequired) {
161
            return false;
162
        }
163
164
        $gracePeriod = $siteConfig->MFAGracePeriodExpires;
165
        if (!$gracePeriod) {
166
            return false;
167
        }
168
169
        /** @var DBDate $gracePeriodDate */
170
        $gracePeriodDate = $siteConfig->dbObject('MFAGracePeriodExpires');
171
        if ($gracePeriodDate->InPast()) {
172
            return false;
173
        }
174
175
        return true;
176
    }
177
}
178