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
multi-ogserver
Jose M. Guisado 2021-03-05 11:06:11 +01:00
parent 149dfdcbfd
commit 9ee0565ac4
8 changed files with 117 additions and 0 deletions

View File

@ -2,4 +2,6 @@
"IP": "127.0.0.1",
"PORT": 8888,
"API_TOKEN": "c3fe7bb0395747ec42a25df027585871"
"USER": "user",
"PASS": "pass"
}

20
ogcp/forms/auth.py 100644
View File

@ -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')
)

5
ogcp/models.py 100644
View File

@ -0,0 +1,5 @@
from flask_login import UserMixin
class User(UserMixin):
def get_id(self):
return 1

View File

@ -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 %}

View File

@ -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') }}">

View File

@ -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>

View File

@ -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):

View File

@ -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