Secure a session#
This feature is not part of the community edition: it needs to be unlocked.
By default, no authentication is required to query an Atoti session and the users have access to all the data in the session.
This shows how to configure:
An authentication mechanism to secure access to the session.
Restrictions to control the data each user is allowed to see.
Configuring authentication#
Atoti supports multiple authentication mechanisms.
Here we’ll use OpenID Connect:
[1]:
from pathlib import Path
resources_directory = Path().cwd().parent / "getting_started" / "tutorial" / "data"
[2]:
import os
import atoti as tt
session_config = tt.SessionConfig(
security=tt.SecurityConfig(
sso=tt.OidcConfig(
access_token_format="opaque", # noqa: S106
client_id=os.environ["OIDC_CLIENT_ID"],
client_secret=os.environ["OIDC_CLIENT_SECRET"],
issuer_url=os.environ["OIDC_ISSUER_URL"],
name_claim="preferred_username",
provider_id="unused",
roles_claims={("resource_access", os.environ["OIDC_CLIENT_ID"], "roles")},
scopes={"openid", "profile", "roles"},
),
),
)
[3]:
session = tt.Session.start(session_config)
[4]:
sales_table = session.read_csv(
resources_directory / "sales.csv", keys={"Sale ID"}, table_name="Sales"
)
[5]:
shops_table = session.read_csv(
resources_directory / "shops.csv", keys={"Shop ID"}, table_name="Shops"
)
[6]:
sales_table.join(shops_table, sales_table["Shop"] == shops_table["Shop ID"])
[7]:
session.tables.schema
[7]:
[8]:
cube = session.create_cube(sales_table)
The users configured in the OIDC provider are:
global-user with the role user.
french-user with the roles france and atoti.
parisian-user with the roles paris and atoti.
Querying the session#
[11]:
def query(session_url: str, /, *, impersonated_username: str):
authentication = tt.OAuth2ResourceOwnerPasswordAuthentication(
client_id=os.environ["OIDC_CLIENT_ID"],
client_secret=os.environ["OIDC_CLIENT_SECRET"],
issuer_url=os.environ["OIDC_ISSUER_URL"],
# To keep things simple in this how-to, all the users share the same password.
password=os.environ["OIDC_USER_PASSWORD"],
scopes={"openid"},
username=impersonated_username,
)
with tt.Session.connect(session_url, authentication=authentication) as session:
cube = next(iter(session.cubes.values()))
return cube.query(
cube.measures["Quantity.SUM"],
levels=[cube.levels["City"]],
include_totals=True,
)
First, we can check that parisian-user can only see data for shops in Paris:
[12]:
query(session.url, impersonated_username="parisian-user")
[12]:
Quantity.SUM | |
---|---|
City | |
Total | 603.00 |
Paris | 603.00 |
french-user can only see data for shops in France:
[13]:
query(session.url, impersonated_username="french-user")
[13]:
Quantity.SUM | |
---|---|
City | |
Total | 3,027.00 |
Lyon | 609.00 |
Marseille | 603.00 |
Nice | 609.00 |
Paris | 603.00 |
Saint-Étienne | 603.00 |
And finally, global-user can see data for all the shops:
[14]:
query(session.url, impersonated_username="global-user")
[14]:
Quantity.SUM | |
---|---|
City | |
Total | 8,077.00 |
Chicago | 603.00 |
Houston | 606.00 |
Los Angeles | 606.00 |
Lyon | 609.00 |
Marseille | 603.00 |
New York | 808.00 |
Nice | 609.00 |
Paris | 603.00 |
Saint-Étienne | 603.00 |
San Antonio | 606.00 |
San Diego | 606.00 |
San Francisco | 612.00 |
San Jose | 603.00 |