Completed
Push — master ( bbb282...43d0b8 )
by Daniel
25s
created

SecurityAdmin::groups()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Admin;
4
5
use SilverStripe\Security\Security;
6
use SilverStripe\Security\Member;
7
use SilverStripe\Security\Group;
8
use SilverStripe\Security\Permission;
9
use SilverStripe\Security\PermissionRole;
10
use SilverStripe\Security\PermissionProvider;
11
use Requirements;
12
use GridField;
13
use GridFieldConfig_RecordEditor;
14
use GridFieldButtonRow;
15
use GridFieldExportButton;
16
use Convert;
17
use FieldList;
18
use TabSet;
19
use Tab;
20
use LiteralField;
21
use HiddenField;
22
use HeaderField;
23
use Form;
24
use ArrayData;
25
use Deprecation;
26
use Config;
27
28
29
/**
30
 * Security section of the CMS
31
 *
32
 * @package framework
33
 * @subpackage admin
34
 */
35
class SecurityAdmin extends LeftAndMain implements PermissionProvider {
36
37
	private static $url_segment = 'security';
38
39
	private static $url_rule = '/$Action/$ID/$OtherID';
40
41
	private static $menu_title = 'Security';
42
43
	private static $tree_class = 'SilverStripe\\Security\\Group';
44
45
	private static $subitem_class = 'SilverStripe\\Security\\Member';
46
47
	private static $required_permission_codes = 'CMS_ACCESS_SecurityAdmin';
48
49
	private static $allowed_actions = array(
50
		'EditForm',
51
		'MemberImportForm',
52
		'memberimport',
53
		'GroupImportForm',
54
		'groupimport',
55
		'groups',
56
		'users',
57
		'roles'
58
	);
59
60
	protected function init() {
61
		parent::init();
62
		Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/SecurityAdmin.js');
63
	}
64
65
	/**
66
	 * Shortcut action for setting the correct active tab.
67
	 *
68
	 * @param SS_HTTPRequest $request
69
	 * @return SS_HTTPResponse
70
	 */
71
	public function users($request) {
72
		return $this->index($request);
73
	}
74
75
	/**
76
	 * Shortcut action for setting the correct active tab.
77
	 *
78
	 * @param SS_HTTPRequest $request
79
	 * @return SS_HTTPResponse
80
	 */
81
	public function groups($request) {
82
		return $this->index($request);
83
	}
84
85
	/**
86
	 * Shortcut action for setting the correct active tab.
87
	 *
88
	 * @param SS_HTTPRequest $request
89
	 * @return SS_HTTPResponse
90
	 */
91
	public function roles($request) {
92
		return $this->index($request);
93
	}
94
95
	public function getEditForm($id = null, $fields = null) {
96
		// TODO Duplicate record fetching (see parent implementation)
97
		if(!$id) $id = $this->currentPageID();
98
		$form = parent::getEditForm($id);
99
100
		// TODO Duplicate record fetching (see parent implementation)
101
		$record = $this->getRecord($id);
102
103
		if($record && !$record->canView()) {
104
			return Security::permissionFailure($this);
105
		}
106
107
		$memberList = GridField::create(
108
			'Members',
109
			false,
110
			Member::get(),
111
			$memberListConfig = GridFieldConfig_RecordEditor::create()
112
				->addComponent(new GridFieldButtonRow('after'))
113
				->addComponent(new GridFieldExportButton('buttons-after-left'))
114
		)->addExtraClass("members_grid");
115
116
		if($record && method_exists($record, 'getValidator')) {
117
			$validator = $record->getValidator();
118
		} else {
119
			$validator = Member::singleton()->getValidator();
120
		}
121
122
		$memberListConfig
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface GridFieldComponent as the method setValidator() does only exist in the following implementations of said interface: GridFieldDetailForm.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
123
			->getComponentByType('GridFieldDetailForm')
124
			->setValidator($validator);
125
126
		$groupList = GridField::create(
127
			'Groups',
128
			false,
129
			Group::get(),
130
			GridFieldConfig_RecordEditor::create()
131
		);
132
		$columns = $groupList->getConfig()->getComponentByType('GridFieldDataColumns');
133
		$columns->setDisplayFields(array(
134
			'Breadcrumbs' => Group::singleton()->fieldLabel('Title')
135
		));
136
		$columns->setFieldFormatting(array(
137
			'Breadcrumbs' => function($val, $item) {
138
				return Convert::raw2xml($item->getBreadcrumbs(' > '));
139
			}
140
		));
141
142
		$fields = new FieldList(
143
			$root = new TabSet(
144
				'Root',
145
				$usersTab = new Tab('Users', _t('SecurityAdmin.Users', 'Users'),
146
147
					new LiteralField('MembersCautionText',
148
						sprintf('<div class="alert alert-warning" role="alert">%s</div>',
149
							_t(
150
								'SecurityAdmin.MemberListCaution',
151
								'Caution: Removing members from this list will remove them from all groups and the database'
152
							)
153
						)
154
					),
155
					$memberList
156
				),
157
				$groupsTab = new Tab('Groups', singleton('SilverStripe\\Security\\Group')->i18n_plural_name(),
158
					$groupList
159
				)
160
			),
161
			// necessary for tree node selection in LeftAndMain.EditForm.js
162
			new HiddenField('ID', false, 0)
163
		);
164
165
		// Add import capabilities. Limit to admin since the import logic can affect assigned permissions
166
		if(Permission::check('ADMIN')) {
167
			$fields->addFieldsToTab('Root.Users', array(
168
				new HeaderField(_t('SecurityAdmin.IMPORTUSERS', 'Import users'), 3),
169
				new LiteralField(
170
					'MemberImportFormIframe',
171
					sprintf(
172
							'<iframe src="%s" id="MemberImportFormIframe" width="100%%" height="250px" frameBorder="0">'
173
						. '</iframe>',
174
						$this->Link('memberimport')
175
					)
176
				)
177
			));
178
			$fields->addFieldsToTab('Root.Groups', array(
179
				new HeaderField(_t('SecurityAdmin.IMPORTGROUPS', 'Import groups'), 3),
180
				new LiteralField(
181
					'GroupImportFormIframe',
182
					sprintf(
183
							'<iframe src="%s" id="GroupImportFormIframe" width="100%%" height="250px" frameBorder="0">'
184
						. '</iframe>',
185
						$this->Link('groupimport')
186
					)
187
				)
188
			));
189
		}
190
191
		// Tab nav in CMS is rendered through separate template
192
		$root->setTemplate('CMSTabSet');
193
194
		// Add roles editing interface
195
		if(Permission::check('APPLY_ROLES')) {
196
			$rolesField = GridField::create('Roles',
197
				false,
198
				PermissionRole::get(),
199
				GridFieldConfig_RecordEditor::create()
200
			);
201
202
			$rolesTab = $fields->findOrMakeTab('Root.Roles', _t('SecurityAdmin.TABROLES', 'Roles'));
203
			$rolesTab->push($rolesField);
204
		}
205
206
		$actionParam = $this->getRequest()->param('Action');
207
		if($actionParam == 'groups') {
208
			$groupsTab->addExtraClass('ui-state-active');
209
		} elseif($actionParam == 'users') {
210
			$usersTab->addExtraClass('ui-state-active');
211
		} elseif($actionParam == 'roles') {
212
			$rolesTab->addExtraClass('ui-state-active');
0 ignored issues
show
Bug introduced by
The variable $rolesTab does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
213
		}
214
215
		$actions = new FieldList();
216
217
		$form = Form::create(
218
			$this,
219
			'EditForm',
220
			$fields,
221
			$actions
222
		)->setHTMLID('Form_EditForm');
223
		$form->addExtraClass('cms-edit-form');
224
		$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
225
		// Tab nav in CMS is rendered through separate template
226
		if($form->Fields()->hasTabset()) {
227
			$form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
228
		}
229
		$form->addExtraClass('center ss-tabset cms-tabset ' . $this->BaseCSSClasses());
230
		$form->setAttribute('data-pjax-fragment', 'CurrentForm');
231
232
		$this->extend('updateEditForm', $form);
233
234
		return $form;
235
	}
236
237
	public function memberimport() {
238
		Requirements::clear();
239
		Requirements::css(FRAMEWORK_ADMIN_DIR . '/client/dist/styles/bundle.css');
240
		Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
241
		Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
242
		Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/MemberImportForm.js');
243
		return $this->renderWith('BlankPage', array(
244
			'Form' => $this->MemberImportForm()->forTemplate(),
245
			'Content' => ' '
246
		));
247
	}
248
249
	/**
250
	 * @see SecurityAdmin_MemberImportForm
251
	 *
252
	 * @return Form
253
	 */
254
	public function MemberImportForm() {
255
		if(!Permission::check('ADMIN')) return false;
256
257
		$group = $this->currentPage();
258
		/** @skipUpgrade */
259
		$form = new MemberImportForm(
260
			$this,
261
			'MemberImportForm'
262
		);
263
		$form->setGroup($group);
264
265
		return $form;
266
	}
267
268
	public function groupimport() {
269
		Requirements::clear();
270
		Requirements::css(FRAMEWORK_ADMIN_DIR . '/client/dist/styles/bundle.css');
271
		Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
272
		Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
273
		Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/MemberImportForm.js');
274
		return $this->renderWith('BlankPage', array(
275
			'Content' => ' ',
276
			'Form' => $this->GroupImportForm()->forTemplate()
277
		));
278
	}
279
280
	/**
281
	 * @see SecurityAdmin_MemberImportForm
282
	 *
283
	 * @return Form
284
	 */
285
	public function GroupImportForm() {
286
		if(!Permission::check('ADMIN')) return false;
287
288
		$form = new GroupImportForm(
289
			$this,
290
			'SilverStripe\\Admin\\GroupImportForm'
291
		);
292
293
		return $form;
294
	}
295
296
	/**
297
	 * Disable GridFieldDetailForm backlinks for this view, as its
298
	 */
299
	public function Backlink() {
300
		return false;
301
	}
302
303
	public function Breadcrumbs($unlinked = false) {
304
		$crumbs = parent::Breadcrumbs($unlinked);
305
306
		// Name root breadcrumb based on which record is edited,
307
		// which can only be determined by looking for the fieldname of the GridField.
308
		// Note: Titles should be same titles as tabs in RootForm().
309
		$params = $this->getRequest()->allParams();
310
		if(isset($params['FieldName'])) {
311
			// TODO FieldName param gets overwritten by nested GridFields,
312
			// so shows "Members" rather than "Groups" for the following URL:
313
			// admin/security/EditForm/field/Groups/item/2/ItemEditForm/field/Members/item/1/edit
314
			$firstCrumb = $crumbs->shift();
315
			if($params['FieldName'] == 'Groups') {
316
				$crumbs->unshift(new ArrayData(array(
317
					'Title' => singleton('SilverStripe\\Security\\Group')->i18n_plural_name(),
318
					'Link' => $this->Link('groups')
319
				)));
320
			} elseif($params['FieldName'] == 'Users') {
321
				$crumbs->unshift(new ArrayData(array(
322
					'Title' => _t('SecurityAdmin.Users', 'Users'),
323
					'Link' => $this->Link('users')
324
				)));
325
			} elseif($params['FieldName'] == 'Roles') {
326
				$crumbs->unshift(new ArrayData(array(
327
					'Title' => _t('SecurityAdmin.TABROLES', 'Roles'),
328
					'Link' => $this->Link('roles')
329
				)));
330
			}
331
			$crumbs->unshift($firstCrumb);
332
		}
333
334
		return $crumbs;
335
	}
336
337
	public function providePermissions() {
338
		$title = $this->menu_title();
339
		return array(
340
			"CMS_ACCESS_SecurityAdmin" => array(
341
				'name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array('title' => $title)),
342
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
343
				'help' => _t(
344
					'SecurityAdmin.ACCESS_HELP',
345
					'Allow viewing, adding and editing users, as well as assigning permissions and roles to them.'
346
				)
347
			),
348
			'EDIT_PERMISSIONS' => array(
349
				'name' => _t('SecurityAdmin.EDITPERMISSIONS', 'Manage permissions for groups'),
350
				'category' => _t('Permissions.PERMISSIONS_CATEGORY', 'Roles and access permissions'),
351
				'help' => _t('SecurityAdmin.EDITPERMISSIONS_HELP',
352
					'Ability to edit Permissions and IP Addresses for a group.'
353
					. ' Requires the "Access to \'Security\' section" permission.'),
354
				'sort' => 0
355
			),
356
			'APPLY_ROLES' => array(
357
				'name' => _t('SecurityAdmin.APPLY_ROLES', 'Apply roles to groups'),
358
				'category' => _t('Permissions.PERMISSIONS_CATEGORY', 'Roles and access permissions'),
359
				'help' => _t('SecurityAdmin.APPLY_ROLES_HELP', 'Ability to edit the roles assigned to a group.'
360
					. ' Requires the "Access to \'Users\' section" permission.'),
361
				'sort' => 0
362
			)
363
		);
364
	}
365
}
366