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

finished mail mapping editing support

parent 48b15013
No related branches found
No related tags found
No related merge requests found
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:///"
LDAP_USER_OBJECTCLASSES=["top", "inetOrgPerson", "organizationalPerson", "person", "posixAccount"]
LDAP_USER_GID=20001
LDAP_USER_MIN_UID=10000
......@@ -21,6 +23,7 @@ MAIL_USERNAME='yourId@gmail.com'
MAIL_PASSWORD='*****'
MAIL_USE_STARTTLS=True
MAIL_FROM_ADDRESS='foo@bar.com'
MAIL_LDAP_OBJECTCLASSES=["top", "postfixVirtual"]
ROLES_BASEROLES=['base']
......
from .ldap import bp as ldap_bp
from .ldap import get_conn, user_conn, escape_filter_chars, uid_to_dn
from .ldap import loginname_to_dn, get_next_uid, loginname_is_safe
from .ldap import loginname_to_dn, mail_to_dn, get_next_uid, loginname_is_safe, mailname_is_safe
from .ldap import get_ldap_array_attribute_safe, get_ldap_attribute_safe
bp = [ldap_bp]
......@@ -44,6 +44,11 @@ def loginname_to_dn(loginname):
return 'uid={},{}'.format(loginname, current_app.config["LDAP_BASE_USER"])
raise Exception('unsafe login name')
def mail_to_dn(uid):
if mailname_is_safe(uid):
return 'uid={},{}'.format(uid, current_app.config["LDAP_BASE_MAIL"])
raise Exception('unsafe mail name')
def loginname_is_safe(value):
if len(value) > 32 or len(value) < 1:
return False
......@@ -52,6 +57,9 @@ def loginname_is_safe(value):
return False
return True
def mailname_is_safe(value):
return loginname_is_safe(value)
def get_next_uid():
conn = get_conn()
conn.search(current_app.config["LDAP_BASE_USER"], '(objectclass=person)')
......
......@@ -32,42 +32,19 @@ class Mail():
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)),
'uid': self.uid,
# same as for update
'givenName': self.displayname,
'displayName': self.displayname,
'cn': self.displayname,
'mail': self.mail,
'mailacceptinggeneralid': self.receivers,
'maildrop': self.destinations,
}
dn = ldap.loginname_to_dn(self.loginname)
result = conn.add(dn, current_app.config['LDAP_USER_OBJECTCLASSES'], attributes)
self.dn = ldap.mail_to_dn(self.uid)
result = conn.add(self.dn, current_app.config['MAIL_LDAP_OBJECTCLASSES'], attributes)
else:
attributes = {
'givenName': [(MODIFY_REPLACE, [self.displayname])],
'displayName': [(MODIFY_REPLACE, [self.displayname])],
'cn': [(MODIFY_REPLACE, [self.displayname])],
'mail': [(MODIFY_REPLACE, [self.mail])],
'mailacceptinggeneralid': [(MODIFY_REPLACE, self.receivers)],
'maildrop': [(MODIFY_REPLACE, self.destinations)],
}
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
result = conn.modify(self.dn, attributes)
return result
{% extends 'base.html' %}
{% block body %}
<form action="{{ url_for("mail.update", uid=mail.uid) }}" method="POST">
<div class="align-self-center">
<div class="form-group col">
<label for="mail-name">Name</label>
<input type="text" class="form-control" id="mail-name" name="mail-uid" {% if mail.uid %} value="{{ mail.uid }}" readonly {% else %} value=""{% endif %}>
<small class="form-text text-muted">
</small>
</div>
<div class="form-group col">
<label for="mail-receivers">Receiving addresses</label>
<textarea rows="10" class="form-control" name="mail-receivers">{{ mail.receivers|join('\n') }}</textarea>
<small class="form-text text-muted">
One address per line
</small>
</div>
<div class="form-group col">
<label for="mail-destinations">Destinations</label>
<textarea rows="10" class="form-control" name="mail-destinations">{{ mail.destinations|join('\n') }}</textarea>
<small class="form-text text-muted">
One address per line
</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("mail.index") }}" class="btn btn-secondary">Cancel</a>
{% if mail.uid %}
<a href="{{ url_for("mail.delete", uid=mail.uid) }}" 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 %}
......@@ -32,92 +32,52 @@ def index():
@bp.route("/<uid>")
@bp.route("/new")
def show(uid=None):
return None
if not uid:
user = User()
ldif = '<none yet>'
mail = Mail()
else:
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_MAIL"], '(&(objectclass=postfixVirtual)(uid={}))'.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())
mail = Mail.from_ldap(conn.entries[0])
return render_template('mail.html', mail=mail)
@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'))
is_newmail = bool(not uid)
if is_newmail:
mail = Mail()
else:
conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format((escape_filter_chars(uid))))
conn = get_conn()
conn.search(current_app.config["LDAP_BASE_MAIL"], '(&(objectclass=postfixVirtual)(uid={}))'.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)
mail = Mail.from_ldap(conn.entries[0])
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 is_newmail:
mail.uid = request.form.get('mail-uid')
mail.receivers = request.form.get('mail-receivers', '').splitlines();
mail.destinations = request.form.get('mail-destinations', '').splitlines();
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()
if mail.to_ldap(new=is_newmail):
flash('Mail mapping updated.')
else:
flash('Error updating user: {}'.format(conn.result['message']))
session.rollback()
return redirect(url_for('user.show', uid=user.uid))
flash('Error updating mail mapping: {}'.format(conn.result['message']))
if is_newmail:
return redirect(url_for('mail.index'))
return redirect(url_for('mail.show', uid=mail.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))))
conn.search(current_app.config["LDAP_BASE_MAIL"], '(&(objectclass=postfixVirtual)(uid={}))'.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)
mail = conn.entries[0]
if conn.delete(conn.entries[0].entry_dn):
flash('Deleted user')
session.commit()
if conn.delete(mail.entry_dn):
flash('Deleted mail mapping.')
else:
flash('Could not delete user: {}'.format(conn.result['message']))
session.rollback()
return redirect(url_for('user.index'))
flash('Could not delete mail mapping: {}'.format(conn.result['message']))
return redirect(url_for('mail.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