Ajout du support de LDAP

Mettre le flag à True dans local_config.py pour l'activer, et la doc 
dans VPS-config pour setup l'environnement
This commit is contained in:
Darks 2019-11-28 13:10:50 +01:00
parent a8756c2990
commit 279c194a59
Signed by: Darks
GPG Key ID: F61F10FA138E797C
6 changed files with 119 additions and 7 deletions

View File

@ -9,6 +9,8 @@ from app.models.notification import Notification
import app.utils.unicode_names as unicode_names
from app.utils.notify import notify
from config import V5Config
from local_config import USE_LDAP
import app.utils.ldap as ldap
import werkzeug.security
import re
@ -108,7 +110,9 @@ class Member(User):
self.name = name
self.norm = unicode_names.normalize(name)
self.email = email
self.set_password(password)
if not USE_LDAP:
self.set_password(password)
# Workflow with LDAP enabled is User → Postgresql → LDAP → set password
self.xp = 0
self.bio = ""
@ -163,8 +167,11 @@ class Member(User):
data = {key: data[key] for key in data if data[key] is not None}
# TODO: verify good type of those args, think about the password mgt
# Beware of LDAP injections
if "email" in data:
self.email = data["email"]
if USE_LDAP:
ldap.set_email(self.norm, self.email)
if "password" in data:
self.set_password(data["password"])
if "bio" in data:
@ -203,13 +210,19 @@ class Member(User):
Set the user's password. Check whether the request sender has the right
to do this!
"""
self.password_hash = werkzeug.security.generate_password_hash(password,
method='pbkdf2:sha512', salt_length=10)
if USE_LDAP:
ldap.set_password(self, password)
else:
self.password_hash = werkzeug.security.generate_password_hash(
password, method='pbkdf2:sha512', salt_length=10)
def check_password(self, password):
"""Compares password against member hash."""
return werkzeug.security.check_password_hash(self.password_hash,
password)
if USE_LDAP:
return ldap.check_password(self, password)
else:
return werkzeug.security.check_password_hash(self.password_hash,
password)
def notify(self, message, href=None):
""" Notify a user with a message.

View File

@ -4,6 +4,8 @@ from app import app, db
from app.forms.account import UpdateAccountForm, RegistrationForm, DeleteAccountForm
from app.models.users import Member
from app.utils.render import render
import app.utils.ldap as ldap
from local_config import USE_LDAP
@app.route('/account', methods=['GET', 'POST'])
@ -60,6 +62,10 @@ def register():
member.newsletter = form.newsletter.data
db.session.add(member)
db.session.commit()
# Workflow with LDAP is User → Postgresql → LDAP → Change password
if USE_LDAP:
ldap.add_member(member)
ldap.set_password(member, form.password.data)
flash('Inscription réussie', 'ok')
return redirect(url_for('validation') + "?email=" + form.email.data)
return render('register.html', title='Register', form=form)

View File

@ -1,6 +1,6 @@
from flask import redirect, url_for, request, flash
from flask_login import login_user, logout_user, login_required, current_user
from urllib.parse import urlparse
from urllib.parse import urlparse, urljoin
from app import app
from app.forms.login import LoginForm
from app.models.users import Member

83
app/utils/ldap.py Normal file
View File

@ -0,0 +1,83 @@
import ldap
from ldap.modlist import addModlist, modifyModlist
from local_config import LDAP_PASSWORD, LDAP_ORGANIZATION
def get_member(username):
""" Get informations about member. Username must be normalized! """
conn = ldap.initialize("ldap://localhost")
# Search for user
r = conn.search_s(LDAP_ORGANIZATION, ldap.SCOPE_SUBTREE, f'(cn={username})')
if len(r) > 0:
return r[0]
else:
return None
def edit(user, fields):
""" Edit a user. Fields is {'name': ['value'], …} """
conn = ldap.initialize("ldap://localhost")
# Connect as root
# conn.simple_bind_s(f'cn=ldap-root,{LDAP_ORGANIZATION}', LDAP_PASSWORD)
# old_value = {"userPassword": ["my_old_password"]}
# new_value = {"userPassword": ["my_new_password"]}
modlist = ldap.modlist.modifyModlist(old_value, new_value)
con.modify_s(dn, modlist)
def set_email(user, email):
pass
def set_password(user, password):
""" Set password for a user. """
conn = ldap.initialize("ldap://localhost")
# Connect as root
conn.simple_bind_s(f'cn=ldap-root,{LDAP_ORGANIZATION}',
LDAP_PASSWORD)
conn.passwd_s(f"cn={user.norm},{LDAP_ORGANIZATION}", None, password)
def check_password(user, password):
""" Try to login a user through LDAP register. """
conn = ldap.initialize("ldap://localhost")
try:
conn.simple_bind_s(f"cn={user.norm},{LDAP_ORGANIZATION}", password)
except ldap.INVALID_CREDENTIALS:
return False
return True
def add_member(member):
""" Add a member to LDAP register. Fields must have been sanitized! """
if get_member(member.norm) is not None:
print("User already exists")
return
conn = ldap.initialize("ldap://localhost")
# Connect as root
conn.simple_bind_s(f'cn=ldap-root,{LDAP_ORGANIZATION}', LDAP_PASSWORD)
# Create fields
dn = f'cn={member.norm},{LDAP_ORGANIZATION}'
modlist = addModlist({
'objectClass': [bytes('inetOrgPerson', 'UTF8')],
'cn': [bytes(member.norm, 'UTF8')],
'sn': [bytes(member.norm, 'UTF8')],
'displayName': [bytes(member.name, 'UTF8')],
'mail': [bytes(member.email, 'UTF8')],
'uid': [bytes(str(member.id), 'UTF8')],
'userPassword': [bytes("", 'UTF8')]
})
# Add the member
conn.add_s(dn, modlist)
def delete_member(member):
""" Remove a member from LDAP register """
conn = ldap.initialize("ldap://localhost")
# Connect as root
conn.simple_bind_s(f'cn=ldap-root,{LDAP_ORGANIZATION}', LDAP_PASSWORD)
# Create fields
dn = f'cn={member.norm},{LDAP_ORGANIZATION}'
# Delete the user
conn.delete_s(dn)

View File

@ -3,7 +3,9 @@ from wtforms.validators import ValidationError
from app.models.users import Member
from app.utils.valid_name import valid_name
from app.utils.unicode_names import normalize
import app.utils.ldap as ldap
from config import V5Config
from local_config import USE_LDAP
def name_valid(form, name):
@ -42,6 +44,13 @@ def name_available(form, name):
if member is not None:
raise ValidationError("Ce nom d'utilisateur est indisponible.")
# Double check with LDAP if needed
if USE_LDAP:
member = ldap.get_member(norm)
if member is not None:
raise ValidationError("Ce nom d'utilisateur est indisponible.")
def email(form, email):
member = Member.query.filter_by(email=email.data).first()

View File

@ -1,3 +1,4 @@
DB_NAME = "pcv5"
LDAP_PASSWORD = "openldap"
USE_LDAP = False
LDAP_PASSWORD = "openldap"
LDAP_ORGANIZATION = "o=planet-casio"