Skip to content

Commit

Permalink
Overhaul Roles and Roles Mapping [#2720] (#2744)
Browse files Browse the repository at this point in the history
Update the roles that users can be granted along with their associated list of scopes.

- Owner: Full Admin
- Viewer: Can view everything
- Contributor: Can manage most things but can't adjust storage and messaging configs
- Approver: Limited view but can approve privacy requests
- Viewer + Approver: Full view and can approve privacy requests
  • Loading branch information
pattisdr authored Mar 3, 2023
1 parent 35186dd commit 663bc54
Show file tree
Hide file tree
Showing 15 changed files with 212 additions and 168 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ The types of changes are:
* Add warning to 'fides deploy' when installed outside of a virtual environment [#2641](https://github.com/ethyca/fides/pull/2641)
* Redesigned the default/init config file to be auto-documented. Also updates the `fides init` logic and analytics consent logic [#2694](https://github.com/ethyca/fides/pull/2694)
* Change how config creation/import is handled across the application [#2622](https://github.com/ethyca/fides/pull/2622)
* Updates Roles->Scopes Mapping [#2744](https://github.com/ethyca/fides/pull/2744)

### Developer Experience

Expand Down
2 changes: 1 addition & 1 deletion scripts/setup/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from fides.api.ops.api.v1 import urn_registry as urls
from fides.api.ops.api.v1.scope_registry import SCOPE_REGISTRY
from fides.core.config import CONFIG
from fides.lib.oauth.roles import ADMIN
from fides.lib.oauth.roles import OWNER

from . import constants

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Redo roles scopes mapping
Revision ID: 9f38dad37628
Revises: eb1e6ec39b83
Create Date: 2023-03-03 16:53:03.553992
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
from sqlalchemy import text
from sqlalchemy.engine import ResultProxy

revision = "9f38dad37628"
down_revision = "eb1e6ec39b83"
branch_labels = None
depends_on = None


def swap_roles(old_role: str, new_role: str):
bind = op.get_bind()
matched_rows: ResultProxy = bind.execute(
text("SELECT * FROM fidesuserpermissions WHERE :old_role = ANY(roles);"),
{"old_role": old_role},
)
for row in matched_rows:
current_roles = row["roles"]
current_roles.remove(old_role)
current_roles.append(new_role)
bind.execute(
text("UPDATE fidesuserpermissions SET roles= :roles WHERE id= :id"),
{"roles": sorted(list(set(current_roles))), "id": row["id"]},
)

matched_client_rows: ResultProxy = bind.execute(
text("SELECT * FROM client WHERE :old_role = ANY(roles);"),
{"old_role": old_role},
)
for row in matched_client_rows:
client_roles = row["roles"]
client_roles.remove(old_role)
client_roles.append(new_role)
bind.execute(
text("UPDATE client SET roles= :roles WHERE id= :id"),
{"roles": sorted(list(set(client_roles))), "id": row["id"]},
)


def upgrade():
swap_roles("admin", "owner")
swap_roles("viewer_and_privacy_request_manager", "viewer_and_approver")
swap_roles("privacy_request_manager", "approver")


def downgrade():
swap_roles("owner", "admin")
swap_roles("viewer_and_approver", "viewer_and_privacy_request_manager")
swap_roles("approver", "privacy_request_manager")
4 changes: 2 additions & 2 deletions src/fides/core/config/security_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from fides.api.ops.api.v1.scope_registry import SCOPE_REGISTRY
from fides.lib.cryptography.cryptographic_util import generate_salt, hash_with_salt
from fides.lib.oauth.roles import ADMIN
from fides.lib.oauth.roles import OWNER

from .fides_settings import FidesSettings

Expand Down Expand Up @@ -98,7 +98,7 @@ class SecuritySettings(FidesSettings):
description="The list of scopes that are given to the root user.",
)
root_user_roles: List[str] = Field(
default=[ADMIN],
default=[OWNER],
description="The list of roles that are given to the root user.",
)
root_password: Optional[str] = Field(
Expand Down
54 changes: 33 additions & 21 deletions src/fides/lib/oauth/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,51 +10,56 @@
CONSENT_READ,
DATAMAP_READ,
DATASET_READ,
MESSAGING_CREATE_OR_UPDATE,
MESSAGING_DELETE,
MESSAGING_READ,
POLICY_READ,
PRIVACY_REQUEST_CALLBACK_RESUME,
PRIVACY_REQUEST_CREATE,
PRIVACY_REQUEST_DELETE,
PRIVACY_REQUEST_NOTIFICATIONS_CREATE_OR_UPDATE,
PRIVACY_REQUEST_NOTIFICATIONS_READ,
PRIVACY_REQUEST_READ,
PRIVACY_REQUEST_REVIEW,
PRIVACY_REQUEST_TRANSFER,
PRIVACY_REQUEST_UPLOAD_DATA,
PRIVACY_REQUEST_VIEW_DATA,
RULE_READ,
SAAS_CONFIG_READ,
SCOPE_READ,
SCOPE_REGISTRY,
STORAGE_CREATE_OR_UPDATE,
STORAGE_DELETE,
STORAGE_READ,
USER_READ,
WEBHOOK_READ,
)

PRIVACY_REQUEST_MANAGER = "privacy_request_manager"
APPROVER = "approver"
CONTRIBUTOR = "contributor"
OWNER = "owner"
VIEWER = "viewer"
VIEWER_AND_PRIVACY_REQUEST_MANAGER = "viewer_and_privacy_request_manager"
ADMIN = "admin"
VIEWER_AND_APPROVER = "viewer_and_approver"


class RoleRegistry(Enum):
"""Enum of available roles"""
"""Enum of available roles
admin = ADMIN
viewer_and_privacy_request_manager = VIEWER_AND_PRIVACY_REQUEST_MANAGER
Owner - Full admin
Viewer - Can view everything
Approver - Limited viewer but can approve Privacy Requests
Viewer + Approver = Full View and can approve Privacy Requests
Contributor - Can't configure storage and messaging
"""

owner = OWNER
viewer_approver = VIEWER_AND_APPROVER
viewer = VIEWER
privacy_request_manager = PRIVACY_REQUEST_MANAGER
approver = APPROVER
contributor = CONTRIBUTOR


privacy_request_manager_scopes = [
PRIVACY_REQUEST_CREATE,
approver_scopes = [
PRIVACY_REQUEST_REVIEW,
PRIVACY_REQUEST_READ,
PRIVACY_REQUEST_DELETE,
PRIVACY_REQUEST_CALLBACK_RESUME,
PRIVACY_REQUEST_NOTIFICATIONS_CREATE_OR_UPDATE,
PRIVACY_REQUEST_NOTIFICATIONS_READ,
PRIVACY_REQUEST_TRANSFER,
PRIVACY_REQUEST_UPLOAD_DATA,
PRIVACY_REQUEST_VIEW_DATA,
]
Expand All @@ -81,13 +86,20 @@ class RoleRegistry(Enum):
USER_READ,
]

not_contributor_scopes = [
STORAGE_CREATE_OR_UPDATE,
STORAGE_DELETE,
MESSAGING_CREATE_OR_UPDATE,
MESSAGING_DELETE,
PRIVACY_REQUEST_NOTIFICATIONS_CREATE_OR_UPDATE,
]

ROLES_TO_SCOPES_MAPPING: Dict[str, List] = {
ADMIN: sorted(SCOPE_REGISTRY),
VIEWER_AND_PRIVACY_REQUEST_MANAGER: sorted(
list(set(viewer_scopes + privacy_request_manager_scopes))
),
OWNER: sorted(SCOPE_REGISTRY),
VIEWER_AND_APPROVER: sorted(list(set(viewer_scopes + approver_scopes))),
VIEWER: sorted(viewer_scopes),
PRIVACY_REQUEST_MANAGER: sorted(privacy_request_manager_scopes),
APPROVER: sorted(approver_scopes),
CONTRIBUTOR: sorted(list(set(SCOPE_REGISTRY) - set(not_contributor_scopes))),
}


Expand Down
31 changes: 13 additions & 18 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@
from fides.lib.models.fides_user import FidesUser
from fides.lib.models.fides_user_permissions import FidesUserPermissions
from fides.lib.oauth.jwt import generate_jwe
from fides.lib.oauth.roles import (
PRIVACY_REQUEST_MANAGER,
VIEWER_AND_PRIVACY_REQUEST_MANAGER,
)
from fides.lib.oauth.roles import APPROVER, CONTRIBUTOR, VIEWER_AND_APPROVER
from tests.fixtures.application_fixtures import *
from tests.fixtures.bigquery_fixtures import *
from tests.fixtures.email_fixtures import *
Expand Down Expand Up @@ -665,16 +662,14 @@ def config_proxy(db):

@pytest.fixture(scope="function")
def oauth_role_client(db: Session) -> Generator:
"""Return a client that has the admin role for authentication purposes"""
"""Return a client that has all roles for authentication purposes
This is not a typical state but this client will then work with any
roles a token is given
"""
client = ClientDetail(
hashed_secret="thisisatest",
salt="thisisstillatest",
roles=[
ADMIN,
PRIVACY_REQUEST_MANAGER,
VIEWER,
VIEWER_AND_PRIVACY_REQUEST_MANAGER,
],
roles=[OWNER, APPROVER, VIEWER, VIEWER_AND_APPROVER, CONTRIBUTOR],
) # Intentionally adding all roles here so the client will always
# have a role that matches a role on a token for testing
db.add(client)
Expand Down Expand Up @@ -718,10 +713,10 @@ def _build_jwt(roles: List[str]) -> Dict[str, str]:


@pytest.fixture
def admin_client(db):
"""Return a client with an "admin" role for authentication purposes."""
def owner_client(db):
"""Return a client with an "owner" role for authentication purposes."""
client = ClientDetail(
hashed_secret="thisisatest", salt="thisisstillatest", scopes=[], roles=[ADMIN]
hashed_secret="thisisatest", salt="thisisstillatest", scopes=[], roles=[OWNER]
)
db.add(client)
db.commit()
Expand All @@ -744,24 +739,24 @@ def viewer_client(db):


@pytest.fixture
def admin_user(db):
def owner_user(db):
user = FidesUser.create(
db=db,
data={
"username": "test_fides_admin_user",
"username": "test_fides_owner_user",
"password": "TESTdcnG@wzJeu0&%3Qe2fGo7",
},
)
client = ClientDetail(
hashed_secret="thisisatest",
salt="thisisstillatest",
scopes=[],
roles=[ADMIN],
roles=[OWNER],
user_id=user.id,
)

FidesUserPermissions.create(
db=db, data={"user_id": user.id, "scopes": [], "roles": [ADMIN]}
db=db, data={"user_id": user.id, "scopes": [], "roles": [OWNER]}
)

db.add(client)
Expand Down
6 changes: 3 additions & 3 deletions tests/ctl/cli/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from fides.core.config import CONFIG
from fides.core.user import get_user_permissions
from fides.core.utils import get_auth_header, read_credentials_file
from fides.lib.oauth.roles import ADMIN
from fides.lib.oauth.roles import OWNER

OKTA_URL = "https://dev-78908748.okta.com"

Expand Down Expand Up @@ -951,7 +951,7 @@ def test_user_create(
credentials.user_id, get_auth_header(), CONFIG.cli.server_url
)
assert scopes == SCOPE_REGISTRY
assert roles == [ADMIN]
assert roles == [OWNER]

@pytest.mark.unit
def test_user_permissions_valid(
Expand Down Expand Up @@ -992,7 +992,7 @@ def test_get_user_permissions(
CONFIG.cli.server_url,
)
assert scopes == SCOPE_REGISTRY
assert roles == [ADMIN]
assert roles == [OWNER]

@pytest.mark.unit
def test_user_permissions_not_found(
Expand Down
58 changes: 1 addition & 57 deletions tests/fixtures/application_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
from fides.lib.models.client import ClientDetail
from fides.lib.models.fides_user import FidesUser
from fides.lib.models.fides_user_permissions import FidesUserPermissions
from fides.lib.oauth.roles import ADMIN, VIEWER
from fides.lib.oauth.roles import OWNER, VIEWER

logging.getLogger("faker").setLevel(logging.ERROR)
# disable verbose faker logging
Expand Down Expand Up @@ -1628,59 +1628,3 @@ def system(db: Session) -> System:
},
)
return system


@pytest.fixture
def admin_user(db):
user = FidesUser.create(
db=db,
data={
"username": "test_fides_admin_user",
"password": "TESTdcnG@wzJeu0&%3Qe2fGo7",
},
)
client = ClientDetail(
hashed_secret="thisisatest",
salt="thisisstillatest",
scopes=[],
roles=[ADMIN],
user_id=user.id,
)

FidesUserPermissions.create(
db=db, data={"user_id": user.id, "scopes": [], "roles": [ADMIN]}
)

db.add(client)
db.commit()
db.refresh(client)
yield user
user.delete(db)


@pytest.fixture
def viewer_user(db):
user = FidesUser.create(
db=db,
data={
"username": "test_fides_viewer_user",
"password": "TESTdcnG@wzJeu0&%3Qe2fGo7",
},
)
client = ClientDetail(
hashed_secret="thisisatest",
salt="thisisstillatest",
scopes=[],
roles=[VIEWER],
user_id=user.id,
)

FidesUserPermissions.create(
db=db, data={"user_id": user.id, "scopes": [], "roles": [VIEWER]}
)

db.add(client)
db.commit()
db.refresh(client)
yield user
user.delete(db)
Loading

0 comments on commit 663bc54

Please sign in to comment.