diff --git a/uffd/selfservice/models.py b/uffd/selfservice/models.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..db19c520385bdb7840c0ea18051bd1cd96381181 100644 --- a/uffd/selfservice/models.py +++ b/uffd/selfservice/models.py @@ -0,0 +1,22 @@ +import datetime +import secrets + +from sqlalchemy import Column, Integer, String, Text, LargeBinary, DateTime, Boolean, ForeignKey + +from uffd.database import db + +def random_token(): + return secrets.token_hex(128) + +class Token(): + token = Column(String(128), primary_key=True, default=random_token) + created = Column(DateTime, default=datetime.datetime.now) + +class PasswordToken(Token, db.Model): + __tablename__ = 'passwortToken' + loginname = Column(String(32)) + +class MailToken(Token, db.Model): + __tablename__ = 'mailToken' + loginname = Column(String(32)) + newmail = Column(String(255)) diff --git a/uffd/selfservice/templates/reset_password.html b/uffd/selfservice/templates/reset_password.html new file mode 100644 index 0000000000000000000000000000000000000000..990a542f2752b7105c20254f20d1006e3934ba02 --- /dev/null +++ b/uffd/selfservice/templates/reset_password.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} + +{% block body %} +<form action="{{ url_for(".self_token_password", token=token) }}" method="POST" onInput="password2.setCustomValidity(password1.value != password2.value ? 'Passwords do not match.' : '') "> +<div class="row mt-2 justify-content-center"> + <div class="col-lg-6 col-md-10" style="background: #f7f7f7; box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); padding: 30px;"> + <div class="text-center"> + <img src="{{ url_for("static", filename="chaosknoten.png") }}" class="col-lg-8 col-md-12" > + </div> + <div class="col-12"> + <h2 class="text-center">Reset password</h2> + </div> + <div class="form-group col-12"> + <label for="user-loginname">login name</label> + <input type="text" class="form-control" id="user-loginname" name="loginname" required="required" tabindex = "1"> + </div> + <div class="form-group col-12"> + <label for="user-password1">new password</label> + <input type="password" class="form-control" id="user-password1" name="password1" required="required" tabindex = "2"> + </div> + <div class="form-group col-12"> + <label for="user-password2">new password again</label> + <input type="password" class="form-control" id="user-password2" name="password2" required="required" tabindex = "2"> + </div> + <div class="form-group col-12"> + <button type="submit" class="btn btn-primary btn-block" tabindex = "3">Set password</button> + </div> + </div> +</div> +</form> +{% endblock %} diff --git a/uffd/selfservice/views.py b/uffd/selfservice/views.py index d23351bbc38056866809fd0b382841e5f9a31cfa..9d745347764194e3647e5bcaff65885963b30664 100644 --- a/uffd/selfservice/views.py +++ b/uffd/selfservice/views.py @@ -1,32 +1,70 @@ +import datetime + from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app from uffd.navbar import register_navbar from uffd.csrf import csrf_protect - from uffd.user.models import User, Group from uffd.session import get_current_user, login_required, is_valid_session -from uffd.ldap import get_conn, escape_filter_chars +from uffd.ldap import get_conn, escape_filter_chars, loginname_to_dn +from uffd.selfservice.models import PasswordToken +from uffd.database import db bp = Blueprint("selfservice", __name__, template_folder='templates', url_prefix='/self/') -@bp.before_request -@login_required() -def self_acl(): - pass - #if not self_acl_check(): - # flash('Access denied') - # return redirect(url_for('index')) - -def self_acl_check(): - return is_valid_session() and get_current_user().is_in_group(current_app.config['ACL_SELFSERVICE_GROUP']) - @bp.route("/") @register_navbar('Selfservice', icon='portrait', blueprint=bp, visible=is_valid_session) +@login_required() def self_index(): return render_template('self.html', user=get_current_user()) @bp.route("/update", methods=(['POST'])) @csrf_protect(blueprint=bp) +@login_required() def self_update(): - pass + # TODO: actualy update the user... + send_passwordreset('uffdtest') + return 'OK', 200 + +@bp.route("/token/password/<token>", methods=(['POST', 'GET'])) +def self_token_password(token): + session = db.session + dbtoken = PasswordToken.query.get(token) + if not dbtoken or dbtoken.created < (datetime.datetime.now() - datetime.timedelta(days=2)): + flash('Token expired, please try again.') + if dbtoken: + session.delete(dbtoken) + session.commit() + return redirect(url_for('session.login')) + if not 'loginname' in request.values: + flash('Please set a new password.') + return render_template('reset_password.html', token=token) + else: + if not request.values['loginname'] == dbtoken.loginname: + flash('That is not the correct login name. Please start the password reset process again') + session.delete(dbtoken) + session.commit() + return redirect(url_for('session.login')) + if not request.values['password1']: + flash('Please specify a new password.') + return render_template('reset_password.html', token=token) + user = User.from_ldap_dn(loginname_to_dn(dbtoken.loginname)) + user.set_password(request.values['password1']) + user.to_ldap() + flash('New password set') + session.delete(dbtoken) + session.commit() + return redirect(url_for('session.login')) + + +def send_passwordreset(loginname): + session = db.session + expired_tokens = PasswordToken.query.filter(PasswordToken.created < (datetime.datetime.now() - datetime.timedelta(days=2))).all() + for i in expired_tokens: + session.delete(i) + token = PasswordToken() + token.loginname = loginname + session.add(token) + # TODO: send mail + session.commit() diff --git a/uffd/user/models.py b/uffd/user/models.py index 2c8792aec698f84f3691c0e06c3106019303cbde..899183358c2e52eaae2aef69c340c219a11fb5dd 100644 --- a/uffd/user/models.py +++ b/uffd/user/models.py @@ -41,7 +41,7 @@ class User(): return None return User.from_ldap(conn.entries[0]) - def to_ldap(self, new): + def to_ldap(self, new=False): conn = ldap.get_conn() if new: attributes= {