1 | <?php |
||||||
2 | |||||||
3 | namespace LeKoala\Admini; |
||||||
4 | |||||||
5 | use SilverStripe\ORM\DB; |
||||||
6 | use SilverStripe\Forms\Form; |
||||||
7 | use SilverStripe\Forms\TabSet; |
||||||
8 | use SilverStripe\ORM\ArrayList; |
||||||
9 | use LeKoala\Base\View\Bootstrap; |
||||||
0 ignored issues
–
show
|
|||||||
10 | use SilverStripe\Security\Group; |
||||||
11 | use SilverStripe\View\ArrayData; |
||||||
12 | use SilverStripe\Forms\FieldList; |
||||||
13 | use SilverStripe\Security\Member; |
||||||
14 | use SilverStripe\Control\Director; |
||||||
15 | use SilverStripe\Forms\HeaderField; |
||||||
16 | use SilverStripe\Security\Security; |
||||||
17 | use LeKoala\Tabulator\TabulatorGrid; |
||||||
18 | use SilverStripe\Forms\LiteralField; |
||||||
19 | use SilverStripe\Control\HTTPRequest; |
||||||
20 | use SilverStripe\Security\Permission; |
||||||
21 | use LeKoala\Admini\Helpers\FileHelper; |
||||||
22 | use SilverStripe\Security\LoginAttempt; |
||||||
23 | use SilverStripe\Security\PermissionRole; |
||||||
24 | use LeKoala\CmsActions\CmsInlineFormAction; |
||||||
0 ignored issues
–
show
The type
LeKoala\CmsActions\CmsInlineFormAction was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
25 | use LeKoala\Admini\Forms\BootstrapAlertField; |
||||||
26 | use SilverStripe\Security\PermissionProvider; |
||||||
27 | |||||||
28 | /** |
||||||
29 | * Security section of the CMS |
||||||
30 | */ |
||||||
31 | class SecurityAdmin extends ModelAdmin implements PermissionProvider |
||||||
32 | { |
||||||
33 | private static $url_segment = 'security'; |
||||||
34 | |||||||
35 | private static $menu_title = 'Security'; |
||||||
36 | |||||||
37 | private static $managed_models = [ |
||||||
0 ignored issues
–
show
|
|||||||
38 | 'users' => [ |
||||||
39 | 'title' => 'Users', |
||||||
40 | 'dataClass' => Member::class |
||||||
41 | ], |
||||||
42 | 'groups' => [ |
||||||
43 | 'title' => 'Groups', |
||||||
44 | 'dataClass' => Group::class |
||||||
45 | ], |
||||||
46 | // 'roles' => [ |
||||||
47 | // 'title' => 'Roles', |
||||||
48 | // 'dataClass' => PermissionRole::class |
||||||
49 | // ], |
||||||
50 | ]; |
||||||
51 | |||||||
52 | private static $required_permission_codes = 'CMS_ACCESS_SecurityAdmin'; |
||||||
53 | |||||||
54 | private static $menu_icon = MaterialIcons::SECURITY; |
||||||
0 ignored issues
–
show
|
|||||||
55 | |||||||
56 | private static $allowed_actions = [ |
||||||
57 | 'doClearLogs', |
||||||
58 | 'doRotateLogs', |
||||||
59 | // new tabs |
||||||
60 | 'security_audit', |
||||||
61 | 'logs', |
||||||
62 | ]; |
||||||
63 | |||||||
64 | public static function getMembersFromSecurityGroupsIDs(): array |
||||||
65 | { |
||||||
66 | $sql = 'SELECT DISTINCT MemberID FROM Group_Members INNER JOIN Permission ON Permission.GroupID = Group_Members.GroupID WHERE Code LIKE \'CMS_%\' OR Code = \'ADMIN\''; |
||||||
67 | return DB::query($sql)->column(); |
||||||
68 | } |
||||||
69 | |||||||
70 | /** |
||||||
71 | * @param array $extraIDs |
||||||
72 | * @return Member[]|ArrayList |
||||||
73 | */ |
||||||
74 | public static function getMembersFromSecurityGroups(array $extraIDs = []) |
||||||
75 | { |
||||||
76 | $ids = array_merge(self::getMembersFromSecurityGroupsIDs(), $extraIDs); |
||||||
77 | return Member::get()->filter('ID', $ids); |
||||||
0 ignored issues
–
show
|
|||||||
78 | } |
||||||
79 | |||||||
80 | public function getManagedModels() |
||||||
81 | { |
||||||
82 | $models = parent::getManagedModels(); |
||||||
83 | |||||||
84 | // Add extra tabs |
||||||
85 | if (Security::config()->login_recording) { |
||||||
86 | $models['security_audit'] = [ |
||||||
87 | 'title' => 'Security Audit', |
||||||
88 | 'dataClass' => LoginAttempt::class, |
||||||
89 | ]; |
||||||
90 | } |
||||||
91 | if (Permission::check('ADMIN')) { |
||||||
92 | $models['logs'] = [ |
||||||
93 | 'title' => 'Logs', |
||||||
94 | 'dataClass' => LoginAttempt::class, // mock class |
||||||
95 | ]; |
||||||
96 | } |
||||||
97 | |||||||
98 | return $models; |
||||||
99 | } |
||||||
100 | |||||||
101 | public function getEditForm($id = null, $fields = null) |
||||||
102 | { |
||||||
103 | $form = parent::getEditForm($id, $fields); |
||||||
104 | |||||||
105 | // In security, we only show group members + current item (to avoid issue when creating stuff) |
||||||
106 | $request = $this->getRequest(); |
||||||
107 | $dirParts = explode('/', $request->remaining()); |
||||||
108 | $currentID = isset($dirParts[3]) ? [$dirParts[3]] : []; |
||||||
109 | |||||||
110 | switch ($this->modelTab) { |
||||||
111 | case 'users': |
||||||
112 | /** @var TabulatorGrid $members */ |
||||||
113 | $members = $form->Fields()->dataFieldByName('users'); |
||||||
114 | $membersOfGroups = self::getMembersFromSecurityGroups($currentID); |
||||||
115 | $members->setList($membersOfGroups); |
||||||
116 | |||||||
117 | // Add some security wise fields |
||||||
118 | $sng = singleton($members->getModelClass()); |
||||||
119 | if ($sng->hasMethod('DirectGroupsList')) { |
||||||
120 | $members->addDisplayFields([ |
||||||
121 | 'DirectGroupsList' => 'Direct Groups' |
||||||
122 | ]); |
||||||
123 | } |
||||||
124 | if ($sng->hasMethod('Is2FaConfigured')) { |
||||||
125 | $members->addDisplayFields([ |
||||||
126 | 'Is2FaConfigured' => '2FA' |
||||||
127 | ]); |
||||||
128 | } |
||||||
129 | break; |
||||||
130 | case 'groups': |
||||||
131 | break; |
||||||
132 | case 'security_audit': |
||||||
133 | if (Security::config()->login_recording) { |
||||||
134 | $this->addAuditTab($form); |
||||||
135 | } |
||||||
136 | break; |
||||||
137 | case 'logs': |
||||||
138 | if (Permission::check('ADMIN')) { |
||||||
139 | $this->addLogTab($form); |
||||||
140 | } |
||||||
141 | break; |
||||||
142 | } |
||||||
143 | |||||||
144 | return $form; |
||||||
145 | } |
||||||
146 | |||||||
147 | protected function getLogFiles(): array |
||||||
148 | { |
||||||
149 | $logDir = Director::baseFolder(); |
||||||
150 | $logFiles = glob($logDir . '/*.log'); |
||||||
151 | return $logFiles; |
||||||
152 | } |
||||||
153 | |||||||
154 | protected function addLogTab(Form $form) |
||||||
155 | { |
||||||
156 | $logFiles = $this->getLogFiles(); |
||||||
157 | $logTab = $form->Fields(); |
||||||
158 | $logTab->removeByName('logs'); |
||||||
159 | |||||||
160 | foreach ($logFiles as $logFile) { |
||||||
161 | $logName = pathinfo($logFile, PATHINFO_FILENAME); |
||||||
162 | |||||||
163 | $logTab->push(new HeaderField($logName, ucwords($logName))); |
||||||
0 ignored issues
–
show
It seems like
$logName can also be of type array ; however, parameter $name of SilverStripe\Forms\HeaderField::__construct() does only seem to accept string , 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
![]() It seems like
$logName can also be of type array ; however, parameter $string of ucwords() does only seem to accept string , 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
![]() |
|||||||
164 | |||||||
165 | $filemtime = filemtime($logFile); |
||||||
166 | $filesize = filesize($logFile); |
||||||
167 | |||||||
168 | $logTab->push(new BootstrapAlertField($logName . 'Alert', _t('SecurityAdmin.LogAlert', "Last updated on {updated}", [ |
||||||
0 ignored issues
–
show
Are you sure
$logName of type array|string can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
169 | 'updated' => date('Y-m-d H:i:s', $filemtime), |
||||||
170 | ]))); |
||||||
171 | |||||||
172 | $lastLines = '<pre>' . FileHelper::tail($logFile, 10) . '</pre>'; |
||||||
0 ignored issues
–
show
Are you sure
LeKoala\Admini\Helpers\F...per::tail($logFile, 10) of type false|string can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
173 | |||||||
174 | $logTab->push(new LiteralField($logName, $lastLines)); |
||||||
0 ignored issues
–
show
It seems like
$logName can also be of type array ; however, parameter $name of SilverStripe\Forms\LiteralField::__construct() does only seem to accept string , 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
![]() |
|||||||
175 | $logTab->push(new LiteralField($logName . 'Size', '<p>' . _t('SecurityAdmin.LogSize', "Total size is {size}", [ |
||||||
176 | 'size' => FileHelper::humanFilesize($filesize) |
||||||
177 | ]) . '</p>')); |
||||||
178 | } |
||||||
179 | |||||||
180 | $clearLogsBtn = new CmsInlineFormAction('doClearLogs', _t('SecurityAdmin.doClearLogs', 'Clear Logs')); |
||||||
181 | $logTab->push($clearLogsBtn); |
||||||
182 | $rotateLogsBtn = new CmsInlineFormAction('doRotateLogs', _t('SecurityAdmin.doRotateLogs', 'Rotate Logs')); |
||||||
183 | $logTab->push($rotateLogsBtn); |
||||||
184 | } |
||||||
185 | |||||||
186 | public function doClearLogs(HTTPRequest $request) |
||||||
0 ignored issues
–
show
The parameter
$request is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
187 | { |
||||||
188 | foreach ($this->getLogFiles() as $logFile) { |
||||||
189 | unlink($logFile); |
||||||
190 | } |
||||||
191 | $msg = "Logs cleared"; |
||||||
192 | return $this->redirectWithStatus($msg); |
||||||
193 | } |
||||||
194 | |||||||
195 | public function doRotateLogs(HTTPRequest $request) |
||||||
0 ignored issues
–
show
The parameter
$request is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
196 | { |
||||||
197 | foreach ($this->getLogFiles() as $logFile) { |
||||||
198 | if (strpos($logFile, '-') !== false) { |
||||||
199 | continue; |
||||||
200 | } |
||||||
201 | $newname = dirname($logFile) . '/' . pathinfo($logFile, PATHINFO_FILENAME) . '-' . date('Ymd') . '.log'; |
||||||
0 ignored issues
–
show
Are you sure
pathinfo($logFile, LeKoa...mini\PATHINFO_FILENAME) of type array|string can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
202 | rename($logFile, $newname); |
||||||
203 | } |
||||||
204 | $msg = "Logs rotated"; |
||||||
205 | return $this->redirectWithStatus($msg); |
||||||
206 | } |
||||||
207 | |||||||
208 | protected function addAuditTab(Form $form) |
||||||
209 | { |
||||||
210 | $auditTab = $form->Fields(); |
||||||
211 | $auditTab->removeByName('security_audit'); |
||||||
212 | |||||||
213 | $Member_SNG = Member::singleton(); |
||||||
214 | $membersLocked = Member::get()->where('LockedOutUntil > NOW()'); |
||||||
215 | if ($membersLocked->count()) { |
||||||
216 | $membersLockedGrid = new TabulatorGrid('MembersLocked', _t('SecurityAdmin.LockedMembers', "Locked Members"), $membersLocked); |
||||||
217 | $membersLockedGrid->setForm($form); |
||||||
218 | $membersLockedGrid->setDisplayFields([ |
||||||
219 | 'Title' => $Member_SNG->fieldLabel('Title'), |
||||||
220 | 'Email' => $Member_SNG->fieldLabel('Email'), |
||||||
221 | 'LockedOutUntil' => $Member_SNG->fieldLabel('LockedOutUntil'), |
||||||
222 | 'FailedLoginCount' => $Member_SNG->fieldLabel('FailedLoginCount'), |
||||||
223 | ]); |
||||||
224 | $auditTab->push($membersLockedGrid); |
||||||
225 | } |
||||||
226 | |||||||
227 | $LoginAttempt_SNG = LoginAttempt::singleton(); |
||||||
228 | |||||||
229 | $getMembersFromSecurityGroupsIDs = self::getMembersFromSecurityGroupsIDs(); |
||||||
230 | $recentAdminLogins = LoginAttempt::get()->filter([ |
||||||
231 | 'Status' => 'Success', |
||||||
232 | 'MemberID' => $getMembersFromSecurityGroupsIDs |
||||||
233 | ])->limit(10)->sort('Created DESC'); |
||||||
234 | $recentAdminLoginsGrid = new TabulatorGrid('RecentAdminLogins', _t('SecurityAdmin.RecentAdminLogins', "Recent Admin Logins"), $recentAdminLogins); |
||||||
235 | $recentAdminLoginsGrid->setDisplayFields([ |
||||||
236 | 'Created' => $LoginAttempt_SNG->fieldLabel('Created'), |
||||||
237 | 'IP' => $LoginAttempt_SNG->fieldLabel('IP'), |
||||||
238 | 'Member.Title' => $Member_SNG->fieldLabel('Title'), |
||||||
239 | 'Member.Email' => $Member_SNG->fieldLabel('Email'), |
||||||
240 | ]); |
||||||
241 | $recentAdminLoginsGrid->setViewOnly(); |
||||||
242 | $recentAdminLoginsGrid->setForm($form); |
||||||
243 | $auditTab->push($recentAdminLoginsGrid); |
||||||
244 | |||||||
245 | $recentPasswordFailures = LoginAttempt::get()->filter('Status', 'Failure')->limit(10)->sort('Created DESC'); |
||||||
246 | $recentPasswordFailuresGrid = new TabulatorGrid('RecentPasswordFailures', _t('SecurityAdmin.RecentPasswordFailures', "Recent Password Failures"), $recentPasswordFailures); |
||||||
247 | $recentPasswordFailuresGrid->setDisplayFields([ |
||||||
248 | 'Created' => $LoginAttempt_SNG->fieldLabel('Created'), |
||||||
249 | 'IP' => $LoginAttempt_SNG->fieldLabel('IP'), |
||||||
250 | 'Member.Title' => $Member_SNG->fieldLabel('Title'), |
||||||
251 | 'Member.Email' => $Member_SNG->fieldLabel('Email'), |
||||||
252 | 'Member.FailedLoginCount' => $Member_SNG->fieldLabel('FailedLoginCount'), |
||||||
253 | ]); |
||||||
254 | $recentPasswordFailuresGrid->setViewOnly(); |
||||||
255 | $recentPasswordFailuresGrid->setForm($form); |
||||||
256 | $auditTab->push($recentPasswordFailuresGrid); |
||||||
257 | } |
||||||
258 | |||||||
259 | public function providePermissions() |
||||||
260 | { |
||||||
261 | $title = $this->menu_title(); |
||||||
262 | return array( |
||||||
263 | "CMS_ACCESS_SecurityAdmin" => [ |
||||||
264 | 'name' => _t( |
||||||
265 | 'LeKoala\\Admini\\LeftAndMain.ACCESS', |
||||||
266 | "Access to '{title}' section", |
||||||
267 | ['title' => $title] |
||||||
268 | ), |
||||||
269 | 'category' => _t('SilverStripe\\Security\\Permission.CMS_ACCESS_CATEGORY', 'CMS Access'), |
||||||
270 | 'help' => _t( |
||||||
271 | __CLASS__ . '.ACCESS_HELP', |
||||||
272 | 'Allow viewing, adding and editing users, as well as assigning permissions and roles to them.' |
||||||
273 | ) |
||||||
274 | ], |
||||||
275 | 'EDIT_PERMISSIONS' => array( |
||||||
276 | 'name' => _t(__CLASS__ . '.EDITPERMISSIONS', 'Manage permissions for groups'), |
||||||
277 | 'category' => _t( |
||||||
278 | 'SilverStripe\\Security\\Permission.PERMISSIONS_CATEGORY', |
||||||
279 | 'Roles and access permissions' |
||||||
280 | ), |
||||||
281 | 'help' => _t( |
||||||
282 | __CLASS__ . '.EDITPERMISSIONS_HELP', |
||||||
283 | 'Ability to edit Permissions and IP Addresses for a group.' |
||||||
284 | . ' Requires the "Access to \'Security\' section" permission.' |
||||||
285 | ), |
||||||
286 | 'sort' => 0 |
||||||
287 | ), |
||||||
288 | 'APPLY_ROLES' => array( |
||||||
289 | 'name' => _t(__CLASS__ . '.APPLY_ROLES', 'Apply roles to groups'), |
||||||
290 | 'category' => _t( |
||||||
291 | 'SilverStripe\\Security\\Permission.PERMISSIONS_CATEGORY', |
||||||
292 | 'Roles and access permissions' |
||||||
293 | ), |
||||||
294 | 'help' => _t( |
||||||
295 | __CLASS__ . '.APPLY_ROLES_HELP', |
||||||
296 | 'Ability to edit the roles assigned to a group.' |
||||||
297 | . ' Requires the "Access to \'Users\' section" permission.' |
||||||
298 | ), |
||||||
299 | 'sort' => 0 |
||||||
300 | ) |
||||||
301 | ); |
||||||
302 | } |
||||||
303 | } |
||||||
304 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths