Skip to content

Commit

Permalink
Add readonly REST API endpoint for roles and permissions (#14664)
Browse files Browse the repository at this point in the history
Co-authored-by: Ash Berlin-Taylor <ash_github@firemirror.com>
  • Loading branch information
ephraimbuddy and ashb authored Mar 16, 2021
1 parent a85d840 commit 0e13458
Show file tree
Hide file tree
Showing 8 changed files with 581 additions and 5 deletions.
66 changes: 66 additions & 0 deletions airflow/api_connexion/endpoints/role_and_permission_endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# 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 flask_appbuilder.security.sqla.models import Permission, Role
from sqlalchemy import func

from airflow.api_connexion import security
from airflow.api_connexion.exceptions import NotFound
from airflow.api_connexion.parameters import check_limit, format_parameters
from airflow.api_connexion.schemas.role_and_permission_schema import (
ActionCollection,
RoleCollection,
action_collection_schema,
role_collection_schema,
role_schema,
)
from airflow.security import permissions


@security.requires_access([(permissions.ACTION_CAN_SHOW, permissions.RESOURCE_ROLE_MODEL_VIEW)])
def get_role(role_name):
"""Get 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")
return role_schema.dump(role)


@security.requires_access([(permissions.ACTION_CAN_LIST, permissions.RESOURCE_ROLE_MODEL_VIEW)])
@format_parameters({'limit': check_limit})
def get_roles(limit=None, offset=None):
"""Get roles"""
appbuilder = current_app.appbuilder
session = appbuilder.get_session
total_entries = session.query(func.count(Role.id)).scalar()
query = session.query(Role)

roles = query.order_by(Role.name).offset(offset).limit(limit).all()

return role_collection_schema.dump(RoleCollection(roles=roles, total_entries=total_entries))


@security.requires_access([(permissions.ACTION_CAN_LIST, permissions.RESOURCE_PERMISSION_MODEL_VIEW)])
@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)
actions = query.offset(offset).limit(limit).all()
return action_collection_schema.dump(ActionCollection(actions=actions, total_entries=total_entries))
144 changes: 143 additions & 1 deletion airflow/api_connexion/openapi/v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,72 @@ paths:
'404':
$ref: '#/components/responses/NotFound'

/roles:
get:
summary: List roles
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
operationId: get_roles
tags: [Role]
parameters:
- $ref: '#/components/parameters/PageLimit'
- $ref: '#/components/parameters/PageOffset'
responses:
'200':
description: Success.
content:
application/json:
schema:
$ref: '#/components/schemas/RoleCollection'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/PermissionDenied'

/roles/{role_name}:
parameters:
- $ref: '#/components/parameters/RoleName'

get:
summary: Get a role
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
operationId: get_role
tags: [Role]
responses:
'200':
description: Success.
content:
application/json:
schema:
$ref: '#/components/schemas/Role'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/PermissionDenied'
'404':
$ref: '#/components/responses/NotFound'

/permissions:
get:
summary: List permissions
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
operationId: get_permissions
tags: [Permission]
parameters:
- $ref: '#/components/parameters/PageLimit'
- $ref: '#/components/parameters/PageOffset'
responses:
'200':
description: Success.
content:
application/json:
schema:
$ref: '#/components/schemas/ActionCollection'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/PermissionDenied'


components:
# Reusable schemas (data models)
schemas:
Expand Down Expand Up @@ -2169,6 +2235,73 @@ components:
$ref: '#/components/schemas/PluginCollectionItem'
- $ref: '#/components/schemas/CollectionInfo'

Role:
description: Role item
type: object
properties:
name:
type: string
description: The name of the role
actions:
type: array
items:
$ref: '#/components/schemas/ActionResource'

RoleCollection:
description: Role Collections
type: object
allOf:
- type: object
properties:
roles:
type: array
items:
$ref: '#/components/schemas/Role'
- $ref: '#/components/schemas/CollectionInfo'

Action:
description: Action Item
type: object
properties:
name:
type: string
description: The name of the permission "action"
nullable: false

ActionCollection:
description: Action Collection
type: object
allOf:
- type: object
properties:
actions:
type: array
items:
$ref: '#/components/schemas/Action'
- $ref: '#/components/schemas/CollectionInfo'

Resource:
description: A "resource" on which permissions are granted.
type: object
properties:
name:
type: string
description: The name of the resource
nullable: false

ActionResource:
description: The Action-Resource item
type: object
properties:
action:
type: object
$ref: '#/components/schemas/Action'
description: The permission action
resource:
type: object
$ref: '#/components/schemas/Resource'
description: The permission resource

# Configuration
ConfigOption:
type: object
Expand Down Expand Up @@ -2672,6 +2805,14 @@ components:
description: The numbers of items to return.

# Database entity fields
RoleName:
in: path
name: role_name
schema:
type: string
required: true
description: The role name

ConnectionID:
in: path
name: connection_id
Expand Down Expand Up @@ -3016,7 +3157,8 @@ tags:
- name: Variable
- name: XCom
- name: Plugin

- name: Role
- name: Permission

externalDocs:
url: https://airflow.apache.org/docs/apache-airflow/stable/
101 changes: 101 additions & 0 deletions airflow/api_connexion/schemas/role_and_permission_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

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


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

class Meta:
"""Meta"""

model = Permission

name = auto_field()


class ResourceSchema(SQLAlchemySchema):
"""View menu Schema"""

class Meta:
"""Meta"""

model = ViewMenu

name = auto_field()


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

actions: List[Permission]
total_entries: int


class ActionCollectionSchema(Schema):
"""Permissions list schema"""

actions = fields.List(fields.Nested(ActionSchema))
total_entries = fields.Int()


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

class Meta:
"""Meta"""

model = PermissionView

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


class RoleSchema(SQLAlchemySchema):
"""Role item schema"""

class Meta:
"""Meta"""

model = Role

name = auto_field()
permissions = fields.List(fields.Nested(ActionResourceSchema), data_key='actions')


class RoleCollection(NamedTuple):
"""List of roles"""

roles: List[Role]
total_entries: int


class RoleCollectionSchema(Schema):
"""List of roles"""

roles = fields.List(fields.Nested(RoleSchema))
total_entries = fields.Int()


role_schema = RoleSchema()
role_collection_schema = RoleCollectionSchema()
action_collection_schema = ActionCollectionSchema()
4 changes: 4 additions & 0 deletions airflow/security/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@
RESOURCE_USER_LDAP_MODELVIEW = "UserLDAPModelView"
RESOURCE_USER_OAUTH_MODELVIEW = "UserOAuthModelView"
RESOURCE_USER_REMOTEUSER_MODELVIEW = "UserRemoteUserModelView"
RESOURCE_ROLE_MODEL_VIEW = "RoleModelView"
RESOURCE_PERMISSION_MODEL_VIEW = "PermissionModelView"

# Action Constants
ACTION_CAN_LIST = "can_list"
ACTION_CAN_SHOW = "can_show"
ACTION_CAN_CREATE = "can_create"
ACTION_CAN_READ = "can_read"
ACTION_CAN_EDIT = "can_edit"
Expand Down
Loading

0 comments on commit 0e13458

Please sign in to comment.