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

Add basic user role support to backend #1094

Merged
merged 16 commits into from
Jan 17, 2024
Merged

Conversation

spwoodcock
Copy link
Member

@spwoodcock spwoodcock commented Jan 10, 2024

What type of PR is this? (check all applicable)

  • πŸ• Feature
  • πŸ› Bug Fix
  • πŸ“ Documentation Update
  • 🎨 Style
  • πŸ§‘β€πŸ’» Code Refactor
  • πŸ”₯ Performance Improvements
  • βœ… Test
  • πŸ€– Build
  • πŸ” CI
  • πŸ“¦ Chore (Release)
  • ⏩ Revert

Related Issue

Fixes #243

Describe this PR

This PR anticipates the merge of #1091 βœ… as it relies on the auth.
Also anticipates merge of #1096 βœ….

As described in discussion #1083 we are in the process of figuring out user roles.

I have implemented a basic configuration as such (this can change as needed):

class UserRole(IntEnum, Enum):
    """Available roles assigned to a user site-wide in FMTM.

    Can be used for global user permissions:
        - READ_ONLY = write access blocked (i.e. banned)
        - MAPPER = default for all
        - ADMIN = super admin with access to everything
    """

    READ_ONLY = -1
    MAPPER = 0
    ADMIN = 1


class ProjectRole(IntEnum, Enum):
    """Available roles assigned to a user for a specific project.

    Invitation is required for a MAPPER to join a project.
    All roles must be assigned by someone higher in the hierarchy:
        - MAPPER = default for all (invitation required)
        - VALIDATOR = can validate the mappers output
        - FIELD_MANAGER = can invite mappers and organise people
        - ASSOCIATE_PROJECT_MANAGER = helps the project manager, cannot delete project
        - PROJECT_MANAGER = has all permissions to manage a project, including delete
        - ORGANIZATION_ADMIN = has project manager permissions for all projects in org
    """

    MAPPER = 0
    VALIDATOR = 1
    FIELD_MANAGER = 2
    ASSOCIATE_PROJECT_MANAGER = 3
    PROJECT_MANAGER = 4
    ORGANIZATION_ADMIN = 5

Above I have divided roles into two types.

  • UserRole is one of three types and is used for site-wide permissions.
  • ProjectRole is related to project specific permissions.

Next Steps

  • Implement the remaining auth levels.
  • Add to endpoints as required.
    • UserRole probably won't be used much, except for granting site-wide admin access or blocking users.
    • ProjectRole endpoints will require the project_id to passed as a param (as part of the dependency injection).

Examples:

@router.get("/superadmin/")
async def superadmin(
    db: Session = Depends(database.get_db),
    user: AuthUser = Depends(super_admin),
):
    return user
@router.get("/validator/")
async def validator(
    db: Session = Depends(database.get_db),
    user: AuthUser = Depends(validator),
):
    return user

Checklist before requesting a review

[optional] What gif best describes this PR or how it makes you feel?

giphy

@github-actions github-actions bot added docs Improvements or additions to documentation frontend Related to frontend code backend Related to backend code migration Contains a DB migration labels Jan 10, 2024
@spwoodcock spwoodcock force-pushed the feat/init-project-roles branch from 1e98457 to aa079a7 Compare January 10, 2024 11:46
@github-actions github-actions bot removed the frontend Related to frontend code label Jan 10, 2024
@spwoodcock spwoodcock self-assigned this Jan 10, 2024
@nrjadkry
Copy link
Member

  1. I think we need to login_required function for invalid access-token. As of now, it throws an error, if invalid access-token is passed.
  2. You have removed organisations from user role table. We might need to have a user having an organisation level clearance, who will have all the access to every projects within that organisations. My thought is we need to save that data in the db too. What do you think?

@spwoodcock
Copy link
Member Author

  1. Excellent find - I'll make a minor commit to handle that.
  2. My thinking was that a project can only be in one organization, so the information is redundant in user_roles, as it is already present in the project table. But you are completely right, for the organization_admin role, we actually need the organization_id present in the user_roles table.

Either project_id is used or organization_id, so we could do one of:

  • convert project_id --> id and use the field for the project or org id, depending on what the role is.
  • add organization_id back in, and use one or the other.

