Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/resources/grant.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ grants:
on: warehouse some_warehouse
to: some_role

# Apps: USAGE on a Streamlit app so a role can open and run it
- priv: USAGE
on: streamlit somedb.someschema.some_streamlit
to: app_viewer_role

# AI: account-level privilege for Cortex AI SQL functions
- priv: USE AI FUNCTIONS
on: ACCOUNT
Expand Down
40 changes: 39 additions & 1 deletion docs/resources/streamlit.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,42 @@ streamlit_repo = Streamlit(
* `query_warehouse` (string) - The name of the warehouse to use for queries in the app.
* `comment` (string) - A comment or description for the Streamlit app.
* `owner` (string or Role) - The role that owns the Streamlit app. Defaults to "SYSADMIN".
* `tags` (dict) - A dictionary of tags to associate with the Streamlit app.
* `tags` (dict) - A dictionary of tags to associate with the Streamlit app.

## Granting access

A Streamlit app is a schema-scoped object. Grant `USAGE` on the app to let a
role open and run it — this is the whole access story for viewers when the app
uses owner's rights (all app queries execute as the app owner, so viewers need
no privileges on the underlying tables):

```yaml
grants:
# Let a viewer role open and run the app.
- priv: USAGE
on: streamlit my_db.my_schema.my_streamlit
to: app_viewer_role

# Schema-scope privilege to allow a role to create Streamlit apps.
- priv: CREATE STREAMLIT
on: schema my_db.my_schema
to: app_developer_role
```

```python
# Grant USAGE so a role can open the app
grant = Grant(
priv="USAGE",
on_streamlit="my_db.my_schema.my_streamlit",
to="app_viewer_role",
)
```

| Privilege | Purpose |
|-------------------|--------------------------------------------------------------------------|
| `USAGE` | Open, view, and run the Streamlit app (and `DESCRIBE` it). |
| `OWNERSHIP` | Full control. Set at create/deploy time — Snowflake does not support transferring streamlit ownership via `GRANT OWNERSHIP`. |
| `ALL` | All privileges above. |

The schema-scope privilege `CREATE STREAMLIT` lets a role create apps in that
schema; creating an app with a `ROOT_LOCATION` stage also needs `CREATE STAGE`.
24 changes: 24 additions & 0 deletions snowcap/data_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -2773,6 +2773,30 @@ def fetch_stream(session: SnowflakeConnection, fqn: FQN):
raise NotImplementedError(f"Unsupported stream source type {data['source_type']}")


def fetch_streamlit(session: SnowflakeConnection, fqn: FQN):
streamlits = _show_resources(session, "STREAMLITS", fqn)
if len(streamlits) == 0:
return None
if len(streamlits) > 1:
raise Exception(f"Found multiple streamlits matching {fqn}")

data = streamlits[0]
desc_result = execute(session, f"DESC STREAMLIT {fqn}", cacheable=True)
properties = desc_result[0]
return {
"name": data["name"],
# root_location is the stage the app was deployed from, e.g.
# '@db.schema.stage/app' — this is the `from_` field.
"from_": properties.get("root_location"),
"version": None,
"main_file": properties.get("main_file"),
"title": properties.get("title") or None,
"query_warehouse": data.get("query_warehouse") or None,
"comment": data.get("comment") or None,
"owner": _get_owner_identifier(data),
}


def fetch_tag(session: SnowflakeConnection, fqn: FQN):
try:
show_result = execute(session, "SHOW TAGS IN ACCOUNT", cacheable=True)
Expand Down
18 changes: 18 additions & 0 deletions tests/test_grant.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,24 @@ def test_grant_on_cortex_search_service():
assert "MONITOR ON CORTEX SEARCH SERVICE" in monitor_grant.create_sql()


def test_grant_on_streamlit():
"""USAGE on a STREAMLIT parses and renders correctly.

Streamlit is a schema-scoped app object. Granting USAGE lets a viewer
role open and run the app:
GRANT USAGE ON STREAMLIT <db>.<schema>.<app> TO ROLE r
OWNERSHIP is established at create/deploy time, not transferred via GRANT.
"""
grant = res.Grant(
priv="USAGE",
on_streamlit="somedb.someschema.someapp",
to="somerole",
)
assert grant._data.on == "SOMEDB.SOMESCHEMA.SOMEAPP"
assert grant._data.on_type == ResourceType.STREAMLIT
assert "USAGE ON STREAMLIT" in grant.create_sql()


def test_grant_database_role_to_database_role():
database = res.Database(name="somedb")
parent = res.DatabaseRole(name="parent", database=database)
Expand Down
19 changes: 19 additions & 0 deletions tests/test_privs.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,25 @@ def test_ownership_privilege(self):
assert CortexSearchServicePriv.OWNERSHIP.value == "OWNERSHIP"


#############################################################################
# StreamlitPriv Tests
#############################################################################


class TestStreamlitPriv:
"""Tests for StreamlitPriv enum values (Snowflake Streamlit apps)."""

def test_all_privilege(self):
assert StreamlitPriv.ALL.value == "ALL"

def test_usage_privilege(self):
"""USAGE is the priv needed to open and run a Streamlit app."""
assert StreamlitPriv.USAGE.value == "USAGE"

def test_ownership_privilege(self):
assert StreamlitPriv.OWNERSHIP.value == "OWNERSHIP"


#############################################################################
# is_ownership_priv() Tests
#############################################################################
Expand Down
Loading