commit 55757200ffceae43af8934374a0714003cad7cf7 Author: Daniel Muckerman Date: Sun Jun 14 12:41:56 2020 -0400 Initial commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a6d698f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.7 + +LABEL maintainer="Dan Muckerman " + +WORKDIR /project +ADD . /project +RUN rm /project/.envrc +RUN rm -rf /project/env + +RUN apt-get update && apt-get install -y python3-dev libldap2-dev libsasl2-dev libssl-dev + +RUN pip install -r requirements.txt +CMD ["flask","run","--host=0.0.0.0"] \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..7ddba92 --- /dev/null +++ b/app.py @@ -0,0 +1,160 @@ +import ldap as l +from ldap3 import Server, Connection, ALL, MODIFY_REPLACE +from flask import Flask, g, request, session, redirect, url_for, render_template +from flask_simpleldap import LDAP +from flask_bootstrap import Bootstrap +from email_validator import validate_email, EmailNotValidError +import os + +app = Flask(__name__) +Bootstrap(app) +app.secret_key = 'asdf' +app.debug = True + +# Base +app.config['LDAP_REALM_NAME'] = 'OpenLDAP Authentication' +app.config['LDAP_HOST'] = os.environ.get('LDAP_HOST') +app.config['LDAP_BASE_DN'] = os.environ.get('LDAP_BASE_DN') +app.config['LDAP_USERNAME'] = os.environ.get('LDAP_USERNAME') +app.config['LDAP_PASSWORD'] = os.environ.get('LDAP_PASSWORD') + +# OpenLDAP +app.config['LDAP_OBJECTS_DN'] = 'dn' +app.config['LDAP_OPENLDAP'] = True +app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=posixAccount)(uid=%s))' + +ldap = LDAP(app) + +server = Server(app.config['LDAP_HOST']) +conn = Connection(server, app.config['LDAP_USERNAME'], app.config['LDAP_PASSWORD'], auto_bind=True) + +@app.before_request +def before_request(): + g.user = None + if 'user_id' in session: + # This is where you'd query your database to get the user info. + g.user = {} + + +@app.route('/') +@ldap.login_required +def index(): + user_dict = ldap.get_object_details(session['user_id']) + + if 'user_id' in session: + user = {'dn': 'cn={},cn=usergroup,ou=users,dc=technicalincompetence,dc=club'.format(user_dict['cn'][0].decode('ascii')), + 'firstName': user_dict['givenName'][0].decode('ascii'), + 'lastName': user_dict['sn'][0].decode('ascii'), + 'email': user_dict['mail'][0].decode('ascii'), + 'userName': user_dict['uid'][0].decode('ascii'), + } + + + return render_template('profile.j2', user = user) + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if g.user: + return redirect(url_for('index')) + if request.method == 'POST': + user = request.form['user'] + passwd = request.form['passwd'] + test = ldap.bind_user(user, passwd) + if test is None or passwd == '': + return render_template('login.j2', error='Invalid credentials') + else: + session['user_id'] = request.form['user'] + session['passwd'] = request.form['passwd'] + return redirect('/') + return render_template('login.j2') + + +@ldap.login_required +@app.route('/update/email', methods=['POST']) +def update_email(): + if request.method == 'POST': + email = request.form['email'] + dn = request.form['dn'] + + if email != None and len(email) > 0: + try: + # Validate. + valid = validate_email(email) + + # Update with the normalized form. + conn.modify(dn, {'mail': [(MODIFY_REPLACE, [valid.email])]}) + return 'Success' + except EmailNotValidError as e: + # email is not valid, exception message is human-readable + print(str(e)) + return 'Invalid email address' + return 'Email cannot be empty' + + +@ldap.login_required +@app.route('/update/name', methods=['POST']) +def update_name(): + if request.method == 'POST': + firstName = request.form['firstName'] + lastName = request.form['lastName'] + dn = request.form['dn'] + + if (firstName != None and len(firstName) > 0) and (lastName != None and len(lastName) > 0): + conn.modify(dn, {'givenName': [(MODIFY_REPLACE, [firstName])], + 'sn': [(MODIFY_REPLACE, [lastName])]}) + return 'Success' + return 'Name cannot be empty' + + +@ldap.login_required +@app.route('/update/username', methods=['POST']) +def update_username(): + if request.method == 'POST': + userName = request.form['userName'] + dn = request.form['dn'] + + if userName != None and len(userName) > 0: + conn.modify(dn, {'uid': [(MODIFY_REPLACE, [userName])]}) + return 'Success' + return 'Username cannot be empty' + + +@ldap.login_required +@app.route('/update/password', methods=['POST']) +def update_password(): + if request.method == 'POST': + currentPassword = request.form['currentPassword'] + newPassword = request.form['newPassword'] + confirmPassword = request.form['confirmPassword'] + dn = request.form['dn'] + + if currentPassword == '': + return 'Please enter your current password' + + if newPassword == '': + return 'Please enter a new password' + + if confirmPassword == '': + return 'Please confirm your new password' + + if newPassword != confirmPassword: + return 'Could not confirm new password, please make sure you typed it correctly' + + test = ldap.bind_user(session['user_id'], currentPassword) + if test is None: + return 'Current password is incorrect' + else: + conn.extend.standard.modify_password(user=dn, new_password=newPassword) + return 'Success' + return 'Error' + + +@app.route('/logout') +def logout(): + session.pop('user_id', None) + return redirect(url_for('index')) + + +if __name__ == '__main__': + app.run() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..60b2455 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +Flask==1.1.2 +Flask-Bootstrap4==4.0.2 +Flask-Login==0.5.0 +Flask-SimpleLDAP==1.4.0 +python-ldap==3.2.0 +ldap3==2.7 +email-validator==1.1.1 \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..b619e59 --- /dev/null +++ b/static/style.css @@ -0,0 +1,117 @@ +@media (max-width: 991.98px) { + #userNameRow, + #firstNameRow { + margin-bottom: 20px; + } + + #passwordButton { + margin-bottom: 50px; + } +} + +@media (prefers-color-scheme: dark) { + body { + background-color: #111 !important; + color: #eee; + } + + .jumbotron { + background-color: #333 !important; + } + + .modal-content { + background-color: #111 !important; + color: #eee; + } + + .modal-header { + border-bottom: 1px solid #555 !important; + } + + .modal-header .close { + color: #eee !important; + text-shadow: 0 1px 0 #555 !important; + } + + .modal-footer { + border-top: 1px solid #555 !important; + } + + .bg-light { + background-color: #333 !important; + } + + .bg-white { + background-color: #000 !important; + } + + .bg-black { + background-color: #eee !important; + } + + .form-control { + display: block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #dee2e6; + background-color: #000; + background-clip: padding-box; + border: 1px solid #6c757d; + border-radius: 0.25rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + } + + @media (prefers-reduced-motion: reduce) { + .form-control { + transition: none; + } + } + + .form-control::-ms-expand { + background-color: transparent; + border: 0; + } + + .form-control:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #dee2e6; + } + + .form-control:focus { + color: #dee2e6; + background-color: #191d21; + border-color: #b3d7ff; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + } + + .form-control::-webkit-input-placeholder { + color: #6c757d; + opacity: 1; + } + + .form-control::-moz-placeholder { + color: #6c757d; + opacity: 1; + } + + .form-control::-ms-input-placeholder { + color: #6c757d; + opacity: 1; + } + + .form-control::placeholder { + color: #6c757d; + opacity: 1; + } + + .form-control:disabled, + .form-control[readonly] { + background-color: #343a40; + opacity: 1; + } +} diff --git a/templates/login.j2 b/templates/login.j2 new file mode 100644 index 0000000..d4935b3 --- /dev/null +++ b/templates/login.j2 @@ -0,0 +1,37 @@ +{% extends "bootstrap/base.html" %} +{% block title %}Manage your Technical Incompetence account{% endblock %} + +{% block styles %} +{{super()}} + +{% endblock %} + +{% block navbar %} + +{% endblock %} + +{% block content %} +
+
+

Sign in for our awesome service

+

Forgot your password? Too bad! We don't have emails working yet!

+
+
+ + {% if error is defined %} + + {% endif %} + +
+ +
+ +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/profile.j2 b/templates/profile.j2 new file mode 100644 index 0000000..e0c55a4 --- /dev/null +++ b/templates/profile.j2 @@ -0,0 +1,258 @@ +{% extends "bootstrap/base.html" %} +{% block title %}Manage your Technical Incompetence account{% endblock %} + +{% block styles %} +{{super()}} + +{% endblock %} + +{% block navbar %} + +{% endblock %} + +{% block content %} +
+ + +
+
+
+ + +
+
+ + +
+
+
+ +
+

+
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+
+

+
+ +
+
+ + + + +{% endblock %} + +{% block scripts %} +{{ super() }} + +{% endblock %} \ No newline at end of file