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.ext.declarative import declared_attr
......@@ -91,6 +91,11 @@ class Role(db.Model):
db_groups = relationship("RoleGroup", backref="role", cascade="all, delete-orphan")
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
def indirect_members(self):
users = set()
......
{% extends 'base.html' %}
{% 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">
<div class="align-self-center">
<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>
<a href="{{ url_for("role.index") }}" class="btn btn-secondary">Cancel</a>
{% 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 %}
<a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
{% endif %}
......@@ -28,7 +34,7 @@
<div class="tab-pane fade show active" id="settings" role="tabpanel" aria-labelledby="settings-tab">
<div class="form-group col">
<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>
</div>
......@@ -40,7 +46,7 @@
</div>
<div class="form-group col">
<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>
{% for group in groups %}
<option value="{{ group.dn }}" {{ 'selected' if group == role.moderator_group }}>{{ group.name }}</option>
......@@ -82,7 +88,7 @@
<td>
<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"
{% if r == role %}disabled{% endif %}
{% if r == role or role.locked %}disabled{% endif %}
{% if r in role.included_roles %}checked{% endif %}>
</div>
</td>
......@@ -121,7 +127,7 @@
<tr id="group-{{ group.gid }}">
<td>
<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>
</td>
<td>
......
......@@ -71,8 +71,9 @@ def update(roleid=None):
db.session.add(role)
else:
role = Role.query.filter_by(id=roleid).one()
role.name = request.values['name']
role.description = request.values['description']
if not role.locked:
role.name = request.values['name']
if not request.values['moderator-group']:
role.moderator_group_dn = None
else:
......@@ -96,6 +97,9 @@ def update(roleid=None):
@csrf_protect(blueprint=bp)
def delete(roleid):
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)
role.members.clear()
db.session.delete(role)
......@@ -104,3 +108,11 @@ def delete(roleid):
db.session.commit()
ldap.session.commit()
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