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
|
|||
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 |
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.