Skip to content

Commit

Permalink
Use fab models (#19121)
Browse files Browse the repository at this point in the history
* Use FAB models.

* Use FAB models.

* Remove incorrect conversions to new permission naming scheme.

* Fix missing FAB renames.

* Remove unused FAB compatibility fixes in models.py.

* Remove additional uses of view_menu.

* Move airflow/www imports from global to function local.
  • Loading branch information
jhtimmins authored Oct 29, 2021
1 parent 2dfe85d commit e6ce871
Show file tree
Hide file tree
Showing 22 changed files with 523 additions and 322 deletions.
3 changes: 2 additions & 1 deletion airflow/api/auth/backend/basic_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@

from flask import Response, current_app, request
from flask_appbuilder.const import AUTH_LDAP
from flask_appbuilder.security.sqla.models import User
from flask_login import login_user

from airflow.www.fab_security.sqla.models import User

CLIENT_AUTH: Optional[Union[Tuple[str, str], Any]] = None


Expand Down
16 changes: 6 additions & 10 deletions airflow/api_connexion/endpoints/role_and_permission_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
# under the License.

from flask import current_app, request
from flask_appbuilder.security.sqla.models import Permission, Role
from marshmallow import ValidationError
from sqlalchemy import func

Expand All @@ -32,6 +31,7 @@
role_schema,
)
from airflow.security import permissions
from airflow.www.fab_security.sqla.models import Action, Role


def _check_action_and_resource(sm, perms):
Expand Down Expand Up @@ -73,13 +73,13 @@ def get_roles(limit, order_by='name', offset=None):
return role_collection_schema.dump(RoleCollection(roles=roles, total_entries=total_entries))


@security.requires_access([(permissions.ACTION_CAN_READ, permissions.RESOURCE_PERMISSION)])
@security.requires_access([(permissions.ACTION_CAN_READ, permissions.RESOURCE_ACTION)])
@format_parameters({'limit': check_limit})
def get_permissions(limit=None, offset=None):
"""Get permissions"""
session = current_app.appbuilder.get_session
total_entries = session.query(func.count(Permission.id)).scalar()
query = session.query(Permission)
total_entries = session.query(func.count(Action.id)).scalar()
query = session.query(Action)
actions = query.offset(offset).limit(limit).all()
return action_collection_schema.dump(ActionCollection(actions=actions, total_entries=total_entries))

Expand Down Expand Up @@ -120,9 +120,7 @@ def patch_role(role_name, update_mask=None):
raise BadRequest(detail=f"'{field}' in update_mask is unknown")
data = data_
if "permissions" in data:
perms = [
(item["permission"]["name"], item["view_menu"]["name"]) for item in data["permissions"] if item
]
perms = [(item["action"]["name"], item["resource"]["name"]) for item in data["permissions"] if item]
_check_action_and_resource(security_manager, perms)
security_manager.bulk_sync_roles([{"role": role_name, "perms": perms}])
new_name = data.get("name")
Expand All @@ -143,9 +141,7 @@ def post_role():
raise BadRequest(detail=str(err.messages))
role = security_manager.find_role(name=data['name'])
if not role:
perms = [
(item['permission']['name'], item['view_menu']['name']) for item in data['permissions'] if item
]
perms = [(item['action']['name'], item['resource']['name']) for item in data['permissions'] if item]
_check_action_and_resource(security_manager, perms)
security_manager.init_role(role_name=data['name'], perms=perms)
return role_schema.dump(role)
Expand Down
2 changes: 1 addition & 1 deletion airflow/api_connexion/endpoints/user_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# specific language governing permissions and limitations
# under the License.
from flask import current_app, request
from flask_appbuilder.security.sqla.models import User
from marshmallow import ValidationError
from sqlalchemy import func
from werkzeug.security import generate_password_hash
Expand All @@ -30,6 +29,7 @@
user_schema,
)
from airflow.security import permissions
from airflow.www.fab_security.sqla.models import User


