Passed
Push — master ( 7e81b0...7eb007 )
by Robbie
12:39 queued 11s
created

src/Extension/MemberExtension.php (1 issue)

calls to non-existent methods.

Bug Major
1
<?php declare(strict_types=1);
2
3
namespace SilverStripe\MFA\Extension;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Forms\FieldList;
7
use SilverStripe\MFA\Authenticator\ChangePasswordHandler;
8
use SilverStripe\MFA\Exception\InvalidMethodException;
9
use SilverStripe\MFA\FormField\RegisteredMFAMethodListField;
10
use SilverStripe\MFA\Method\MethodInterface;
11
use SilverStripe\MFA\Model\RegisteredMethod;
12
use SilverStripe\ORM\DataExtension;
13
use SilverStripe\ORM\HasManyList;
14
use SilverStripe\Security\Member;
15
use SilverStripe\Security\Permission;
16
use SilverStripe\Security\PermissionProvider;
17
use SilverStripe\Security\Security;
18
19
/**
20
 * Extend Member to add relationship to registered methods and track some specific preferences
21
 *
22
 * @method RegisteredMethod[]|HasManyList RegisteredMFAMethods
23
 * @property MethodInterface DefaultRegisteredMethod
24
 * @property string DefaultRegisteredMethodID
25
 * @property bool HasSkippedMFARegistration
26
 * @property Member|MemberExtension owner
27
 */
28
class MemberExtension extends DataExtension implements PermissionProvider
29
{
30
    const MFA_ADMINISTER_REGISTERED_METHODS = 'MFA_ADMINISTER_REGISTERED_METHODS';
31
32
    private static $has_many = [
33
        'RegisteredMFAMethods' => RegisteredMethod::class,
34
    ];
35
36
    private static $db = [
37
        'DefaultRegisteredMethodID' => 'Int',
38
        'HasSkippedMFARegistration' => 'Boolean',
39
    ];
40
41
    /**
42
     * Accessor for the `DefaultRegisteredMethod` property.
43
     *
44
     * This is replicating the usual functionality of a has_one relation but does it like this so we can ensure the same
45
     * instance of the MethodInterface is provided regardless if you access it through the has_one or the has_many.
46
     *
47
     * @return RegisteredMethod|null
48
     */
49
    public function getDefaultRegisteredMethod(): ?RegisteredMethod
50
    {
51
        return $this->owner->RegisteredMFAMethods()->byId($this->owner->DefaultRegisteredMethodID);
52
    }
53
54
    /**
55
     * Set the default registered method for the current member. Does not write the owner record.
56
     *
57
     * @param RegisteredMethod $registeredMethod
58
     * @return Member
59
     * @throws InvalidMethodException
60
     */
61
    public function setDefaultRegisteredMethod(RegisteredMethod $registeredMethod): Member
62
    {
63
        if ($registeredMethod->Member()->ID != $this->owner->ID) {
64
            throw new InvalidMethodException('The provided method does not belong to this member');
65
        }
66
        $this->owner->DefaultRegisteredMethodID = $registeredMethod->ID;
67
        return $this->owner;
68
    }
69
70
    public function updateCMSFields(FieldList $fields): FieldList
71
    {
72
        $fields->removeByName(['DefaultRegisteredMethodID', 'HasSkippedMFARegistration', 'RegisteredMFAMethods']);
73
74
        if (!$this->owner->exists() || !$this->currentUserCanViewMFAConfig()) {
0 ignored issues
show
The method exists() does not exist on SilverStripe\MFA\Extension\MemberExtension. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

74
        if (!$this->owner->/** @scrutinizer ignore-call */ exists() || !$this->currentUserCanViewMFAConfig()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
75
            return $fields;
76
        }
77
78
        $fields->addFieldToTab(
79
            'Root.Main',
80
            $methodListField = RegisteredMFAMethodListField::create(
81
                'MFASettings',
82
                _t(__CLASS__ . '.MFA_SETTINGS_FIELD_LABEL', 'Multi-factor authentication settings (MFA)'),
83
                $this->owner
84
            )
85
        );
86
87
        if (!$this->currentUserCanEditMFAConfig()) {
88
            $methodListField->setReadonly(true);
89
        }
90
91
        return $fields;
92
    }
93
94
    /**
95
     * Determines whether the logged in user has sufficient permission to see the MFA config for this Member.
96
     *
97
     * @return bool
98
     */
99
    public function currentUserCanViewMFAConfig(): bool
100
    {
101
        return (Permission::check(self::MFA_ADMINISTER_REGISTERED_METHODS)
102
            || $this->currentUserCanEditMFAConfig());
103
    }
104
105
    /**
106
     * Determines whether the logged in user has sufficient permission to modify the MFA config for this Member.
107
     * Note that this is different from being able to _reset_ the config (which administrators can do).
108
     *
109
     * @return bool
110
     */
111
    public function currentUserCanEditMFAConfig(): bool
112
    {
113
        return (Security::getCurrentUser() && Security::getCurrentUser()->ID === $this->owner->ID);
114
    }
115
116
    /**
117
     * Provides the MFA view/reset permission for selection in the permission list in the CMS.
118
     *
119
     * @return array
120
     */
121
    public function providePermissions(): array
122
    {
123
        $label = _t(
124
            __CLASS__ . '.MFA_PERMISSION_LABEL',
125
            'View/reset MFA configuration for other members'
126
        );
127
128
        $category = _t(
129
            'SilverStripe\\Security\\Permission.PERMISSIONS_CATEGORY',
130
            'Roles and access permissions'
131
        );
132
133
        $description = _t(
134
            __CLASS__ . '.MFA_PERMISSION_DESCRIPTION',
135
            'Ability to view and reset registered MFA methods for other members.'
136
            . ' Requires the "Access to \'Security\' section" permission.'
137
        );
138
139
        return [
140
            self::MFA_ADMINISTER_REGISTERED_METHODS => [
141
                'name' => $label,
142
                'category' => $category,
143
                'help' => $description,
144
                'sort' => 200,
145
            ],
146
        ];
147
    }
148
149
    /**
150
     * Clear any temporary multi-factor authentication related session keys when a member is successfully logged in.
151
     */
152
    public function afterMemberLoggedIn(): void
153
    {
154
        if (!Controller::has_curr()) {
155
            return;
156
        }
157
158
        Controller::curr()
159
            ->getRequest()
160
            ->getSession()
161
            ->clear(ChangePasswordHandler::MFA_VERIFIED_ON_CHANGE_PASSWORD);
162
    }
163
}
164