Skip to content

Commit

Permalink
Refactor permission models to make it easier to use with dynamic conf…
Browse files Browse the repository at this point in the history
…ig generation and without any parsers
  • Loading branch information
littleK0i committed May 21, 2024
1 parent c846f90 commit e43f17e
Show file tree
Hide file tree
Showing 15 changed files with 161 additions and 98 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [0.28.1] - 2024-05-21

- Refactored default permission model to init into `Config` class directly. No longer depends on parser.
- Refactored `DatabaseBlueprint` and `SchemaBlueprint` to make `permission_model` back to string and make it optional. It should help to simplify dynamic config generation scenarios when permission models do not matter.

## [0.28.0] - 2024-05-16

- Implemented more advanced pattern matching with wildcards, which is used primarily for business roles.
Expand Down
10 changes: 9 additions & 1 deletion snowddl/blueprint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@
T_Blueprint,
)

from .column import DynamicTableColumn, ExternalTableColumn, TableColumn, ViewColumn, ArgumentWithType, NameWithType, SearchOptimizationItem
from .column import (
DynamicTableColumn,
ExternalTableColumn,
TableColumn,
ViewColumn,
ArgumentWithType,
NameWithType,
SearchOptimizationItem,
)
from .data_type import BaseDataType, DataType
from .edition import Edition
from .grant import Grant, AccountGrant, FutureGrant
Expand Down
15 changes: 11 additions & 4 deletions snowddl/blueprint/blueprint.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
from abc import ABC
from typing import Optional, List, Dict, Set, Union, TypeVar

from .column import DynamicTableColumn, ExternalTableColumn, TableColumn, ViewColumn, ArgumentWithType, NameWithType, SearchOptimizationItem
from .column import (
DynamicTableColumn,
ExternalTableColumn,
TableColumn,
ViewColumn,
ArgumentWithType,
NameWithType,
SearchOptimizationItem,
)
from .data_type import DataType
from .grant import AccountGrant, Grant, FutureGrant
from .ident import (
Expand All @@ -19,7 +27,6 @@
TableConstraintIdent,
)
from .object_type import ObjectType
from .permission_model import PermissionModel
from .reference import ForeignKeyReference, IndexReference, MaskingPolicyReference, RowAccessPolicyReference, TagReference
from .stage import StageWithPath
from ..model import BaseModelWithConfig
Expand Down Expand Up @@ -64,7 +71,7 @@ class BusinessRoleBlueprint(RoleBlueprint):

class DatabaseBlueprint(AbstractBlueprint):
full_name: DatabaseIdent
permission_model: PermissionModel
permission_model: Optional[str] = None
is_transient: Optional[bool] = None
retention_time: Optional[int] = None
is_sandbox: Optional[bool] = None
Expand Down Expand Up @@ -266,7 +273,7 @@ class RowAccessPolicyBlueprint(SchemaObjectBlueprint):

class SchemaBlueprint(AbstractBlueprint):
full_name: SchemaIdent
permission_model: PermissionModel
permission_model: Optional[str] = None
is_transient: Optional[bool] = None
retention_time: Optional[int] = None
is_sandbox: Optional[bool] = None
Expand Down
79 changes: 74 additions & 5 deletions snowddl/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@
from fnmatch import translate
from pathlib import Path
from re import compile
from typing import Dict, List, Type, Union
from typing import Dict, List, Type, Optional, Union

from snowddl.blueprint import AbstractBlueprint, AbstractIdentWithPrefix, PermissionModel, T_Blueprint
from snowddl.blueprint import (
AbstractBlueprint,
AbstractIdentWithPrefix,
ObjectType,
PermissionModel,
PermissionModelRuleset,
PermissionModelCreateGrant,
PermissionModelFutureGrant,
T_Blueprint,
)


class SnowDDLConfig:
Expand All @@ -31,7 +40,7 @@ def __init__(self, env_prefix=None):
self.errors: List[dict] = []

self.placeholders: Dict[str, Union[bool, float, int, str]] = {}
self.permission_models: Dict[str, PermissionModel] = {}
self.permission_models: Dict[str, PermissionModel] = self._init_permission_models()

