summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJose M. Guisado <jguisado@soleta.eu>2021-03-05 11:06:11 +0100
committerJose M. Guisado <jguisado@soleta.eu>2021-03-05 11:47:27 +0100
commit9ee0565ac41661a0521630fbfe1ea9e896fcec52 (patch)
tree12dd36a028ed7f9795855c599b2a05b76e058c01
parent149dfdcbfd31bae79e9872e6f465d127d70ad32b (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.json2
-rw-r--r--ogcp/forms/auth.py20
-rw-r--r--ogcp/models.py5
-rw-r--r--ogcp/templates/auth/login.html12
-rw-r--r--ogcp/templates/base.html2
-rw-r--r--ogcp/templates/nav.html18
-rw-r--r--ogcp/views.py57
-rw-r--r--requirements.txt1
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