Skip to content
Snippets Groups Projects
Verified Commit 90913400 authored by nd's avatar nd
Browse files

added login and acls

parent a719690e
No related branches found
No related tags found
No related merge requests found
import os import os
from flask import Flask from flask import Flask, redirect, url_for
from werkzeug.routing import IntegerConverter from werkzeug.routing import IntegerConverter
from uffd.database import db, SQLAlchemyJSON from uffd.database import db, SQLAlchemyJSON
...@@ -40,12 +40,15 @@ def create_app(test_config=None): ...@@ -40,12 +40,15 @@ def create_app(test_config=None):
db.init_app(app) db.init_app(app)
# pylint: disable=C0415 # pylint: disable=C0415
from uffd import user, group, csrf, ldap from uffd import user, group, selfservice, session, csrf, ldap
# pylint: enable=C0415 # pylint: enable=C0415
for i in user.bp + group.bp + csrf.bp + ldap.bp: for i in user.bp + group.bp + selfservice.bp + session.bp + csrf.bp + ldap.bp:
app.register_blueprint(i) app.register_blueprint(i)
app.add_url_rule("/", endpoint="index")
@app.route("/")
def index():
return redirect(url_for('selfservice.self_index'))
return app return app
......
...@@ -7,3 +7,4 @@ LDAP_USER_OBJECTCLASSES=["vmailUser", "top", "inetOrgPerson", "organizationalPer ...@@ -7,3 +7,4 @@ LDAP_USER_OBJECTCLASSES=["vmailUser", "top", "inetOrgPerson", "organizationalPer
LDAP_USER_GID=20001 LDAP_USER_GID=20001
LDAP_USER_MIN_UID=10000 LDAP_USER_MIN_UID=10000
LDAP_USER_MAX_UID=18999 LDAP_USER_MAX_UID=18999
SESSION_LIFETIME_SECONDS=3600
...@@ -2,11 +2,17 @@ from flask import Blueprint, current_app, render_template ...@@ -2,11 +2,17 @@ from flask import Blueprint, current_app, render_template
from uffd.navbar import register_navbar from uffd.navbar import register_navbar
from uffd.ldap import get_conn, escape_filter_chars from uffd.ldap import get_conn, escape_filter_chars
from uffd.session import login_required
from .models import Group from .models import Group
bp = Blueprint("group", __name__, template_folder='templates', url_prefix='/group/') bp = Blueprint("group", __name__, template_folder='templates', url_prefix='/group/')
@bp.before_request
@login_required
def group_acl():
pass
@bp.route("/") @bp.route("/")
@register_navbar('Groups', icon='layer-group', blueprint=bp) @register_navbar('Groups', icon='layer-group', blueprint=bp)
def group_list(): def group_list():
......
from .ldap import bp as ldap_bp from .ldap import bp as ldap_bp
from .ldap import get_conn, escape_filter_chars, uid_to_dn, loginname_to_dn, get_next_uid from .ldap import get_conn, user_conn, escape_filter_chars, uid_to_dn, loginname_to_dn, get_next_uid
bp = [ldap_bp] bp = [ldap_bp]
from flask import Blueprint, request, session, current_app from flask import Blueprint, request, session, current_app
from ldap3.utils.conv import escape_filter_chars
from ldap3.utils.dn import escape_rdn
from ldap3.core.exceptions import LDAPBindError
from ldap3 import Server, Connection, ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES from ldap3 import Server, Connection, ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES
from ldap3.utils.conv import escape_filter_chars
bp = Blueprint("ldap", __name__) bp = Blueprint("ldap", __name__)
...@@ -17,15 +19,19 @@ def service_conn(): ...@@ -17,15 +19,19 @@ def service_conn():
server = Server(current_app.config["LDAP_SERVICE_URL"], get_info=ALL) server = Server(current_app.config["LDAP_SERVICE_URL"], get_info=ALL)
return Connection(server, current_app.config["LDAP_SERVICE_BIND_DN"], current_app.config["LDAP_SERVICE_BIND_PASSWORD"], auto_bind=True) return Connection(server, current_app.config["LDAP_SERVICE_BIND_DN"], current_app.config["LDAP_SERVICE_BIND_PASSWORD"], auto_bind=True)
def user_conn(): def user_conn(loginname, password):
pass server = Server(current_app.config["LDAP_SERVICE_URL"], get_info=ALL)
try:
return fix_connection(Connection(server, loginname_to_dn(loginname), password, auto_bind=True))
except LDAPBindError:
return False
def get_conn(): def get_conn():
conn = service_conn() conn = service_conn()
return fix_connection(conn) return fix_connection(conn)
def uid_to_dn(uid): def uid_to_dn(uid):
conn = service_conn() conn = get_conn()
conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format(escape_filter_chars(uid))) conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format(escape_filter_chars(uid)))
if not len(conn.entries) == 1: if not len(conn.entries) == 1:
return None return None
...@@ -33,10 +39,10 @@ def uid_to_dn(uid): ...@@ -33,10 +39,10 @@ def uid_to_dn(uid):
return conn.entries[0].entry_dn return conn.entries[0].entry_dn
def loginname_to_dn(loginname): def loginname_to_dn(loginname):
return 'uid={},{}'.format(escape_filter_chars(loginname), current_app.config["LDAP_BASE_USER"]) return 'uid={},{}'.format(escape_rdn(loginname), current_app.config["LDAP_BASE_USER"])
def get_next_uid(): def get_next_uid():
conn = service_conn() conn = get_conn()
conn.search(current_app.config["LDAP_BASE_USER"], '(objectclass=person)') conn.search(current_app.config["LDAP_BASE_USER"], '(objectclass=person)')
max_uid = current_app.config["LDAP_USER_MIN_UID"] max_uid = current_app.config["LDAP_USER_MIN_UID"]
for i in conn.entries: for i in conn.entries:
......
from .views import bp as bp_ui
bp = [bp_ui]
{% extends 'base.html' %}
{% block body %}
<form action="{{ url_for(".self_update") }}" method="POST" onInput="password2.setCustomValidity(password1.value != password2.value ? 'Passwords do not match.' : '')">
<div class="align-self-center row">
<div class="form-group col-md-6">
<label for="user-uid">uid</label>
<input type="number" class="form-control" id="user-uid" name="uid" value="{{ user.uid }}" readonly>
</div>
<div class="form-group col-md-6">
<label for="user-loginname">login name</label>
<input type="text" class="form-control" id="user-loginname" name="loginname" value="{{ user.loginname }}" readonly>
</div>
<div class="form-group col-md-6">
<label for="user-displayname">display name</label>
<input type="text" class="form-control" id="user-displayname" name="displayname" value="{{ user.displayname }}">
</div>
<div class="form-group col-md-6">
<label for="user-mail">mail</label>
<input type="email" class="form-control" id="user-mail" name="mail" value="{{ user.mail }}">
<small class="form-text text-muted">
</small>
</div>
<div class="form-group col-md-6">
<label for="user-password1">password</label>
<input type="password" class="form-control" id="user-password1" name="password1" placeholder="do not change">
<small class="form-text text-muted">
No special requirements but please don't be stupid and use a password manager.
</small>
</div>
<div class="form-group col-md-6">
<label for="user-password2">password repeat</label>
<input type="password" class="form-control" id="user-password2" name="password2" placeholder="do not change">
</div>
<div class="form-group col-md-12">
<button type="submit" class="btn btn-primary float-right"><i class="fa fa-save" aria-hidden="true"></i> Save</button>
</div>
<div class="form-group col-md-12" "id="accordion">
<div class="card">
<div class="card-header" id="user-group">
<h5 class="mb-0">
<a class="btn btn-link collapsed" data-toggle="collapse" data-target="#user-group-body" aria-expanded="false" aria-controls="user-group">
groups
</a>
</h5>
</div>
<div id="user-group-body" class="collapse" aria-labelledby="user-group" data-parent="#accordion">
<div class="card-body">
<ul class="list-group">
{% for group in user.get_groups() %}
<li class="list-group-item"><a href="{{ url_for("group.group_show", gid=group.gid) }}">{{ group.name }}</a></li>
{% endfor %}
</ul>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="user-role">
<h5 class="mb-0">
<a class="btn btn-link" data-toggle="collapse" data-target="#user-role-body" aria-expanded="true" aria-controls="user-role">
roles
</a>
</h5>
</div>
<div id="user-role-body" class="collapse show" aria-labelledby="user-role" data-parent="#accordion">
<div class="card-body">
roles.
</div>
</div>
</div>
</div>
</div>
</form>
{% endblock %}
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
from uffd.group.models import Group
from uffd.session import get_current_user, login_required
from uffd.ldap import get_conn, escape_filter_chars
bp = Blueprint("selfservice", __name__, template_folder='templates', url_prefix='/self/')
@bp.before_request
@login_required
def self_acl():
pass
@bp.route("/")
@register_navbar('Selfservice', icon='portrait', blueprint=bp)
def self_index():
return render_template('self.html', user=get_current_user())
@bp.route("/update", methods=(['POST']))
@csrf_protect
def self_update():
pass
from .views import bp as bp_ui, get_current_user, login_required, is_user_in_group
bp = [bp_ui]
{% extends 'base.html' %}
{% block body %}
<form action="{{ url_for(".login") }}" method="POST">
<div class="row mt-2 justify-content-center">
<div class="col-lg-6 col-md-10" style="background: #f7f7f7; box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); padding: 30px;">
<div class="text-center">
<img src="{{ url_for("static", filename="chaosknoten.png") }}" class="col-lg-8 col-md-12" >
</div>
<div class="col-12">
<h2 class="text-center">Login</h2>
</div>
<div class="form-group col-12">
<label for="user-loginname">login name</label>
<input type="text" class="form-control" id="user-loginname" name="loginname" required="required" tabindex = "1">
</div>
<div class="form-group col-12">
<label for="user-password1">password</label>
<input type="password" class="form-control" id="user-password1" name="password" required="required" tabindex = "2">
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block" tabindex = "3">Login</button>
</div>
<div class="clearfix col-12">
<a href="#" class="float-left">Register</a>
<a href="#" class="float-right">Forgot Password?</a>
</div>
</div>
</div>
</form>
{% endblock %}
import datetime
import functools
from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app, session
from uffd.navbar import register_navbar
from uffd.csrf import csrf_protect
from uffd.user.models import User
from uffd.ldap import get_conn, user_conn, uid_to_dn
bp = Blueprint("session", __name__, template_folder='templates', url_prefix='/')
@register_navbar('Logout', icon='sign-out-alt', blueprint=bp)
@bp.route("/logout")
def logout():
session.clear()
return redirect(url_for('.login'))
@bp.route("/login", methods=('GET', 'POST'))
def login():
if request.method == 'GET':
return render_template('login.html')
username = request.form['loginname']
password = request.form['password']
conn = user_conn(username, password)
if not conn:
flash('Login name or password is wrong')
return redirect(url_for('.login'))
conn.search(conn.user, '(objectClass=person)')
if not len(conn.entries) == 1:
flash('Login name or password is wrong')
return redirect(url_for('.login'))
user = User.from_ldap(conn.entries[0])
session['user_uid'] = user.uid
session['logintime'] = datetime.datetime.now().timestamp()
return redirect(url_for('index'))
def get_current_user():
if not session.get('user_uid'):
return None
return User.from_ldap_dn(uid_to_dn(session['user_uid']))
def is_valid_session():
user = get_current_user()
if not user:
return False
if datetime.datetime.now().timestamp() > session['logintime'] + current_app.config['SESSION_LIFETIME_SECONDS']:
flash('Session timed out')
return False
return True
def is_user_in_group(user, group):
return True
def login_required(view, group=None):
@functools.wraps(view)
def wrapped_view(**kwargs):
if not is_valid_session():
flash('You need to login first')
return redirect(url_for('session.login'))
if not is_user_in_group(get_current_user, group):
flash('Access denied')
return redirect(url_for('index'))
return view(**kwargs)
return wrapped_view
uffd/static/chaosknoten.png

