[TODO] Provide a one paragraph overview.
Permissions on Sitetheory are extremely granular and powerful. They fall in line with a basic structure: * Scope * Identity * Asset * Sentinel
It's important to understand the entities and their relationships involved in controlling permission. Permissions are controled in the Core universal database (versus the Nest which is limited to a cluster, e.g. site content).
Users (core.user) are universal to the entire sitetheory system (NOTE: this may change). They can be granted permission to "assets" (site, bundle, entity), by defining their "UserPermissions". This allows a User, once authenticated, to assume other identities in the form of Roles as well as gain permissions for any shared or owned content.
Permissions (core.user_permissions) can be granted for any "identity" (user or role) to access any "asset" (site, bundle, entity), and confined to a specific "scope" (vendor, site).
NOTE: normally siteId, vendorId & userId are the standard fields defining what site and user created the record the first time. But in the case of user permissions, that is not the case, since permissions are universal. When a user is first created by the system, it will define the site as the one where the user was created, and the user as itself. FYI, The normal "audit trail" fields are editUserId (if the permissions are changed by a user at some point, we record who made the change, but that isn’t related to the permission level.
Control Fields on UserPermissions --------------
Control fields create "Locking Realms" for permissions. They contain:
- scope: Site or Vendor
- siteId, vendorId: these are the who "owns" the record when it was first created, they determine the scope. master: This should be NULL for most permissions and only set to 1 if you want to elevate a user to have "root" access to all assets (SECURITY RISK!). Only some people on our internal team will have this ability, so that our support team can access sites without being granted specific access.
- Identity Fields - Authentication (either user or role, not both)
- identityUserid: the user being granted permissions.
- identityRoleId - the role being granted permissions.
- asset: a string defining a bundle, or an entity that an identity is being granted permissions for. If this is a bundle, all entities in the bundle are granted access. If this is an entity, all entity records are granted permissions, unless a specific assetId is set in the other field.
- assetId: the specific asset record id being granted access.
- permission: a byte value (bitwise inclusive "OR")
- scopes: Site or Vendor
Roles (core.user_role) are an "identity" (like User) that can be granted permissions. Role is a list of all the roles created for specific sites. Roles allow you to create premade permissions that can assign standard roles to users (and change permissions of entire groups of people assigned to that role, e.g. moderator, writer, editor, publisher, etc). The role then must have one or more permissions (user_permissions) assigned to it in order to define the access of that role. AssignedRole (core.user_assigned_role): AssignedRole is a simple entity that associates a user with a specific role.
Settings (core.user_settings) is not currently used because most user settings are need to be unique for each site, so they are stored in the nest.user_profile.
Every user stores profile and preferences uniquely for each site (nest.user_profile), these can vary from site to site, since users are global on our entire system.
The authHelper constructs a "Sentinel" for every asset that determines what a given user can do with it (e.g. create, read, update, delete, etc). The Sentinel access flags are based on the "Asset" (e.g. Site, Content, Media, etc) for the current "Scope" (e.g. Site or Vendor) and specifies what access is available to the "Identity" (User Role) based on the permissions for that Identity.
The Sentinel is requested "on demand" when the system needs to know, e.g. EnvironmentHelper needs to know the Sentinel for the current site, the APIController needs to know the Sentinel for the current asset/record being edited, etc.
The Sentinel Entity is very simple, it just contains true or false flags for the basic CRUD permissions (plus a few extra) of create, edit, delete, publish, design, dev. These can then be checked on a case by case basis, to know if a user has access to the asset.
A user has permissions defined which give access level to a specific site. In the EnvironmentHelper we create a Sentinel for the Site by calling AuthHelper#getSentinel()
AuthHelper#getSentinel() uses Fuzzy Logic (https://en.wikipedia.org/wiki/Fuzzy_logic) (learn more: https://plato.stanford.edu/entries/logic-fuzzy/#FuzzLogiVagu) to determine if someone should be allowed access based on their defined permissions. This requires fuzzy logic, because they may have permission to the site, but not specifically to the article. But if the article hasn’t been locked (e.g. a specific permission defined for that article) then their permission level to the site is used to give them access to the article. We just look up the tree to see the closest permissions they have. The priority order is: - Site - Bundle - Entity - Record
The AuthHelper#getSentinel() fetches permissions on the current asset that you passed. Which means it checks to see if there are any "permissions" set for the current asset. If there are no permissions defined, then there are no restrictions, which is why when we first create a site, we have to assign permissions to someone.
The Sentinel then contains true or false for each CRUD level, so it can easily be used to determine what kind of access is granted.
The EnvironmentHelper then checks to see if you have access to the Site, or the Page, or the APIController checks if you have access to view or edit a specific bundle, asset, record or field.
The AuthHelper should never need "debugging", it’s just a tool that will always work the same way. But if you need to debug the authHelper, give Yourself "Auditor" (but be careful because it’s a TON of backtrace information that will be dumped). To give yourself "auditor" flag the "auditor" field in your "user" record (temporarily).
In most cases you will debug the use case where the getSentinel() is called, and then dump the values going into that, e.g. the Asset, Scope, and Identity, and then look at the sentinel that is returned. Then debug back up to figure why those values are wrong, e.g. why you don’t have "edit" permissions (or the permissions you expect).
Check the Permissions table to confirm what permissions you have.
00000001 (001) - View 00000010 (002) - Create 00000100 (004) - Edit 00001000 (008) - Delete 00010000 (016) - Publish 00100000 (032) - Designer 01000000 (064) - Dev 10000000 (128) - Master (can do anything on the current asset)
00000001 (001) Authenticated - View 00000011 (003) Writer - View, Create 00000111 (015) Editor - View, Create, Edit, Delete 00001111 (031) Publisher - View, Create, Edit, Publish, Delete 00010101 (029) Moderator - View, Edit, Publish, Delete 10000000 (128) Admin - Full
When a new user is created, they are granted Permissions to themselves (so they can edit their user account):
- vendorId & siteId: NULL (because ownership of your own user asset is not restricted to a specific site or vendor scope) userId & identityUserId: equals the user.id
-asset: "SitetheoryUserBundle:User"
- assetId: equals their user.id
- permissions: 128 (full master ownership)
The user is also associated with the site where they created their user account, by granting basic View access to this site. This allows the site to have permission to know which users were created on their site. If the same user creates an account on another site, a new user is not created, instead they just get another view permission on that site. The site will only have permission to view the username, email, and phone of the user (plus any other public information or information the user agreed to share).
- siteId: site.id (where user was created)
- userId: NULL (user scope not restricted) identityUserId: equals their user.id (the owning identity) - asset: "SitetheoryHostingBundle:Site" - assetId: equals the site.id - permissions: 1 (view only)
When you create a new site you are granted full ownership permissions on the site.
- siteId: equals site.id just created
- userId: NULL (user scope not restricted)
- identityUserId: equals their user.id (the owning identity)
- asset: "SitetheoryHostingBundle:Site"
- assetId: equals site.id just created
- permissions: 128 (full master ownership)
When you are invited to edit a site that you didn’t create, your permissions will be restricted to whatever access you were given by the administrator. See Proposal System for more info on workflow.
- siteId: equals site.id being invited to edit (where invitation was sent from)
- userId: NULL (user scope not restricted)
- identityUserId: equals their user.id (the owning identity)
- asset: "SitetheoryHostingBundle:Site"
- assetId: equals site.id being invited to edit
- permissions: byte value signifying permissions being granted
- Roles: Roles can be created (core.user_role) and granted to existing users who have accepted permission (e.g. have a permission record where the asset is for the current site), or users can be invited by granting a role. The process is the same as specifying permissions, it’s just that permissions are assigned to the role first (roleIdentityId versus userIdentityId), and then the user is granted that role in AssignedRoles (core.user_assigned_role).
- siteId: equals site.id being edited when the role is created
- userId: NULL (user scope not restricted)
- identityUserId: NULL (permissions for entire role, not an individual user)
- identityRoleId: equals their role.id (the owning identity)
- asset: "SitetheoryHostingBundle:Site"
- assetId: equals site.id being edited
- permissions: byte value signifying permissions being granted for the entire role.