@security.requires_access([(permissions.ACTION_CAN_READ, permissions.RESOURCE_USER)])
Expand Down
21 changes: 11 additions & 10 deletions airflow/api_connexion/schemas/role_and_permission_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@

from typing import List, NamedTuple

from flask_appbuilder.security.sqla.models import Permission, PermissionView, Role, ViewMenu
from marshmallow import Schema, fields
from marshmallow_sqlalchemy import SQLAlchemySchema, auto_field

from airflow.www.fab_security.sqla.models import Action, Permission, Resource, Role


class ActionSchema(SQLAlchemySchema):
"""Permission Action Schema"""
"""Action Action Schema"""

class Meta:
"""Meta"""

model = Permission
model = Action

name = auto_field()

Expand All @@ -39,15 +40,15 @@ class ResourceSchema(SQLAlchemySchema):
class Meta:
"""Meta"""

model = ViewMenu
model = Resource

name = auto_field()


class ActionCollection(NamedTuple):
"""Permission Action Collection"""
"""Action Action Collection"""

actions: List[Permission]
actions: List[Action]
total_entries: int


Expand All @@ -59,15 +60,15 @@ class ActionCollectionSchema(Schema):


class ActionResourceSchema(SQLAlchemySchema):
"""Permission View Schema"""
"""Action View Schema"""

class Meta:
"""Meta"""

model = PermissionView
model = Permission

permission = fields.Nested(ActionSchema, data_key="action")
view_menu = fields.Nested(ResourceSchema, data_key="resource")
action = fields.Nested(ActionSchema, data_key="action")
resource = fields.Nested(ResourceSchema, data_key="resource")


class RoleSchema(SQLAlchemySchema):
Expand Down
2 changes: 1 addition & 1 deletion airflow/api_connexion/schemas/user_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
# under the License.
from typing import List, NamedTuple

from flask_appbuilder.security.sqla.models import User
from marshmallow import Schema, fields
from marshmallow_sqlalchemy import SQLAlchemySchema, auto_field

from airflow.api_connexion.parameters import validate_istimezone
from airflow.api_connexion.schemas.role_and_permission_schema import RoleSchema
from airflow.www.fab_security.sqla.models import User


