Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend Access Control Registry APIs to protect post ones #551

Merged
merged 2 commits into from
Aug 4, 2022
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
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()