atoti_query.security.Security.restrictions#

property Security.restrictions: MutableMapping[str, Condition[ColumnIdentifier, Literal['eq', 'isin'], Constant, Literal['and'] | None]]#

Mapping from role name to corresponding restriction.

There are reserved roles for which restrictions cannot be declared:

  • ROLE_USER: gives access the application

  • ROLE_ADMIN: gives full access (read, write, delete, etc) to the application

The restrictions associated with a role can be modified at any time.

  • Restrictions apply on table columns and are inherited by all hierarchies based on these columns.

  • Restrictions on different hierarchies are intersected.

  • However, if a user has several roles with restrictions on the same hierarchies, access to the union of restricted elements will be granted.

Example

>>> df = pd.DataFrame(
...     [
...         ("Asia", "Korea", "KRW"),
...         ("Asia", "Japan", "JPY"),
...         ("Europe", "France", "EUR"),
...         ("Europe", "Germany", "EUR"),
...         ("Europe", "Norway", "NOK"),
...         ("Europe", "Sweden", "SEK"),
...     ],
...     columns=["Continent", "Country", "Currency"],
... )
>>> session = tt.Session(authentication=tt.BasicAuthenticationConfig())
>>> table = session.read_pandas(
...     df,
...     keys=["Continent", "Country", "Currency"],
...     table_name="Restrictions example",
... )
>>> cube = session.create_cube(table)
>>> h, l, m = cube.hierarchies, cube.levels, cube.measures
>>> cube.hierarchies["Geography"] = [
...     table["Continent"],
...     table["Country"],
... ]
>>> for name in cube.hierarchies["Geography"].levels:
...     del cube.hierarchies[name]
...
>>> username, password = "john", "abcdef123456"
>>> user = session.security.basic.credentials[username] = password

The user initially has no individual roles:

>>> username in session.security.individual_roles
False

Adding ROLE_USER to grant access to the application:

>>> session.security.individual_roles[username] = {"ROLE_USER"}

Opening a query session to authenticate as the user just created:

>>> query_session = tt.QuerySession(
...     f"http://localhost:{session.port}",
...     auth=tt.BasicAuthentication(
...         username=username, password=password
...     ),
... )
>>> query_cube = query_session.cubes[cube.name]
>>> l, m = query_cube.levels, query_cube.measures

ROLE_USER has no restrictions so all the countries and currencies are accessible:

>>> query_cube.query(
...     m["contributors.COUNT"], levels=[l["Country"], l["Currency"]]
... )
                           contributors.COUNT
Continent Country Currency
Asia      Japan   JPY                       1
          Korea   KRW                       1
Europe    France  EUR                       1
          Germany EUR                       1
          Norway  NOK                       1
          Sweden  SEK                       1

Adding a restricting role to the user so that only France is accessible:

>>> session.security.restrictions["ROLE_FRANCE"] = (
...     table["Country"] == "France"
... )
>>> session.security.individual_roles[username] |= {"ROLE_FRANCE"}
>>> query_cube.query(m["contributors.COUNT"], levels=[l["Country"]])
                  contributors.COUNT
Continent Country
Europe    France                   1

Restrictions on the same hierarchy grant access to the union of the restricted elements:

>>> session.security.restrictions["ROLE_GERMANY"] = (
...     table["Country"] == "Germany"
... )
>>> session.security.individual_roles[username] |= {"ROLE_GERMANY"}
>>> query_cube.query(m["contributors.COUNT"], levels=[l["Country"]])
                  contributors.COUNT
Continent Country
Europe    France                   1
          Germany                  1

Restrictions can grant access to multiple elements:

>>> session.security.restrictions["ROLE_NORDIC"] = table[
...     "Country"
... ].isin("Norway", "Sweden")
>>> session.security.individual_roles[username] |= {"ROLE_NORDIC"}
>>> query_cube.query(m["contributors.COUNT"], levels=[l["Country"]])
                  contributors.COUNT
Continent Country
Europe    France                   1
          Germany                  1
          Norway                   1
          Sweden                   1

Also give access to the Asian countries with a restriction on Continent:

>>> session.security.restrictions["ROLE_ASIA"] = (
...     table["Continent"] == "Asia"
... )
>>> session.security.individual_roles[username] |= {"ROLE_ASIA"}
>>> query_cube.query(m["contributors.COUNT"], levels=[l["Country"]])
                  contributors.COUNT
Continent Country
Asia      Japan                    1
          Korea                    1
Europe    France                   1
          Germany                  1
          Norway                   1
          Sweden                   1

Restrictions on different hierarchies are intersected:

>>> session.security.restrictions["ROLE_EUR"] = (
...     table["Currency"] == "EUR"
... )
>>> session.security.individual_roles[username] |= {"ROLE_EUR"}
>>> query_cube.query(
...     m["contributors.COUNT"], levels=[l["Country"], l["Currency"]]
... )
                           contributors.COUNT
Continent Country Currency
Europe    France  EUR                       1
          Germany EUR                       1

Removing the roles granting access to France and Germany leaves no remaining accessible countries:

>>> session.security.individual_roles[username] -= {
...     "ROLE_FRANCE",
...     "ROLE_GERMANY",
... }
>>> query_cube.query(m["contributors.COUNT"], levels=[l["Country"]])
Empty DataFrame
Columns: [contributors.COUNT]
Index: []