Start working on perms, roles, namespaces
This commit is contained in:
parent
feb4366a54
commit
1204e50c52
14 changed files with 497 additions and 73 deletions
|
|
@ -5,4 +5,5 @@
|
|||
- flask-login (for logging in)
|
||||
- wtforms (for parsing form data)
|
||||
- psycopg2 (for postgresql)
|
||||
- pyargon2 (for HashV1)
|
||||
- pyargon2 (for HashV1)
|
||||
- humanize (for generating human-readable timedeltas)
|
||||
150
src/taskflower/auth/permission/__init__.py
Normal file
150
src/taskflower/auth/permission/__init__.py
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
|
||||
from enum import IntFlag
|
||||
|
||||
from taskflower.db import db
|
||||
from taskflower.db.model.namespace import Namespace
|
||||
from taskflower.db.model.role import NamespaceRole, UserRole, UserToNamespaceRole
|
||||
from taskflower.db.model.user import User
|
||||
from taskflower.types.either import Either, Left, Right
|
||||
|
||||
|
||||
class NamespacePermissionType(IntFlag):
|
||||
NO_PERMS = 0
|
||||
READ = (1 << 0)
|
||||
ADMINISTRATE = (1 << 1)
|
||||
CREATE_IN = (1 << 2)
|
||||
COMPLETE_OWN = (1 << 3)
|
||||
COMPLETE_ALL = (1 << 4)
|
||||
UNCOMPLETE_OWN = (1 << 5)
|
||||
UNCOMPLETE_ALL = (1 << 6)
|
||||
EDIT_OWN = (1 << 7)
|
||||
EDIT_ALL = (1 << 8)
|
||||
DELETE_OWN = (1 << 9)
|
||||
DELETE_ALL = (1 << 10)
|
||||
EDIT_ROLES = (1 << 11)
|
||||
|
||||
class UserPermissionType(IntFlag):
|
||||
NO_PERMS = 0
|
||||
READ_PROFILE = (1 << 0)
|
||||
EDIT_DISPLAY_NAME = (1 << 1)
|
||||
EDIT_USERNAME = (1 << 2)
|
||||
EDIT_PROFILE = (1 << 3)
|
||||
SEE_ALL_TASKS_OF = (1 << 4)
|
||||
COMPLETE_ALL_TASKS_OF = (1 << 5)
|
||||
UNCOMPLETE_ALL_TASKS_OF = (1 << 6)
|
||||
EDIT_ALL_TASKS_OF = (1 << 7)
|
||||
DELETE_ALL_TASKS_OF = (1 << 8)
|
||||
EDIT_ROLES = (1 << 9)
|
||||
ADMINISTRATE = (1 << 10)
|
||||
|
||||
SELF_USER_PERMISSIONS = (
|
||||
UserPermissionType.READ_PROFILE
|
||||
| UserPermissionType.EDIT_DISPLAY_NAME
|
||||
| UserPermissionType.EDIT_USERNAME
|
||||
| UserPermissionType.EDIT_PROFILE
|
||||
| UserPermissionType.SEE_ALL_TASKS_OF
|
||||
| UserPermissionType.COMPLETE_ALL_TASKS_OF
|
||||
| UserPermissionType.UNCOMPLETE_ALL_TASKS_OF
|
||||
| UserPermissionType.EDIT_ALL_TASKS_OF
|
||||
| UserPermissionType.DELETE_ALL_TASKS_OF
|
||||
| UserPermissionType.EDIT_ROLES
|
||||
| UserPermissionType.ADMINISTRATE
|
||||
)
|
||||
|
||||
SELF_NAMESPACE_PERMISSIONS = (
|
||||
NamespacePermissionType.READ
|
||||
| NamespacePermissionType.ADMINISTRATE
|
||||
| NamespacePermissionType.CREATE_IN
|
||||
| NamespacePermissionType.COMPLETE_OWN
|
||||
| NamespacePermissionType.COMPLETE_ALL
|
||||
| NamespacePermissionType.UNCOMPLETE_OWN
|
||||
| NamespacePermissionType.UNCOMPLETE_ALL
|
||||
| NamespacePermissionType.EDIT_OWN
|
||||
| NamespacePermissionType.EDIT_ALL
|
||||
| NamespacePermissionType.DELETE_OWN
|
||||
| NamespacePermissionType.DELETE_ALL
|
||||
| NamespacePermissionType.EDIT_ROLES
|
||||
)
|
||||
|
||||
def _create_user_role(user: User) -> Either[Exception, UserRole]:
|
||||
try:
|
||||
self_role = UserRole(
|
||||
is_self=True, # pyright:ignore[reportCallIssue]
|
||||
name='Self', # pyright:ignore[reportCallIssue]
|
||||
description=f'Self-role for @user@{user.id}', # pyright:ignore[reportCallIssue]
|
||||
permissions=int(SELF_USER_PERMISSIONS), # pyright:ignore[reportCallIssue]
|
||||
perms_deny=0, # pyright:ignore[reportCallIssue]
|
||||
priority=0, # pyright:ignore[reportCallIssue]
|
||||
user=user.id # pyright:ignore[reportCallIssue]
|
||||
)
|
||||
|
||||
db.session.add(self_role)
|
||||
db.session.commit()
|
||||
return Right(self_role)
|
||||
except Exception as e:
|
||||
return Left(e)
|
||||
|
||||
|
||||
def _gen_user_namespace(user: User) -> Either[Exception, Namespace]:
|
||||
try:
|
||||
new_ns = Namespace(
|
||||
name=f'@user@{user.id}\'s Namespace', # pyright:ignore[reportCallIssue]
|
||||
description='Your default namespace!' # pyright:ignore[reportCallIssue]
|
||||
)
|
||||
|
||||
db.session.add(new_ns)
|
||||
db.session.commit()
|
||||
return Right(new_ns)
|
||||
except Exception as e:
|
||||
return Left(e)
|
||||
|
||||
def _create_namespace_role(
|
||||
namespace: Namespace
|
||||
) -> Either[Exception, NamespaceRole]:
|
||||
try:
|
||||
new_ns_role = NamespaceRole(
|
||||
name='Administrator', # pyright:ignore[reportCallIssue]
|
||||
description='Default role for the namespace administrator.', # pyright:ignore[reportCallIssue]
|
||||
permissions = SELF_NAMESPACE_PERMISSIONS, # pyright:ignore[reportCallIssue]
|
||||
perms_deny = 0, # pyright:ignore[reportCallIssue]
|
||||
priority = 0, # pyright:ignore[reportCallIssue]
|
||||
namespace = namespace.id # pyright:ignore[reportCallIssue]
|
||||
)
|
||||
|
||||
db.session.add(new_ns_role)
|
||||
db.session.commit()
|
||||
|
||||
return Right(new_ns_role)
|
||||
except Exception as e:
|
||||
return Left(e)
|
||||
|
||||
def _associate_namespace_role(
|
||||
user: User,
|
||||
namespace_role: NamespaceRole
|
||||
) -> Either[Exception, UserToNamespaceRole]:
|
||||
try:
|
||||
associate_ns_role = UserToNamespaceRole(
|
||||
user=user.id, # pyright:ignore[reportCallIssue]
|
||||
role=namespace_role.id # pyright:ignore[reportCallIssue]
|
||||
)
|
||||
|
||||
db.session.add(associate_ns_role)
|
||||
db.session.commit()
|
||||
return Right(associate_ns_role)
|
||||
except Exception as e:
|
||||
return Left(e)
|
||||
|
||||
|
||||
def initialize_user_permissions(user: User):
|
||||
return _create_user_role(user).flat_map(
|
||||
lambda _: _gen_user_namespace(user)
|
||||
).flat_map(
|
||||
lambda user_ns: _create_namespace_role(user_ns)
|
||||
).flat_map(
|
||||
lambda user_ns_role: _associate_namespace_role(
|
||||
user,
|
||||
user_ns_role
|
||||
)
|
||||
).map(
|
||||
lambda _: user
|
||||
)
|
||||
18
src/taskflower/auth/permission/lookups.py
Normal file
18
src/taskflower/auth/permission/lookups.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
from taskflower.db import db
|
||||
from taskflower.db.model.namespace import Namespace
|
||||
from taskflower.db.model.role import NamespaceRole, UserToNamespaceRole
|
||||
from taskflower.db.model.user import User
|
||||
|
||||
def get_namespaces_for_user(user: User):
|
||||
return db.session.query(
|
||||
Namespace
|
||||
).join(
|
||||
NamespaceRole,
|
||||
Namespace.id == NamespaceRole.namespace
|
||||
).join(
|
||||
UserToNamespaceRole,
|
||||
NamespaceRole.id == UserToNamespaceRole.role
|
||||
).filter(
|
||||
UserToNamespaceRole.user == user.id
|
||||
).all()
|
||||
40
src/taskflower/auth/permission/resolve.py
Normal file
40
src/taskflower/auth/permission/resolve.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
|
||||
|
||||
from taskflower.auth.permission import NamespacePermissionType
|
||||
from taskflower.db import db
|
||||
from taskflower.db.model.namespace import Namespace
|
||||
from taskflower.db.model.role import NamespaceRole, UserToNamespaceRole
|
||||
from taskflower.db.model.user import User
|
||||
|
||||
def resolve_perms_on_namespace(
|
||||
user: User,
|
||||
namespace: Namespace
|
||||
) -> NamespacePermissionType:
|
||||
roles = db.session.query(
|
||||
NamespaceRole
|
||||
).join(
|
||||
Namespace,
|
||||
NamespaceRole.namespace == Namespace.id
|
||||
).join(
|
||||
UserToNamespaceRole,
|
||||
NamespaceRole.id == UserToNamespaceRole.role
|
||||
).join(
|
||||
User,
|
||||
User.id == UserToNamespaceRole.user
|
||||
).filter(
|
||||
User.id == user.id
|
||||
).filter(
|
||||
Namespace.id == namespace.id
|
||||
).order_by(
|
||||
NamespaceRole.priority.desc()
|
||||
).all()
|
||||
|
||||
perms: NamespacePermissionType = NamespacePermissionType.NO_PERMS
|
||||
|
||||
for role in roles:
|
||||
perms = (
|
||||
(perms | role.permissions)
|
||||
& ~(role.perms_deny)
|
||||
)
|
||||
|
||||
return perms
|
||||
22
src/taskflower/db/model/namespace.py
Normal file
22
src/taskflower/db/model/namespace.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from datetime import datetime
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, String, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from taskflower.db import db
|
||||
|
||||
|
||||
class Namespace(db.Model):
|
||||
__tablename__: str = 'namespace'
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(256))
|
||||
description: Mapped[str] = mapped_column(String)
|
||||
created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
class TaskToNamespace(db.Model):
|
||||
__tablename__: str = 'task_to_namespace'
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
namespace: Mapped[int] = mapped_column(Integer, ForeignKey('namespace.id'))
|
||||
task: Mapped[int] = mapped_column(Integer, ForeignKey('task.id'))
|
||||
43
src/taskflower/db/model/role.py
Normal file
43
src/taskflower/db/model/role.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
from datetime import datetime
|
||||
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from taskflower.db import db
|
||||
|
||||
|
||||
class NamespaceRole(db.Model):
|
||||
__tablename__: str = 'namespace_role'
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(256))
|
||||
description: Mapped[str] = mapped_column(String)
|
||||
permissions: Mapped[int] = mapped_column(Integer)
|
||||
perms_deny: Mapped[int] = mapped_column(Integer)
|
||||
priority: Mapped[int] = mapped_column(Integer)
|
||||
created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
namespace: Mapped[int] = mapped_column(Integer, ForeignKey('namespace.id'))
|
||||
|
||||
class UserToNamespaceRole(db.Model):
|
||||
__tablename__: str = 'user_to_namespace_role'
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
user: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'))
|
||||
role: Mapped[int] = mapped_column(Integer, ForeignKey('namespace_role.id'))
|
||||
|
||||
class UserRole(db.Model):
|
||||
__tablename__: str = 'user_role'
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
is_self: Mapped[bool] = mapped_column(Boolean, default=False) # Self-roles can't be assigned to anyone other than the user whose account they are associated with
|
||||
name: Mapped[str] = mapped_column(String(256))
|
||||
description: Mapped[str] = mapped_column(String)
|
||||
permissions: Mapped[int] = mapped_column(Integer)
|
||||
perms_deny: Mapped[int] = mapped_column(Integer)
|
||||
priority: Mapped[int] = mapped_column(Integer)
|
||||
created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
user: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'))
|
||||
|
||||
class UserToUserRole(db.Model):
|
||||
__tablename__: str = 'user_to_user_role'
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
user: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'))
|
||||
role: Mapped[int] = mapped_column(Integer, ForeignKey('user_role.id'))
|
||||
|
|
@ -46,7 +46,7 @@ class Task(db.Model, APISerializable):
|
|||
__tablename__: str = 'task'
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String)
|
||||
name: Mapped[str] = mapped_column(String(256))
|
||||
description: Mapped[str] = mapped_column(String)
|
||||
due: Mapped[datetime] = mapped_column(DateTime(timezone=True))
|
||||
created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
|
|
|||
61
src/taskflower/static/list-view.css
Normal file
61
src/taskflower/static/list-view.css
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
.list{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.list{
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.list tr {
|
||||
background-color: var(--table-row-bg-1);
|
||||
color: var(--on-table-row);
|
||||
}
|
||||
|
||||
.list tr:nth-child(odd) {
|
||||
background-color: var(--table-row-bg-2);
|
||||
color: var(--on-table-row);
|
||||
}
|
||||
|
||||
.list .task-header-row {
|
||||
border-bottom: 8px solid var(--bg);
|
||||
}
|
||||
|
||||
.list td, .list th{
|
||||
border-right: 4px solid var(--bg);
|
||||
border-left: 4px solid var(--bg);
|
||||
}
|
||||
|
||||
.list td {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.list p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.list .detail-view {
|
||||
display: none;
|
||||
border-bottom: 4px solid var(--bg);
|
||||
border-top: 1px solid var(--bg);
|
||||
}
|
||||
|
||||
.list .detail-view-elem {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.list .detail-view.shown {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.list .detail-view-elem .small-details {
|
||||
font-size: small;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.list .detail-view-elem h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.list .detail-view-elem .main-description {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
15
src/taskflower/templates/namespace/_listentry.html
Normal file
15
src/taskflower/templates/namespace/_listentry.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{% macro namespace_header() %}
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Link</th>
|
||||
</tr>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro inline_namespace(ns) %}
|
||||
<tr id={{ "row-"~ns.id }}>
|
||||
<td id={{ "name-"~ns.id }}>{{ ns.name }}</td>
|
||||
<td id={{ "desc-"~ns.id }}>{{ ns.description }}</td>
|
||||
<td id={{ "link-"~ns.id }}><a href={{ url_for("web.namespace.get", id=ns.id) }}>GO ➪</a></td>
|
||||
</tr>
|
||||
{% endmacro %}
|
||||
27
src/taskflower/templates/namespace/list.html
Normal file
27
src/taskflower/templates/namespace/list.html
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{% extends "main.html" %}
|
||||
{% from "namespace/_listentry.html" import inline_namespace, namespace_header %}
|
||||
|
||||
|
||||
{% block head_extras %}
|
||||
<link rel="stylesheet" href={{ url_for("static", filename="list-view.css") }} />
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}My Namespaces{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1>My Namespaces</h1>
|
||||
<table class="list">
|
||||
<colgroup>
|
||||
<col span="1"/>
|
||||
<col span="1"/>
|
||||
<col span="1"/>
|
||||
</colgroup>
|
||||
|
||||
<tbody>
|
||||
{{ namespace_header() }}
|
||||
{% for ns in namespaces %}
|
||||
{{ inline_namespace(ns) }}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
|
@ -1,18 +1,22 @@
|
|||
{% extends "main.html" %}
|
||||
{% from "task/_shorttask.html" import inline_task, inline_task_header %}
|
||||
|
||||
{% block head_extras %}
|
||||
<link rel="stylesheet" href={{ url_for("static", filename="list-view.css") }} />
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}My Tasks{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1>My Tasks</h1>
|
||||
<table class="task-list">
|
||||
<table class="list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 0;"/>
|
||||
<col span="1"/>
|
||||
<col span="1" style="width: 20%;"/>
|
||||
</colgroup>
|
||||
|
||||
<tbody class="task-list">
|
||||
<tbody class="list">
|
||||
{{ inline_task_header() }}
|
||||
{% for task in tasks %}
|
||||
{{ inline_task(task) }}
|
||||
|
|
@ -21,77 +25,13 @@
|
|||
</table>
|
||||
|
||||
<style>
|
||||
.task-list{
|
||||
/* display: grid;
|
||||
grid-template-columns: min-content 20% auto max-content; */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.task-list{
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.task-list tr {
|
||||
background-color: var(--table-row-bg-1);
|
||||
color: var(--on-table-row);
|
||||
}
|
||||
|
||||
.task-list tr:nth-child(odd) {
|
||||
background-color: var(--table-row-bg-2);
|
||||
color: var(--on-table-row);
|
||||
}
|
||||
|
||||
.task-list .task-header-row {
|
||||
border-bottom: 8px solid var(--bg);
|
||||
}
|
||||
|
||||
.task-list * td, .task-list * th{
|
||||
border-right: 4px solid var(--bg);
|
||||
border-left: 4px solid var(--bg);
|
||||
}
|
||||
|
||||
.task-list * td {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.task-list * .checkbox {
|
||||
.list .checkbox {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.task-list * .task-due {
|
||||
.list .task-due {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.task-list * p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.task-list * .detail-view {
|
||||
display: none;
|
||||
border-bottom: 4px solid var(--bg);
|
||||
border-top: 1px solid var(--bg);
|
||||
}
|
||||
|
||||
.task-list * .detail-view-elem {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.task-list * .detail-view.shown {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.task-list * .detail-view-elem .small-details {
|
||||
font-size: small;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.task-list * .detail-view-elem h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.task-list * .detail-view-elem .main-description {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
from flask import Blueprint
|
||||
from taskflower.web.auth import web_auth
|
||||
from taskflower.web.namespace import web_namespace
|
||||
from taskflower.web.task import web_tasks
|
||||
from taskflower.web.user import web_user
|
||||
from taskflower.web.auth import web_auth
|
||||
|
||||
web_base = Blueprint('web', __name__, url_prefix='/')
|
||||
|
||||
web_base.register_blueprint(web_tasks)
|
||||
web_base.register_blueprint(web_user)
|
||||
web_base.register_blueprint(web_auth)
|
||||
web_base.register_blueprint(web_auth)
|
||||
web_base.register_blueprint(web_namespace)
|
||||
102
src/taskflower/web/namespace/__init__.py
Normal file
102
src/taskflower/web/namespace/__init__.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
|
||||
from dataclasses import dataclass
|
||||
from flask import Blueprint, redirect, render_template, url_for
|
||||
from flask_login import current_user, login_required # pyright:ignore[reportUnknownVariableType,reportMissingTypeStubs]
|
||||
|
||||
from taskflower.auth.permission import NamespacePermissionType
|
||||
from taskflower.auth.permission.lookups import get_namespaces_for_user
|
||||
from taskflower.auth.permission.resolve import resolve_perms_on_namespace
|
||||
from taskflower.db.model.namespace import Namespace
|
||||
from taskflower.db.model.user import User
|
||||
from taskflower.types.either import Either, Left, Right, gather_successes
|
||||
|
||||
|
||||
web_namespace = Blueprint(
|
||||
'namespace',
|
||||
__name__,
|
||||
'/templates',
|
||||
url_prefix='/namespace'
|
||||
)
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class NamespacePermsForUser:
|
||||
read: bool
|
||||
create_in: bool
|
||||
complete_own: bool
|
||||
complete_all: bool
|
||||
uncomplete_own: bool
|
||||
uncomplete_all: bool
|
||||
edit_own: bool
|
||||
edit_all: bool
|
||||
delete_own: bool
|
||||
delete_all: bool
|
||||
edit_roles: bool
|
||||
administrate: bool
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class NamespaceForUser():
|
||||
id: int
|
||||
name: str
|
||||
description: str
|
||||
perms: NamespacePermsForUser
|
||||
|
||||
@staticmethod
|
||||
def from_user(ns: Namespace, user: User) -> Either[Exception, 'NamespaceForUser']:
|
||||
perms = resolve_perms_on_namespace(
|
||||
user,
|
||||
ns
|
||||
)
|
||||
|
||||
if not (
|
||||
NamespacePermissionType.READ in perms
|
||||
or NamespacePermissionType.ADMINISTRATE in perms
|
||||
):
|
||||
return Left(KeyError('No such namespace or insufficient permissions.'))
|
||||
|
||||
return Right(
|
||||
NamespaceForUser(
|
||||
ns.id,
|
||||
ns.name,
|
||||
ns.description,
|
||||
NamespacePermsForUser(
|
||||
NamespacePermissionType.READ in perms,
|
||||
NamespacePermissionType.CREATE_IN in perms,
|
||||
NamespacePermissionType.COMPLETE_OWN in perms,
|
||||
NamespacePermissionType.COMPLETE_ALL in perms,
|
||||
NamespacePermissionType.UNCOMPLETE_OWN in perms,
|
||||
NamespacePermissionType.UNCOMPLETE_ALL in perms,
|
||||
NamespacePermissionType.EDIT_OWN in perms,
|
||||
NamespacePermissionType.EDIT_ALL in perms,
|
||||
NamespacePermissionType.DELETE_OWN in perms,
|
||||
NamespacePermissionType.DELETE_ALL in perms,
|
||||
NamespacePermissionType.EDIT_ROLES in perms,
|
||||
NamespacePermissionType.ADMINISTRATE in perms
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@web_namespace.route('/')
|
||||
@login_required
|
||||
def all():
|
||||
cur_usr: User = current_user # pyright:ignore[reportAssignmentType]
|
||||
namespace_list: list[Namespace] = get_namespaces_for_user(cur_usr)
|
||||
|
||||
namespaces_parsed = gather_successes([
|
||||
NamespaceForUser.from_user(
|
||||
ns, cur_usr
|
||||
)
|
||||
for ns in namespace_list
|
||||
])
|
||||
|
||||
return render_template(
|
||||
'namespace/list.html',
|
||||
namespaces=namespaces_parsed
|
||||
)
|
||||
|
||||
@web_namespace.route('/<int:id>')
|
||||
@login_required
|
||||
def get(id: int):
|
||||
return redirect(url_for('web.namespace.all'))
|
||||
|
|
@ -5,6 +5,7 @@ from wtforms.validators import DataRequired, EqualTo, Length
|
|||
|
||||
from taskflower.auth import password_breach_count
|
||||
from taskflower.auth.hash import make_hash_v1
|
||||
from taskflower.auth.permission import initialize_user_permissions
|
||||
from taskflower.db import db
|
||||
from taskflower.db.model.user import User
|
||||
from taskflower.types import ann
|
||||
|
|
@ -254,10 +255,12 @@ def create_user_page():
|
|||
if request.method == 'POST' and form_data.validate():
|
||||
return _user_from_form(form_data).flat_map(
|
||||
lambda usr: _add_to_db(usr)
|
||||
).flat_map(
|
||||
lambda usr: initialize_user_permissions(usr)
|
||||
).side_effect(
|
||||
lambda usr: login_user(usr)
|
||||
).and_then(
|
||||
lambda usr: redirect(url_for('web.task.all_tasks_view')),
|
||||
lambda usr: redirect(url_for('web.task.all')),
|
||||
lambda exc: response_from_exception(exc)
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue