atoti_query.security.security module#

class atoti_query.security.Security#

Manage the parts of the security config that can be changed without restarting the Session.

The roles and restrictions are stored in the user content storage. Multiple sessions configured with the same user content storage will thus share their roles and restrictions.

Note

Users without the ROLE_USER will not be able to access the application.

property basic: BasicSecurity#
property individual_roles: IndividualRoles#
property kerberos: KerberosSecurity#
property ldap: LdapSecurity#
property oidc: OidcSecurity#
property restrictions: MutableMapping[str, Condition[ColumnCoordinates, 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: []