diff options
author | Jose M. Guisado <jguisado@soleta.eu> | 2021-03-05 11:06:11 +0100 |
---|---|---|
committer | Jose M. Guisado <jguisado@soleta.eu> | 2021-03-05 11:47:27 +0100 |
commit | 9ee0565ac41661a0521630fbfe1ea9e896fcec52 (patch) | |
tree | 12dd36a028ed7f9795855c599b2a05b76e058c01 | |
parent | 149dfdcbfd31bae79e9872e6f465d127d70ad32b (diff) |
Add login
Ogcp requires a simple login page in order to avoid exposure of the
ogServer API to anyone trying to access the web page.
Because the main authorization mechanism in ogServer is the api token
the login implemented for the ogcp does not include registration process
but a single user and password specified in the ogcp.json.
"USER": "user",
"PASS": "pass"
Adds two new views: /login and /logout. They are used to login the user so
that the rest of views regarding ogServer functionality can be accessed
in a "login required" fashion. Index view (/) is an exception, it can be
accessed logged in or not so different data can be displayed.
Templates can now access a variable "current_user" to get information
about login status. This is a Flask-Login feature.
- Templates regarding login can be found in templates/auth/
- Login form is defined in forms/auth.py to separate it from
action_forms.py
- Adds Flask-Login module to requirements.txt
- Adds default user and pass in ogcp.json
-rw-r--r-- | ogcp/cfg/ogcp.json | 2 | ||||
-rw-r--r-- | ogcp/forms/auth.py | 20 | ||||
-rw-r--r-- | ogcp/models.py | 5 | ||||
-rw-r--r-- | ogcp/templates/auth/login.html | 12 | ||||
-rw-r--r-- | ogcp/templates/base.html | 2 | ||||
-rw-r--r-- | ogcp/templates/nav.html | 18 | ||||
-rw-r--r-- | ogcp/views.py | 57 | ||||
-rw-r--r-- | requirements.txt | 1 |
8 files changed, 117 insertions, 0 deletions
diff --git a/ogcp/cfg/ogcp.json b/ogcp/cfg/ogcp.json index 9e715d4..cff1cad 100644 --- a/ogcp/cfg/ogcp.json +++ b/ogcp/cfg/ogcp.json @@ -2,4 +2,6 @@ "IP": "127.0.0.1", "PORT": 8888, "API_TOKEN": "c3fe7bb0395747ec42a25df027585871" + "USER": "user", + "PASS": "pass" } diff --git a/ogcp/forms/auth.py b/ogcp/forms/auth.py new file mode 100644 index 0000000..8c84e84 --- /dev/null +++ b/ogcp/forms/auth.py @@ -0,0 +1,20 @@ +from wtforms import ( + Form, SubmitField, HiddenField, SelectField, BooleanField, IntegerField, + StringField, RadioField, PasswordField +) +from wtforms.validators import InputRequired +from flask_wtf import FlaskForm +from flask_babel import _ + +class LoginForm(FlaskForm): + user = StringField( + label=_('User'), + validators=[InputRequired()] + ) + pwd = PasswordField( + label=_('Password'), + validators=[InputRequired()] + ) + submit = SubmitField( + label=_('Login') + ) diff --git a/ogcp/models.py b/ogcp/models.py new file mode 100644 index 0000000..668c623 --- /dev/null +++ b/ogcp/models.py @@ -0,0 +1,5 @@ +from flask_login import UserMixin + +class User(UserMixin): + def get_id(self): + return 1 diff --git a/ogcp/templates/auth/login.html b/ogcp/templates/auth/login.html new file mode 100644 index 0000000..220f69e --- /dev/null +++ b/ogcp/templates/auth/login.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} +{% import "bootstrap/wtf.html" as wtf %} + +{% block content %} + +{{ wtf.quick_form(form, + method='post', + form_type='basic', + button_map={'submit':'primary'}, + extra_classes='p-5') }} + +{% endblock %} diff --git a/ogcp/templates/base.html b/ogcp/templates/base.html index 5f07fe7..3f55555 100644 --- a/ogcp/templates/base.html +++ b/ogcp/templates/base.html @@ -17,6 +17,8 @@ <div class="alert alert-info alert-dismissible fade show m-1" role="alert"> {% elif category == 'error' %} <div class="alert alert-danger alert-dismissible fade show m-1" role="alert"> + {% else %} + <div class="alert alert-warning alert-dismissible fade show m-1" role="alert"> {% endif %} {{ message }} <button type="button" class="close" data-dismiss="alert" aria-label="{{ _('Close') }}"> diff --git a/ogcp/templates/nav.html b/ogcp/templates/nav.html index 5147a37..d7741ca 100644 --- a/ogcp/templates/nav.html +++ b/ogcp/templates/nav.html @@ -9,9 +9,27 @@ <li class="nav-item active"> <a class="nav-link" href="{{ url_for('index') }}">{{ _('Home') }}<span class="sr-only">(current)</span></a> </li> + {% if current_user.is_authenticated %} <li class="nav-item"> <a class="nav-link" href="{{ url_for('scopes') }}">{{ _('Scopes') }}</a> </li> + {% endif %} + </ul> + + <ul class="nav navbar-nav navbar-right"> + {% if current_user.is_authenticated %} + + <li class="nav-item"> + <a class="btn btn-danger" href="{{ url_for('logout') }}">{{ _('Logout') }}</a> + </li> + + {% else %} + + <li class="nav-item"> + <a class="btn btn-primary" href="{{ url_for('login') }}">{{ _('Login') }}</a> + </li> + + {% endif %} </ul> </div> </nav> diff --git a/ogcp/views.py b/ogcp/views.py index 7502067..c7a05e7 100644 --- a/ogcp/views.py +++ b/ogcp/views.py @@ -5,6 +5,14 @@ from ogcp.forms.action_forms import ( WOLForm, PartitionForm, ClientDetailsForm, HardwareForm, SessionForm, ImageRestoreForm, ImageCreateForm, SoftwareForm, BootModeForm ) +from flask_login import ( + current_user, LoginManager, + login_user, logout_user, + login_required +) + +from ogcp.models import User +from ogcp.forms.auth import LoginForm from ogcp.og_server import OGServer from flask_babel import _ from ogcp import app @@ -33,6 +41,10 @@ PART_SCHEME_CODES = { 2: 'GPT' } +login_manager = LoginManager() +login_manager.init_app(app) +login_manager.login_view = 'login' + def validate_ips(ips, min_len=1, max_len=float('inf')): valid = True if len(ips) < min_len: @@ -74,6 +86,12 @@ def parse_scopes_from_tree(tree, scope_type): scopes += parse_scopes_from_tree(scope, scope_type) return scopes +@login_manager.user_loader +def load_user(user_id): + if user_id == 1: + return User() + return None + @app.before_request def load_config(): g.server = OGServer() @@ -90,7 +108,31 @@ def server_error(error): def index(): return render_template('base.html') +@app.route('/login', methods=['GET', 'POST']) +def login(): + form = LoginForm(request.form) + if request.method == 'POST' and form.validate(): + user = User() + form_user = request.form['user'] + pwd = request.form['pwd'] + if form_user != app.config['USER']: + flash(_('Incorrect user name')) + return render_template('auth/login.html', form=form) + if pwd != app.config['PASS']: + flash(_('Incorrect password')) + return render_template('auth/login.html', form=form) + login_user(user) + return redirect(url_for('scopes')) + return render_template('auth/login.html', form=LoginForm()) + +@app.route("/logout") +@login_required +def logout(): + logout_user() + return redirect(url_for('index')) + @app.route('/scopes/') +@login_required def scopes(): def add_state_and_ips(scope, clients): if 'ip' in scope: @@ -115,6 +157,7 @@ def scopes(): return render_template('scopes.html', scopes=scopes, clients=clients) @app.route('/action/poweroff', methods=['POST']) +@login_required def action_poweroff(): ips = parse_ips(request.form.to_dict()) payload = {'clients': list(ips)} @@ -122,6 +165,7 @@ def action_poweroff(): return redirect(url_for("scopes")) @app.route('/action/wol', methods=['GET', 'POST']) +@login_required def action_wol(): form = WOLForm(request.form) if request.method == 'POST' and form.validate(): @@ -140,6 +184,7 @@ def action_wol(): return redirect(url_for('scopes')) @app.route('/action/setup', methods=['GET']) +@login_required def action_setup_show(): ips = parse_ips(request.args.to_dict()) db_partitions = get_client_setup(ips) @@ -161,6 +206,7 @@ def action_setup_show(): return render_template('actions/setup.html', forms=forms) @app.route('/action/setup/modify', methods=['POST']) +@login_required def action_setup_modify(): form = PartitionForm(request.form) if form.validate(): @@ -208,6 +254,7 @@ def action_setup_modify(): return make_response("400 Bad Request", 400) @app.route('/action/setup/delete', methods=['POST']) +@login_required def action_setup_delete(): form = PartitionForm(request.form) if form.validate(): @@ -243,6 +290,7 @@ def action_setup_delete(): return make_response("400 Bad Request", 400) @app.route('/action/image/restore', methods=['GET', 'POST']) +@login_required def action_image_restore(): def search_image(images_list, image_id): for image in images_list: @@ -315,6 +363,7 @@ def action_image_restore(): return render_template('actions/image_restore.html', form=form) @app.route('/action/hardware', methods=['GET', 'POST']) +@login_required def action_hardware(): form = HardwareForm(request.form) if request.method == 'POST': @@ -335,6 +384,7 @@ def action_hardware(): hardware=hardware) @app.route('/action/software', methods=['GET', 'POST']) +@login_required def action_software(): form = SoftwareForm(request.form) if request.method == 'POST': @@ -374,6 +424,7 @@ def action_software(): return render_template('actions/software.html', form=form) @app.route('/action/session', methods=['GET', 'POST']) +@login_required def action_session(): form = SessionForm(request.form) if request.method == 'POST': @@ -400,6 +451,7 @@ def action_session(): return render_template('actions/session.html', form=form) @app.route('/action/client/info', methods=['GET']) +@login_required def action_client_info(): form = ClientDetailsForm() ips = parse_ips(request.args.to_dict()) @@ -437,6 +489,7 @@ def action_client_info(): return render_template('actions/client_details.html', form=form) @app.route('/action/client/add', methods=['GET', 'POST']) +@login_required def action_client_add(): form = ClientDetailsForm(request.form) if request.method == 'POST': @@ -472,6 +525,7 @@ def action_client_add(): return render_template('actions/client_details.html', form=form) @app.route('/action/mode', methods=['GET', 'POST']) +@login_required def action_mode(): form = BootModeForm(request.form) if request.method == 'POST': @@ -500,6 +554,7 @@ def action_mode(): @app.route('/action/image/create', methods=['GET', 'POST']) +@login_required def action_image_create(): form = ImageCreateForm(request.form) if request.method == 'POST': @@ -545,6 +600,7 @@ def action_image_create(): return render_template('actions/image_create.html', form=form) @app.route('/action/reboot', methods=['POST']) +@login_required def action_reboot(): ips = parse_ips(request.form.to_dict()) if not validate_ips(ips): @@ -559,6 +615,7 @@ def action_reboot(): return redirect(url_for("scopes")) @app.route('/action/refresh', methods=['POST']) +@login_required def action_refresh(): ips = parse_ips(request.form.to_dict()) if not validate_ips(ips): diff --git a/requirements.txt b/requirements.txt index aba7d15..b22ad33 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ Flask==1.1.2 Flask-Babel==2.0.0 Flask-Bootstrap==3.3.7.1 Flask-WTF==0.14.3 +Flask-Login==0.5.0 idna==2.10 itsdangerous==1.1.0 Jinja2==2.11.2 |