class UserCollectionItemSchema(SQLAlchemySchema):
Expand Down
44 changes: 22 additions & 22 deletions airflow/migrations/versions/849da589634d_prefix_dag_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
"""

from flask_appbuilder import SQLA
from flask_appbuilder.security.sqla.models import Permission, PermissionView, ViewMenu

from airflow import settings
from airflow.security import permissions
from airflow.www.fab_security.sqla.models import Action, Permission, Resource

# revision identifiers, used by Alembic.
revision = '849da589634d'
Expand All @@ -41,17 +41,17 @@ def prefix_individual_dag_permissions(session):
dag_perms = ['can_dag_read', 'can_dag_edit']
prefix = "DAG:"
perms = (
session.query(PermissionView)
.join(Permission)
.filter(Permission.name.in_(dag_perms))
.join(ViewMenu)
.filter(ViewMenu.name != 'all_dags')
.filter(ViewMenu.name.notlike(prefix + '%'))
session.query(Permission)
.join(Action)
.filter(Action.name.in_(dag_perms))
.join(Resource)
.filter(Resource.name != 'all_dags')
.filter(Resource.name.notlike(prefix + '%'))
.all()
)
resource_ids = {permission.view_menu.id for permission in perms}
vm_query = session.query(ViewMenu).filter(ViewMenu.id.in_(resource_ids))
vm_query.update({ViewMenu.name: prefix + ViewMenu.name}, synchronize_session=False)
resource_ids = {permission.resource.id for permission in perms}
vm_query = session.query(Resource).filter(Resource.id.in_(resource_ids))
vm_query.update({Resource.name: prefix + Resource.name}, synchronize_session=False)
session.commit()


Expand All @@ -60,7 +60,7 @@ def get_or_create_dag_resource(session):
if dag_resource:
return dag_resource

dag_resource = ViewMenu()
dag_resource = Resource()
dag_resource.name = permissions.RESOURCE_DAG
session.add(dag_resource)
session.commit()
Expand All @@ -73,7 +73,7 @@ def get_or_create_action(session, action_name):
if action:
return action

action = Permission()
action = Action()
action.name = action_name
session.add(action)
session.commit()
Expand All @@ -82,39 +82,39 @@ def get_or_create_action(session, action_name):


def get_resource_query(session, resource_name):
return session.query(ViewMenu).filter(ViewMenu.name == resource_name)
return session.query(Resource).filter(Resource.name == resource_name)


def get_action_query(session, action_name):
return session.query(Permission).filter(Permission.name == action_name)
return session.query(Action).filter(Action.name == action_name)


def get_permission_with_action_query(session, action):
return session.query(PermissionView).filter(PermissionView.permission == action)
return session.query(Permission).filter(Permission.action == action)


def get_permission_with_resource_query(session, resource):
return session.query(PermissionView).filter(PermissionView.view_menu_id == resource.id)
return session.query(Permission).filter(Permission.resource_id == resource.id)


def update_permission_action(session, permission_query, action):
permission_query.update({PermissionView.permission_id: action.id}, synchronize_session=False)
permission_query.update({Permission.action_id: action.id}, synchronize_session=False)
session.commit()


def get_permission(session, resource, action):
return (
session.query(PermissionView)
.filter(PermissionView.view_menu == resource)
.filter(PermissionView.permission == action)
session.query(Permission)
.filter(Permission.resource == resource)
.filter(Permission.action == action)
.first()
)


def update_permission_resource(session, permission_query, resource):
for permission in permission_query.all():
if not get_permission(session, resource, permission.permission):
permission.view_menu = resource
if not get_permission(session, resource, permission.action):
permission.resource = resource
else:
session.delete(permission)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@

mapping = {
("PermissionModelView", "can_list"): [
(permissions.ACTION_CAN_READ, permissions.RESOURCE_PERMISSION),
(permissions.ACTION_CAN_READ, permissions.RESOURCE_ACTION),
],
("PermissionViewModelView", "can_list"): [
(permissions.ACTION_CAN_READ, permissions.RESOURCE_PERMISSION_VIEW),
(permissions.ACTION_CAN_READ, permissions.RESOURCE_PERMISSION),
],
("ResetMyPasswordView", "can_this_form_get"): [
(permissions.ACTION_CAN_READ, permissions.RESOURCE_MY_PASSWORD),
Expand Down Expand Up @@ -76,25 +76,25 @@
(permissions.ACTION_CAN_CREATE, permissions.RESOURCE_ROLE),
],
("ViewMenuModelView", "can_list"): [
(permissions.ACTION_CAN_READ, permissions.RESOURCE_VIEW_MENU),
(permissions.ACTION_CAN_READ, permissions.RESOURCE_RESOURCE),
],
("UserDBModelView", "can_add"): [
(permissions.ACTION_CAN_CREATE, permissions.RESOURCE_VIEW_MENU),
(permissions.ACTION_CAN_CREATE, permissions.RESOURCE_RESOURCE),
],
("UserDBModelView", "can_userinfo"): [
(permissions.ACTION_CAN_READ, permissions.RESOURCE_MY_PROFILE),
],
("UserDBModelView", "can_download"): [
(permissions.ACTION_CAN_READ, permissions.RESOURCE_VIEW_MENU),
(permissions.ACTION_CAN_READ, permissions.RESOURCE_RESOURCE),
],
("UserDBModelView", "can_show"): [
(permissions.ACTION_CAN_READ, permissions.RESOURCE_VIEW_MENU),
(permissions.ACTION_CAN_READ, permissions.RESOURCE_RESOURCE),
],
("UserDBModelView", "can_list"): [
(permissions.ACTION_CAN_READ, permissions.RESOURCE_VIEW_MENU),
(permissions.ACTION_CAN_READ, permissions.RESOURCE_RESOURCE),
],
("UserDBModelView", "can_edit"): [
(permissions.ACTION_CAN_EDIT, permissions.RESOURCE_VIEW_MENU),
(permissions.ACTION_CAN_EDIT, permissions.RESOURCE_RESOURCE),
],
("UserDBModelView", "resetmypassword"): [
(permissions.ACTION_CAN_READ, permissions.RESOURCE_MY_PASSWORD),
Expand All @@ -106,7 +106,7 @@
(permissions.ACTION_CAN_EDIT, permissions.RESOURCE_MY_PROFILE),
],
("UserDBModelView", "can_delete"): [
(permissions.ACTION_CAN_DELETE, permissions.RESOURCE_VIEW_MENU),
(permissions.ACTION_CAN_DELETE, permissions.RESOURCE_RESOURCE),
],
("UserInfoEditView", "can_this_form_get"): [
(permissions.ACTION_CAN_READ, permissions.RESOURCE_MY_PROFILE),
Expand Down
17 changes: 8 additions & 9 deletions airflow/models/dagbag.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,25 +630,24 @@ def _serialize_dag_capturing_errors(dag, session):
@provide_session
def _sync_perm_for_dag(self, dag, session: Optional[Session] = None):
"""Sync DAG specific permissions, if necessary"""
from flask_appbuilder.security.sqla import models as sqla_models

from airflow.security.permissions import DAG_ACTIONS, resource_name_for_dag
from airflow.www.fab_security.sqla.models import Action, Permission, Resource

def needs_perm_views(dag_id: str) -> bool:
def needs_perms(dag_id: str) -> bool:
dag_resource_name = resource_name_for_dag(dag_id)
for permission_name in DAG_ACTIONS:
if not (
session.query(sqla_models.PermissionView)
.join(sqla_models.Permission)
.join(sqla_models.ViewMenu)
.filter(sqla_models.Permission.name == permission_name)
.filter(sqla_models.ViewMenu.name == dag_resource_name)
session.query(Permission)
.join(Action)
.join(Resource)
.filter(Action.name == permission_name)
.filter(Resource.name == dag_resource_name)
.one_or_none()
):
return True
return False

if dag.access_control or needs_perm_views(dag.dag_id):
if dag.access_control or needs_perms(dag.dag_id):
self.log.debug("Syncing DAG permissions: %s to the DB", dag.dag_id)
from airflow.www.security import ApplessAirflowSecurityManager

Expand Down
6 changes: 3 additions & 3 deletions airflow/security/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# under the License.

# Resource Constants
RESOURCE_ACTION = "Permissions"
RESOURCE_ADMIN_MENU = "Admin"
RESOURCE_AIRFLOW = "Airflow"
RESOURCE_AUDIT_LOG = "Audit Logs"
Expand All @@ -35,11 +36,11 @@
RESOURCE_MY_PASSWORD = "My Password"
RESOURCE_MY_PROFILE = "My Profile"
RESOURCE_PASSWORD = "Passwords"
RESOURCE_PERMISSION = "Permissions"
RESOURCE_PERMISSION_VIEW = "Permission Views" # Refers to a Perm <-> View mapping, not an MVC View.
RESOURCE_PERMISSION = "Permission Views" # Refers to a Perm <-> View mapping, not an MVC View.
RESOURCE_POOL = "Pools"
RESOURCE_PLUGIN = "Plugins"
RESOURCE_PROVIDER = "Providers"
RESOURCE_RESOURCE = "View Menus"
RESOURCE_ROLE = "Roles"
RESOURCE_SLA_MISS = "SLA Misses"
RESOURCE_TASK_INSTANCE = "Task Instances"
Expand All @@ -49,7 +50,6 @@
RESOURCE_USER = "Users"
RESOURCE_USER_STATS_CHART = "User Stats Chart"
RESOURCE_VARIABLE = "Variables"
RESOURCE_VIEW_MENU = "View Menus"
RESOURCE_WEBSITE = "Website"
RESOURCE_XCOM = "XComs"

Expand Down
Loading

0 comments on commit e6ce871

Please sign in to comment.