Make permissions hierarchical

This commit is contained in:
evilchili 2025-10-06 17:50:10 -07:00
parent 0e8fd9a1b0
commit 582fd2d9a1
4 changed files with 31 additions and 19 deletions

View File

@ -145,18 +145,17 @@ VIEW_URI=/
admins = self.add_member(groups, admins) admins = self.add_member(groups, admins)
admin = self.add_member(admins, admin) admin = self.add_member(admins, admin)
groups.set_permissions(admins, permissions=[ groups.set_permissions(
schema.Permissions.READ, admins,
schema.Permissions.WRITE, permissions=[schema.Permissions.READ, schema.Permissions.WRITE, schema.Permissions.DELETE],
schema.Permissions.DELETE db=self.db,
], db=self.db) )
users.set_permissions(admins, permissions=[
schema.Permissions.READ,
schema.Permissions.WRITE,
schema.Permissions.DELETE
], db=self.db)
users.set_permissions(
admins,
permissions=[schema.Permissions.READ, schema.Permissions.WRITE, schema.Permissions.DELETE],
db=self.db,
)
sys.modules[__name__] = ApplicationContext() sys.modules[__name__] = ApplicationContext()

View File

@ -76,10 +76,20 @@ class Page(Record):
class Entity(Page): class Entity(Page):
def has_permission(self, record: Record, requested: str, db) -> bool: def has_permission(self, record: Record, requested: str, db) -> bool | None:
# if there's no ACL at all, the record is world-readable. # Find a non-empty ACL to use by starting with the requested reecord and traversing
if not getattr(record, "acl", None): # the hierarchy upwards. If we get to the root and there's no ACL anywhere, default
# to READ permissions.
def find_acl(obj):
if hasattr(obj, 'acl') and obj.acl:
return obj.acl
if not hasattr(obj, "parent"):
return None
return find_acl(obj.parent)
acl = find_acl(record)
if not acl:
return requested == Permissions.READ return requested == Permissions.READ
# Use the grant specific to this entity, if there is one # Use the grant specific to this entity, if there is one
@ -87,9 +97,11 @@ class Entity(Page):
if entry.entity.uid == self.uid: if entry.entity.uid == self.uid:
return requested in entry.grants return requested in entry.grants
# Check for grants for each of the entity's groups, if any
for group in db.Group.search(Query()["members"].any([self.reference])): for group in db.Group.search(Query()["members"].any([self.reference])):
if group.has_permission(record, requested, db): if group.has_permission(record, requested, db):
return True return True
return False return False
def can_read(self, record: Record, db): def can_read(self, record: Record, db):
@ -106,7 +118,6 @@ class User(Entity):
""" """
A website user, editable as a wiki page. A website user, editable as a wiki page.
""" """
def check_credentials(self, username: str, password: str) -> bool: def check_credentials(self, username: str, password: str) -> bool:
return username == self.name and self._metadata.fields["password"].compare(password, self.password) return username == self.name and self._metadata.fields["password"].compare(password, self.password)

View File

@ -19,7 +19,7 @@
{% if session['user_id'] == 1 %} {% if session['user_id'] == 1 %}
Welcome, {{ user['name'] }}. [ <a href="{{ url_for('login') }}">LOGIN</a> ] Welcome, {{ user['name'] }}. [ <a href="{{ url_for('login') }}">LOGIN</a> ]
{% else %} {% else %}
Welcome, <a href="{{ url_for('User/' + user['name']) }}">{{ user['name'] }}</a>. Welcome, <a href="{{ url_for('view', path='User/' + user['name']) }}">{{ user['name'] }}</a>.
{% endif %} {% endif %}
<nav> <nav>
Menu: Menu:

View File

@ -64,7 +64,7 @@ def get_page(path: str = "", table: str = "Page", create_okay: bool = False):
def rendered(page: schema.Record, template: str = "page.html"): def rendered(page: schema.Record, template: str = "page.html"):
if not page: if not page:
return Response("Page not found", status=404) return Response("Page not found", status=404)
return render_template(template, page=page, app=app, breadcrumbs=breadcrumbs(), user=session['user'], g=g) return render_template(template, page=page, app=app, breadcrumbs=breadcrumbs(), user=session["user"], g=g)
def get_static(path): def get_static(path):
@ -94,13 +94,16 @@ def do_login(username: str, password: str) -> bool:
Update the session with the user record if the credenteials are valid. Update the session with the user record if the credenteials are valid.
""" """
if not (username and password): if not (username and password):
app.web.logger.debug("Need both username and password to login")
return False return False
user = app.db.User.get(where("name") == "username") user = app.db.User.get(where("name") == username)
if not user: if not user:
app.web.logger.debug(f"No user matching {username}")
return False return False
if not user.check_credentials(username, password): if not user.check_credentials(username, password):
app.web.logger.debug(f"Invalid credentials for {username}")
return False return False
app.web.logger.debug(f"Session for {user.name} ({user.doc_id}) started.") app.web.logger.debug(f"Session for {user.name} ({user.doc_id}) started.")
@ -113,7 +116,6 @@ def do_login(username: str, password: str) -> bool:
@app.web.route("/login", methods=["GET", "POST"]) @app.web.route("/login", methods=["GET", "POST"])
def login(): def login():
app.web.session_interface.regenerate(session) app.web.session_interface.regenerate(session)
if request.method == "POST": if request.method == "POST":
username = request.form.get("username") username = request.form.get("username")
password = request.form.get("password") password = request.form.get("password")