This bugtracker is archived (announcement). New tickets are created on github.com. See all framework issues, cms issues, and search the module listings for more specific bugtrackers.
wiki:development/permissions
Last modified 6 years ago Last modified on 24/09/08 12:37:31

Permissions Development Plan

Contact

  • Ingo Schommer (ingo at silverstripe dot com)
  • Sam Minnée (sam at silverstripe dot com)

Status

Permission-Control is already in 2.2 on different levels:

  • DataObject::can*()
  • Permission::check()
  • $myMember->inGroup()7
  • Controller::$allowed_actions

Discussion

See http://groups.google.com/group/silverstripe-dev/browse_frm/thread/4e22a14e874f95e4

See this ticket for historical discussion.

For 2.3

Controller Permissions

Stays as currently implemented:

class MyPage_Controller extends Page_Controller {
  static $allowed_actions = array(
    'myaction' => 'ADMIN'
  )
}

Specifying Object-Level Permissions

  • Same capabilities as Controller::$allowed_actions, but limited array keys (not direct method names)
  • Allowed values for $permission array keys would be: view,edit,create,delete
  • Custom can*($member) methods should always be protected and not directly accessed.
  • Public API usage is: $myObj->can('view', $member)
  • $member parameter is compulsory for custom can*($member) methods, but optional for built-in can($action, $member=null) - defaults to currently logged-in member. Has to be member object, usage of member IDs is deprecated.
  • Custom can*() methods need to support ID=0 scenarios for records just being created

Example:

class MyObj extends DataObject {
	static $permissions = array(
           'view', // can be accessed by anyone, any time. 
           'edit' => true, // So can this
           'create' => 'ADMIN', // can only be people with ADMIN privilege
           'delete' => '->canDelete' // can only be accessed if $this->canDelete($memberObj) returns true
       );
      
       function canDelete($member) {
              return Permission::check("ADMIN", $member) || $member->ID = $this->OwneerID;
       }
}

DataObject? default settings:

class DataObject extends ViewableData {
	static $permissions = array(
		'view' => 'ADMIN',
		'create' => 'ADMIN',
		'edit' => 'ADMIN',
		'delete' => 'ADMIN',
	);
}

For core datamodel, we would define sensible defaults:

class SiteTree extends DataObject {
	static $permissions = array(
		'view' => true,
		'create' => 'CMS_AUTHOR',
		'edit' => 'CMS_AUTHOR',
		'delete' => 'CMS_AUTHOR',
		'publish' => 'CMS_PUBLISHER', // you can create custom permissions for your own data object.
	);
}

Application of ORM-level permissions (record and field level)

In 2.3, this will be left to the diligence of individual developers.

$api_access

Simple:

$api_access = true;

More complex:

$api_access = array(
  'view' => array('FirstName','Surname','Email','Groups'),
  'edit' => array('Email'),
);

Permission Codes

We're going to simplify the Permission code system, removing the Arg attribute and the strict option. DENY and GRANT will still exist, but INHERIT will be removed, since INHERIT is the default behaviour from group to sub-group.

Permission Codes API

  • Change parameter signature for Permission::check() and Permission::checkMember() to be more useable
    // old
    public static function check($code, $arg = "any", $member = null, $strict = true) {}
    // new
    public static function check($code, $member = null) {}
    // deprecated
    public static function checkMember($member, $code) {}
    

Permission Code inheritance

Rather than represent permission inheritance by auto-adding Members to the parent groups, update Permission::check() to traverse up the Group hierarchy itself.

  • By default, a permission code from a parent group will be inherited.
  • If a subgroup has a DENY permission code, then it won't be inherited.

Permission Codes tab in SecurityAdmin

Since we've ditched the Arg attribute, the permission tab in SecurityAdmin can be simplified to a checkboxset field. It will list all permissions applied to the group, including those inherited from the parent group.

The following two methods will help facilitate this.

// Returns permission codes for the group and all parent groups, except for those that have been denied.
Permission::all_for_group($group);
// Sets the permission codes for the given group, creating GRANT and DENY records as necessary.
Permission::set_all_for_group($group);

2.4

Field-Level Permissions - leave until 2.4

  • Gets more necessary if exposing models through public APIs (e.g. RestfulServer)
  • Intrusive change, as any locking down affects a lot of legacy code.
  • Defaults to allow all viewing and editing of fields, provided the necessary object-level permission is granted
  • The permission "edit" on field level means "create", "edit", "delete"
  • TODO What counts as a "field"? Relation methods? All "get*()" and "set*()" methods? All public class methods?
  • Enforcing of canViewFields() on template level through Object->XML_val()
  • Enforcing of canEditFields() in DataObject::setField()
  • TODO Update all internal DataObject? methods to use setField() rather than setting $record directly to enforce permission control
  • TODO Do we really want to lock down DataObject::setField()? Should be useable regardless of logged-in user. How about DataObject::filteredUpdate() ?
  • Permission cache for both canViewFields() and canEditFields() based on passed $member
  • TODO When to invalidate permission cache? Every call of DataObject::set()?
  • No automatic permission control when dealing with SQLQuery INSERT/UPDATE
  • TODO Are canViewFields() methods for public usage? Or wrapped in canField('view')?
  • Internally allow custom code to access all fields regardless of currently logged in member, to allow for custom filtering etc.
  • TODO Syntactically canField('view', 'MyField?', $member) is counterintuitive, but consistent with can() - any suggestions?
  • Templates return "(hidden)" string by default if value viewing is not allowed

DataObject? default:

class DataObject extends ViewableData {
  protected function canViewFields($member ,) {
      return ($this->can('view', $member) ? $this->allFields() : array();  
  }

  protected function canEditFields($member) {
      return ($this->can('edit', $member) ? $this->allFields() : array();
    }
  }
  public function canField($action, $fieldName, $member = null) {
    if(!$member) $member = Member::currentUser();
    $permissionMethod = "can" . ucfirst($action) . "Fields";
    return in_array($fieldName, $this->{$permissionMethod}());
  }
  
  // TODO Currently returns $record, naming conflict
  public function allFields() {
    return $this->databaseFields() + $this->allMethodNames();
  }
}

Example for Member.php sensible defaults:

class Member extends DataObject {
  protected function canViewFields($member) {
    if(Permission::check('ADMIN', $member) {
      return $this->allFields();
    } else {
      return array(
        'FirstName',
        'LastName',
      );
    }
  }
}

Template usage:

<h3>Your profile</h3>
<% if canField('view', 'Password') %>
$Password
<% end_if %>

Application of ORM-level permissions (record and field level)

...who knows? Something fun with the data mapper

Session Nesting

Because we're checking field "view" permissions in the template renderer, we can't pass a specific member object context, and assume the currently logged-in member. This might not always be valid, for example when sending bulk emails as an Administrator - the rendered content should check for the context of the recipient rather than the sender.

The session nesting system that is implemented in the controller stack will need to be used to alter the value of Member::currentUser() when the Email is being rendered. This will be implemented within the Email class.

  • TODO INGO Identify use cases needing session nesting in core and modules

Restricting access to relations and methods

These would be implemented through field permissions, e.g.

class Member extends DataObject {
  function canViewFields() {
    return array('FirstName', 'Groups', );
  }
  static $permissions = array(
    'Groups' => 'ADMIN'
  );
}

Permission Scenarios

Pageviews: Anonymous create through script, public view through API

Problem: For example, objects of class PageView? shoud be creatable by controller code without requiring a logged-in user, in the context of "an anonymous user viewing the page". This means the objects "create" permissions have to be set to "anonymous access". Now if we want to enable read-only "view" access through the Restful API, we automatically expose "create" permissions to everybody. We currently have no way to limit creation of an objects based on the context - which is sometimes desireable, but in this case an obstacle and security risk. Possible Solution: ???