Completed
Push — master ( fa0cb8...7d06ec )
by Robbie
15s queued 11s
created

EnforcementManager::isMFAEnabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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