def get_blueprints_by_type(self, cls: Type[T_Blueprint]) -> Dict[str, T_Blueprint]:
return self.blueprints.get(cls, {})
Expand Down Expand Up @@ -78,8 +87,10 @@ def get_placeholder(self, name: str) -> Union[bool, float, int, str]:

return self.placeholders[name]

def get_permission_model(self, name: str) -> PermissionModel:
if name not in self.permission_models:
def get_permission_model(self, name: Optional[str]) -> PermissionModel:
if name is None:
name = self.DEFAULT_PERMISSION_MODEL
elif name not in self.permission_models:
raise ValueError(f"Unknown permission model [{name}]")

return self.permission_models[name]
Expand Down Expand Up @@ -118,3 +129,61 @@ def _init_env_prefix(self, env_prefix):
return f"{env_prefix}__"

return ""

def _init_permission_models(self):
return {
self.DEFAULT_PERMISSION_MODEL: PermissionModel(
ruleset=PermissionModelRuleset.SCHEMA_OWNER,
owner_create_grants=[
PermissionModelCreateGrant(on=ObjectType.FILE_FORMAT),
PermissionModelCreateGrant(on=ObjectType.FUNCTION),
PermissionModelCreateGrant(on=ObjectType.PROCEDURE),
PermissionModelCreateGrant(on=ObjectType.TABLE),
PermissionModelCreateGrant(on=ObjectType.VIEW),
],
owner_future_grants=[
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.ALERT),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.DYNAMIC_TABLE),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.EVENT_TABLE),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.EXTERNAL_TABLE),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.FILE_FORMAT),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.FUNCTION),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.MATERIALIZED_VIEW),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.PIPE),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.PROCEDURE),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.SEQUENCE),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.STAGE),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.STREAM),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.TABLE),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.TASK),
PermissionModelFutureGrant(privilege="OWNERSHIP", on=ObjectType.VIEW),
],
write_future_grants=[
PermissionModelFutureGrant(privilege="READ", on=ObjectType.STAGE),
PermissionModelFutureGrant(privilege="WRITE", on=ObjectType.STAGE),
PermissionModelFutureGrant(privilege="USAGE", on=ObjectType.STAGE),
PermissionModelFutureGrant(privilege="USAGE", on=ObjectType.SEQUENCE),
PermissionModelFutureGrant(privilege="INSERT", on=ObjectType.TABLE),
PermissionModelFutureGrant(privilege="UPDATE", on=ObjectType.TABLE),
PermissionModelFutureGrant(privilege="DELETE", on=ObjectType.TABLE),
PermissionModelFutureGrant(privilege="TRUNCATE", on=ObjectType.TABLE),
],
read_future_grants=[
PermissionModelFutureGrant(privilege="SELECT", on=ObjectType.DYNAMIC_TABLE),
PermissionModelFutureGrant(privilege="SELECT", on=ObjectType.EXTERNAL_TABLE),
PermissionModelFutureGrant(privilege="REFERENCES", on=ObjectType.EXTERNAL_TABLE),
PermissionModelFutureGrant(privilege="USAGE", on=ObjectType.FILE_FORMAT),
PermissionModelFutureGrant(privilege="USAGE", on=ObjectType.FUNCTION),
PermissionModelFutureGrant(privilege="SELECT", on=ObjectType.MATERIALIZED_VIEW),
PermissionModelFutureGrant(privilege="REFERENCES", on=ObjectType.MATERIALIZED_VIEW),
PermissionModelFutureGrant(privilege="USAGE", on=ObjectType.PROCEDURE),
PermissionModelFutureGrant(privilege="READ", on=ObjectType.STAGE),
PermissionModelFutureGrant(privilege="USAGE", on=ObjectType.STAGE),
PermissionModelFutureGrant(privilege="SELECT", on=ObjectType.STREAM),
PermissionModelFutureGrant(privilege="SELECT", on=ObjectType.TABLE),
PermissionModelFutureGrant(privilege="REFERENCES", on=ObjectType.TABLE),
PermissionModelFutureGrant(privilege="SELECT", on=ObjectType.VIEW),
PermissionModelFutureGrant(privilege="REFERENCES", on=ObjectType.VIEW),
],
)
}
2 changes: 1 addition & 1 deletion snowddl/parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .network_policy import NetworkPolicyParser
from .network_rule import NetworkRuleParser
from .outbound_share import OutboundShareParser
from .permission_model import PermissionModelParser, default_permission_models
from .permission_model import PermissionModelParser
from .pipe import PipeParser
from .placeholder import PlaceholderParser
from .procedure import ProcedureParser
Expand Down
10 changes: 7 additions & 3 deletions snowddl/parser/business_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ def build_database_role_grants(self, full_database_name, grant_type):
grants = []

