atoti.Cube.restrictions#

property Cube.restrictions: MutableMapping[str, IsInCondition[LevelIdentifier, 'IS_IN', Constant] | RelationalCondition[LevelIdentifier, 'EQ', Constant] | LogicalCondition[IsInCondition[LevelIdentifier, 'IS_IN', Constant] | RelationalCondition[LevelIdentifier, 'EQ', Constant], 'AND']]#

Mapping from role name to the corresponding restriction.

Restrictions limit the data accessible to users based on their roles.

  • Restrictions on different hierarchies are intersected.

  • Restrictions on the same hierarchy are unioned.

Warning

A QueryCube only enforces its own restrictions, it ignores the ones of the contributing data cubes.

Example

>>> session_config = tt.SessionConfig(security=tt.SecurityConfig())
>>> session = tt.Session.start(session_config)
>>> 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"],
... )
>>> 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 level_name in cube.hierarchies["Geography"]:
...     del cube.hierarchies[level_name]

Adding a user to the session:

>>> password = "abcdef123456"
>>> session.security.basic_authentication.credentials["Rose"] = password
>>> session.security.individual_roles["Rose"] = {"ROLE_USER"}
>>> rose_session = tt.Session.connect(
...     session.url,
...     authentication=tt.BasicAuthentication("Rose", password),
... )
>>> rose_cube = rose_session.cubes[cube.name]

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

>>> rose_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

Assigning a role to Rose to limit her access to France only:

>>> cube.restrictions["ROLE_FRANCE"] = l["Country"] == "France"
>>> session.security.individual_roles["Rose"] |= {"ROLE_FRANCE"}
>>> rose_cube.query(
...     m["contributors.COUNT"], include_totals=True, levels=[l["Country"]]
... )
                  contributors.COUNT
Continent Country
Total                              1
Europe                             1
          France                   1

Unlike atoti.tables.Tables.restrictions, cube restrictions have no impact on tables:

>>> rose_table = rose_session.tables[table.name]
>>> rose_table.query().set_index(["Continent", "Country"]).sort_index()
                  Currency
Continent Country
Asia      Japan        JPY
          Korea        KRW
Europe    France       EUR
          Germany      EUR
          Norway       NOK
          Sweden       SEK

Adding Lena with ROLE_GERMANY limiting her access to Germany only:

>>> cube.restrictions["ROLE_GERMANY"] = l["Country"] == "Germany"
>>> session.security.basic_authentication.credentials["Lena"] = password
>>> session.security.individual_roles["Lena"] = {
...     "ROLE_GERMANY",
...     "ROLE_USER",
... }
>>> lena_session = tt.Session.connect(
...     session.url,
...     authentication=tt.BasicAuthentication("Lena", password),
... )
>>> lena_cube = lena_session.cubes[cube.name]
>>> lena_cube.query(m["contributors.COUNT"], levels=[l["Country"]])
                  contributors.COUNT
Continent Country
Europe    Germany                  1

Assigning ROLE_GERMANY to Rose lets her access the union of the restricted countries:

>>> session.security.individual_roles["Rose"] |= {"ROLE_GERMANY"}
>>> cube.restrictions
{'ROLE_FRANCE': l['Restrictions example', 'Geography', 'Country'] == 'France', 'ROLE_GERMANY': l['Restrictions example', 'Geography', 'Country'] == 'Germany'}
>>> rose_cube.query(m["contributors.COUNT"], levels=[l["Country"]])
                  contributors.COUNT
Continent Country
Europe    France                   1
          Germany                  1

Restrictions can include multiple elements:

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

Since Country and Continent are part of the same Geography hierarchy, restrictions on these two levels are unioned:

>>> cube.restrictions["ROLE_ASIA"] = l["Continent"] == "Asia"
>>> session.security.individual_roles["Rose"] |= {"ROLE_ASIA"}
>>> rose_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

Currency is part of a different hierarchy so restrictions on it are intersected with the ones from Geography:

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

Removing the ROLE_FRANCE and ROLE_GERMANY roles leaves no remaining accessible countries:

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

A QuerySession has cubes but no tables so there is nothing to merge cube restrictions with. However, data cubes have their restrictions merged with the ones from the session’s tables:

>>> session.tables.restrictions.update(
...     {
...         "ROLE_SEK": table["Currency"] == "SEK",
...         "ROLE_JPY": table["Currency"] == "JPY",
...     }
... )
>>> session.security.individual_roles["Rose"] = {
...     "ROLE_ASIA",  # Cube restriction
...     "ROLE_NORDIC",  # Cube restriction
...     "ROLE_SEK",  # Tables restriction
...     "ROLE_JPY",  # Tables restriction
...     "ROLE_USER",
... }
>>> rose_table.query().set_index(["Continent", "Country"]).sort_index()
                  Currency
Continent Country
Asia      Japan        JPY
Europe    Sweden       SEK
>>> rose_cube.query(
...     m["contributors.COUNT"], levels=[l["Country"], l["Currency"]]
... )
                           contributors.COUNT
Continent Country Currency
Asia      Japan   JPY                       1
Europe    Sweden  SEK                       1