Browse Source

Initial commit

mistress
Daniel Muckerman 3 years ago
commit
55757200ff
6 changed files with 592 additions and 0 deletions
  1. +13
    -0
      Dockerfile
  2. +160
    -0
      app.py
  3. +7
    -0
      requirements.txt
  4. +117
    -0
      static/style.css
  5. +37
    -0
      templates/login.j2
  6. +258
    -0
      templates/profile.j2

+ 13
- 0
Dockerfile View File

@ -0,0 +1,13 @@
FROM python:3.7
LABEL maintainer="Dan Muckerman <danielmuckerman@me.com>"
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"]

+ 160
- 0
app.py View File

@ -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()

+ 7
- 0
requirements.txt View File

@ -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

+ 117
- 0
static/style.css View File

@ -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;
}
}

+ 37
- 0
templates/login.j2 View File

@ -0,0 +1,37 @@
{% extends "bootstrap/base.html" %}
{% block title %}Manage your Technical Incompetence account{% endblock %}
{% block styles %}
{{super()}}
<link rel="stylesheet" href="{{url_for('.static', filename='style.css')}}">
{% endblock %}
{% block navbar %}
<nav class="navbar navbar-expand-lg sticky-top navbar-dark bg-dark">
<div class="navbar-brand">Technical Incompetence Account</div>
</nav>
{% endblock %}
{% block content %}
<div class="container" style="margin-top: 15px">
<div class="jumbotron">
<h1>Sign in for our awesome service</h1>
<p>Forgot your password? Too bad! We don't have emails working yet!</p>
</div>
<div class="col-md-12">
{% if error is defined %}
<div id="error-alert" class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endif %}
<form action="" method="post">
<label for="exampleFormControlInput1">Username</label>
<input name="user" class="form-control"><br>
<label for="exampleFormControlInput2">Password</label>
<input type="password" name="passwd" class="form-control"><br>
<button type="submit" class="btn btn-primary">Log In</button>
</form>
</div>
{% endblock %}

+ 258
- 0
templates/profile.j2 View File

