Skip to content

Commit

Permalink
add policy generation from hydroshar
Browse files Browse the repository at this point in the history
  • Loading branch information
sblack-usu committed Jan 7, 2024
1 parent 201349f commit 5fc51c3
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 54 deletions.
14 changes: 12 additions & 2 deletions app/api/subsetter/app/db.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from enum import Enum
from functools import lru_cache
from typing import List, Optional, Tuple

import httpx
Expand All @@ -9,10 +10,14 @@

from subsetter.config import get_settings

DATABASE_URL = get_settings().mongo_url
client = motor.motor_asyncio.AsyncIOMotorClient(DATABASE_URL, uuidRepresentation="standard")
client = motor.motor_asyncio.AsyncIOMotorClient(get_settings().mongo_url, uuidRepresentation="standard")
db = client[get_settings().mongo_database]

client_hydroshare = motor.motor_asyncio.AsyncIOMotorClient(
get_settings().hydroshare_mongo_url, uuidRepresentation="standard"
)
db_hydroshare = client_hydroshare[get_settings().hydroshare_mongo_database]


class OAuthAccount(BaseOAuthAccount):
pass
Expand Down Expand Up @@ -80,3 +85,8 @@ async def update_submission(self, submission: Submission) -> None:

async def get_user_db():
yield BeanieUserDatabase(User, OAuthAccount)


@lru_cache
def get_hydroshare_access_db():
return db_hydroshare
44 changes: 23 additions & 21 deletions app/api/subsetter/app/routers/access_control/policy_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def refresh_minio_policy(user):
'''

import copy
from typing import Dict


def bucket_name(resource_id: str):
Expand All @@ -52,7 +53,7 @@ def bucket_name(resource_id: str):
return "subsetter-outputs"


def create_view_statements(owner, submissions) -> list:
def create_view_statements(user, views: Dict[str, list[str]]) -> list:
view_statement_template_get = {
"Effect": "Allow",
"Action": ["s3:GetBucketLocation", "s3:GetObject"],
Expand All @@ -64,35 +65,36 @@ def create_view_statements(owner, submissions) -> list:
"Resource": [],
"Condition": {"StringLike": {"s3:prefix": []}},
}
bucketname = bucket_name("blah")
get_resources = [f"arn:aws:s3:::{bucketname}/{owner.username}/*"]

get_resources = [f"arn:aws:s3:::{user.username}/*"]
view_statement = copy.deepcopy(view_statement_template_listing)
view_statement["Resource"] = [f"arn:aws:s3:::{bucketname}"]
view_statement["Condition"]["StringLike"]["s3:prefix"] = [f"{owner.username}/*"]
view_statement["Resource"] = [f"arn:aws:s3:::{user.username}"]
del view_statement["Condition"]
list_statements = [view_statement]
for user, submission in submissions:
bucketname = bucket_name(submission.workflow_id)
get_resources.append(
f"arn:aws:s3:::{bucketname}/{user.username}/{submission.workflow_name}/{submission.workflow_id}/*"
)
for bucket_owner, resource_paths in views.items():
get_resources = get_resources + [
f"arn:aws:s3:::{bucket_owner}/{resource_path}/*" for resource_path in resource_paths
]
view_statement = copy.deepcopy(view_statement_template_listing)
view_statement["Resource"] = [f"arn:aws:s3:::{bucketname}"]
view_statement["Resource"] = [f"arn:aws:s3:::{bucket_owner}"]
view_statement["Condition"]["StringLike"]["s3:prefix"] = [
f"{user.username}/{submission.workflow_name}/{submission.workflow_id}/*"
f"{resource_path}/*" for resource_path in resource_paths
]
list_statements.append(view_statement)
view_statement_template_get["Resource"] = get_resources
return list_statements + [view_statement_template_get]


# def create_edit_owner_statements(resource_ids: list[str]) -> list:
# edit_statement_template = {"Effect": "Allow", "Action": ["s3:*"], "Resource": []}
# edit_statement_template["Resource"] = [
# f"arn:aws:s3:::{bucket_name(resource_id)}/hydroshare/{resource_id}/*" for resource_id in resource_ids
# ]
# return [edit_statement_template]
def create_edit_statements(user, edits: Dict[str, list[str]]) -> list:
edit_statement_template = {"Effect": "Allow", "Action": ["s3:*"], "Resource": []}
resources = []
for bucket_owner, resource_paths in edits.items():
resources = resources + [f"arn:aws:s3:::{bucket_owner}/{resource_path}/*" for resource_path in resource_paths]
edit_statement_template["Resource"] = resources
return [edit_statement_template]


def minio_policy(user, view_submissions):
view_statements = create_view_statements(user, view_submissions)
return {"Version": "2012-10-17", "Statement": view_statements}
def minio_policy(user, owners: Dict[str, list[str]], edits: Dict[str, list[str]], views: Dict[str, list[str]]):
statements = create_view_statements(user, views)
statements = statements + create_edit_statements(user, edits)
return {"Version": "2012-10-17", "Statement": statements}
78 changes: 48 additions & 30 deletions app/api/subsetter/app/routers/access_control/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,72 @@
import os
import subprocess
import tempfile
from typing import Dict

from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends
from pydantic import BaseModel

from subsetter.app.db import Submission, User
from subsetter.app.db import User, get_hydroshare_access_db
from subsetter.app.routers.access_control.policy_generation import minio_policy
from subsetter.app.users import current_active_user

router = APIRouter()


class ShareWorkflowBody(BaseModel):
class UserAccess(BaseModel):
owner: list[str]
edit: list[str]
view: list[str]


class MinioUserResourceAccess(BaseModel):
owners: list[str]
resource_id: str
minio_resource_url: str


class MinioUserAccess(BaseModel):
owner: list[MinioUserResourceAccess]
edit: list[MinioUserResourceAccess]
view: list[MinioUserResourceAccess]


class UserPrivilege(BaseModel):
username: str
workflow_id: str
all: UserAccess
minio: MinioUserAccess


@router.post('/policy/add')
async def share_workflow_with_user(share_params: ShareWorkflowBody, user: User = Depends(current_active_user)):
submission: Submission = user.get_submission(share_params.workflow_id)
if submission:
submission.add_user(share_params.username)
await user.update_submission(submission)
return user
else:
return HTTPException(status_code=400)
def check_owners_in_bucket_path(resource_access: MinioUserResourceAccess):
for owner in resource_access.owners:
if f"/browser/{owner}/" in resource_access.minio_resource_url:
return owner
return None


@router.delete('/policy/remove')
async def unshare_workflow_with_user(share_params: ShareWorkflowBody, user: User = Depends(current_active_user)):
submission: Submission = user.get_submission(share_params.workflow_id)
if submission:
submission.remove_user(share_params.username)
await user.update_submission(submission)
return user
else:
return HTTPException(status_code=400)
def sort_privileges(user_accesses: list[MinioUserResourceAccess]):
authorized_users = {}
for user_access in user_accesses:
bucket_owner = check_owners_in_bucket_path(user_access)
if bucket_owner:
resource_path = user_access.minio_resource_url.split(f"{bucket_owner}/", 1)[-1]
authorized_users.setdefault(bucket_owner, []).append(resource_path)
return authorized_users


@router.get('/policy')
async def generate_user_policy(user: User = Depends(current_active_user)):
users = await User.find({"submissions.view_users": user.username}).to_list()
# this should be rewritten to query all on the db, but I don't have time to figure that out now
matching_submissions = []
for u in users:
for submission in u.submissions:
if user.username in submission.view_users:
matching_submissions.append((u, submission))
return minio_policy(user, matching_submissions)
hydroshare_access_db = get_hydroshare_access_db()
user_privilege = await hydroshare_access_db.userprivileges.find_one({"username": user.username})
user_privilege: UserPrivilege = UserPrivilege(**user_privilege)

# Check Authorization
minio_user_access: MinioUserAccess = user_privilege.minio
authorized_owners: Dict[str, list[str]] = sort_privileges(minio_user_access.owner)
authorized_edits: Dict[str, list[str]] = sort_privileges(minio_user_access.edit)
authorized_views: Dict[str, list[str]] = sort_privileges(minio_user_access.view)

return minio_policy(user, authorized_owners, authorized_edits, authorized_views)


@router.get('/profile')
Expand Down
2 changes: 1 addition & 1 deletion app/api/subsetter/app/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from httpx_oauth.oauth2 import OAuth2

from subsetter.app.db import User, get_user_db
from subsetter.config import get_settings, get_minio_client
from subsetter.config import get_minio_client, get_settings

SECRET = "SECRET"

Expand Down
3 changes: 3 additions & 0 deletions app/api/subsetter/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class Settings(BaseSettings):
mongo_url: str
mongo_database: str

hydroshare_mongo_url: str
hydroshare_mongo_database: str

oauth2_client_id: str
oauth2_client_secret: str
oauth2_redirect_url: str
Expand Down

0 comments on commit 5fc51c3

Please sign in to comment.