diff options
author | Javier Sánchez Parra <jsanchez@soleta.eu> | 2022-04-26 17:16:52 +0200 |
---|---|---|
committer | Javier Sánchez Parra <jsanchez@soleta.eu> | 2022-04-27 17:27:50 +0200 |
commit | 661254b76edd51c36090edd0f898bdca16f23277 (patch) | |
tree | 7882d0097515358435206a795d4f8b815965f529 | |
parent | d8bac16a980634f182e9c753a01e114c3a482af0 (diff) |
Add 'Add user' to Users section
Creates "Add user" form with the following inputs: username, password,
password confirmation, role (administrator or regular), allowed scopes.
-rw-r--r-- | ogcp/forms/auth.py | 31 | ||||
-rw-r--r-- | ogcp/static/js/ogcp.js | 22 | ||||
-rw-r--r-- | ogcp/templates/auth/add_user.html | 26 | ||||
-rw-r--r-- | ogcp/templates/users.html | 2 | ||||
-rw-r--r-- | ogcp/views.py | 71 |
5 files changed, 150 insertions, 2 deletions
diff --git a/ogcp/forms/auth.py b/ogcp/forms/auth.py index c02ecc7..b10d8fe 100644 --- a/ogcp/forms/auth.py +++ b/ogcp/forms/auth.py @@ -7,7 +7,7 @@ from wtforms import ( Form, SubmitField, HiddenField, SelectField, BooleanField, IntegerField, - StringField, RadioField, PasswordField + StringField, RadioField, PasswordField, SelectMultipleField ) from wtforms.validators import InputRequired from flask_wtf import FlaskForm @@ -28,3 +28,32 @@ class LoginForm(FlaskForm): submit_btn = SubmitField( label=_l('Login') ) + + +class UserForm(FlaskForm): + username = StringField( + label=_l('Username'), + validators=[InputRequired()] + ) + pwd = PasswordField( + label=_l('Password'), + ) + pwd_hash = HiddenField( + validators=[InputRequired()] + ) + pwd_confirm = PasswordField( + label=_l('Repeat password'), + ) + pwd_hash_confirm = HiddenField( + validators=[InputRequired()] + ) + admin = BooleanField( + label=_l('Administrator'), + ) + scopes = SelectMultipleField( + label=_l('Allowed scopes'), + description=_l('Leave this empty to give full permissions'), + ) + submit_btn = SubmitField( + label=_l('Submit') + ) diff --git a/ogcp/static/js/ogcp.js b/ogcp/static/js/ogcp.js index 6a7e625..4605e5d 100644 --- a/ogcp/static/js/ogcp.js +++ b/ogcp/static/js/ogcp.js @@ -231,3 +231,25 @@ function digestLoginPassword() { $(this).submit() }); } + +function digestUserFormPassword() { + const loginForm = $('#user-form') + loginForm.one('submit', async function (event) { + event.preventDefault() + + const pwdInput = $('#pwd'); + const pwdHashInput = $('#pwd_hash'); + const pwdStr = pwdInput.val(); + const pwdStrHash = await digestMessage(pwdStr); + + const pwdConfirmInput = $('#pwd_confirm'); + const pwdHashConfirmInput = $('#pwd_hash_confirm'); + const pwdConfirmStr = pwdConfirmInput.val(); + const pwdConfirmStrHash = await digestMessage(pwdConfirmStr); + + pwdInput.prop( "disabled", true ); + pwdHashInput.val(pwdStrHash); + pwdHashConfirmInput.val(pwdConfirmStrHash); + $(this).submit() + }); +} diff --git a/ogcp/templates/auth/add_user.html b/ogcp/templates/auth/add_user.html new file mode 100644 index 0000000..af44caa --- /dev/null +++ b/ogcp/templates/auth/add_user.html @@ -0,0 +1,26 @@ +{% extends 'users.html' %} +{% import "bootstrap/wtf.html" as wtf %} + +{% set sidebar_state = 'disabled' %} +{% set btn_back = true %} + +{% block nav_user_add %}active{% endblock %} +{% block content %} + +<h1 class="m-5">{{_('Add a user')}}</h1> + +{{ wtf.quick_form(form, + action=url_for('user_add_post'), + method='post', + button_map={'submit_btn':'primary'}, + id='user-form') }} + +<script> + document.addEventListener('readystatechange', () => { + if (document.readyState === 'complete') { + digestUserFormPassword() + } + }); +</script> + +{% endblock %} diff --git a/ogcp/templates/users.html b/ogcp/templates/users.html index c97f113..c14aae6 100644 --- a/ogcp/templates/users.html +++ b/ogcp/templates/users.html @@ -24,6 +24,8 @@ {% endblock %} {% block commands %} + <input class="btn btn-light {% block nav_user_add %}{% endblock %}" type="submit" value="{{ _('Add user') }}" + form="usersForm" formaction="{{ url_for('user_add_get') }}" formmethod="get"> {% if btn_back %} <button class="btn btn-danger ml-3" type="button" id="backButton" onclick="history.back()"> {{ _("Back") }} diff --git a/ogcp/views.py b/ogcp/views.py index c10d2c0..48486c7 100644 --- a/ogcp/views.py +++ b/ogcp/views.py @@ -23,13 +23,15 @@ from flask_login import ( from pathlib import Path from ogcp.models import User -from ogcp.forms.auth import LoginForm +from ogcp.forms.auth import LoginForm, UserForm from ogcp.og_server import OGServer from flask_babel import lazy_gettext as _l from flask_babel import _ from ogcp import app import requests import datetime +import json +import os import re FS_CODES = { @@ -1194,6 +1196,73 @@ def users(): return render_template('users.html', users=users) +def get_available_scopes(): + resp = g.server.get('/scopes') + centers = parse_scopes_from_tree(resp.json(), 'center') + centers = [(center['name'], center['name']) for center in centers] + rooms = parse_scopes_from_tree(resp.json(), 'room') + rooms = [(room['name'], room['name']) for room in rooms] + return centers + rooms + + +def save_user(form): + username = form.username.data + + pwd_hash = form.pwd_hash.data + pwd_hash_confirm = form.pwd_hash_confirm.data + if not pwd_hash == pwd_hash_confirm: + flash(_('Passwords do not match'), category='error') + return redirect(url_for('users')) + + admin = form.admin.data + scopes = form.scopes.data + + user = { + 'USER': username, + 'PASS': pwd_hash, + 'ADMIN': admin, + 'SCOPES': scopes, + } + + filename = os.path.join(app.root_path, 'cfg', 'ogcp.json') + with open(filename, 'r+') as file: + config = json.load(file) + + config['USERS'].append(user) + + file.seek(0) + json.dump(config, file, indent='\t') + file.truncate() + + app.config['USERS'].append(user) + + return redirect(url_for('users')) + + +@app.route('/user/add', methods=['GET']) +@login_required +def user_add_get(): + form = UserForm() + form.scopes.choices = get_available_scopes() + return render_template('auth/add_user.html', form=form) + + +@app.route('/user/add', methods=['POST']) +@login_required +def user_add_post(): + form = UserForm(request.form) + form.scopes.choices = get_available_scopes() + if not form.validate(): + flash(form.errors, category='error') + return redirect(url_for('users')) + + if get_user(form.username.data): + flash(_('This username already exists'), category='error') + return redirect(url_for('users')) + + return save_user(form) + + @app.route('/action/image/info', methods=['GET']) @login_required def action_image_info(): |