2018-09-17 21:11:35 +02:00
#!/usr/bin/env python3
2019-02-11 15:53:17 +01:00
# -*- coding: utf-8 -*-
2015-06-16 21:57:44 +02:00
2015-06-17 20:22:52 +02:00
import uuid
2018-09-17 21:12:47 +02:00
2019-02-09 13:23:36 +01:00
import ldap
import ldap . modlist
2018-09-17 21:12:47 +02:00
from flask import Flask , render_template , redirect , url_for , session
2020-09-10 11:18:41 +02:00
from flask_wtf import FlaskForm
2018-09-17 21:12:47 +02:00
from passlib . hash import ldap_salted_sha1
from redis import Redis
2019-02-09 13:23:36 +01:00
from wtforms . fields import IntegerField , PasswordField , StringField , SubmitField
from wtforms . validators import EqualTo , DataRequired
2018-09-17 21:12:47 +02:00
2015-06-16 21:57:44 +02:00
app = Flask ( __name__ )
2015-06-17 20:22:52 +02:00
app . config . from_pyfile ( ' config.cfg ' )
2015-06-16 21:57:44 +02:00
app . jinja_env . trim_blocks = True
app . jinja_env . lstrip_blocks = True
2018-09-17 22:21:41 +02:00
rdb = Redis ( host = app . config . get ( ' REDIS_HOST ' , ' 127.0.0.1 ' ) , password = app . config . get ( ' REDIS_PASSWD ' ) , decode_responses = True )
2015-06-17 20:13:55 +02:00
2017-01-21 18:24:14 +01:00
ldap . set_option ( ldap . OPT_X_TLS_REQUIRE_CERT , ldap . OPT_X_TLS_DEMAND )
ldap . set_option ( ldap . OPT_REFERRALS , 0 )
if ' LDAP_CA ' in app . config . keys ( ) :
2019-02-09 13:23:36 +01:00
ldap . set_option ( ldap . OPT_X_TLS_CACERTFILE , app . config . get ( ' LDAP_CA ' ) )
2017-01-21 18:24:14 +01:00
2015-06-17 20:13:55 +02:00
2020-09-10 11:18:41 +02:00
class CreateForm ( FlaskForm ) :
2019-02-09 13:23:36 +01:00
user = StringField ( ' Username ' , validators = [ DataRequired ( ) ] )
uid = IntegerField ( ' User ID ' , validators = [ DataRequired ( ) ] )
gn = StringField ( ' Given Name ' , validators = [ DataRequired ( ) ] )
sn = StringField ( ' Family Name ' , validators = [ DataRequired ( ) ] )
pwd1 = PasswordField ( ' Password ' , validators = [ DataRequired ( ) ] )
pwd2 = PasswordField ( ' Password (repeat) ' , validators = [ DataRequired ( ) , EqualTo ( ' pwd1 ' , " Passwords must match " ) ] )
submit = SubmitField ( ' Submit ' )
2016-02-10 17:03:09 +01:00
2020-09-10 11:18:41 +02:00
class EditForm ( FlaskForm ) :
2019-04-01 19:29:06 +02:00
user = StringField ( ' Username ' , render_kw = { ' readonly ' : True } )
2019-02-09 13:23:36 +01:00
pwd1 = PasswordField ( ' New Password ' , validators = [ DataRequired ( ) ] )
pwd2 = PasswordField ( ' New Password (repeat) ' , validators = [ DataRequired ( ) , EqualTo ( ' pwd1 ' , " Passwords must match " ) ] )
submit = SubmitField ( ' Submit ' )
2015-06-18 19:14:24 +02:00
2020-09-10 11:18:41 +02:00
class LoginForm ( FlaskForm ) :
2019-02-09 13:23:36 +01:00
user = StringField ( ' Username ' , validators = [ DataRequired ( ) ] )
pswd = PasswordField ( ' Password ' , validators = [ DataRequired ( ) ] )
submit = SubmitField ( ' Login ' )
2015-06-17 20:13:55 +02:00
2015-06-16 21:57:44 +02:00
2019-02-09 13:30:05 +01:00
def make_secret ( password ) :
2019-02-09 13:23:36 +01:00
return ldap_salted_sha1 . encrypt ( password )
2016-11-10 08:11:25 +01:00
2019-02-09 13:30:05 +01:00
def is_admin ( ) :
return is_loggedin ( ) and rdb . hget ( session [ ' uuid ' ] , ' user ' ) in app . config . get ( ' ADMINS ' , [ ] )
2019-02-09 13:23:36 +01:00
2016-02-10 17:03:09 +01:00
2019-02-09 13:30:05 +01:00
def is_loggedin ( ) :
2019-02-09 13:23:36 +01:00
return ' uuid ' in session and rdb . exists ( session [ ' uuid ' ] )
2015-10-01 16:39:19 +02:00
2019-02-09 13:30:05 +01:00
def build_nav ( ) :
2019-02-09 13:23:36 +01:00
nav = [ ]
2019-02-09 13:30:05 +01:00
if is_loggedin ( ) :
2019-02-09 13:23:36 +01:00
nav . append ( ( ' Edit own Account ' , ' edit ' ) )
2019-02-09 13:30:05 +01:00
if is_admin ( ) :
2019-02-09 13:23:36 +01:00
nav . append ( ( ' List Accounts ' , ' list_users ' ) )
nav . append ( ( ' Create Account ' , ' create ' ) )
nav . append ( ( ' Logout ' , ' logout ' ) )
else :
nav . append ( ( ' Login ' , ' login ' ) )
return nav
2016-02-10 17:00:40 +01:00
@app.route ( ' / ' )
def index ( ) :
2019-02-09 13:30:05 +01:00
return render_template ( ' index.html ' , nav = build_nav ( ) )
2015-06-16 21:57:44 +02:00
2015-06-17 20:22:52 +02:00
2016-02-10 17:03:09 +01:00
@app.route ( ' /create ' , methods = [ ' GET ' , ' POST ' ] )
def create ( ) :
2019-02-09 13:30:05 +01:00
if not is_loggedin ( ) :
return render_template ( ' error.html ' , message = " You are not logged in. Please log in first. " , nav = build_nav ( ) )
2019-02-09 13:23:36 +01:00
2019-03-22 12:57:02 +01:00
if not is_admin ( ) :
return render_template ( ' error.html ' , message = " You do not have administrative privileges. Please log in using an administrative account. " , nav = build_nav ( ) )
2019-02-09 13:23:36 +01:00
form = CreateForm ( )
if form . validate_on_submit ( ) :
l = ldap . initialize ( app . config . get ( ' LDAP_URI ' , ' ldaps://127.0.0.1 ' ) )
try :
l . simple_bind_s ( rdb . hget ( session [ ' uuid ' ] , ' user ' ) , rdb . hget ( session [ ' uuid ' ] , ' pswd ' ) )
d = {
' user ' : form . user . data ,
' uid ' : form . uid . data ,
' gn ' : form . gn . data ,
' sn ' : form . sn . data ,
2019-02-09 13:30:05 +01:00
' pass ' : make_secret ( form . pwd1 . data )
2019-02-09 13:23:36 +01:00
}
# add user
user_dn = app . config . get ( ' USER_DN ' ) . format ( * * d )
attrs = { }
2019-02-11 16:16:40 +01:00
for k , v in app . config . get ( ' USER_ATTRS ' ) . items ( ) :
2019-02-09 13:23:36 +01:00
if isinstance ( v , str ) :
2019-02-11 16:16:40 +01:00
attrs [ k ] = v . format ( * * d ) . encode ( )
2019-02-09 13:23:36 +01:00
elif isinstance ( v , list ) :
attrs [ k ] = [ ]
for e in v :
2019-02-11 16:16:40 +01:00
attrs [ k ] . append ( e . format ( * * d ) . encode ( ) )
2019-02-09 13:23:36 +01:00
l . add_s ( user_dn , ldap . modlist . addModlist ( attrs ) )
# add user to group
group_dn = app . config . get ( ' GROUP_DN ' ) . format ( * * d )
2019-02-26 14:04:19 +01:00
l . modify_s ( group_dn , [ ( ldap . MOD_ADD , ' memberUid ' , str ( form . user . data ) . encode ( ) ) ] )
2019-02-09 13:23:36 +01:00
except ldap . LDAPError as e :
l . unbind_s ( )
message = " LDAP Error "
2019-02-11 16:16:40 +01:00
if ' desc ' in e . args [ 0 ] :
message = message + " " + e . args [ 0 ] [ ' desc ' ]
if ' info ' in e . args [ 0 ] :
message = message + " : " + e . args [ 0 ] [ ' info ' ]
2019-02-09 13:30:05 +01:00
return render_template ( ' error.html ' , message = message , nav = build_nav ( ) )
2019-02-09 13:23:36 +01:00
else :
l . unbind_s ( )
2019-02-09 13:30:05 +01:00
return render_template ( ' success.html ' , message = " User successfully created. " , nav = build_nav ( ) )
2019-02-09 13:23:36 +01:00
2019-02-09 13:30:05 +01:00
return render_template ( ' create.html ' , form = form , nav = build_nav ( ) )
2016-02-10 17:03:09 +01:00
2015-06-18 19:14:24 +02:00
@app.route ( ' /edit ' , methods = [ ' GET ' , ' POST ' ] )
def edit ( ) :
2019-02-09 13:30:05 +01:00
if not is_loggedin ( ) :
return render_template ( ' error.html ' , message = " You are not logged in. Please log in first. " , nav = build_nav ( ) )
2019-02-09 13:23:36 +01:00
form = EditForm ( )
creds = rdb . hgetall ( session [ ' uuid ' ] )
if form . validate_on_submit ( ) :
npwd = form . pwd1 . data
l = ldap . initialize ( app . config . get ( ' LDAP_URI ' , ' ldaps://127.0.0.1 ' ) )
try :
l . simple_bind_s ( creds [ ' user ' ] , creds [ ' pswd ' ] )
l . passwd_s ( creds [ ' user ' ] , creds [ ' pswd ' ] , npwd )
2019-02-25 09:59:37 +01:00
except ldap . INVALID_CREDENTIALS :
2019-02-11 16:16:40 +01:00
form . user . errors . append ( ' Invalid credentials ' )
2019-02-09 13:23:36 +01:00
l . unbind_s ( )
2019-02-09 13:30:05 +01:00
return render_template ( ' edit.html ' , form = form , nav = build_nav ( ) )
2019-02-09 13:23:36 +01:00
else :
rdb . hset ( session [ ' uuid ' ] , ' pswd ' , npwd )
l . unbind_s ( )
2019-02-09 13:30:05 +01:00
return render_template ( ' success.html ' , message = " User successfully edited. " , nav = build_nav ( ) )
2019-02-09 13:23:36 +01:00
form . user . data = creds [ ' user ' ]
2019-02-09 13:30:05 +01:00
return render_template ( ' edit.html ' , form = form , nav = build_nav ( ) )
2015-06-18 19:14:24 +02:00
2015-09-28 21:27:56 +02:00
2016-03-22 01:06:25 +01:00
@app.route ( ' /list ' )
2016-04-16 21:03:11 +02:00
def list_users ( ) :
2019-02-09 13:30:05 +01:00
if not is_loggedin ( ) :
return render_template ( ' error.html ' , message = " You are not logged in. Please log in first. " , nav = build_nav ( ) )
2016-03-22 01:06:25 +01:00
2019-03-22 12:57:02 +01:00
if not is_admin ( ) :
return render_template ( ' error.html ' , message = " You do not have administrative privileges. Please log in using an administrative account. " , nav = build_nav ( ) )
2019-02-09 13:23:36 +01:00
l = ldap . initialize ( app . config . get ( ' LDAP_URI ' , ' ldaps://127.0.0.1 ' ) )
l . simple_bind_s ( rdb . hget ( session [ ' uuid ' ] , ' user ' ) , rdb . hget ( session [ ' uuid ' ] , ' pswd ' ) )
2019-05-16 16:13:05 +02:00
sr = l . search_s ( app . config . get ( ' LDAP_BASE ' ) , ldap . SCOPE_SUBTREE , ' (objectClass=posixAccount) ' , [ ' cn ' , ' uidNumber ' ] )
accounts = [ ( attr [ ' cn ' ] [ 0 ] . decode ( errors = ' ignore ' ) , attr [ ' uidNumber ' ] [ 0 ] . decode ( errors = ' ignore ' ) , dn ) for dn , attr in sr ]
2019-02-11 15:53:17 +01:00
return render_template ( ' list.html ' , accounts = accounts , nav = build_nav ( ) )
2016-03-22 01:06:25 +01:00
2015-06-17 20:13:55 +02:00
@app.route ( ' /login ' , methods = [ ' GET ' , ' POST ' ] )
def login ( ) :
2019-02-09 13:23:36 +01:00
form = LoginForm ( )
if form . validate_on_submit ( ) :
if form . user . data . endswith ( app . config . get ( ' LDAP_BASE ' , ' ' ) ) :
user = form . user . data
else :
user = app . config . get ( ' USER_DN ' ) . format ( user = form . user . data )
pswd = form . pswd . data
l = ldap . initialize ( app . config . get ( ' LDAP_URI ' , ' ldaps://127.0.0.1 ' ) )
try :
l . simple_bind_s ( user , pswd )
2019-02-09 13:30:05 +01:00
except ldap . INVALID_CREDENTIALS :
2019-02-11 16:16:40 +01:00
form . pswd . errors . append ( ' Invalid credentials ' )
2019-02-09 13:23:36 +01:00
l . unbind_s ( )
2019-02-09 13:30:05 +01:00
return render_template ( ' login.html ' , form = form , nav = build_nav ( ) )
2019-02-09 13:23:36 +01:00
l . unbind_s ( )
session [ ' uuid ' ] = str ( uuid . uuid4 ( ) )
credentials = { ' user ' : user , ' pswd ' : pswd }
rdb . hmset ( session [ ' uuid ' ] , credentials )
# TODO refactor this and reuse
rdb . expire ( session [ ' uuid ' ] , app . config . get ( ' SESSION_TIMEOUT ' , 3600 ) )
return redirect ( url_for ( ' index ' ) )
2019-02-09 13:30:05 +01:00
return render_template ( ' login.html ' , form = form , nav = build_nav ( ) )
2015-06-16 21:57:44 +02:00
2015-06-17 20:22:52 +02:00
@app.route ( ' /logout ' )
def logout ( ) :
2019-02-09 13:23:36 +01:00
if ' uuid ' in session :
rdb . delete ( session [ ' uuid ' ] )
del session [ ' uuid ' ]
return redirect ( url_for ( ' index ' ) )
2015-06-17 20:22:52 +02:00
2015-06-16 21:57:44 +02:00
if __name__ == ' __main__ ' :
2019-02-09 13:23:36 +01:00
app . run ( host = ' 0.0.0.0 ' , port = 5000 )