11.6 KiB

...@@ -2,12 +2,18 @@ from flask import Blueprint, render_template, request, url_for, redirect, flash, ...@@ -2,12 +2,18 @@ from flask import Blueprint, render_template, request, url_for, redirect, flash,
from uffd.navbar import register_navbar from uffd.navbar import register_navbar
from uffd.csrf import csrf_protect from uffd.csrf import csrf_protect
from uffd.ldap import get_conn, escape_filter_chars
from uffd.session import login_required
from .models import User from .models import User
from uffd.ldap import get_conn, escape_filter_chars
bp = Blueprint("user", __name__, template_folder='templates', url_prefix='/user/') bp = Blueprint("user", __name__, template_folder='templates', url_prefix='/user/')
@bp.before_request
@login_required
def user_acl():
pass
@bp.route("/") @bp.route("/")
@register_navbar('Users', icon='users', blueprint=bp) @register_navbar('Users', icon='users', blueprint=bp)
def user_list(): def user_list():
...@@ -59,8 +65,8 @@ def user_update(uid=False): ...@@ -59,8 +65,8 @@ def user_update(uid=False):
flash('Error updating user: {}'.format(conn.result['message'])) flash('Error updating user: {}'.format(conn.result['message']))
return redirect(url_for('.user_list')) return redirect(url_for('.user_list'))
@csrf_protect
@bp.route("/<int:uid>/del") @bp.route("/<int:uid>/del")
@csrf_protect
def user_delete(uid): def user_delete(uid):
conn = get_conn() conn = get_conn()
conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format((escape_filter_chars(uid)))) conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format((escape_filter_chars(uid))))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment