You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As outlined in #100 subitem 6, Coaster's current PermissionMixin mechanism is inadequate for query-based API calls (like GraphQL), as PermissionMixin only controls access to an API endpoint. We need a finegrained mechanism that determines read and write access to each column in a model.
We need a new RoleAccessMixin that provides mechanisms for defining and controlling access per column. It works like this:
Access is defined in terms of roles rather than permissions.
For each column, a list of read- and write-access roles are defined. (To be decided: does write access automatically imply read access? It does not in the case of password fields, but are there any other common examples?)
Access may be defined by adding a __roles__ dictionary to the model that uses the column name (or name of any attribute on the model) as the key, with the value as either a dictionary with write and read keys, or a two-tuple in (write_roles_list, read_roles_list) order, or a single-member tuple (write_roles_list) where read access is implicitly (and only) granted to anyone with write access. The roles are always a list. Subclasses must define roles as __roles__ = ParentClass.__roles__ + {…}. (Subclassing will require a bit more consideration, for whenever we actually run into the problem.)
This variable syntax for __roles__ makes defining it easy but reading it harder, so either we need a metaclass that rewrites it (similar to LabeledEnum's), or we restrict syntax to just the dictionary method {'column': {'read': ['all'], 'write': ['owner']}.
Analogous to PermissionMixin's permissions method, which accepts user and inherited parameters, we now have a roles method that takes the same parameters and returns all roles available to the user.
The roles method also accepts a token object instead of a user object. Tokens (as defined in API access architecture #100 subitem 3) using libmacaroons can provide access to a subset of a user's resources. It is unclear at this time how tokens will be defined, and a separate ticket is pending.
The role names all and user are reserved. The all role is always present, and the user role is granted to anyone logged in (ie, the user parameter to the roles method is a valid user or token).
Beware of having an owner role. As we've learnt in our user concepts exercise, creator and owner are distinct concepts. It helps to define these clearly at the start, perhaps as additional comments on this ticket.
We still need a mechanism to control access. A method named access_for (tentative name) takes a user, token or roles parameter (any one). If it gets user or token, it calls the roles method to get a list of roles. It then returns a wrapper dictionary object (with attribute access for easier syntax in Python code) that contains only the attributes that were in the __roles__ dictionary and for which the caller has a matching role. For the sake of efficiency, it lazy-loads contents.
The GraphQL API always returns the result of the access_for method, never the raw database object.
The text was updated successfully, but these errors were encountered:
As outlined in #100 subitem 6, Coaster's current
PermissionMixin
mechanism is inadequate for query-based API calls (like GraphQL), asPermissionMixin
only controls access to an API endpoint. We need a finegrained mechanism that determines read and write access to each column in a model.We need a new
RoleAccessMixin
that provides mechanisms for defining and controlling access per column. It works like this:Access is defined in terms of roles rather than permissions.
For each column, a list of read- and write-access roles are defined. (To be decided: does write access automatically imply read access? It does not in the case of password fields, but are there any other common examples?)
Access may be defined by adding a
__roles__
dictionary to the model that uses the column name (or name of any attribute on the model) as the key, with the value as either a dictionary withwrite
andread
keys, or a two-tuple in(write_roles_list, read_roles_list)
order, or a single-member tuple(write_roles_list)
where read access is implicitly (and only) granted to anyone with write access. The roles are always a list. Subclasses must define roles as__roles__ = ParentClass.__roles__ + {…}
. (Subclassing will require a bit more consideration, for whenever we actually run into the problem.)This variable syntax for
__roles__
makes defining it easy but reading it harder, so either we need a metaclass that rewrites it (similar toLabeledEnum
's), or we restrict syntax to just the dictionary method{'column': {'read': ['all'], 'write': ['owner']}
.Analogous to
PermissionMixin
'spermissions
method, which acceptsuser
andinherited
parameters, we now have aroles
method that takes the same parameters and returns all roles available to the user.The
roles
method also accepts atoken
object instead of auser
object. Tokens (as defined in API access architecture #100 subitem 3) using libmacaroons can provide access to a subset of a user's resources. It is unclear at this time how tokens will be defined, and a separate ticket is pending.The role names
all
anduser
are reserved. Theall
role is always present, and theuser
role is granted to anyone logged in (ie, theuser
parameter to theroles
method is a valid user or token).Beware of having an
owner
role. As we've learnt in our user concepts exercise,creator
andowner
are distinct concepts. It helps to define these clearly at the start, perhaps as additional comments on this ticket.We still need a mechanism to control access. A method named
access_for
(tentative name) takes auser
,token
orroles
parameter (any one). If it getsuser
ortoken
, it calls theroles
method to get a list of roles. It then returns a wrapper dictionary object (with attribute access for easier syntax in Python code) that contains only the attributes that were in the__roles__
dictionary and for which the caller has a matching role. For the sake of efficiency, it lazy-loads contents.The GraphQL API always returns the result of the
access_for
method, never the raw database object.The text was updated successfully, but these errors were encountered: