Skip to content
Snippets Groups Projects
Commit 4d6673fe authored by Julian's avatar Julian
Browse files

Add locking mechanism for externally managed roles

parent 0d48e6cb
No related branches found
No related tags found
No related merge requests found
"""added role.locked
Revision ID: a594d3b3e05b
Revises: 5cab70e95bf8
Create Date: 2021-06-14 00:32:47.792794
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a594d3b3e05b'
down_revision = '5cab70e95bf8'
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table('role', schema=None) as batch_op:
batch_op.add_column(sa.Column('locked', sa.Boolean(name=op.f('ck_role_locked')), nullable=False, default=False))
def downgrade():
with op.batch_alter_table('role', schema=None) as batch_op:
batch_op.drop_column('locked')
from sqlalchemy import Column, String, Integer, Text, ForeignKey from sqlalchemy import Column, String, Integer, Text, ForeignKey, Boolean
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
...@@ -91,6 +91,11 @@ class Role(db.Model): ...@@ -91,6 +91,11 @@ class Role(db.Model):
db_groups = relationship("RoleGroup", backref="role", cascade="all, delete-orphan") db_groups = relationship("RoleGroup", backref="role", cascade="all, delete-orphan")
groups = DBRelationship('db_groups', Group, RoleGroup, backattr='role', backref='roles') groups = DBRelationship('db_groups', Group, RoleGroup, backattr='role', backref='roles')
# Roles that are managed externally (e.g. by Ansible) can be locked to
# prevent accidental editing of name, moderator group, included roles
# and groups as well as deletion in the web interface.
locked = Column(Boolean(), default=False, nullable=False)
@property @property
def indirect_members(self): def indirect_members(self):
users = set() users = set()
......
{% extends 'base.html' %} {% extends 'base.html' %}
{% block body %} {% block body %}
{% if role.locked %}
<div class="alert alert-warning" role="alert">
Name, moderator group, included roles and groups of this role are managed externally. <a href="{{ url_for("role.unlock", roleid=role.id) }}" class="alert-link">Unlock this role</a> to edit them at the risk of having your changes overwritten.
</div>
{% endif %}
<form action="{{ url_for("role.update", roleid=role.id) }}" method="POST"> <form action="{{ url_for("role.update", roleid=role.id) }}" method="POST">
<div class="align-self-center"> <div class="align-self-center">
<div class="float-sm-right pb-2"> <div class="float-sm-right pb-2">
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> Save</button> <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> Save</button>
<a href="{{ url_for("role.index") }}" class="btn btn-secondary">Cancel</a> <a href="{{ url_for("role.index") }}" class="btn btn-secondary">Cancel</a>
{% if role.id %} {% if role.id %}
<a href="{{ url_for("role.delete", roleid=role.id) }}" onClick="return confirm('Are you sure?');" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a> <a href="{{ url_for("role.delete", roleid=role.id) }}" onClick="return confirm('Are you sure?');" class="btn btn-danger {{ 'disabled' if role.locked }}"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
{% else %} {% else %}
<a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a> <a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
{% endif %} {% endif %}
...@@ -28,7 +34,7 @@ ...@@ -28,7 +34,7 @@
<div class="tab-pane fade show active" id="settings" role="tabpanel" aria-labelledby="settings-tab"> <div class="tab-pane fade show active" id="settings" role="tabpanel" aria-labelledby="settings-tab">
<div class="form-group col"> <div class="form-group col">
<label for="role-name">Role Name</label> <label for="role-name">Role Name</label>
<input type="text" class="form-control" id="role-name" name="name" value="{{ role.name or '' }}"> <input type="text" class="form-control" id="role-name" name="name" value="{{ role.name or '' }}" {{ 'disabled' if role.locked }}>
<small class="form-text text-muted"> <small class="form-text text-muted">
</small> </small>
</div> </div>
...@@ -40,7 +46,7 @@ ...@@ -40,7 +46,7 @@
</div> </div>
<div class="form-group col"> <div class="form-group col">
<label for="moderator-group">Moderator Group</label> <label for="moderator-group">Moderator Group</label>
<select class="form-control" id="moderator-group" name="moderator-group"> <select class="form-control" id="moderator-group" name="moderator-group" {{ 'disabled' if role.locked }}>
<option value="" class="text-muted">No Moderator Group</option> <option value="" class="text-muted">No Moderator Group</option>
{% for group in groups %} {% for group in groups %}
<option value="{{ group.dn }}" {{ 'selected' if group == role.moderator_group }}>{{ group.name }}</option> <option value="{{ group.dn }}" {{ 'selected' if group == role.moderator_group }}>{{ group.name }}</option>
...@@ -82,7 +88,7 @@ ...@@ -82,7 +88,7 @@
<td> <td>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="include-role-{{ r.id }}-checkbox" name="include-role-{{ r.id }}" value="1" aria-label="enabled" <input class="form-check-input" type="checkbox" id="include-role-{{ r.id }}-checkbox" name="include-role-{{ r.id }}" value="1" aria-label="enabled"
{% if r == role %}disabled{% endif %} {% if r == role or role.locked %}disabled{% endif %}
{% if r in role.included_roles %}checked{% endif %}> {% if r in role.included_roles %}checked{% endif %}>
</div> </div>
</td> </td>
...@@ -121,7 +127,7 @@ ...@@ -121,7 +127,7 @@
<tr id="group-{{ group.gid }}"> <tr id="group-{{ group.gid }}">
<td> <td>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="group-{{ group.gid }}-checkbox" name="group-{{ group.gid }}" value="1" aria-label="enabled" {% if group in role.groups %}checked{% endif %}> <input class="form-check-input" type="checkbox" id="group-{{ group.gid }}-checkbox" name="group-{{ group.gid }}" value="1" aria-label="enabled" {% if group in role.groups %}checked{% endif %} {{ 'disabled' if role.locked }}>
</div> </div>
</td> </td>
<td> <td>
......
...@@ -71,8 +71,9 @@ def update(roleid=None): ...@@ -71,8 +71,9 @@ def update(roleid=None):
db.session.add(role) db.session.add(role)
else: else:
role = Role.query.filter_by(id=roleid).one() role = Role.query.filter_by(id=roleid).one()
role.name = request.values['name']
role.description = request.values['description'] role.description = request.values['description']
if not role.locked:
role.name = request.values['name']
if not request.values['moderator-group']: if not request.values['moderator-group']:
role.moderator_group_dn = None role.moderator_group_dn = None
else: else:
...@@ -96,6 +97,9 @@ def update(roleid=None): ...@@ -96,6 +97,9 @@ def update(roleid=None):
@csrf_protect(blueprint=bp) @csrf_protect(blueprint=bp)
def delete(roleid): def delete(roleid):
role = Role.query.filter_by(id=roleid).one() role = Role.query.filter_by(id=roleid).one()
if role.locked:
flash('Locked roles cannot be deleted')
return redirect(url_for('role.show', roleid=role.id))
oldmembers = set(role.members).union(role.indirect_members) oldmembers = set(role.members).union(role.indirect_members)
role.members.clear() role.members.clear()
db.session.delete(role) db.session.delete(role)
...@@ -104,3 +108,11 @@ def delete(roleid): ...@@ -104,3 +108,11 @@ def delete(roleid):
db.session.commit() db.session.commit()
ldap.session.commit() ldap.session.commit()
return redirect(url_for('role.index')) return redirect(url_for('role.index'))
@bp.route("/<int:roleid>/unlock")
@csrf_protect(blueprint=bp)
def unlock(roleid):
role = Role.query.filter_by(id=roleid).one()
role.locked = False
db.session.commit()
return redirect(url_for('role.show', roleid=role.id))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment