Passed
Pull Request — master (#127)
by Garion
02:31
created

MemberMFAExtension::providePermissions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 24
rs 9.7333
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
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\FormField\RegisteredMFAMethodListField;
9
use SilverStripe\MFA\Method\MethodInterface;
10
use SilverStripe\MFA\Model\RegisteredMethod;
11
use SilverStripe\ORM\DataExtension;
12
use SilverStripe\ORM\HasManyList;
13
use SilverStripe\Security\Member;
14
use SilverStripe\Security\Permission;
15
use SilverStripe\Security\PermissionProvider;
16
use SilverStripe\Security\Security;
17
18
/**
19
 * Extend Member to add relationship to registered methods and track some specific preferences
20
 *
21
 * @method RegisteredMethod[]|HasManyList RegisteredMFAMethods
22
 * @property MethodInterface DefaultRegisteredMethod
23
 * @property string DefaultRegisteredMethodID
24
 * @property bool HasSkippedMFARegistration
25
 * @property Member|MemberMFAExtension owner
26
 */
27
class MemberMFAExtension extends DataExtension implements PermissionProvider
28
{
29
    const MFA_ADMINISTER_REGISTERED_METHODS = 'MFA_ADMINISTER_REGISTERED_METHODS';
30
31
    private static $has_many = [
0 ignored issues
show
introduced by
The private property $has_many is not used, and could be removed.
Loading history...
32
        'RegisteredMFAMethods' => RegisteredMethod::class,
33
    ];
34
35
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
36
        'DefaultRegisteredMethodID' => 'Int',
37
        'HasSkippedMFARegistration' => 'Boolean',
38
    ];
39
40
    /**
41
     * Accessor for the `DefaultRegisteredMethod` property
42
     *
43
     * This is replicating the usual functionality of a has_one relation but does it like this so we can ensure the same
44
     * instance of the MethodInterface is provided regardless if you access it through the has_one or the has_many.
45
     *
46
     * @return MethodInterface
47
     */
48
    public function getDefaultRegisteredMethod()
49
    {
50
        return $this->owner->RegisteredMFAMethods()->byId($this->owner->DefaultRegisteredMethodID);
0 ignored issues
show
Bug introduced by
It seems like $this->owner->DefaultRegisteredMethodID can also be of type string; however, parameter $id of SilverStripe\ORM\DataList::byID() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

50
        return $this->owner->RegisteredMFAMethods()->byId(/** @scrutinizer ignore-type */ $this->owner->DefaultRegisteredMethodID);
Loading history...
Bug Best Practice introduced by
The expression return $this->owner->Reg...aultRegisteredMethodID) returns the type SilverStripe\ORM\DataObject which is incompatible with the documented return type SilverStripe\MFA\Method\MethodInterface.
Loading history...
51
    }
52
53
    public function updateCMSFields(FieldList $fields)
54
    {
55
        $fields->removeByName(['DefaultRegisteredMethodID', 'HasSkippedMFARegistration', 'RegisteredMFAMethods']);
56
57
        if (!$this->currentUserCanViewMFAConfig() || !$this->owner->exists()) {
0 ignored issues
show
Bug introduced by
The method exists() does not exist on SilverStripe\MFA\Extension\MemberMFAExtension. ( Ignorable by Annotation )

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

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

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...
58
            return $fields;
59
        }
60
61
        $fields->addFieldToTab(
62
            'Root.Main',
63
            $methodListField = RegisteredMFAMethodListField::create(
64
                'MFASettings',
65
                _t(__CLASS__ . '.MFA_SETTINGS_FIELD_LABEL', 'Multi Factor Authentication settings (MFA)'),
66
                $this->owner
67
            )
68
        );
69
70
        if (!$this->currentUserCanEditMFAConfig()) {
71
            $methodListField->setReadonly(true);
72
        }
73
74
        return $fields;
75
    }
76
77
    /**
78
     * Determines whether the logged in user has sufficient permission to see the MFA config for this Member.
79
     *
80
     * @return bool
81
     */
82
    public function currentUserCanViewMFAConfig()
83
    {
84
        return (Permission::check(self::MFA_ADMINISTER_REGISTERED_METHODS)
85
            || $this->currentUserCanEditMFAConfig());
86
    }
87
88
    /**
89
     * Determines whether the logged in user has sufficient permission to modify the MFA config for this Member.
90
     * Note that this is different from being able to _reset_ the config (which administrators can do).
91
     *
92
     * @return bool
93
     */
94
    public function currentUserCanEditMFAConfig()
95
    {
96
        return (Security::getCurrentUser() && Security::getCurrentUser()->ID === $this->owner->ID);
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on SilverStripe\MFA\Extension\MemberMFAExtension. Did you maybe forget to declare it?
Loading history...
97
    }
98
99
    /**
100
     * Provides the MFA view/reset permission for selection in the permission list in the CMS.
101
     *
102
     * @return array
103
     */
104
    public function providePermissions()
105
    {
106
        $label = _t(
107
            __CLASS__ . '.MFA_PERMISSION_LABEL',
108
            'View/reset MFA configuration for other members'
109
        );
110
111
        $category = _t(
112
            'SilverStripe\\Security\\Permission.PERMISSIONS_CATEGORY',
113
            'Roles and access permissions'
114
        );
115
116
        $description = _t(
117
            __CLASS__ . '.MFA_PERMISSION_DESCRIPTION',
118
            'Ability to view and reset registered MFA methods for other members.'
119
            . ' Requires the "Access to \'Security\' section" permission.'
120
        );
121
122
        return [
123
            self::MFA_ADMINISTER_REGISTERED_METHODS => [
124
                'name' => $label,
125
                'category' => $category,
126
                'help' => $description,
127
                'sort' => 200,
128
            ],
129
        ];
130
    }
131
132
    /**
133
     * Clear any temporary multi-factor authentication related session keys when a member is successfully logged in.
134
     */
135
    public function afterMemberLoggedIn()
136
    {
137
        if (!Controller::has_curr()) {
138
            return;
139
        }
140
141
        Controller::curr()
142
            ->getRequest()
143
            ->getSession()
144
            ->clear(ChangePasswordHandler::MFA_VERIFIED_ON_CHANGE_PASSWORD);
145
    }
146
}
147