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

added view only mail configuration

parent 205dbe97
No related branches found
No related tags found
No related merge requests found
......@@ -39,10 +39,10 @@ def create_app(test_config=None):
db.init_app(app)
# pylint: disable=C0415
from uffd import user, selfservice, role, session, csrf, ldap
from uffd import user, selfservice, role, mail, session, csrf, ldap
# pylint: enable=C0415
for i in user.bp + selfservice.bp + role.bp + session.bp + csrf.bp + ldap.bp:
for i in user.bp + selfservice.bp + role.bp + mail.bp + session.bp + csrf.bp + ldap.bp:
app.register_blueprint(i)
@app.route("/")
......
LDAP_BASE_USER="ou=users,dc=example,dc=com"
LDAP_BASE_GROUPS="ou=groups,dc=example,dc=com"
LDAP_BASE_MAIL="ou=postfix,dc=example,dc=com"
LDAP_SERVICE_BIND_DN=""
LDAP_SERVICE_BIND_PASSWORD=""
LDAP_SERVICE_URL="ldapi:///"
......
from .views import bp as bp_ui
bp = [bp_ui]
import secrets
from ldap3 import MODIFY_REPLACE, MODIFY_DELETE, MODIFY_ADD, HASHED_SALTED_SHA512
from flask import current_app
from uffd import ldap
class Mail():
def __init__(self, uid=None, destinations=[], receivers=[], dn=None):
self.uid = uid
self.receivers = receivers
self.destinations = destinations
self.dn = dn
@classmethod
def from_ldap(cls, ldapobject):
return Mail(
uid=ldapobject['uid'].value,
receivers=ldap.get_ldap_array_attribute_safe(ldapobject, 'mailacceptinggeneralid'),
destinations=ldap.get_ldap_array_attribute_safe(ldapobject, 'maildrop'),
dn=ldapobject.entry_dn,
)
@classmethod
def from_ldap_dn(cls, dn):
conn = ldap.get_conn()
conn.search(dn, '(objectClass=postfixVirtual)')
if not len(conn.entries) == 1:
return None
return Mail.from_ldap(conn.entries[0])
def to_ldap(self, new=False):
conn = ldap.get_conn()
if new:
self.uid = ldap.get_next_uid()
attributes = {
'uidNumber': self.uid,
'gidNumber': current_app.config['LDAP_USER_GID'],
'homeDirectory': '/home/'+self.loginname,
'sn': ' ',
'userPassword': hashed(HASHED_SALTED_SHA512, secrets.token_hex(128)),
# same as for update
'givenName': self.displayname,
'displayName': self.displayname,
'cn': self.displayname,
'mail': self.mail,
}
dn = ldap.loginname_to_dn(self.loginname)
result = conn.add(dn, current_app.config['LDAP_USER_OBJECTCLASSES'], attributes)
else:
attributes = {
'givenName': [(MODIFY_REPLACE, [self.displayname])],
'displayName': [(MODIFY_REPLACE, [self.displayname])],
'cn': [(MODIFY_REPLACE, [self.displayname])],
'mail': [(MODIFY_REPLACE, [self.mail])],
}
if self.newpassword:
attributes['userPassword'] = [(MODIFY_REPLACE, [hashed(HASHED_SALTED_SHA512, self.newpassword)])]
dn = ldap.uid_to_dn(self.uid)
result = conn.modify(dn, attributes)
self.dn = dn
group_conn = ldap.get_conn()
for group in self.initial_groups_ldap:
if not group in self.groups_ldap:
group_conn.modify(group, {'uniqueMember': [(MODIFY_DELETE, [self.dn])]})
for group in self.groups_ldap:
if not group in self.initial_groups_ldap:
group_conn.modify(group, {'uniqueMember': [(MODIFY_ADD, [self.dn])]})
self.groups_changed = False
return result
{% extends 'base.html' %}
{% block body %}
<div class="row">
<div class="col">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">name</th>
<th scope="col">receiving address</th>
<th scope="col">destinations</th>
<th scope="col">
<p class="text-right">
<a type="button" class="btn btn-primary" href="{{ url_for("mail.show") }}">
<i class="fa fa-plus" aria-hidden="true"></i> New
</a>
</p>
</th>
</tr>
</thead>
<tbody>
{% for mail in mails|sort(attribute="uid") %}
<tr id="mail-{{ mail.uid }}">
<th scope="row">
<a href="{{ url_for("mail.show", uid=mail.uid) }}">
{{ mail.uid }}
</a>
</th>
<td>
<ul>
{% for i in mail.receivers %}
<li>{{ i }}</li>
{% endfor %}
</ul>
</td>
<td>
<ul>
{% for i in mail.destinations %}
<li>{{ i }}</li>
{% endfor %}
</ul>
</td>
<td>
<p class="text-right">
<a href="{{ url_for("mail.show", uid=mail.uid) }}" class="btn btn-primary">
<i class="fa fa-edit" aria-hidden="true"></i> Edit
</a>
</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</dev>
{% 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.ldap import get_conn, escape_filter_chars
from uffd.session import login_required, is_valid_session, get_current_user
from uffd.mail.models import Mail
bp = Blueprint("mail", __name__, template_folder='templates', url_prefix='/mail/')
@bp.before_request
@login_required()
def mail_acl(): #pylint: disable=inconsistent-return-statements
if not mail_acl_check():
flash('Access denied')
return redirect(url_for('index'))
def mail_acl_check():
return is_valid_session() and get_current_user().is_in_group(current_app.config['ACL_ADMIN_GROUP'])
@bp.route("/")
@register_navbar('Mail', icon='envelope', blueprint=bp, visible=mail_acl_check)
def index():
conn = get_conn()
conn.search(current_app.config["LDAP_BASE_MAIL"], '(objectclass=postfixVirtual)')
mails = []
for i in conn.entries:
mails.append(Mail.from_ldap(i))
print(mails)
return render_template('mail_list.html', mails=mails)
@bp.route("/<uid>")
@bp.route("/new")
def show(uid=None):
return None
if not uid:
user = User()
ldif = '<none yet>'
else:
conn = get_conn()
conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format((escape_filter_chars(uid))))
assert len(conn.entries) == 1
user = User.from_ldap(conn.entries[0])
ldif = conn.entries[0].entry_to_ldif()
return render_template('user.html', user=user, user_ldif=ldif, roles=Role.query.all())
@bp.route("/<uid>/update", methods=['POST'])
@bp.route("/new", methods=['POST'])
@csrf_protect(blueprint=bp)
def update(uid=False):
return None
conn = get_conn()
is_newuser = bool(not uid)
if is_newuser:
user = User()
if not user.set_loginname(request.form['loginname']):
flash('Login name does not meet requirements')
return redirect(url_for('user.show'))
else:
conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format((escape_filter_chars(uid))))
assert len(conn.entries) == 1
user = User.from_ldap(conn.entries[0])
if not user.set_mail(request.form['mail']):
flash('Mail is invalide.')
return redirect(url_for('user.show', uid=uid))
new_displayname = request.form['displayname'] if request.form['displayname'] else request.form['loginname']
if not user.set_displayname(new_displayname):
flash('Display name does not meet requirements')
return redirect(url_for('user.show', uid=uid))
new_password = request.form.get('password')
if new_password and not is_newuser:
user.set_password(new_password)
session = db.session
roles = Role.query.all()
for role in roles:
role_member_dns = role.member_dns()
if request.values.get('role-{}'.format(role.id), False) or role.name in current_app.config["ROLES_BASEROLES"]:
if user.dn in role_member_dns:
continue
role.add_member(user)
elif user.dn in role_member_dns:
role.del_member(user)
usergroups = set()
for role in Role.get_for_user(user).all():
usergroups.update(role.group_dns())
user.replace_group_dns(usergroups)
if user.to_ldap(new=is_newuser):
if is_newuser:
send_passwordreset(user.loginname)
flash('User created. We sent the user a password reset link by mail')
session.commit()
else:
flash('User updated')
session.commit()
else:
flash('Error updating user: {}'.format(conn.result['message']))
session.rollback()
return redirect(url_for('user.show', uid=user.uid))
@bp.route("/<uid>/del")
@csrf_protect(blueprint=bp)
def delete(uid):
return None
conn = get_conn()
conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format((escape_filter_chars(uid))))
assert len(conn.entries) == 1
user = User.from_ldap(conn.entries[0])
session = db.session
roles = Role.query.all()
for role in roles:
if user.dn in role.member_dns():
role.del_member(user)
if conn.delete(conn.entries[0].entry_dn):
flash('Deleted user')
session.commit()
else:
flash('Could not delete user: {}'.format(conn.result['message']))
session.rollback()
return redirect(url_for('user.index'))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment