This repository has been archived by the owner on Nov 30, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Track user permissions across sessions * Add tests for user permission updates * Switch to autogenerated migration * Update ClientDetail when user scope is updated * Update UserPermissions validation * Update permission URN
- Loading branch information
1 parent
d7a5e6d
commit 894aa1b
Showing
14 changed files
with
559 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
src/fidesops/api/v1/endpoints/user_permission_endpoints.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import logging | ||
from fastapi import Security, Depends, APIRouter, HTTPException | ||
from starlette.status import HTTP_404_NOT_FOUND, HTTP_201_CREATED, HTTP_400_BAD_REQUEST | ||
|
||
from fidesops.api import deps | ||
from fidesops.api.v1 import urn_registry as urls | ||
from fidesops.api.v1.urn_registry import V1_URL_PREFIX | ||
from fidesops.models.fidesops_user import FidesopsUser | ||
from fidesops.models.fidesops_user_permissions import FidesopsUserPermissions | ||
from fidesops.schemas.oauth import AccessToken | ||
from fidesops.util.oauth_util import verify_oauth_client | ||
from sqlalchemy.orm import Session | ||
from fidesops.api.v1.scope_registry import ( | ||
USER_PERMISSION_CREATE, | ||
USER_PERMISSION_UPDATE, | ||
USER_PERMISSION_READ, | ||
) | ||
from fidesops.schemas.user_permission import ( | ||
UserPermissionsResponse, | ||
UserPermissionsCreate, | ||
UserPermissionsEdit, | ||
) | ||
|
||
logger = logging.getLogger(__name__) | ||
router = APIRouter(tags=["User Permissions"], prefix=V1_URL_PREFIX) | ||
|
||
|
||
def validate_user_id(db: Session, user_id: str) -> FidesopsUser: | ||
user = FidesopsUser.get_by(db, field="id", value=user_id) | ||
|
||
if not user: | ||
raise HTTPException( | ||
status_code=HTTP_404_NOT_FOUND, detail=f"No user found with id {user_id}." | ||
) | ||
return user | ||
|
||
|
||
@router.post( | ||
urls.USER_PERMISSIONS, | ||
dependencies=[Security(verify_oauth_client, scopes=[USER_PERMISSION_CREATE])], | ||
status_code=HTTP_201_CREATED, | ||
response_model=UserPermissionsResponse, | ||
) | ||
def create_user_permissions( | ||
*, | ||
db: Session = Depends(deps.get_db), | ||
user_id: str, | ||
permissions: UserPermissionsCreate, | ||
) -> FidesopsUserPermissions: | ||
user = validate_user_id(db, user_id) | ||
if user.permissions is not None: | ||
raise HTTPException( | ||
status_code=HTTP_400_BAD_REQUEST, | ||
detail=f"This user already has permissions set.", | ||
) | ||
logger.info("Created FidesopsUserPermission record") | ||
return FidesopsUserPermissions.create( | ||
db=db, data={"user_id": user_id, **permissions.dict()} | ||
) | ||
|
||
|
||
@router.put( | ||
urls.USER_PERMISSIONS, | ||
dependencies=[Security(verify_oauth_client, scopes=[USER_PERMISSION_UPDATE])], | ||
response_model=UserPermissionsResponse, | ||
) | ||
def update_user_permissions( | ||
*, | ||
db: Session = Depends(deps.get_db), | ||
user_id: str, | ||
permissions: UserPermissionsEdit, | ||
) -> FidesopsUserPermissions: | ||
user = validate_user_id(db, user_id) | ||
logger.info("Updated FidesopsUserPermission record") | ||
if user.client: | ||
user.client.update(db=db, data={"scopes": permissions.scopes}) | ||
return FidesopsUserPermissions.create_or_update( | ||
db=db, | ||
data={"id": user.permissions.id, "user_id": user_id, **permissions.dict()}, | ||
) | ||
|
||
|
||
@router.get( | ||
urls.USER_PERMISSIONS, | ||
dependencies=[Security(verify_oauth_client, scopes=[USER_PERMISSION_READ])], | ||
response_model=UserPermissionsResponse, | ||
) | ||
def get_user_permissions( | ||
*, db: Session = Depends(deps.get_db), user_id: str | ||
) -> FidesopsUserPermissions: | ||
validate_user_id(db, user_id) | ||
logger.info("Retrieved FidesopsUserPermission record") | ||
return FidesopsUserPermissions.get_by(db, field="user_id", value=user_id) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from sqlalchemy import Column, String, ARRAY, ForeignKey | ||
from sqlalchemy.orm import relationship, backref | ||
|
||
from fidesops.db.base_class import Base | ||
from fidesops.models.fidesops_user import FidesopsUser | ||
from fidesops.api.v1.scope_registry import PRIVACY_REQUEST_READ | ||
|
||
|
||
class FidesopsUserPermissions(Base): | ||
"""The DB ORM model for FidesopsUserPermissions""" | ||
|
||
user_id = Column(String, ForeignKey(FidesopsUser.id), nullable=False, unique=True) | ||
# escaping curly braces requires doubling them. Not a "\". So {{{test123}}} renders as {test123} | ||
scopes = Column( | ||
ARRAY(String), nullable=False, default=f"{{{PRIVACY_REQUEST_READ}}}" | ||
) | ||
user = relationship( | ||
FidesopsUser, | ||
backref=backref("permissions", cascade="all,delete", uselist=False), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from typing import List | ||
from pydantic import validator | ||
from fastapi import HTTPException | ||
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY | ||
from fidesops.schemas.base_class import BaseSchema | ||
from fidesops.api.v1.scope_registry import SCOPE_REGISTRY | ||
|
||
|
||
class UserPermissionsCreate(BaseSchema): | ||
"""Data required to create a FidesopsUserPermissions record""" | ||
|
||
scopes: List[str] | ||
|
||
@validator("scopes") | ||
def validate_scopes(cls, scopes: List[str]) -> List[str]: | ||
"""Validates that all incoming scopes are valid""" | ||
diff = set(scopes).difference(set(SCOPE_REGISTRY)) | ||
if len(diff) > 0: | ||
raise HTTPException( | ||
status_code=HTTP_422_UNPROCESSABLE_ENTITY, | ||
detail=f"Invalid Scope(s) {diff}. Scopes must be one of {SCOPE_REGISTRY}.", | ||
) | ||
return scopes | ||
|
||
|
||
class UserPermissionsEdit(UserPermissionsCreate): | ||
"""Data required to edit a FidesopsUserPermissions record""" | ||
|
||
id: str | ||
|
||
|
||
class UserPermissionsResponse(UserPermissionsCreate): | ||
"""Response after creating, editing, or retrieving a FidesopsUserPermissions record""" | ||
|
||
id: str | ||
user_id: str |
60 changes: 60 additions & 0 deletions
60
src/migrations/versions/90070db16d05_add_fidesops_user_permissions.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
"""add fidesops user permissions | ||
Revision ID: 90070db16d05 | ||
Revises: 530fb8533ca4 | ||
Create Date: 2022-04-27 17:24:31.548916 | ||
""" | ||
from alembic import op | ||
import sqlalchemy as sa | ||
|
||
|
||
# revision identifiers, used by Alembic. | ||
revision = "90070db16d05" | ||
down_revision = "530fb8533ca4" | ||
branch_labels = None | ||
depends_on = None | ||
|
||
|
||
def upgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.create_table( | ||
"fidesopsuserpermissions", | ||
sa.Column("id", sa.String(length=255), nullable=False), | ||
sa.Column( | ||
"created_at", | ||
sa.DateTime(timezone=True), | ||
server_default=sa.text("now()"), | ||
nullable=True, | ||
), | ||
sa.Column( | ||
"updated_at", | ||
sa.DateTime(timezone=True), | ||
server_default=sa.text("now()"), | ||
nullable=True, | ||
), | ||
sa.Column("user_id", sa.String(), nullable=False), | ||
sa.Column("scopes", sa.ARRAY(sa.String()), nullable=False), | ||
sa.ForeignKeyConstraint( | ||
["user_id"], | ||
["fidesopsuser.id"], | ||
), | ||
sa.PrimaryKeyConstraint("id"), | ||
sa.UniqueConstraint("user_id"), | ||
) | ||
op.create_index( | ||
op.f("ix_fidesopsuserpermissions_id"), | ||
"fidesopsuserpermissions", | ||
["id"], | ||
unique=False, | ||
) | ||
# ### end Alembic commands ### | ||
|
||
|
||
def downgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.drop_index( | ||
op.f("ix_fidesopsuserpermissions_id"), table_name="fidesopsuserpermissions" | ||
) | ||
op.drop_table("fidesopsuserpermissions") | ||
# ### end Alembic commands ### |
Oops, something went wrong.