Skip to content
Merged
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
89 changes: 87 additions & 2 deletions airflow/api_connexion/endpoints/role_and_permission_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from flask import current_app

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

from airflow.api_connexion import security
from airflow.api_connexion.exceptions import NotFound
from airflow.api_connexion.exceptions import AlreadyExists, BadRequest, NotFound
from airflow.api_connexion.parameters import apply_sorting, check_limit, format_parameters
from airflow.api_connexion.schemas.role_and_permission_schema import (
ActionCollection,
Expand All @@ -31,6 +34,19 @@
from airflow.security import permissions


def _check_action_and_resource(sm, perms):
"""
Checks if the action or resource exists and raise 400 if not

This function is intended for use in the REST API because it raise 400
"""
for item in perms:
if not sm.find_permission(item[0]):
raise BadRequest(detail=f"The specified action: '{item[0]}' was not found")
if not sm.find_view_menu(item[1]):
raise BadRequest(detail=f"The specified resource: '{item[1]}' was not found")


@security.requires_access([(permissions.ACTION_CAN_SHOW, permissions.RESOURCE_ROLE_MODEL_VIEW)])
def get_role(role_name):
"""Get role"""
Expand Down Expand Up @@ -66,3 +82,72 @@ def get_permissions(limit=None, offset=None):
query = session.query(Permission)
actions = query.offset(offset).limit(limit).all()
return action_collection_schema.dump(ActionCollection(actions=actions, total_entries=total_entries))


@security.requires_access([(permissions.ACTION_CAN_DELETE, permissions.RESOURCE_ROLE_MODEL_VIEW)])
def delete_role(role_name):
"""Delete a role"""
ab_security_manager = current_app.appbuilder.sm
role = ab_security_manager.find_role(name=role_name)
if not role:
raise NotFound(title="Role not found", detail=f"The Role with name `{role_name}` was not found")
ab_security_manager.delete_role(role_name=role_name)
Comment thread
ephraimbuddy marked this conversation as resolved.
Outdated
return NoContent, 204


@security.requires_access([(permissions.ACTION_CAN_EDIT, permissions.RESOURCE_ROLE_MODEL_VIEW)])
def patch_role(role_name, update_mask=None):
"""Update a role"""
appbuilder = current_app.appbuilder
security_manager = appbuilder.sm
body = request.json
try:
data = role_schema.load(body)
except ValidationError as err:
raise BadRequest(detail=str(err.messages))
role = security_manager.find_role(name=role_name)
if not role:
raise NotFound(title="Role not found", detail=f"Role with name: `{role_name} was not found")
if update_mask:
update_mask = [i.strip() for i in update_mask]
data_ = {}
for field in update_mask:
if field in data and not field == "permissions":
data_[field] = data[field]
elif field == "actions":
data_["permissions"] = data['permissions']
else:
raise BadRequest(detail=f"'{field}' in update_mask is unknown")
data = data_
perms = data.get("permissions", [])
if perms:
perms = [
(item['permission']['name'], item['view_menu']['name']) for item in data['permissions'] if item
]
_check_action_and_resource(security_manager, perms)
security_manager.update_role(pk=role.id, name=data['name'])
security_manager.init_role(role_name=data['name'], perms=perms or role.permissions)
return role_schema.dump(role)


@security.requires_access([(permissions.ACTION_CAN_ADD, permissions.RESOURCE_ROLE_MODEL_VIEW)])
Comment thread
ephraimbuddy marked this conversation as resolved.
Outdated
def post_role():
"""Create a new role"""
appbuilder = current_app.appbuilder
security_manager = appbuilder.sm
body = request.json
try:
data = role_schema.load(body)
except ValidationError as err:
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
]
_check_action_and_resource(security_manager, perms)
security_manager.init_role(role_name=data['name'], perms=perms)
return role_schema.dump(role)
raise AlreadyExists(
detail=f"Role with name `{role.name}` already exist. Please update with patch endpoint"
)
72 changes: 72 additions & 0 deletions airflow/api_connexion/openapi/v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,31 @@ paths:
'403':
$ref: '#/components/responses/PermissionDenied'

post:
summary: Create a role
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
operationId: post_role
tags: [Role]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Role'
responses:
'200':
description: Success.
content:
application/json:
schema:
$ref: '#/components/schemas/Role'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/PermissionDenied'

/roles/{role_name}:
parameters:
- $ref: '#/components/parameters/RoleName'
Expand All @@ -1435,6 +1460,53 @@ paths:
'404':
$ref: '#/components/responses/NotFound'

patch:
summary: Update a role
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
operationId: patch_role
tags: [Role]
parameters:
- $ref: '#/components/parameters/UpdateMask'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Role'

responses:
'200':
description: Success.
content:
application/json:
schema:
$ref: '#/components/schemas/Role'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/PermissionDenied'
'404':
$ref: '#/components/responses/NotFound'

delete:
summary: Delete a role
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
operationId: delete_role
tags: [Role]
responses:
'204':
description: Success.
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/PermissionDenied'
'404':
$ref: '#/components/responses/NotFound'

/permissions:
get:
summary: List permissions
Expand Down
1 change: 1 addition & 0 deletions airflow/security/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
RESOURCE_PERMISSION_MODEL_VIEW = "PermissionModelView"

# Action Constants
ACTION_CAN_ADD = "can_add"
Comment thread
ephraimbuddy marked this conversation as resolved.
Outdated
ACTION_CAN_LIST = "can_list"
ACTION_CAN_SHOW = "can_show"
ACTION_CAN_CREATE = "can_create"
Expand Down
Loading