@ -0,0 +1,258 @@
{% extends "bootstrap/base.html" %}
{% block title %}Manage your Technical Incompetence account{% endblock %}
{% block styles %}
{{super()}}
<link rel="stylesheet" href="{{url_for('.static', filename='style.css')}}">
{% endblock %}
{% block navbar %}
<nav class="navbar navbar-expand-lg sticky-top navbar-dark bg-dark">
<div class="navbar-brand">Technical Incompetence Account</div>
<form class="form-inline ml-auto">
{# <div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="darkSwitch" />
<label class="custom-control-label" for="darkSwitch">Dark Mode</label>
</div>
<div class="navbar-text" style="margin-right: 20px; ">{{ user['userName'] }}</div> #}
<a class="btn btn-primary" href="/logout" role="button">Logout</a>
</form>
</nav>
{% endblock %}
{% block content %}
<div class="container" style="margin-top: 15px">
<div id="success-alert" class="alert alert-success" role="alert" style="display: none;">
This is a success alert—check it out!
</div>
<div id="error-alert" class="alert alert-danger" role="alert" style="display: none;">
This is a danger alert—check it out!
</div>
<form>
<div class="row">
<div class="col-lg-6" id="firstNameRow">
<label for="formGroupExampleInput1">First name</label>
<input id="firstName" type="text" class="form-control" placeholder="First name" value="{{ user['firstName'] }}">
</div>
<div class="col-lg-6">
<label for="formGroupExampleInput2">Last name</label>
<input id="lastName" type="text" class="form-control" placeholder="Last name" value="{{ user['lastName'] }}">
</div>
</div>
<br>
<button type="button" class="btn btn-primary" onclick="updateName();">Change Name</button>
</form>
<br><br>
<form>
<div class="row">
<div class="col-lg-6" id="userNameRow">
<label for="formGroupExampleInput3">Username</label>
<input id="userName" type="text" class="form-control" placeholder="Username" value="{{ user['userName'] }}">
<br>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#userNameWarningModal">Change Username</button>
<br>
</div>
<div class="col-lg-6">
<label for="formGroupExampleInput4">Email</label>
<input id="email" type="email" class="form-control" placeholder="Email" value="{{ user['email'] }}">
<br>
<button type="button" class="btn btn-primary" onclick="updateEmail();">Change Email</button>
<br>
</div>
</div>
</form>
<br><br>
<form>
<button id="passwordButton" type="button" class="btn btn-danger" data-toggle="modal" data-target="#passwordChangeModal">Change Password</button>
</form>
</div>
<div id="userNameWarningModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Warning</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="username-alert" class="alert alert-danger" role="alert" style="display: none;">
This is a danger alert—check it out!
</div>
<p>If you change your username, you will be logged out here and likely need to re-login in everywhere with your new username.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" onclick="updateUserName();">Save new username</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div id="passwordChangeModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Update Password</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="password-alert" class="alert alert-danger" role="alert" style="display: none;">
This is a danger alert—check it out!
</div>
<p>If you change your username, you will be logged out here and likely need to re-login in everywhere with your new username.</p>
<form style="padding: .75rem 1.25rem;">
<div class="row">
<label for="formGroupExampleInput5">Current Password</label>
<input id="currentPassword" type="password" class="form-control" placeholder="Current Password">
</div>
<br>
<div class="row">
<label for="formGroupExampleInput6">New Password</label>
<input id="newPassword" type="password" class="form-control" placeholder="New Password">
</div>
<br>
<div class="row">
<label for="formGroupExampleInput6">Confirm New Password</label>
<input id="confirmNewPassword" type="password" class="form-control" placeholder="Confirm New Password">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" onclick="updatePassword();">Change Password</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
function updateEmail() {
email = $('#email').val();
if (email.trim().length === 0) {
showError('Email is required');
return false;
}
dn = '{{ user['dn'] }}';
$.ajax({
url: '/update/email',
method: 'POST',
data: { "email": email,
"dn": dn },
success: function(data) {
if (data === 'Success')
showSuccess('Email updated!');
else
showError(data);
}
});
}
function updateName() {
first = $('#firstName').val();
last = $('#lastName').val();
if (first.trim().length === 0 || last.trim().length === 0) {
showError('Both first and last name are required');
return false;
}
dn = '{{ user['dn'] }}';
$.ajax({
url: '/update/name',
method: 'POST',
data: { "firstName": first,
"lastName": last,
"dn": dn },
success: function(data) {
if (data === 'Success')
showSuccess('Name updated!');
else
showError(data);
}
});
}
function updateUserName() {
user = $('#userName').val();
if (user.trim().length === 0) {
showUsernameError('Username is required');
return false;
}
dn = '{{ user['dn'] }}';
$.ajax({
url: '/update/username',
method: 'POST',
data: { "userName": user,
"dn": dn },
success: function(data) {
if (data === 'Success')
window.location = window.location.origin + "/logout";
else
showUsernameError(data);
}
});
}
function updatePassword() {
currentPassword = $('#currentPassword').val();
newPassword = $('#newPassword').val();
confirmPassword = $('#confirmNewPassword').val();
dn = '{{ user['dn'] }}';
$.ajax({
url: '/update/password',
method: 'POST',
data: { "currentPassword": currentPassword,
"newPassword": newPassword,
"confirmPassword": confirmPassword,
"dn": dn },
success: function(data) {
if (data === 'Success')
window.location = window.location.origin + "/logout";
else
showPasswordError(data)
}
});
}
function showError(error) {
hideSuccess();
$('#error-alert').text(error);
$('#error-alert').show();
}
function showPasswordError(error) {
$('#password-alert').text(error);
$('#password-alert').show();
}
function showUsernameError(error) {
$('#username-alert').text(error);
$('#username-alert').show();
}
function hideError() {
$('#error-alert').hide();
}
function showSuccess(message) {
hideError();
$('#success-alert').text(message);
$('#success-alert').show();
}
function hideSuccess() {
$('#success-alert').hide();
}
</script>
{% endblock %}

Loading…
Cancel
Save