graphql-engine/docs/graphql/manual/auth/authorization/roles-variables.rst

247 lines
9.3 KiB
ReStructuredText
Raw Normal View History

2019-05-17 15:03:35 +03:00
Roles & Session variables
=========================
.. contents:: Table of contents
:backlinks: none
:depth: 1
:local:
Roles
-----
Every table/view can have permission rules for users based on their role.
By default, there is an ``admin`` role that can perform any operation on any table.
You can define roles and then create permissions for each of these roles:
Examples:
+-----------+-----------------------------------+---------------------------------------+
| user | A logged-in user | CRUD on data that belongs to them |
+-----------+-----------------------------------+---------------------------------------+
| anonymous | A not logged-in user | Only read from some tables/views |
+-----------+-----------------------------------+---------------------------------------+
| manager | A user that has access to other | CRUD on all users data |
| | user's data | |
+-----------+-----------------------------------+---------------------------------------+
.. admonition:: Role-based schemas
For every role that you create, Hasura automatically publishes a different GraphQL schema that represents the
right queries, fields, and mutations that are available to that role.
Dynamic session variables
-------------------------
When you create a permission, or an access control rule, the permission rule itself needs access to some variables
that are derived from the request itself. Let's refer to these as *session variables*.
For example: If a user makes a request, the session token maps to a ``user-id``. This ``user-id`` can be used in
a permission to show that inserts into a table are only allowed if the ``user_id`` column has a value equal to that
of ``user-id``, the session variable.
When you are constructing permission rules, however, there might be several variables that represent the business logic
of having access to data. For example, if you have a SaaS application, you might restrict access based on a ``client_id``
2019-09-11 10:17:14 +03:00
variable. If you want to provide different levels of access on different devices, you might restrict access based on a
2019-05-17 15:03:35 +03:00
``device_type`` variable.
Hasura allows you to create permission rules that can use any dynamic variable that is a property of the request.
All your dynamic variables must follow the naming convention ``X-Hasura-*``.
Examples:
.. list-table::
:header-rows: 1
:widths: 20 10 20 50
* - Example
- Role
- Condition
- Permission expression
* - Allow access to user's own row
- ``user``
- ``user_id`` column is equal to ``session-user-id`` from a request
-
.. code-block:: json
{
"user_id": {
"_eq": "X-Hasura-User-Id"
}
}
* - Allow project admins access to anything that belongs to the project
- ``project-admin``
- ``project_id`` column is equal to ``project-id`` of the "session user"
-
.. code-block:: json
{
"project_id": {
"_eq": "X-Hasura-Project-Id"
}
}
Modelling Roles in Hasura
-------------------------
General guidelines for modelling roles in Hasura.
2019-09-11 10:17:14 +03:00
Roles are typically modelled in two ways:
2019-05-17 15:03:35 +03:00
1. **Hierarchical roles**: Access scopes are nested depending on available roles. `Roles in Github for organisations <https://help.github.com/en/articles/managing-peoples-access-to-your-organization-with-roles>`_
is a great example of such modelling where access scopes are inherited by deeper roles:
.. thumbnail:: ../../../../img/graphql/manual/auth/github-org-hierarchical-roles.png
2. **Flat roles**: Non-hierarchical roles with each role requiring an independent access scope to be defined.
**Roles in Hasura have to be defined in the latter way i.e. in a flat, non-hierarchical model**.
To convert the above hierarchical roles model into the one expected by Hasura, you will need to model roles as
partially captured by the table below (*showing access permissions for the* ``user`` *&* ``org-member`` *roles*,
``repositories`` *table and* ``select`` *operation*):
.. list-table::
:header-rows: 1
:widths: 25 20 45
* - Role
- Access Permissions
- Example permission rule
* - user
- Allow access to personally created repositories
-
.. code-block:: json
{
"creator_id": {
"_eq": "X-Hasura-User-Id"
}
}
* - org-member
2019-09-11 10:17:14 +03:00
- Allow access to personally created repositories and the organisation's repositories
2019-05-17 15:03:35 +03:00
-
.. code-block:: json
{
"_or": [
{
"creator_id": {
"_eq": "X-Hasura-User-Id"
}
},
{
"organization": {
"members": {
"member_id" : {
"_eq" : "X-Hasura-User-Id"
}
}
}
}
]
}
Making role-based user information available
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Effective permission rules require that information about which roles have access to which objects is available
when processing the permission rule. Different users with the same role or the same user with different roles
may have access to different sets of rows of the same table.
In some cases this is straightforward - for example, to restrict access for authors to only their articles, a
trivial row-level permission like ``"creator_id": {"_eq": "X-Hasura-User-Id"}`` will suffice. In others, like
our example in the previous section, this user information (*ownership or relationship*) must be available for
defining a permission rule.
2019-09-11 10:17:14 +03:00
These non-trivial use cases are to be handled differently based on whether this information is available in the same
2019-05-17 15:03:35 +03:00
database or not.
Relationship information is available in the same database
**********************************************************
Let's take a closer look at the permission rule for the ``org-member`` rule in the example from the previous
section. The rule reads as "*allow access to this repository if it was created by this user or if this user is
a member of the organisation that this repository belongs to*".
2019-09-11 10:17:14 +03:00
The crucial piece of user information that is presumed to be available in the same database and that makes this an
effective rule, is the mapping of users (*members*) to organizations.
2019-05-17 15:03:35 +03:00
Since this information is available in the same database, it can be easily leveraged via
:ref:`Relationships in permissions <relationships-in-permissions>` (*see this reference for another
example of the same kind*).
Relationship information is **not** available in the same database
******************************************************************
When this user information is not available in the database that Hasura is configured to use, session variables
are the only avenue to pass this information to a permission rule. In our example, the mapping of users (members)
2019-09-11 10:17:14 +03:00
to organizations may not have been available in the same database.
2019-05-17 15:03:35 +03:00
To convey this information, a session variable, say ``X-Hasura-Allowed-Organisations`` can be used by the
configured authentication to relay this information. We can then check for the following condition to emulate
2019-09-11 10:17:14 +03:00
the same rule: *is the organization that this repository belongs to part of the list of the organizations the
2019-05-17 15:03:35 +03:00
user is a member of*.
The permission for ``org-member`` role changes to this:
.. code-block:: json
{
"_or": [
{
"creator_id": {
"_eq": "X-Hasura-User-Id"
}
},
{
"organization_id": {
"_in": "X-Hasura-Allowed-Organisations"
}
}
]
}
2019-05-17 15:03:35 +03:00
.. admonition:: Arrays in permission rules
Support for using session variables for array operators like ``_in``, ``_nin``, ``_has_any_keys``,
``_has_all_keys`` is only added in ``beta.3`` release.
Format of session variables
---------------------------
Session variables are currently expected to be Strings and should be encoded as Postgres's literals for
the relevant type.
For example, in the above example, let's say ``creator_id`` and ``organisation_id`` columns are of
type ``integer``, then the values of ``X-Hasura-User-Id`` and ``X-Hasura-Allowed-Organisations`` should
be of type ``integer`` and ``integer[]`` (an integer array) respectively. To pass say a value ``1`` for
``X-Hasura-User-Id``, it'll be "``1``" and if the allowed organisations are ``1``, ``2`` and ``3``, then
``X-Hasura-Allowed-Organisations`` will be "``{1,2,3}``". ``{}`` is the syntax for specifying
`arrays in Postgres <https://www.postgresql.org/docs/current/arrays.html#ARRAYS-INPUT>`_.
The types and their formats are detailed `here <https://www.postgresql.org/docs/current/datatype.html>`_. When
in doubt about the Postgres format for a type, you can always test it in the SQL window. To check
if ``s`` is a valid literal for type ``t`` then, you can check it as follows:
.. code-block:: sql
select 's'::t;
If the above command returns data, then ``s`` is a valid literal of type ``t``. For example, to check
if ``{hello,world}`` is a valid format of type ``text[]``, you can run:
2019-05-17 15:03:35 +03:00
.. code-block:: sql
2019-05-17 15:03:35 +03:00
select '{hello,world}'::text[];
2019-05-17 15:03:35 +03:00
.. admonition:: JSON format
2019-05-17 15:03:35 +03:00
In future, we'll add support for passing session variables as JSON values where possible (i.e, auth
webhook and JWT but not in headers).