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

Minor updates, ci, style, and refactoring #66

Merged
merged 7 commits into from
Sep 26, 2023
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
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ commit_version_number = true
tag_commit = true
changelog_file = "CHANGELOG.md"
build_command = "python -m pip install build && python -m build"
allowed_tags = [
"build",
"chore",
"ci",
"docs",
"feat",
"fix",
"perf",
"style",
"refactor",
"test",
"revert"
]
minor_tags = ["feat", "build"]
patch_tags = ["chore", "ci", "fix", "perf", "style", "refactor", "test", "revert"]

[tool.flake8]
max-line-length = 120
Expand Down
7 changes: 7 additions & 0 deletions src/unipoll_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from . import account_manager as AccountManager # noqa: F401
from . import app as App # noqa: F401
from . import config as Config # noqa: F401
from . import dependencies as Dependencies # noqa: F401
from . import documents as Documents # noqa: F401
from . import mongo_db as MongoDB # noqa: F401
from . import websocket_manager as WebsocketManager # noqa: F401
7 changes: 2 additions & 5 deletions src/unipoll_api/account_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
from beanie import PydanticObjectId
from fastapi import Depends, Request
from fastapi_users import BaseUserManager, FastAPIUsers
from fastapi_users.authentication import (AuthenticationBackend,
CookieTransport)
from fastapi_users.authentication import AuthenticationBackend, CookieTransport
from unipoll_api.utils.auth_transport import BearerTransport
from unipoll_api.config import get_settings
from fastapi_users_db_beanie import BeanieUserDatabase, ObjectIDIDMixin
# from fastapi_users_db_beanie.access_token import BeanieAccessTokenDatabase, BeanieBaseAccessToken
from unipoll_api.utils.token_db import BeanieAccessTokenDatabase
# from fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy
from unipoll_api.utils.auth_strategy import DatabaseStrategy
from unipoll_api.documents import Account, AccessToken
from unipoll_api.utils import colored_dbg
Expand Down Expand Up @@ -83,4 +80,4 @@ def get_database_strategy(access_token_db=Depends(get_access_token_db)) -> Datab
fastapi_users = FastAPIUsers[Account, PydanticObjectId](get_user_manager, [jwt_backend, cookie_backend]) # type: ignore

get_current_active_user = fastapi_users.current_user(active=True)
current_active_user: ContextVar = ContextVar("current_active_user")
active_user: ContextVar = ContextVar("active_user")
6 changes: 6 additions & 0 deletions src/unipoll_api/actions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from . import account as AccountActions # noqa: F401
from . import group as GroupActions # noqa: F401
from . import poll as PollActions # noqa: F401
from . import authentication as AuthActions # noqa: F401
from . import workspace as WorkspaceActions # noqa: F401
from . import permissions as PermissionsActions # noqa: F401
23 changes: 11 additions & 12 deletions src/unipoll_api/actions/account.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
# Account actions
from unipoll_api import documents as Documents
from unipoll_api.account_manager import current_active_user
from unipoll_api.exceptions import account as AccountExceptions
from unipoll_api.documents import Account, Workspace, Group, AccessToken
from unipoll_api.account_manager import active_user
from unipoll_api.exceptions import AccountExceptions


# Delete account
async def delete_account(account: Documents.Account | None = None) -> None:
async def delete_account(account: Account | None = None) -> None:
if not account:
account = current_active_user.get()
account = active_user.get()

# Delete account
await Documents.Account.delete(account)
await Account.delete(account)

# Delete all policies associated with account
# BUG: This doesn't work due to type mismatch
# await Documents.Policy.find({"policy_holder": account}).delete() # type: ignore
# await Policy.find({"policy_holder": account}).delete() # type: ignore

# Remove account from all workspaces
workspaces = await Documents.Workspace.find(Documents.Workspace.members.id == account.id).to_list() # type: ignore
workspaces = await Workspace.find(Workspace.members.id == account.id).to_list() # type: ignore
for workspace in workspaces:
await workspace.remove_member(account) # type: ignore

# Remove account from all groups
groups = await Documents.Group.find(Documents.Group.members.id == account.id).to_list() # type: ignore
groups = await Group.find(Group.members.id == account.id).to_list() # type: ignore
for group in groups:
await group.remove_member(account) # type: ignore

# Check if account was deleted
if await Documents.Account.get(account.id): # type: ignore
if await Account.get(account.id): # type: ignore
raise AccountExceptions.ErrorWhileDeleting(account.id) # type: ignore

# Delete access tokens associated with account
await Documents.AccessToken.find(Documents.AccessToken.user_id == account.id).delete() # type: ignore
await AccessToken.find(AccessToken.user_id == account.id).delete() # type: ignore
3 changes: 1 addition & 2 deletions src/unipoll_api/actions/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from fastapi import Depends

from unipoll_api.documents import Account
from unipoll_api.exceptions import authentication as AuthExceptions
from unipoll_api.exceptions import account as AccountExceptions
from unipoll_api.exceptions import AuthExceptions, AccountExceptions
from unipoll_api.utils import colored_dbg as Debug
from unipoll_api.account_manager import jwt_backend, get_database_strategy, get_access_token_db

Expand Down
65 changes: 42 additions & 23 deletions src/unipoll_api/actions/group.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
from beanie import DeleteRules
from beanie.operators import In
from unipoll_api.account_manager import current_active_user
from unipoll_api import AccountManager
from unipoll_api.documents import Policy, ResourceID, Workspace, Group, Account
from unipoll_api.schemas import account as AccountSchemas
from unipoll_api.schemas import group as GroupSchemas
from unipoll_api.schemas import policy as PolicySchemas
# from unipoll_api.schemas import workspace as WorkspaceSchema
from unipoll_api.schemas import member as MemberSchemas
from unipoll_api.exceptions import account as AccountExceptions
from unipoll_api.exceptions import group as GroupExceptions
from unipoll_api.exceptions import workspace as WorkspaceExceptions
from unipoll_api.exceptions import resource as GenericExceptions
from unipoll_api.exceptions import policy as PolicyExceptions
from unipoll_api.schemas import AccountSchemas, GroupSchemas, MemberSchemas, PolicySchemas, WorkspaceSchemas
from unipoll_api.exceptions import (AccountExceptions, GroupExceptions, PolicyExceptions,
ResourceExceptions, WorkspaceExceptions)
from unipoll_api.utils import permissions as Permissions


Expand All @@ -28,8 +21,34 @@


# Get group
async def get_group(group: Group) -> GroupSchemas.Group:
return GroupSchemas.Group(**group.dict())
async def get_group(group: Group, include_members: bool = False, include_policies: bool = False) -> GroupSchemas.Group:
members = None
policies = None

account = AccountManager.active_user.get()
# Get the permissions(allowed actions) of the current user
permissions = await Permissions.get_all_permissions(group, account)
# Fetch the resources if the user has the required permissions
if include_members:
req_permissions = Permissions.GroupPermissions["get_group_members"] # type: ignore
if Permissions.check_permission(permissions, req_permissions):
members = (await get_group_members(group)).members
if include_policies:
req_permissions = Permissions.GroupPermissions["get_group_policies"] # type: ignore
if Permissions.check_permission(permissions, req_permissions):
policies = (await get_group_policies(group)).policies

workspace = WorkspaceSchemas.Workspace(**group.workspace.dict(exclude={"members", # type: ignore
"policies",
"groups"}))

# Return the workspace with the fetched resources
return GroupSchemas.Group(id=group.id,
name=group.name,
description=group.description,
workspace=workspace,
members=members,
policies=policies)


# Update a group
Expand Down Expand Up @@ -109,10 +128,10 @@ async def remove_group_member(group: Group, account_id: ResourceID | None):
if not account:
raise AccountExceptions.AccountNotFound(account_id)
else:
account = current_active_user.get()
account = AccountManager.active_user.get()
# Check if the account exists
if not account:
raise GenericExceptions.InternalServerError("remove_group_member() -> Account not found")
raise ResourceExceptions.InternalServerError("remove_group_member() -> Account not found")
# Check if account is a member of the group
if account.id not in [ResourceID(member.ref.id) for member in group.members]:
raise GroupExceptions.UserNotMember(group, account)
Expand All @@ -135,10 +154,10 @@ async def get_group_policies(group: Group) -> PolicySchemas.PolicyList:
elif policy.policy_holder_type == 'group':
policy_holder = await Group.get(policy.policy_holder.ref.id)
else:
raise GenericExceptions.InternalServerError("Invalid policy_holder_type")
raise ResourceExceptions.InternalServerError("Invalid policy_holder_type")
if not policy_holder:
# TODO: Replace with custom exception
raise GenericExceptions.InternalServerError("get_group_policies() => Policy holder not found")
raise ResourceExceptions.InternalServerError("get_group_policies() => Policy holder not found")
# Convert the policy_holder to a Member schema
policy_holder = MemberSchemas.Member(**policy_holder.dict()) # type: ignore
policy_list.append(PolicySchemas.PolicyShort(id=policy.id,
Expand All @@ -157,10 +176,10 @@ async def get_group_policy(group: Group, account_id: ResourceID | None):
if not account:
raise AccountExceptions.AccountNotFound(account_id)
else:
account = current_active_user.get()
account = AccountManager.active_user.get()

if not account:
raise GenericExceptions.InternalServerError("get_group_policy() => Account not found")
raise ResourceExceptions.InternalServerError("get_group_policy() => Account not found")

# Check if account is a member of the group
# if account.id not in [member.id for member in group.members]:
Expand Down Expand Up @@ -191,10 +210,10 @@ async def set_group_policy(group: Group,
if not account:
raise AccountExceptions.AccountNotFound(input_data.account_id)
else:
account = current_active_user.get()
account = AccountManager.active_user.get()
# Make sure the account is loaded
if not account:
raise GenericExceptions.InternalServerError("set_group_policy() => Account not found")
raise ResourceExceptions.InternalServerError("set_group_policy() => Account not found")
try:
# Find the policy for the account
# NOTE: To set a policy for a user, the user must be a member of the group, therefore the policy must exist
Expand All @@ -205,14 +224,14 @@ async def set_group_policy(group: Group,
policy = p
break
except Exception as e:
raise GenericExceptions.InternalServerError(str(e))
raise ResourceExceptions.InternalServerError(str(e))
# Calculate the new permission value
new_permission_value = 0
for i in input_data.permissions:
try:
new_permission_value += Permissions.GroupPermissions[i].value # type: ignore
except KeyError:
raise GenericExceptions.InvalidPermission(i)
raise ResourceExceptions.InvalidPermission(i)
# Update the policy
policy.permissions = Permissions.GroupPermissions(new_permission_value) # type: ignore
await Policy.save(policy)
Expand Down
4 changes: 2 additions & 2 deletions src/unipoll_api/actions/permissions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from unipoll_api.schemas import policy as PolicySchemas
from unipoll_api.utils import permissions as Permissions
from unipoll_api.schemas import PolicySchemas
from unipoll_api.utils import Permissions


# Get All Workspace Permissions
Expand Down
53 changes: 23 additions & 30 deletions src/unipoll_api/actions/poll.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,29 @@
from unipoll_api import AccountManager
from unipoll_api.documents import Poll, Policy, Group, Account
from unipoll_api.schemas import poll as PollSchemas
from unipoll_api.schemas import question as QuestionSchemas
from unipoll_api.schemas import policy as PolicySchemas
from unipoll_api.schemas import member as MemberSchemas
from unipoll_api.schemas import workspace as WorkspaceSchema
from unipoll_api.utils import permissions as Permissions
from unipoll_api.exceptions import resource as GenericExceptions
from unipoll_api.account_manager import current_active_user
from unipoll_api.schemas import PollSchemas, QuestionSchemas, PolicySchemas, MemberSchemas, WorkspaceSchemas
from unipoll_api.utils import Permissions
from unipoll_api.exceptions import ResourceExceptions


async def get_poll(poll: Poll, include: list[str]) -> PollSchemas.PollResponse:
account = current_active_user.get()
async def get_poll(poll: Poll,
include_questions: bool = False,
include_policies: bool = False) -> PollSchemas.PollResponse:
account = AccountManager.active_user.get()
questions = []
policies = None

if include:
# Get the permissions(allowed actions) of the current user
permissions = await Permissions.get_all_permissions(poll, account)
# If "all" is in the list, include all resources
if "all" in include:
include = ["policies", "questions"]
# Fetch the resources if the user has the required permissions
if "questions" in include:
req_permissions = Permissions.PollPermissions["get_poll_questions"] # type: ignore
if Permissions.check_permission(permissions, req_permissions) or poll.public:
questions = (await get_poll_questions(poll)).questions
if "policies" in include:
req_permissions = Permissions.PollPermissions["get_poll_policies"] # type: ignore
if Permissions.check_permission(permissions, req_permissions):
policies = (await get_poll_policies(poll)).policies
permissions = await Permissions.get_all_permissions(poll, account)
# Fetch the resources if the user has the required permissions
if include_questions:
req_permissions = Permissions.PollPermissions["get_poll_questions"] # type: ignore
if Permissions.check_permission(permissions, req_permissions) or poll.public:
questions = (await get_poll_questions(poll)).questions
if include_policies:
req_permissions = Permissions.PollPermissions["get_poll_policies"] # type: ignore
if Permissions.check_permission(permissions, req_permissions):
policies = (await get_poll_policies(poll)).policies

workspace = WorkspaceSchema.WorkspaceShort(**poll.workspace.dict()) # type: ignore
workspace = WorkspaceSchemas.WorkspaceShort(**poll.workspace.dict()) # type: ignore

# Return the workspace with the fetched resources
return PollSchemas.PollResponse(id=poll.id,
Expand Down Expand Up @@ -65,10 +58,10 @@ async def get_poll_policies(poll: Poll) -> PolicySchemas.PolicyList:
elif policy.policy_holder_type == 'group':
policy_holder = await Group.get(policy.policy_holder.ref.id)
else:
raise GenericExceptions.InternalServerError("Invalid policy_holder_type")
raise ResourceExceptions.InternalServerError("Invalid policy_holder_type")
if not policy_holder:
# TODO: Replace with custom exception
raise GenericExceptions.InternalServerError("get_poll_policies() => Policy holder not found")
raise ResourceExceptions.InternalServerError("get_poll_policies() => Policy holder not found")
# Convert the policy_holder to a Member schema
policy_holder = MemberSchemas.Member(**policy_holder.dict()) # type: ignore
policy_list.append(PolicySchemas.PolicyShort(id=policy.id,
Expand All @@ -94,7 +87,7 @@ async def update_poll(poll: Poll, data: PollSchemas.UpdatePollRequest) -> PollSc

# Save the updated poll
await Poll.save(poll)
return await get_poll(poll, [])
return await get_poll(poll, include_questions=True)


async def delete_poll(poll: Poll):
Expand All @@ -103,4 +96,4 @@ async def delete_poll(poll: Poll):

# Check if the poll was deleted
if await Poll.get(poll.id):
raise GenericExceptions.InternalServerError("Poll not deleted")
raise ResourceExceptions.InternalServerError("Poll not deleted")
2 changes: 1 addition & 1 deletion src/unipoll_api/actions/superuser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from unipoll_api.documents import Workspace
from unipoll_api.schemas import workspace as WorkspaceSchemas
from unipoll_api.schemas import WorkspaceSchemas


# Get all workspaces
Expand Down
Loading