for database_bp in self.config.get_blueprints_by_type_and_pattern(DatabaseBlueprint, full_database_name).values():
if database_bp.permission_model.ruleset.create_database_owner_role:
database_permission_model = self.config.get_permission_model(database_bp.permission_model)

if database_permission_model.ruleset.create_database_owner_role:
# Databases with "database owner" permission model are assigned directly
grants.append(
Grant(
Expand All @@ -175,7 +177,7 @@ def build_database_role_grants(self, full_database_name, grant_type):
),
)
)
elif database_bp.permission_model.ruleset.create_schema_owner_role:
elif database_permission_model.ruleset.create_schema_owner_role:
# Databases with "schema owner" permission model are automatically expanded into individual schema roles
grants.extend(self.build_schema_role_grants(f"{database_bp.full_name.database}.*", grant_type))

Expand All @@ -188,7 +190,9 @@ def build_schema_role_grants(self, full_schema_name, grant_type):
grants = []

for schema_bp in self.config.get_blueprints_by_type_and_pattern(SchemaBlueprint, full_schema_name).values():
if not schema_bp.permission_model.ruleset.create_schema_owner_role and grant_type == self.config.OWNER_ROLE_TYPE:
schema_permission_model = self.config.get_permission_model(schema_bp.permission_model)

