Skip to content

Commit

Permalink
Extend Access Control Registry APIs to protect post ones (#551)
Browse files Browse the repository at this point in the history
* Extend rbac registry APIs to post ones
* Update auth.py to support live.com CLI tokens
  • Loading branch information
Yuqing-cat authored Aug 4, 2022
1 parent eb261d1 commit 755c1f0
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 17 deletions.
14 changes: 6 additions & 8 deletions registry/access_control/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,42 +66,40 @@ def get_feature_lineage(feature: str, requestor: User = Depends(get_user)) -> di

@router.post("/projects", name="Create new project with definition [Auth Required]")
def new_project(definition: dict, requestor: User = Depends(get_user)) -> dict:
rbac.init_userrole(requestor, definition["name"])
response = requests.post(url=f"{registry_url}/projects", params=definition,
rbac.init_userrole(requestor.username, definition["name"])
response = requests.post(url=f"{registry_url}/projects", json=definition,
headers=get_api_header(requestor)).content.decode('utf-8')
return json.loads(response)


@router.post("/projects/{project}/datasources", name="Create new data source of my project [Write Access Required]")
def new_project_datasource(project: str, definition: dict, requestor: User = Depends(project_write_access)) -> dict:
response = requests.post(url=f"{registry_url}/projects/{project}/datasources", params=definition, headers=get_api_header(
response = requests.post(url=f"{registry_url}/projects/{project}/datasources", json=definition, headers=get_api_header(
requestor)).content.decode('utf-8')
return json.loads(response)


@router.post("/projects/{project}/anchors", name="Create new anchors of my project [Write Access Required]")
def new_project_anchor(project: str, definition: dict, requestor: User = Depends(project_write_access)) -> dict:
response = requests.post(url=f"{registry_url}/projects/{project}/anchors", params=definition, headers=get_api_header(
response = requests.post(url=f"{registry_url}/projects/{project}/anchors", json=definition, headers=get_api_header(
requestor)).content.decode('utf-8')
return json.loads(response)


@router.post("/projects/{project}/anchors/{anchor}/features", name="Create new anchor features of my project [Write Access Required]")
def new_project_anchor_feature(project: str, anchor: str, definition: dict, requestor: User = Depends(project_write_access)) -> dict:
response = requests.post(url=f"{registry_url}/projects/{project}/anchors/{anchor}/features", params=definition, headers=get_api_header(
response = requests.post(url=f"{registry_url}/projects/{project}/anchors/{anchor}/features", json=definition, headers=get_api_header(
requestor)).content.decode('utf-8')
return json.loads(response)


@router.post("/projects/{project}/derivedfeatures", name="Create new derived features of my project [Write Access Required]")
def new_project_derived_feature(project: str, definition: dict, requestor: User = Depends(project_write_access)) -> dict:
response = requests.post(url=f"{registry_url}/projects/{project}/derivedfeatures",
params=definition, headers=get_api_header(requestor)).content.decode('utf-8')
json=definition, headers=get_api_header(requestor)).content.decode('utf-8')
return json.loads(response)

# Below are access control management APIs


@router.get("/userroles", name="List all active user role records [Project Manage Access Required]")
def get_userroles(requestor: User = Depends(get_user)) -> list:
return rbac.list_userroles(requestor.username)
Expand Down
8 changes: 6 additions & 2 deletions registry/access_control/rbac/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ def _get_user_from_token(decoded_token: Mapping) -> User:
elif aad_app_key in decoded_token:
appid = decoded_token.get(aad_app_key)
# Azure CLI User Impersonation token
if decoded_token.get("scp") == str(UserType.USER_IMPERSONATION):
username = decoded_token.get("upn")
if decoded_token.get("scp") == str(UserType.USER_IMPERSONATION.value):
if "upn" in decoded_token:
username = decoded_token.get("upn")
# live.com account token doesn't have upn
else:
username = decoded_token.get("email")
type = UserType.USER_IMPERSONATION
# Other AAD App token
else:
Expand Down
39 changes: 32 additions & 7 deletions registry/access_control/rbac/db_rbac.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
from fastapi import HTTPException, status
from typing import Any
from rbac import config
from rbac.database import connect
from rbac.models import AccessType, UserRole, RoleType, SUPER_ADMIN_SCOPE
from rbac.interface import RBAC
import os
import logging

class BadRequest(HTTPException):
def __init__(self, detail: Any = None) -> None:
super().__init__(status_code=status.HTTP_400_BAD_REQUEST,
detail=detail, headers={"WWW-Authenticate": "Bearer"})


class DbRBAC(RBAC):
def __init__(self):
Expand Down Expand Up @@ -122,16 +129,34 @@ def delete_userrole(self, project_name: str, user_name: str, role_name: str, del
self.get_userroles()
return

def init_userrole(self, creator_name: str, project_name: str):
"""initialize user role relationship when a new project is created
TODO: project name cannot be `global`.
def init_userrole(self, creator_name: str, project_name:str):
"""Project name validation and project admin initialization
"""
# project name cannot be `global`
if project_name.casefold() == SUPER_ADMIN_SCOPE.casefold():
raise BadRequest(f"{SUPER_ADMIN_SCOPE} is keyword for Global Admin (admin of all projects), please try other project name.")
else:
# check if project already exist (have valid rbac records)
# no 400 exception to align the registry api behaviors
query = fr"""select project_name, user_name, role_name, create_by, create_reason, create_time, delete_reason, delete_time
from userroles
where delete_reason is null and project_name ='%s'"""
rows = self.conn.query(query%(project_name))
if len(rows) > 0:
logging.warning(f"{project_name} already exist, please pick another name.")
return
else:
# initialize project admin if project not exist:
self.init_project_admin(creator_name, project_name)


def init_project_admin(self, creator_name: str, project_name: str):
"""initialize the creator as project admin when a new project is created
"""
create_by = "system"
create_reason = "creator of project, get admin by default."
query = fr"""insert into userroles (project_name, user_name, role_name, create_by, create_reason, create_time)
values ('%s','%s','%s','%s','%s', getutcdate())"""
self.conn.update(query % (project_name, creator_name,
RoleType.ADMIN.value, create_by, create_reason))
logging.info(
f"Userrole initialized with query: {query%(project_name, creator_name, RoleType.ADMIN.value, create_by, create_reason)}")
self.conn.update(query % (project_name, creator_name, RoleType.ADMIN.value, create_by, create_reason))
logging.info(f"Userrole initialized with query: {query%(project_name, creator_name, RoleType.ADMIN.value, create_by, create_reason)}")
return self.get_userroles()

0 comments on commit 755c1f0

Please sign in to comment.