From 3bc5f0e6f56a3c3a02eeaa40b472e7179ccf59fb Mon Sep 17 00:00:00 2001 From: AlxCzl Date: Sun, 28 Nov 2021 21:52:29 +0100 Subject: [PATCH 01/10] feat(git): completed gitignore --- .gitignore | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 224ddc7..5634b05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ -*.pyc +*pyc +instance/* +__pycache__/* +*.log* +.idea/* +*.swp +#~ +~# venv \ No newline at end of file -- 2.49.1 From ec451d82c07b2e838d022f67ca42823960ec26fb Mon Sep 17 00:00:00 2001 From: AlxCzl Date: Mon, 29 Nov 2021 00:10:59 +0100 Subject: [PATCH 02/10] feat: created a base form with some validators --- .dockerignore | 3 +- app/__init__.py | 10 ++++ app/reset.py | 39 ++++++++++++++ app/ui/static/css/main.css | 84 ++++++++++++++++++++++++++++++ app/ui/static/js/validate.js | 37 +++++++++++++ app/ui/templates/base.html | 26 +++++++++ app/ui/templates/reset.html | 42 +++++++++++++++ app/entrypoint.sh => entrypoint.sh | 0 requirements.txt | 3 +- wsgi.py | 6 +++ 10 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 app/__init__.py create mode 100644 app/reset.py create mode 100644 app/ui/static/css/main.css create mode 100644 app/ui/static/js/validate.js create mode 100644 app/ui/templates/base.html create mode 100644 app/ui/templates/reset.html rename app/entrypoint.sh => entrypoint.sh (100%) create mode 100644 wsgi.py diff --git a/.dockerignore b/.dockerignore index d259ec7..6ea5688 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ *.pyc .git/ -Dockerfile \ No newline at end of file +Dockerfile +__pycache__/* \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..cf00692 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,10 @@ +from flask import Flask, render_template +from . import reset + +def create_app(): + app = Flask(__name__, instance_relative_config=True, template_folder="ui/templates", static_folder="ui/static") + app.config.from_object("instance.config.DebugConfig") + + app.register_blueprint(reset.bp) + + return app \ No newline at end of file diff --git a/app/reset.py b/app/reset.py new file mode 100644 index 0000000..204c2e1 --- /dev/null +++ b/app/reset.py @@ -0,0 +1,39 @@ +import functools + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, BooleanField, \ + SubmitField +from wtforms.validators import ValidationError, DataRequired, \ + Email, EqualTo, Length +from werkzeug.security import check_password_hash, generate_password_hash + + +bp = Blueprint('reset', __name__, url_prefix='/reset') + +class ResetPasswordForm(FlaskForm): + username = StringField(label=('Username'), + validators=[DataRequired(), + Length(max=64)]) + currentpassword = PasswordField(label=('Current password'), + validators=[DataRequired()]) + newpassword = PasswordField(label=('New password'), + validators=[DataRequired(), + Length(min=8, message='Password should be at least %(min)d characters long')], + render_kw={"onkeyup": "validate_form()"}) + confirm_password = PasswordField( + label=('Confirm Password'), + validators=[DataRequired(message='* Required'), + EqualTo('newpassword', message='Both password fields must be equal!')], + render_kw={"onkeyup": "validate_confirm()"}) + + submit = SubmitField(label=('Change my password'), render_kw={"onclick": "validate_form()"}) + +@bp.route('/', methods=('GET', 'POST')) +def reset(): + form = ResetPasswordForm() + if form.validate_on_submit(): + return f'''

Welcome {form.username.data}

''' + return render_template('reset.html', form=form) \ No newline at end of file diff --git a/app/ui/static/css/main.css b/app/ui/static/css/main.css new file mode 100644 index 0000000..0e5b919 --- /dev/null +++ b/app/ui/static/css/main.css @@ -0,0 +1,84 @@ +html, +body { + margin: 0; + height: 100%; + min-width: 310px; +} + +body { + min-height: 100%; + font-family: 'Roboto Mono', monospace; + color: white; + background: rgb(0, 0, 0); + background: linear-gradient(8deg, rgb(35, 155, 21) 0%, rgba(14, 47, 11, 1) 40%, rgba(0, 0, 0, 1) 70%); + background-repeat: no-repeat; + background-attachment: fixed; +} + +.vcenter { + position: absolute; + left: 50%; + top: 50%; + -webkit-transform: translateX(-50%) translateY(-50%); + transform: translateX(-50%) translateY(-50%); +} + +.container { + animation: fadein ease 3s; + animation-iteration-count: 1; + animation-fill-mode: forwards; +} + +a { + text-decoration: none; +} + +a:link>span { + color: rgb(216, 216, 216); +} + +a:visited>span { + color: rgb(216, 216, 216); +} + +a:hover>span { + color: rgb(255, 255, 255); +} + +.icon2x { + height: 32px; + width: 32px; + font-size: 32px; +} + +.errorinput { + border-color: #f44246 !important; +} + +#confirm-msg, +#password-msg { + color: #f44246; + opacity: 0; + transition: opacity 225ms cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +.errorinput:focus { + box-shadow: 0 0 0 .10rem rgba(244, 66, 70, 0.50) !important; + -webkit-box-shadow: 0 0 0 .10rem rgba(244, 66, 70, 0.50) !important; +} + +.form-control:focus { + border-color: #5cb85c; + box-shadow: 0 0 0 .10rem rgba(92, 184, 92, 0.50); + -webkit-box-shadow: 0 0 0 .10rem rgba(92, 184, 92, 0.50); +} + +#reset-form { + background: #4e4e4e; + border-radius: .50rem; +} + +.fade { + opacity: 1 !important; + transition: opacity 300ms cubic-bezier(0.55, 0.085, 0.68, 0.53); +} \ No newline at end of file diff --git a/app/ui/static/js/validate.js b/app/ui/static/js/validate.js new file mode 100644 index 0000000..3f90df1 --- /dev/null +++ b/app/ui/static/js/validate.js @@ -0,0 +1,37 @@ +function validate_form() { + var pass = validate_password(); + + return validate_confirm() && pass; +} + +function validate_confirm() { + var password = document.getElementById("newpassword"); + var confirm = document.getElementById("confirm_password"); + + if (password.value != confirm.value) { + confirm.classList.add("errorinput"); + document.getElementById("confirm-msg").classList.add("fade"); + return false; + } + + confirm.classList.remove("errorinput"); + document.getElementById("confirm-msg").classList.remove("fade"); + + return true; +} + +function validate_password() { + var password = document.getElementById("newpassword"); + var reg = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/; + + if (reg.test(password.value) != true) { + password.classList.add("errorinput"); + document.getElementById("password-msg").classList.add("fade"); + return false; + } + + password.classList.remove("errorinput"); + document.getElementById("password-msg").classList.remove("fade"); + + return true; +} \ No newline at end of file diff --git a/app/ui/templates/base.html b/app/ui/templates/base.html new file mode 100644 index 0000000..b640b1b --- /dev/null +++ b/app/ui/templates/base.html @@ -0,0 +1,26 @@ + + + + + + + + AlxCzl - LDAP Interface + + + + + + + + + {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} +
+ {% block main_block %}{% endblock main_block %} +
+ {% block script_block %}{% endblock script_block %} + + + diff --git a/app/ui/templates/reset.html b/app/ui/templates/reset.html new file mode 100644 index 0000000..dcd0031 --- /dev/null +++ b/app/ui/templates/reset.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} + +{% block main_block %} +
+ {% for field, errors in form.errors.items() %} + {{ ', '.join(errors) }} + {% endfor %} +
+ {{ form.csrf_token() }} +
+ {{ form.username.label }} + {{ form.username(class="form-control") }} +
+
+ {{ form.currentpassword.label }} + {{ form.currentpassword(class="form-control") }} +
+
+ {{ form.newpassword.label }} + + The new password should contain at least : 8 characters, one numeric digit, one lowercase and one uppercase characters. + + {{ form.newpassword(class="form-control") }} +
+
+ {{ form.confirm_password.label }} + + Passwords should match + + {{ form.confirm_password(class="form-control") }} +
+
+
+ {{ form.submit(class="btn btn-primary")}} +
+
+
+{% endblock main_block %} + +{% block script_block %} + +{% endblock script_block %} \ No newline at end of file diff --git a/app/entrypoint.sh b/entrypoint.sh similarity index 100% rename from app/entrypoint.sh rename to entrypoint.sh diff --git a/requirements.txt b/requirements.txt index 9c6a7e4..03fa1b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,5 @@ MarkupSafe==2.0.1 typing_extensions==4.0.0 Werkzeug==2.0.2 zipp==3.6.0 -python-ldap \ No newline at end of file +python-ldap +Flask-WTF==1.0.0 \ No newline at end of file diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..3f0abc1 --- /dev/null +++ b/wsgi.py @@ -0,0 +1,6 @@ +import os, sys +app_path = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, app_path) + +from app import create_app +application = create_app() -- 2.49.1 From 442f9206812df95023c48a0e18c2d840a35a2f8d Mon Sep 17 00:00:00 2001 From: AlxCzl Date: Mon, 29 Nov 2021 01:18:29 +0100 Subject: [PATCH 03/10] feat(reset): added a better validator to the form --- app/reset.py | 33 ++++++++++++++++----- app/ui/static/css/main.css | 34 ++++++++++++++------- app/ui/static/js/validate.js | 57 ++++++++++++++++++++++++++---------- app/ui/templates/reset.html | 22 +++++++++----- 4 files changed, 106 insertions(+), 40 deletions(-) diff --git a/app/reset.py b/app/reset.py index 204c2e1..482ab04 100644 --- a/app/reset.py +++ b/app/reset.py @@ -7,29 +7,48 @@ from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, \ SubmitField from wtforms.validators import ValidationError, DataRequired, \ - Email, EqualTo, Length + Email, EqualTo, Length, Regexp from werkzeug.security import check_password_hash, generate_password_hash - +import re bp = Blueprint('reset', __name__, url_prefix='/reset') class ResetPasswordForm(FlaskForm): - username = StringField(label=('Username'), + # Minimal password length + minlength = 9 + + # Form + username = StringField(label=('Login'), validators=[DataRequired(), Length(max=64)]) currentpassword = PasswordField(label=('Current password'), validators=[DataRequired()]) newpassword = PasswordField(label=('New password'), validators=[DataRequired(), - Length(min=8, message='Password should be at least %(min)d characters long')], - render_kw={"onkeyup": "validate_form()"}) + Length(min=minlength, message='Password should be at least %(min)d characters long'), + Regexp("^(?=.*[a-z])", message="Password must have a lowercase character"), + Regexp("^(?=.*[A-Z])", message="Password must have an uppercase character"), + Regexp("^(?=.*\\d)", message="Password must contain a number"), + #Regexp( + # "(?=.*[@$!%*#?&])", message="Password must contain a special character" + #),], + ], + render_kw={"onkeyup": f"validate_form({minlength})"}) confirm_password = PasswordField( label=('Confirm Password'), validators=[DataRequired(message='* Required'), EqualTo('newpassword', message='Both password fields must be equal!')], - render_kw={"onkeyup": "validate_confirm()"}) + render_kw={"onkeyup": f"validate_confirm({minlength})"}) - submit = SubmitField(label=('Change my password'), render_kw={"onclick": "validate_form()"}) + submit = SubmitField(label=('Change my password'), render_kw={"onclick": f"validate_form({minlength})"}) + + # Validators + def validate_username(self, username): + excluded_chars = " *?!'^+%&/()=}][{$#;\\\"" + for char in self.username.data: + if char in excluded_chars: + raise ValidationError( + f"Character {char} is not allowed in a login.") @bp.route('/', methods=('GET', 'POST')) def reset(): diff --git a/app/ui/static/css/main.css b/app/ui/static/css/main.css index 0e5b919..fb91c40 100644 --- a/app/ui/static/css/main.css +++ b/app/ui/static/css/main.css @@ -51,17 +51,36 @@ a:hover>span { font-size: 32px; } +#confirm-msg, +#password-msg { + color: #d4d4d4; + font-size: small; + transition: color 225ms cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +#password-msg li { + list-style: none; + content: "" +} + +#password-msg li::before { + content: "☑ "; +} + .errorinput { border-color: #f44246 !important; } -#confirm-msg, -#password-msg { - color: #f44246; - opacity: 0; - transition: opacity 225ms cubic-bezier(0.25, 0.46, 0.45, 0.94); +.errormsg { + color: #f44246 !important; + } +li.errormsg::before { + content: "☒ " !important; +} + + .errorinput:focus { box-shadow: 0 0 0 .10rem rgba(244, 66, 70, 0.50) !important; -webkit-box-shadow: 0 0 0 .10rem rgba(244, 66, 70, 0.50) !important; @@ -77,8 +96,3 @@ a:hover>span { background: #4e4e4e; border-radius: .50rem; } - -.fade { - opacity: 1 !important; - transition: opacity 300ms cubic-bezier(0.55, 0.085, 0.68, 0.53); -} \ No newline at end of file diff --git a/app/ui/static/js/validate.js b/app/ui/static/js/validate.js index 3f90df1..5ab48b5 100644 --- a/app/ui/static/js/validate.js +++ b/app/ui/static/js/validate.js @@ -1,5 +1,5 @@ -function validate_form() { - var pass = validate_password(); +function validate_form(minlength) { + var pass = validate_password(minlength); return validate_confirm() && pass; } @@ -10,28 +10,55 @@ function validate_confirm() { if (password.value != confirm.value) { confirm.classList.add("errorinput"); - document.getElementById("confirm-msg").classList.add("fade"); + document.getElementById("confirm-msg").classList.add("errormsg"); return false; } confirm.classList.remove("errorinput"); - document.getElementById("confirm-msg").classList.remove("fade"); + document.getElementById("confirm-msg").classList.remove("errormsg"); return true; } -function validate_password() { +function validate_password(minlength) { + // Did the checks pass ? + var status = true; + // Target element var password = document.getElementById("newpassword"); - var reg = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/; - - if (reg.test(password.value) != true) { - password.classList.add("errorinput"); - document.getElementById("password-msg").classList.add("fade"); - return false; + // Check the length + if (password.value.length < minlength) + { + status = false; + document.getElementById("minlen").classList.add("errormsg"); } + else + document.getElementById("minlen").classList.remove("errormsg"); + // Look for a digit + if (/.*\d/.test(password.value) != true) { + status = false; + document.getElementById("digit").classList.add("errormsg"); + } + else + document.getElementById("digit").classList.remove("errormsg"); + // Look for a lowercase char + if (/.*[a-z]/.test(password.value) != true) { + status = false; + document.getElementById("lower").classList.add("errormsg"); + } + else + document.getElementById("lower").classList.remove("errormsg"); + // Look for an uppercase char + if (/.*[A-Z]/.test(password.value) != true) { + status = false; + document.getElementById("upper").classList.add("errormsg"); + } + else + document.getElementById("upper").classList.remove("errormsg"); + // Change the color of the inputbox + if (status == false) + password.classList.add("errorinput"); + else + password.classList.remove("errorinput"); - password.classList.remove("errorinput"); - document.getElementById("password-msg").classList.remove("fade"); - - return true; + return status; } \ No newline at end of file diff --git a/app/ui/templates/reset.html b/app/ui/templates/reset.html index dcd0031..a57e89a 100644 --- a/app/ui/templates/reset.html +++ b/app/ui/templates/reset.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block main_block %} -
+
{% for field, errors in form.errors.items() %} {{ ', '.join(errors) }} {% endfor %} @@ -17,20 +17,26 @@
{{ form.newpassword.label }} - - The new password should contain at least : 8 characters, one numeric digit, one lowercase and one uppercase characters. - +
+ The new password should contain at least : +
    +
  • {{ form.minlength }} characters
  • +
  • 1 numeric digit [0-9]
  • +
  • 1 lowercase character [a-z]
  • +
  • 1 uppercase character [A-Z]
  • +
+
{{ form.newpassword(class="form-control") }}
{{ form.confirm_password.label }} - - Passwords should match - +
+ Passwords must match +
{{ form.confirm_password(class="form-control") }}

-
+
{{ form.submit(class="btn btn-primary")}}
-- 2.49.1 From 45295860c3ebeb6216d75b05742b4b10bf065ba8 Mon Sep 17 00:00:00 2001 From: AlxCzl Date: Mon, 29 Nov 2021 02:00:06 +0100 Subject: [PATCH 04/10] feat(reset): added a name validator --- app/reset.py | 13 +++++++------ app/ui/static/css/main.css | 1 + app/ui/static/js/validate.js | 21 ++++++++++++++++++++- app/ui/templates/reset.html | 8 ++++---- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/app/reset.py b/app/reset.py index 482ab04..4d3827d 100644 --- a/app/reset.py +++ b/app/reset.py @@ -20,15 +20,16 @@ class ResetPasswordForm(FlaskForm): # Form username = StringField(label=('Login'), validators=[DataRequired(), - Length(max=64)]) + Length(max=64)], + render_kw={"onkeyup": "validate_username()"}) currentpassword = PasswordField(label=('Current password'), validators=[DataRequired()]) newpassword = PasswordField(label=('New password'), validators=[DataRequired(), - Length(min=minlength, message='Password should be at least %(min)d characters long'), - Regexp("^(?=.*[a-z])", message="Password must have a lowercase character"), - Regexp("^(?=.*[A-Z])", message="Password must have an uppercase character"), - Regexp("^(?=.*\\d)", message="Password must contain a number"), + Length(min=minlength), + Regexp("^(?=.*[a-z])"), + Regexp("^(?=.*[A-Z])"), + Regexp("^(?=.*\\d)"), #Regexp( # "(?=.*[@$!%*#?&])", message="Password must contain a special character" #),], @@ -37,7 +38,7 @@ class ResetPasswordForm(FlaskForm): confirm_password = PasswordField( label=('Confirm Password'), validators=[DataRequired(message='* Required'), - EqualTo('newpassword', message='Both password fields must be equal!')], + EqualTo('newpassword')], render_kw={"onkeyup": f"validate_confirm({minlength})"}) submit = SubmitField(label=('Change my password'), render_kw={"onclick": f"validate_form({minlength})"}) diff --git a/app/ui/static/css/main.css b/app/ui/static/css/main.css index fb91c40..b0faa5f 100644 --- a/app/ui/static/css/main.css +++ b/app/ui/static/css/main.css @@ -51,6 +51,7 @@ a:hover>span { font-size: 32px; } +#username-msg, #confirm-msg, #password-msg { color: #d4d4d4; diff --git a/app/ui/static/js/validate.js b/app/ui/static/js/validate.js index 5ab48b5..8d7feeb 100644 --- a/app/ui/static/js/validate.js +++ b/app/ui/static/js/validate.js @@ -1,7 +1,8 @@ function validate_form(minlength) { + var user = validate_username(); var pass = validate_password(minlength); - return validate_confirm() && pass; + return validate_confirm() && pass && user; } function validate_confirm() { @@ -20,6 +21,22 @@ function validate_confirm() { return true; } +function validate_username() { + var username = document.getElementById("username"); + var forbidden = /[*?!'\^+%\&/()=}{\$#;,\\"]+/; + + if (username.value.length > 64 || forbidden.test(username.value) == true) + { + document.getElementById("username-msg").classList.add("errormsg"); + username.classList.add("errorinput"); + return false; + } + + document.getElementById("username-msg").classList.remove("errormsg"); + username.classList.remove("errorinput"); + return true; +} + function validate_password(minlength) { // Did the checks pass ? var status = true; @@ -56,7 +73,9 @@ function validate_password(minlength) { document.getElementById("upper").classList.remove("errormsg"); // Change the color of the inputbox if (status == false) + { password.classList.add("errorinput"); + } else password.classList.remove("errorinput"); diff --git a/app/ui/templates/reset.html b/app/ui/templates/reset.html index a57e89a..b593e02 100644 --- a/app/ui/templates/reset.html +++ b/app/ui/templates/reset.html @@ -2,13 +2,13 @@ {% block main_block %}
- {% for field, errors in form.errors.items() %} - {{ ', '.join(errors) }} - {% endfor %}
{{ form.csrf_token() }}
{{ form.username.label }} +
+ The username can contain at most 64 characters and cannot contain one of the following characters : [*?!'^+%&/()=}{$#;,\" +
{{ form.username(class="form-control") }}
@@ -36,7 +36,7 @@ {{ form.confirm_password(class="form-control") }}

-
+
{{ form.submit(class="btn btn-primary")}}
-- 2.49.1 From 8a7546e5822b593c7ac2f7f0a52f1a809c369271 Mon Sep 17 00:00:00 2001 From: AlxCzl Date: Mon, 29 Nov 2021 05:12:56 +0100 Subject: [PATCH 05/10] feat(reset): successfully linked the app with the LDAP server --- app/ldap_client.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++ app/reset.py | 31 +++++++++++++++++------- 2 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 app/ldap_client.py diff --git a/app/ldap_client.py b/app/ldap_client.py new file mode 100644 index 0000000..976c67a --- /dev/null +++ b/app/ldap_client.py @@ -0,0 +1,60 @@ +import ldap3 +from typing import Tuple + +class Client(): + def __init__(self, address: str, port: int, base_dn: str, primary_attribute: str = "uid", tls: bool = False): + self.server = ldap3.Server(host=address, port=port, use_ssl=tls) + self.base_dn = base_dn + self.address = address + self.port = port + self.tls = tls + self.primary_attribute = primary_attribute + + def bind(self, user: str, bind_passwd: str) -> Tuple[bool, str]: + user_dn = f"{self.primary_attribute}={user},{self.base_dn}" + + self.link = ldap3.Connection(self.server, user=user_dn, password=bind_passwd) + + try: + status = self.link.bind() + except Exception as _: + status = False + + if status == False: + print(f"[!!] Could not bind {user_dn} to the LDAP directory: {self.link.last_error}") + return (status, "") + + return (status, user_dn) + + def unbind(self) -> bool: + if self.link.bound != True: + return False + + try: + self.link.unbind() + except Exception as e: + pass + + return True + + def change_pwd(self, user_dn: str, new_password: str) -> bool: + if self.link.bound == False: + print("[!!] Can't change the password: not bound to the server") + return False + + status = self.link.modify(user_dn, {'userPassword': [(ldap3.MODIFY_REPLACE, [new_password])]}) + if status == True: + print(f"[++] Changed password of user {user_dn}") + else: + print(f"[!!] Could not change password of user {user_dn}: {self.link.last_error}") + + return status + +if __name__ == "__main__": + client = Client("dc01.lan.alxczl.fr", 636, "cn=users,cn=accounts,dc=lan,dc=alxczl,dc=fr", True) + client_dn = "uid=alexandre,cn=users,cn=accounts,dc=lan,dc=alxczl,dc=fr" + res = client.bind(client_dn, "Getshrektm8") + if res[0] == False: + print(client.link.result["description"]) + + #client.link.unbind() \ No newline at end of file diff --git a/app/reset.py b/app/reset.py index 4d3827d..f6036fd 100644 --- a/app/reset.py +++ b/app/reset.py @@ -1,15 +1,18 @@ -import functools +from . import ldap_client from flask import ( - Blueprint, flash, g, redirect, render_template, request, session, url_for + Blueprint, render_template, flash, + current_app ) from flask_wtf import FlaskForm -from wtforms import StringField, PasswordField, BooleanField, \ +from wtforms import ( + StringField, PasswordField, SubmitField -from wtforms.validators import ValidationError, DataRequired, \ - Email, EqualTo, Length, Regexp -from werkzeug.security import check_password_hash, generate_password_hash -import re +) +from wtforms.validators import ( + ValidationError, DataRequired, + EqualTo, Length, Regexp +) bp = Blueprint('reset', __name__, url_prefix='/reset') @@ -49,11 +52,21 @@ class ResetPasswordForm(FlaskForm): for char in self.username.data: if char in excluded_chars: raise ValidationError( - f"Character {char} is not allowed in a login.") + f"Character {char} is not allowed in an username.") @bp.route('/', methods=('GET', 'POST')) def reset(): form = ResetPasswordForm() if form.validate_on_submit(): - return f'''

Welcome {form.username.data}

''' + client = ldap_client.Client(address=current_app.config["LDAP_ADDR"], port=current_app.config["LDAP_PORT"], base_dn=current_app.config["BASE_DN"], tls=current_app.config["LDAP_TLS"]) + bind_status = client.bind(form.username._value(), form.currentpassword._value()) + if bind_status[0] == False: + flash(f"Connection failed, are you sure that your login and password are correct ? ({client.link.last_error})") + elif client.change_pwd(bind_status[1], form.newpassword._value()) == False: + flash(f"An error occured and your password was not changed, sorry. ({client.link.last_error})") + client.unbind() + else: + flash('Your password has been changed !') + client.unbind() + return render_template('reset.html', form=form) \ No newline at end of file -- 2.49.1 From 275f2b4a24e5a55cc09f5ca043e7524a0ed2917f Mon Sep 17 00:00:00 2001 From: AlxCzl Date: Mon, 29 Nov 2021 05:14:04 +0100 Subject: [PATCH 06/10] feat(python): updated requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 03fa1b2..89e7f5c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,5 +8,5 @@ MarkupSafe==2.0.1 typing_extensions==4.0.0 Werkzeug==2.0.2 zipp==3.6.0 -python-ldap +ldap3 Flask-WTF==1.0.0 \ No newline at end of file -- 2.49.1 From 12b62bbd97344861c3c23c3d04fe7a67e7f48346 Mon Sep 17 00:00:00 2001 From: AlxCzl Date: Mon, 29 Nov 2021 05:19:04 +0100 Subject: [PATCH 07/10] feat(docker): reduced Dockerfile and added .drone.yml to dockerignore --- .dockerignore | 3 ++- Dockerfile | 6 +----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.dockerignore b/.dockerignore index 6ea5688..153fb8c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ *.pyc .git/ Dockerfile -__pycache__/* \ No newline at end of file +__pycache__/* +drone.yml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e73b6e0..3ed6954 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,9 @@ FROM python:3.9-alpine -COPY requirements.txt / +COPY . / RUN pip3 install -r /requirements.txt -COPY ./app/ /app - -WORKDIR /app - EXPOSE 80/tcp ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file -- 2.49.1 From 327196b846ae3804eec2d98397d6c0bc92da3937 Mon Sep 17 00:00:00 2001 From: AlxCzl Date: Mon, 29 Nov 2021 05:23:56 +0100 Subject: [PATCH 08/10] feat(html): added favicons and main.css integrity hash --- app/ui/static/images/apple-touch-icon.png | Bin 0 -> 6723 bytes app/ui/static/images/favicon-16x16.png | Bin 0 -> 605 bytes app/ui/static/images/favicon-32x32.png | Bin 0 -> 1263 bytes app/ui/templates/base.html | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 app/ui/static/images/apple-touch-icon.png create mode 100644 app/ui/static/images/favicon-16x16.png create mode 100644 app/ui/static/images/favicon-32x32.png diff --git a/app/ui/static/images/apple-touch-icon.png b/app/ui/static/images/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..59a51f1cef29ab07c9e5e9bfe2ec12197df429c8 GIT binary patch literal 6723 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 Z{{u>Bld27gb5sBT002ovPDHLkV1lVG+)@Al literal 0 HcmV?d00001 diff --git a/app/ui/static/images/favicon-16x16.png b/app/ui/static/images/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..176d7962f5c9b1d63ddc272486cf52da879a7310 GIT binary patch literal 605 zcmV-j0;2tiP)Px%7fD1xR5(v%lgVxqK@>$#rS0z6!PtriY{#;KVj%&F5X-Ds!56r`CQ^3!C>Dqn z8zdHpgehbZgF-B@@esGicA={3A%WVIs@`d*!bN~mpl^XfA#U8nKPXUw zg@9urrJMt24;Dm-d>mK zwvX$3II|Rf@fvU9;q|tte5r*^TB?L>OI9DM;EghzKl@Dc?S0Zy_f`wW z+q9q*N(fD=fv2zY=O?sYJwhqL*^3h%yxfmwn=q)|Edo6@8~pRhcV@X6^TLG9K^C9ZSzn`xmF r9HBTw=#Bh8NxYr6w$)5+f#CQr0z>fHXx@d700000NkvXXu0mjfDHRao literal 0 HcmV?d00001 diff --git a/app/ui/static/images/favicon-32x32.png b/app/ui/static/images/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..2d5f6121883abf05542ffdf00ad959302cc5f540 GIT binary patch literal 1263 zcmVPx(sYygZR9Hu?S6fdTM;JY8b^%|(6vvP_2IEjracCc^rp;T_rVs67?LX0GSfTXLRE z?>VvzN3Cw;K9~j>23d3ed4tpiNxdJV0&v%t2O41}a~*CjsHN zb6D7k+sc?L#KeFR9p|AFYNPM%u?X6s3zXN75jYNF_8rf3pcQSPx>UwYeh$GRjhZlz zm~MD&fZI6Xc(w~vmldhKj3K-qQTP9N;}CQUVt*tMEX^?GQxI}Rv2ScmnuG?C=pKk* zouHH|pqe;E{CN`olMu^vI;3zbhx2Fy$*1d>Y+0kW*g4-aw_;?6n@a8CN)yS)8?p^6 zo1VS<4+c{`tZjcH7e`0;F>-CA0(<-8Z?kt_$x_??Sj9&b@$WY;FxBv4<>z$+Zp+r1 zJjuNl5CP4_8rmP6Gn#3uBu_EXn_$Vtqo;uC@d~8YB!Z<7li+mShnd2h2F51I)Eux- zt?OLcD4?1wE51&98f9mjUIYjo&EneM38hSOi`6FP2OR?gc1q|3+vo+l%%(3Q4bdFgE^d&4jSpC<)K?5SrSrG;WT`z{$7EBJ+TNYl=mtAj+unp{ z;E~mDM(;DL@#?20%OnZ@zs2u`6QrNlJGH&>usbZO-*f$fe9u z&og8TN#|2YKKn>T+w*sj`|>q>Cjl%yUv97i#H zswR5bU^RmNtXAw&md34EuIXe1W-Z4y=d$-(*DWA<3v8}tJpJTSI;Ns*;vNaTC|^z? z+_x>;4hE&>Cf!SV002ovPDHLkV1myBMUDUf literal 0 HcmV?d00001 diff --git a/app/ui/templates/base.html b/app/ui/templates/base.html index b640b1b..70d2ffb 100644 --- a/app/ui/templates/base.html +++ b/app/ui/templates/base.html @@ -11,7 +11,7 @@ - + {% for message in get_flashed_messages() %} -- 2.49.1 From aea0844a59cdc2208c6301b4b9b0985377ef84da Mon Sep 17 00:00:00 2001 From: AlxCzl Date: Mon, 29 Nov 2021 05:32:13 +0100 Subject: [PATCH 09/10] feat(config): now using envvars instead of a config file --- app/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index cf00692..2409ddb 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,9 +1,13 @@ -from flask import Flask, render_template +from flask import Flask from . import reset def create_app(): app = Flask(__name__, instance_relative_config=True, template_folder="ui/templates", static_folder="ui/static") - app.config.from_object("instance.config.DebugConfig") + #app.config.from_object("instance.config.DebugConfig") + app.config.from_envvar("LDAP_ADDR") + app.config.from_envvar("LDAP_PORT") + app.config.from_envvar("LDAP_TLS") + app.config.from_envvar("BASE_DN") app.register_blueprint(reset.bp) -- 2.49.1 From 94f92951c12256e36a26a58320aace66e9137572 Mon Sep 17 00:00:00 2001 From: AlxCzl Date: Mon, 29 Nov 2021 05:34:36 +0100 Subject: [PATCH 10/10] fix(drone): added auto_tag and replaced repository by registry --- .drone.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 089ad61..979ad14 100644 --- a/.drone.yml +++ b/.drone.yml @@ -11,13 +11,13 @@ steps: - name: docker image: plugins/docker settings: + repo: registry.alxczl.fr/ldap-interface username: from_secret: docker_username password: from_secret: docker_password - repo: registry.alxczl.fr/ldap-interface - repository: registry.alxczl.fr - tags: latest + registry: registry.alxczl.fr + auto_tag: true when: branch: - master \ No newline at end of file -- 2.49.1