Then in roles.py we we make org_admin we would still be receiving a project_id from and endpoint, so would first need filter DbProject to get the org_id, then filter DbUserRole to check if they are org admin for that org.

@nrjadkry
Copy link
Member

I think we need to add organization_id back in.
My guess is only super admin can create an organisation and then Organization admin role can be assigned to the users, probably by super admin only.
It seems that, in the beginning, the organization may not have any projects. Then, only assigned organisation admin can create a project within that organisation.
In this case, backtracking from project might not be feasible.

@spwoodcock
Copy link
Member Author

It's a bit of a pain to need one person (admin) to approve all organisations.

Not a problem for organisation specific instances, but for the HOT maintained one it's extra work and may put off users if it's a slow process (we also need a mechanism for org admins to request creation too).

Could we possibly allow anyone to create a new organosation, then that person immediately becomes org admin (as the first user in the org). It's not a perfect solution, so perhaps there are other ideas too.

@spwoodcock
Copy link
Member Author

Perhaps part of the solution is to have an organization called other that is generated by default (along with the super admin being automatically generated on first start).

Then an user that wants to make a project, but does not have an organization, can get started and create one in the other org (the project could perhaps be moved if they request a organization be made for them too).

status_code=HTTPStatus.FORBIDDEN, detail="User must be an administrator"
)

return user_data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @spwoodcock
What is the purpose of this function?
Does this check if the user is super_admin or not? If that is the case, we might need to do something like
user.role == UserRole.ADMIN after we get the user from db.
How does this work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this line do that?

match = db.query(DbUser).filter_by(id=user_id, role=UserRole.ADMIN).first()

I definitely could have got something wrong here though - thanks for the sanity check!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@spwoodcock my understanding is that this line filters data from user role with id=user_id and role=admin.
But, we need to do this.

user_role = db.query(DbUser).filter_by(id=user_id,).first().role

if user_role == UserRole.ADMIN:
    return True
else:
    return False

Copy link
Member Author

@spwoodcock spwoodcock Jan 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are basically the same thing.

The line further down does a check if not match:.

  • If no user with the user id and role admin exists, then return a HTTPException.
  • If a user exists and has the role admin, then continue to return the user details.

Does that make sense?

@nrjadkry
Copy link
Member

Can a user be a project admin in one project and be just a mapper in other. Likewise, an organisation admin in one organisation, project admin in the project of some other organisation and just a mapper for another?

If this can be true, we might need to think differently?

@spwoodcock
Copy link
Member Author

spwoodcock commented Jan 16, 2024

Yes they can definitely be all of the above.

I have divided roles into two types:

UserRole:

  • One of three types and is used for site-wide permissions.
    • ADMIN (super admin)
    • MAPPER (most users)
    • READ_ONLY (write access blocked)
  • This is used for user.role in the database.

ProjectRole:

  • Related to project specific permissions.
  • Stored in user_roles link table, with the project_id and user_id being a composite primary key.
    • This means the user can multiple roles across projects (multiple entries in the table).
    • BUT, can only have one role within a project.
  • This is where we define the roles like org admin, project admin, validator, etc.

The role types are used differently.
The role user.role is only used to designate super admin & block users.
The roles under user_roles are specific to projects.

@spwoodcock
Copy link
Member Author

Should we merge this, so we can get the linting fixes in, then continue in another PR?

@nrjadkry
Copy link
Member

@spwoodcock let's merge this. If we encounter any issues or lag, then we can obviously fix them in another PR.

@spwoodcock spwoodcock merged commit f8cf65f into development Jan 17, 2024
5 of 6 checks passed
@spwoodcock spwoodcock deleted the feat/init-project-roles branch January 17, 2024 09:41
spwoodcock added a commit that referenced this pull request Jan 25, 2024
* build: remove ref to script_domain (move to separate repo)

* docs: add CNAME for custom docs domain

* build: move install.sh to fmtm-installer repo

* ci: remove install script from pr labeller

* docs: update info for using easy install script