if not schema_permission_model.ruleset.create_schema_owner_role and grant_type == self.config.OWNER_ROLE_TYPE:
raise ValueError(
f"Cannot use parameter [schema_owner] for schema [{schema_bp.full_name.database}.{schema_bp.full_name.schema}] due to permission model. "
f"Ownership can only be granted on database level via [database_owner] parameter"
Expand Down
9 changes: 4 additions & 5 deletions snowddl/parser/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,11 @@ def load_blueprints(self):
if database_path.name.startswith("__"):
continue

database_name = database_path.name.upper()
database_params = self.parse_single_file(database_path / "params.yaml", database_json_schema)

database_name = database_path.name.upper()
database_permission_model = self.config.get_permission_model(
database_params.get("permission_model", self.config.DEFAULT_PERMISSION_MODEL).upper()
)
databases_permission_model_name = database_params.get("permission_model", self.config.DEFAULT_PERMISSION_MODEL).upper()
database_permission_model = self.config.get_permission_model(databases_permission_model_name)

if not database_permission_model.ruleset.create_database_owner_role:
for k in database_params:
Expand All @@ -92,7 +91,7 @@ def load_blueprints(self):

bp = DatabaseBlueprint(
full_name=DatabaseIdent(self.env_prefix, database_name),
permission_model=database_permission_model,
permission_model=databases_permission_model_name,
is_transient=database_params.get("is_transient", False),
retention_time=database_params.get("retention_time", None),
is_sandbox=database_params.get("is_sandbox", False),
Expand Down
9 changes: 8 additions & 1 deletion snowddl/parser/dynamic_table.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
from snowddl.blueprint import AccountObjectIdent, DynamicTableBlueprint, SchemaObjectIdent, DynamicTableColumn, Ident, build_schema_object_ident
from snowddl.blueprint import (
AccountObjectIdent,
DynamicTableBlueprint,
SchemaObjectIdent,
DynamicTableColumn,
Ident,
build_schema_object_ident,
)
from snowddl.parser.abc_parser import AbstractParser, ParsedFile


Expand Down
52 changes: 0 additions & 52 deletions snowddl/parser/permission_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,64 +62,12 @@
}


default_permission_models = {
"default": {
"ruleset": "SCHEMA_OWNER",
"owner_create_grants": [
"FILE_FORMAT",
"FUNCTION",
"PROCEDURE",
"TABLE",
"VIEW",
],
"owner_future_grants": {
"ALERT": ["OWNERSHIP"],
"DYNAMIC_TABLE": ["OWNERSHIP"],
"EVENT_TABLE": ["OWNERSHIP"],
"EXTERNAL_TABLE": ["OWNERSHIP"],
"FILE_FORMAT": ["OWNERSHIP"],
"FUNCTION": ["OWNERSHIP"],
"MATERIALIZED_VIEW": ["OWNERSHIP"],
"PIPE": ["OWNERSHIP"],
"PROCEDURE": ["OWNERSHIP"],
"SEQUENCE": ["OWNERSHIP"],
"STAGE": ["OWNERSHIP"],
"STREAM": ["OWNERSHIP"],
"TABLE": ["OWNERSHIP"],
"TASK": ["OWNERSHIP"],
"VIEW": ["OWNERSHIP"],
},
"write_future_grants": {
"STAGE": ["READ", "WRITE", "USAGE"],
"SEQUENCE": ["USAGE"],
"TABLE": ["INSERT", "UPDATE", "DELETE", "TRUNCATE"],
},
"read_future_grants": {
"DYNAMIC_TABLE": ["SELECT"],
"EXTERNAL_TABLE": ["SELECT", "REFERENCES"],
"FILE_FORMAT": ["USAGE"],
"FUNCTION": ["USAGE"],
"MATERIALIZED_VIEW": ["SELECT", "REFERENCES"],
"PROCEDURE": ["USAGE"],
"STAGE": ["READ", "USAGE"],
"STREAM": ["SELECT"],
"TABLE": ["SELECT", "REFERENCES"],
"VIEW": ["SELECT", "REFERENCES"],
},
},
}
# fmt: on


class PermissionModelParser(AbstractParser):
def load_blueprints(self):
# This is a special parser that does not load any blueprints, but it loads permission models instead
pass

def load_permission_models(self):
for name, params in default_permission_models.items():
self.build_permission_model(name, params)

custom_permission_models = self.parse_single_file(self.base_path / "permission_model.yaml", permission_model_json_schema)

for name, params in custom_permission_models.items():
Expand Down
10 changes: 5 additions & 5 deletions snowddl/parser/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ def load_blueprints(self):
database_name = database_path.name.upper()
schema_name = schema_path.name.upper()

database_permission_model_name = database_params.get("permission_model", self.config.DEFAULT_PERMISSION_MODEL)
schema_permission_model_name = schema_params.get("permission_model", database_permission_model_name)
database_permission_model_name = database_params.get("permission_model", self.config.DEFAULT_PERMISSION_MODEL).upper()
schema_permission_model_name = schema_params.get("permission_model", database_permission_model_name).upper()

database_permission_model = self.config.get_permission_model(database_permission_model_name.upper())
schema_permission_model = self.config.get_permission_model(schema_permission_model_name.upper())
database_permission_model = self.config.get_permission_model(database_permission_model_name)
schema_permission_model = self.config.get_permission_model(schema_permission_model_name)

if database_permission_model.ruleset != schema_permission_model.ruleset:
raise ValueError(
Expand Down Expand Up @@ -132,7 +132,7 @@ def load_blueprints(self):

bp = SchemaBlueprint(
full_name=SchemaIdent(self.env_prefix, database_name, schema_name),
permission_model=schema_permission_model,
permission_model=schema_permission_model_name,
is_transient=combined_params.get("is_transient", False),
retention_time=combined_params.get("retention_time", None),
is_sandbox=combined_params.get("is_sandbox", False),
Expand Down
Loading

0 comments on commit e43f17e

Please sign in to comment.