diff --git a/uffd/__init__.py b/uffd/__init__.py index c14c1bd82e06b870ddd2d12a82cf6e01992f6c2d..521aba6287d26343744b0d4b0d8d7e97b2b3d470 100644 --- a/uffd/__init__.py +++ b/uffd/__init__.py @@ -39,10 +39,10 @@ def create_app(test_config=None): db.init_app(app) # pylint: disable=C0415 - from uffd import user, selfservice, session, csrf, ldap + from uffd import user, selfservice, role, session, csrf, ldap # pylint: enable=C0415 - for i in user.bp + selfservice.bp + session.bp + csrf.bp + ldap.bp: + for i in user.bp + selfservice.bp + role.bp + session.bp + csrf.bp + ldap.bp: app.register_blueprint(i) @app.route("/") diff --git a/uffd/role/__init__.py b/uffd/role/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..671578662f91c82cb987ffe679c1f102dc493d1f --- /dev/null +++ b/uffd/role/__init__.py @@ -0,0 +1,3 @@ +from .views import bp as bp_ui + +bp = [bp_ui] diff --git a/uffd/role/models.py b/uffd/role/models.py new file mode 100644 index 0000000000000000000000000000000000000000..f91d9cc2ef98a44684361fe4ebb0bcac0da73199 --- /dev/null +++ b/uffd/role/models.py @@ -0,0 +1,41 @@ +from sqlalchemy import Column, String, Integer, Text, ForeignKey +from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declared_attr + +from uffd.database import db +from uffd.user.models import User, Group + +class Role(db.Model): + __tablename__ = 'role' + id = Column(Integer(), primary_key=True, autoincrement=True) + name = Column(String(32), unique=True) + description = Column(Text()) + members = relationship("RoleUser", backref="role") + groups = relationship("RoleGroup", backref="role") + + def __init__(self, name='', description=''): + self.name = name + self.description = description + +class LdapMapping(): + id = Column(Integer(), primary_key=True, autoincrement=True) + dn = Column(String(128)) + @declared_attr + def role_id(cls): + return Column(ForeignKey('role.id')) + ldapclass = None + + def get_ldap(self): + return self.ldapclass.from_ldap_dn(dn) + + def set_ldap(self, value): + self.dn = value['dn'] + +class RoleGroup(LdapMapping, db.Model): + __tablename__ = 'role-group' + ldapclass = User + +class RoleUser(LdapMapping, db.Model): + __tablename__ = 'role-user' + ldapclass = Group + diff --git a/uffd/role/templates/role.html b/uffd/role/templates/role.html new file mode 100644 index 0000000000000000000000000000000000000000..45672dafdedb5d0e451ec9e13d60b923f3c51487 --- /dev/null +++ b/uffd/role/templates/role.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} + +{% block body %} +<form action="{{ url_for("role.update", roleid=role.id) }}" method="POST"> +<div class="align-self-center"> + <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 }}"> + <small class="form-text text-muted"> + </small> + </div> + <div class="form-group col"> + <label for="role-description">Description</label> + <textarea class="form-control" id="role-description" name="description" rows="5">{{ role.description }}</textarea> + <small class="form-text text-muted"> + </small> + </div> + + <div class="form-group col"> + <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) }}" class="btn btn-danger"><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 %} + </div> +</div> +</form> +{% endblock %} diff --git a/uffd/role/templates/role_list.html b/uffd/role/templates/role_list.html new file mode 100644 index 0000000000000000000000000000000000000000..4cfcb60d745323e5d11a1f6e1d09fe363e4ff08e --- /dev/null +++ b/uffd/role/templates/role_list.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} + +{% block body %} +<div class="row"> + <div class="col"> + <table class="table table-striped"> + <thead> + <tr> + <th scope="col">name</th> + <th scope="col">description</th> + <th scope="col"> + <p class="text-right"> + <a type="button" class="btn btn-primary" href="{{ url_for("role.show") }}"> + <i class="fa fa-plus" aria-hidden="true"></i> New + </a> + </p> + </th> + </tr> + </thead> + <tbody> + {% for role in roles|sort(attribute="name") %} + <tr id="role-{{ role.id }}"> + <th scope="row"> + {{ role.name }} + </th> + <td> + {{ role.description }} + </td> + <td> + <p class="text-right"> + <a href="{{ url_for("role.show", roleid=role.id) }}" class="btn btn-primary"> + <i class="fa fa-edit" aria-hidden="true"></i> Edit + </a> + </p> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</dev> +{% endblock %} diff --git a/uffd/role/views.py b/uffd/role/views.py new file mode 100644 index 0000000000000000000000000000000000000000..8d4a28c8924d5669e81dc2c2b6182115a3d918ba --- /dev/null +++ b/uffd/role/views.py @@ -0,0 +1,60 @@ +from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app + +from uffd.navbar import register_navbar +from uffd.csrf import csrf_protect +from uffd.user.models import User, Group +from uffd.role.models import Role +from uffd.session import get_current_user, login_required, is_valid_session +from uffd.ldap import loginname_to_dn +from uffd.database import db + +bp = Blueprint("role", __name__, template_folder='templates', url_prefix='/role/') +@bp.before_request +@login_required() +def role_acl(): #pylint: disable=inconsistent-return-statements + if not role_acl_check(): + flash('Access denied') + return redirect(url_for('index')) + +def role_acl_check(): + return is_valid_session() and get_current_user().is_in_group(current_app.config['ACL_ADMIN_GROUP']) + +@bp.route("/") +@register_navbar('Roles', icon='key', blueprint=bp, visible=role_acl_check) +def index(): + return render_template('role_list.html', roles=Role.query.all()) + +@bp.route("/<int:roleid>") +@bp.route("/new") +def show(roleid=False): + if not roleid: + role = Role() + else: + role = Role.query.get_or_404(roleid) + return render_template('role.html', role=role) + +@bp.route("/<int:roleid>/update", methods=['POST']) +@bp.route("/new", methods=['POST']) +@csrf_protect(blueprint=bp) +def update(roleid=False): + is_newrole = bool(not roleid) + session = db.session + if is_newrole: + role = Role() + session.add(role) + else: + role = session.query(Role).get_or_404(roleid) + role.name = request.values['name'] + role.description = request.values['description'] + print(role) + session.commit() + return redirect(url_for('role.index')) + +@bp.route("/<int:roleid>/del") +@csrf_protect(blueprint=bp) +def delete(roleid): + session = db.session + role = session.query(Role).get_or_404(roleid) + session.delete(role) + session.commit() + return redirect(url_for('role.index'))