* build: add justfile to repo for basic commands (#1098)

* build: add justfile to repo for basic commands

* build: remove Makefile, update Justfile

* build: add Justfile commands to clean databases

* docs: add Justfile to pr auto labeller

* refactor: organization routes/crud/schemas to use best practice (#1096)

* fix: use enums for HTTPStatus over codes

* refactor: update organization routes/crud/schemas best pracice

* added int to the union with str in org_exists functions

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: Niraj Adhikari <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* feat: add basic user role support to backend (#1094)

* feat: update role enums: UserRole and ProjectRole

* feat: update DbUserRole model to use ProjectRole enum + comp key

* build: update base schema with new enum types

* fix: osm login methods to async

* feat: add basic roles: super admin & validator

* build: add migration for ProjectRole db enum type

* build: fix migrations with revert for projectrole

* build: fix enum migrations with intermediate varchar

* fix: handle invalid access tokens

* refactor: correct use of 403 http status over 401

* refactor: tidy minor code edits & lint fixes

* build: default to 4 workers on dev uvicorn container

* feat: add project_deps with get_project_by_id logic

* feat: add org_admin role to role deps

* fix: add user role to response /me for frontend

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* refactor: fix all pre-commit.ci linting errors (#1101)

* build: add migration for ProjectRole db enum type

* fix: database model backrefs for DbUserRole --> (DbProject/DbUser)

* fix: add debug user to bypass auth during tests

* refactor: simplify project deletion endpoint /project/{id}

* test: add test for project deletion endpoint

* refactor: rename upload_multi_polygon --> custom_task_boundaries

* ci: disable markdownlint rule MD033 (allow inline html img resize)

* docs: update docs home page to use ref links

* refactor: fix all linting errors for code

* ci: add CONTRIBUTING.md to prettier ignore list

* docs: fix linting errors for all markdown docs

* refactor: rename LICENSE --> LICENSE.md

* refactor: update badges in readme

* docs: add allcontributors to badge table

* ci: add LICENSE.md to markdownlint ignore

* ci: update markdownlint ignore syntax for ci

* ci: add LICENSE.md to prettier ignore

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* style: revert prettier changes to LICENSE.md

* ci: update pre-commit hook versions

* docs: fix markdown linting errors after update

* ci: disable hadolint until bundled binary

* ci: do not run shellcheck on contrib dir

* ci: ignore SC2188,SC2143 from shellcheck

* refactor: fix lint errors for all shell/bash scripts

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* fix: editable vector layer in ol for project creation (#1102)

* fix: backend import error fix

* fix (vectorLayer): style - conditionaly apply style on onModify present

* fix (splitTasks): map - edit added to splitted taskLayer

* fix (splitTasks): onModify - edited geojson set to dividedTaskGeojson state

* feat (createNewProject): only enable generate task btn if fgb file fetch is completed

* fix (createNewProject): splitTasks - logic fix

* fix (createNewProject): splitTasks - clear dividedTaskGeojson, splitTasksSelection, and dataExtractGeojson state on previous click

* feat (createNewProject): splitTasks - show loader and message until FGB file is fetching

* fix (createNewProject): taskSplit - display error on taskSplit fail

* fix vectorLayer: on modifyEnd return area of boundary as well

* fix button: loading text added to the button

* fix NewDefineAreaMap: removed data extraction in progress message from mapComponent

* fix (createNewProject): splitTasks - clearing state on step toggle remove

* fix (createNewProject): uploadArea - clear step4 & step5 step on AOI edit

* fix (createNewProject): dataExtract - generateTaskBTN added, disable next until taskGeneration success, state logic changed to track extractWays & featureType state validation

* fix (createNewProject): dataExtract - clear file state on reset click or if generateDataExtract click

* fix (createNewProject): customLine, customPolygon file state clear on AOI edit

* fix (createNewProject): dataExtract - clear previous extractGeojson, customLine, customPolygon on generate extract, btn disable state update

* fix: limit project area during create (#1109)

* fix: backend import error fix

* fix (vectorLayer): style - conditionaly apply style on onModify present

* fix (splitTasks): map - edit added to splitted taskLayer

* fix (splitTasks): onModify - edited geojson set to dividedTaskGeojson state

* feat (createNewProject): only enable generate task btn if fgb file fetch is completed

* fix (createNewProject): splitTasks - logic fix

* fix (createNewProject): splitTasks - clear dividedTaskGeojson, splitTasksSelection, and dataExtractGeojson state on previous click

* feat (createNewProject): splitTasks - show loader and message until FGB file is fetching

* fix (createNewProject): taskSplit - display error on taskSplit fail

* fix vectorLayer: on modifyEnd return area of boundary as well

* fix button: loading text added to the button

* fix NewDefineAreaMap: removed data extraction in progress message from mapComponent

* fix (createNewProject): splitTasks - clearing state on step toggle remove

* fix (createNewProject): uploadArea - clear step4 & step5 step on AOI edit

* fix (createNewProject): dataExtract - generateTaskBTN added, disable next until taskGeneration success, state logic changed to track extractWays & featureType state validation

* fix (createNewProject): dataExtract - clear file state on reset click or if generateDataExtract click

* fix (createNewProject): customLine, customPolygon file state clear on AOI edit

* fix (createNewProject): dataExtract - clear previous extractGeojson, customLine, customPolygon on generate extract, btn disable state update

* fix (createNewProject): uploadArea - warning & error shown if AOI exceeds 100 & 1000 sq.km respectively

* fix: use organization_manager table for org admins

* fix (newProjectDetails): searchInput - width increased (#1111)

* fix (frontend): projection validity check using coordinates of extent (#1112)

* docs: link to docs.hotosm.org for versioning info

* refactor: remove variable from docker install script

* ci: add py310 version to black code formatter

* docs: add link to pre-commit info in docs

* refactor: renaming for consistency with database (#1114)

* docs: update info about interactive api debugging port

* docs: add timestamp to doc pages via plugin

* build: add git-revision-date-localized plugin to docs group

* ci: docs build use image from gh-workflows

* build: upgrade osm-fieldwork-->0.4.1, osm-rawdata-->0.2.6

* Fix condition in elif statement for Feature type detection

* docs: update markdown links from inline to reference (footnote) style (#1116)

* Clean up links in contributing

* done: CONTRIBUTING.md

* done: INSTALL.md

* done: User-Mantual-For-Project-Managers.md

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Moving script file to a new directory

* move new script file to a new directory

* deleted superfluous XINSTALL copy.md

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* refactor: fix linting errors in project_crud

* feat: paginated submissions by project (#1110)

* feat: submission form fields for the submision table

* feat: paginated submissions by project

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* refactor: format submission_routes, remove dup import

* refactor: fix incorrect import from app.submission

* run pre-commit

---------

Co-authored-by: sujanadh <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: spwoodcock <[email protected]>

* fix: tile archive download for projects

* refactor: refactor odk appuser + qrcode function naming

* refactor: add metadata_username to odk qr code as test

* docs: use code of conduct on docs.hotosm.org

* fix divide by square on multipolygon geojson

* build: upgrade fmtm-splitter v0.2.6 --> v1.0.0rc0 (#1122)

* feat: add test coverage metric (#1129)

* build: add coverage.py to test dependencies

* docs: add dev info about coverage and profiling

* refactor: add doxygen output to gitignore

* build: add coverage-badge to test deps

* ci: update all workflows v1.4.2 --> v1.4.3

* ci: add coverage output during deploy pytest

* docs: add coverage stats to readme

* build: lock dependencies after merge coverage+fmtm-splitter

* build: add coverage files to gitignore

* ci: update pytest workflow test to use coverage

* ci: update all workflows v1.4.3 --> v1.4.4

* docs: add coverage link to docs page sidebar

* hotfix: geom type not populated with feature type

* ci: add simple smoke test to frontend deploy

* feat: organisation approval and admin endpoints (#1126)

* dependencies to cehck if user_exists

* endpoint to add organisation admin

* added approved field in organisation model

* fix: table name in migration file for organisations

* feat: organisations list api updated according to role

* feat: endpoint to approve organisations

* update: get organisation endpoint for filtering approval

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* added docstrings

* fix: pre-commit linting errors

* added docstring in user_deps file

* build: add default odk credentials to organisations

* build: merge migrations to organisations table

* refactor: fix linting errors

* refactor: remove subscription_tier field for orgs

* build: add public.organisation.approved field to base schema

* refactor: remove extra url field from DbOrganisation

* refactor: fix organizationModel dir --> organisation for import

* fix: remove router get_db global dependency (on routes)

* fix: use separate super_admin + check_super_admin deps

* fix: update org_admin to also allow super_admin

* refactor: remove missed log.warning from organisations endpoint

* fix: separate Depends from logic, working org approval

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: spwoodcock <[email protected]>

* ci: fix frontend smoke test with .env file

* refactor: update project dashboard to use deps functions (#1127)

* Refactor: updated code to use deps function

* Created new deps function org_from_project

* fix: call check_org_exists logic from dependency

* refactor: var naming for consistency

---------

Co-authored-by: sujanadh <[email protected]>
Co-authored-by: spwoodcock <[email protected]>

* feat: paginated submissions per task (#1128)

* merge development

* feat: paginated submissions by task

---------

Co-authored-by: sujanadh <[email protected]>

* fix: Depends usage for task_submissions endpoint

* build: replace project.private field with project.visibility enum (#1132)

* build: remove project_allowed_users table from db

* build: remove odk_central_src field from project table

* refactor: tidy/reorder project db model fields

* build: remove project.task_creation_mode field + enum

* build: replace project.private field with project.visibility enum

* fix: fix visibility migration column add syntax

* fix: apply migration to projects table

* build: fix check for projectvisibility type in migration

* ci: update workflows 1.4.5 for mkdocs --dirty

* ci: add sleep 5 for frontend smoke test dev startup

* ci: replace black with ruff-format

* build: update docker install script to handle root user

* docs: add info about using pyright for type checking

* ci: add tool.pyright config section to pyproject.toml

* fix: use optional params for extra AuthUser items

* refactor: fix return type for organisation url validator

* feat: added withcredential to include cookies on organization list api

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* build: add deployment container replicas and resource constraints (#1139)

* build: update backend image to use 1 uvicorn worker

* build: add api deploy replicas, remove container_name

* build: default to 1 local replica for easier debug

* docs: update container name for docker logs cmd

* fix: return of AuthUser during debug tests + linting

* feat: endpoint to return count of validated and mapped tasks (#1138)

* feat: new endpoint to return count of validated and mapped task

* refactor: move to tasks router + use pydantic model

* refactor: rename task_activity route --> tasks/activity

---------

Co-authored-by: sujanadh <[email protected]>
Co-authored-by: spwoodcock <[email protected]>

* feat: add AOI editing to map during project creation (#1135)

* feat (createNewProject): mapControlComponent - mapControlComponent including edit, undo added

* feat AssetModules: icons added

* feat (createNewProject): splitTasks - edit task on editBtn click

* fix codeRefactor: consoles removed

* fix mapControlComponent: undoIcon removed

* refactor(frontend): use absolute (aliased) imports over relative imports (#1136)

* fix: add vite alias @ for src dir

* refactor(frontend): replace all relative imports with @ alias

* build: add lint command to package.json (eslint)

* build: add @ alias to tsconfig for type imports

* refactor: fix all linting errors from eslint

* fix: browser console errors incorrect React usage

* fix: add axios interceptor withCredentials, plus API const

* refactor: fix remaining relative imports

* refactor: remove withCredentials from org endpoint (use interceptor)

---------

Co-authored-by: Niraj Adhikari <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Nishit Suwal <[email protected]>
Co-authored-by: Niraj Adhikari <[email protected]>
Co-authored-by: sujanadh <[email protected]>
Co-authored-by: JC CorMan <[email protected]>
Co-authored-by: Sujan Adhikari <[email protected]>
Co-authored-by: Deepak Pradhan <[email protected]>
Co-authored-by: Deepak Pradhan (Varun) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend Related to backend code devops Related to deployment or configuration docs Improvements or additions to documentation frontend Related to frontend code migration Contains a DB migration
Projects
Development

Successfully merging this pull request may close these issues.

Implement support for roles in the backend
3 participants