atoti.tables.Tables.restrictions#

property Tables.restrictions: MutableMapping[str, TablesRestrictionCondition]#

Mapping from role name to corresponding restriction.

Restrictions limit the data accessible to users based on their roles. Restrictions apply on table columns and are inherited by all hierarchies based on these columns.

  • Restrictions on different columns/hierarchies are intersected.

  • Restrictions on the same column/hierarchy are unioned.

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_table = rose_session.tables[table.name]
>>> rose_cube = rose_session.cubes[cube.name]

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

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

And 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:

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

Adding Lena with ROLE_GERMANY limiting her access to Germany only:

>>> session.tables.restrictions["ROLE_GERMANY"] = (
...     table["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_table = lena_session.tables[table.name]
>>> lena_cube = lena_session.cubes[cube.name]
>>> lena_table.query()
  Continent  Country Currency
0    Europe  Germany      EUR
>>> 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"}
>>> session.tables.restrictions
{'ROLE_FRANCE': t['Restrictions example']['Country'] == 'France', 'ROLE_GERMANY': t['Restrictions example']['Country'] == 'Germany'}
>>> rose_table.query().set_index(["Continent", "Country"]).sort_index()
                  Currency
Continent Country
Europe    France       EUR
          Germany      EUR
>>> rose_cube.query(m["contributors.COUNT"], levels=[l["Country"]])
                  contributors.COUNT
Continent Country
Europe    France                   1
          Germany                  1

Restrictions can include multiple elements:

>>> session.tables.restrictions["ROLE_NORDIC"] = table["Country"].isin(
...     "Norway", "Sweden"
... )
>>> session.security.individual_roles["Rose"] |= {"ROLE_NORDIC"}
>>> rose_table.query().set_index(["Continent", "Country"]).sort_index()
                  Currency
Continent Country
Europe    France       EUR
          Germany      EUR
          Norway       NOK
          Sweden       SEK
>>> 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:

>>> session.tables.restrictions["ROLE_ASIA"] = table["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:

>>> session.tables.restrictions["ROLE_EUR"] = table["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_table.query()
Empty DataFrame
Columns: [Continent, Country, Currency]
Index: []
>>> rose_cube.query(m["contributors.COUNT"], levels=[l["Country"]])
Empty DataFrame
Columns: [contributors.COUNT]
Index: []