Skip to content
Snippets Groups Projects
Commit 573ce5f7 authored by Jan Mach's avatar Jan Mach
Browse files

Merged blueprints auth_dev

(Redmine issue: #7544)
parent 31c11af0
No related branches found
No related tags found
No related merge requests found
......@@ -43,19 +43,75 @@ __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea
import flask
from flask_babel import lazy_gettext
import hawat.const
import vial.forms
import vial.db
import vial.blueprints.auth_dev
from vial.blueprints.auth_dev import BLUEPRINT_NAME, LoginView, DevAuthBlueprint
from hawat.blueprints.auth_dev.forms import RegisterUserAccountForm
import hawat.forms
import hawat.db
from hawat.app import HawatBlueprint
from hawat.view import BaseLoginView, BaseRegisterView
from hawat.view.mixin import HTMLMixin, SQLAlchemyMixin
from hawat.blueprints.auth_dev.forms import LoginForm, RegisterUserAccountForm
class RegisterView(vial.blueprints.auth_dev.RegisterView):
BLUEPRINT_NAME = 'auth_dev'
"""Name of the blueprint as module global constant."""
class LoginView(HTMLMixin, SQLAlchemyMixin, BaseLoginView):
"""
View enabling special developer login.
"""
methods = ['GET', 'POST']
@classmethod
def get_view_title(cls, **kwargs):
return lazy_gettext('Developer login')
@classmethod
def get_menu_title(cls, **kwargs):
return lazy_gettext('Login (dev)')
@property
def dbmodel(self):
return self.get_model(hawat.const.MODEL_USER)
@property
def search_by(self):
return self.dbmodel.login
def get_user_login(self):
form = LoginForm()
self.response_context.update(
form = form
)
if form.validate_on_submit():
return form.login.data
return None
class RegisterView(HTMLMixin, SQLAlchemyMixin, BaseRegisterView):
"""
View responsible for registering new user account into application.
"""
methods = ['GET', 'POST']
@classmethod
def get_menu_title(cls, **kwargs):
return lazy_gettext('Register (dev)')
@classmethod
def get_view_title(cls, **kwargs):
return lazy_gettext('User account registration (dev)')
@property
def dbmodel(self):
return self.get_model(hawat.const.MODEL_USER)
@property
def dbchlogmodel(self):
return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
@staticmethod
def get_item_form(item):
......@@ -78,10 +134,21 @@ class RegisterView(vial.blueprints.auth_dev.RegisterView):
#-------------------------------------------------------------------------------
class DevAuthBlueprint(HawatBlueprint):
"""Pluggable module - developer authentication service (*auth_dev*)."""
@classmethod
def get_module_title(cls):
return lazy_gettext('Developer authentication service')
#-------------------------------------------------------------------------------
def get_blueprint():
"""
Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
must return a valid instance of :py:class:`vial.app.VialBlueprint` or
Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
:py:class:`flask.Blueprint`.
"""
......
......@@ -19,13 +19,54 @@ __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea
import wtforms
from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField
import flask
import flask_wtf
from flask_babel import lazy_gettext
from vial.forms import check_login, check_unique_login, get_available_groups
import hawat.forms
import hawat.db
from hawat.forms import check_login, check_unique_login, get_available_groups
from hawat.blueprints.users.forms import BaseUserAccountForm
class LoginForm(flask_wtf.FlaskForm):
"""
Class representing developer authentication login form. This form provides
list of all currently existing user accounts in simple selectbox, so that
the developer can quickly login as different user.
"""
login = wtforms.SelectField(
lazy_gettext('User account:'),
validators = [
wtforms.validators.DataRequired(),
check_login
]
)
submit = wtforms.SubmitField(
lazy_gettext('Login')
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_choices()
def set_choices(self):
"""
Load list of all user accounts and populate the ``choices`` attribute of
the ``login`` selectbox.
"""
dbsess = hawat.db.db_get().session
user_model = flask.current_app.get_model(hawat.const.MODEL_USER)
users = dbsess.query(user_model).order_by(user_model.login).all()
choices = []
for usr in users:
choices.append((usr.login, "{} ({}, #{})".format(usr.fullname, usr.login, usr.id)))
choices = sorted(choices, key=lambda x: x[1])
self.login.choices = choices
class RegisterUserAccountForm(BaseUserAccountForm):
"""
Class representing user account registration form.
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Use of this source is governed by the MIT license, see LICENSE file.
#-------------------------------------------------------------------------------
"""
This Hawat pluggable module provides special authentication method, that is
particularly usable for developers and enables them to impersonate any user.
After enabling this module special authentication endpoint will be available
and will provide simple authentication form with list of all currently available
user accounts. It will be possible for that user to log in as any other user
without entering password.
This module is disabled by default in *production* environment and enabled by
default in *development* environment.
.. warning::
This module must never ever be enabled on production systems, because it is
a huge security risk and enables possible access control management violation.
You have been warned!
Provided endpoints
--------------------------------------------------------------------------------
``/auth_dev/login``
Page providing special developer login form.
* *Authentication:* no authentication
* *Methods:* ``GET``, ``POST``
"""
import flask
from flask_babel import lazy_gettext
import hawat.const
import vial.forms
import vial.db
from vial.app import VialBlueprint
from vial.view import BaseLoginView, BaseRegisterView
from vial.view.mixin import HTMLMixin, SQLAlchemyMixin
from vial.blueprints.auth_dev.forms import LoginForm, RegisterUserAccountForm
BLUEPRINT_NAME = 'auth_dev'
"""Name of the blueprint as module global constant."""
class LoginView(HTMLMixin, SQLAlchemyMixin, BaseLoginView):
"""
View enabling special developer login.
"""
methods = ['GET', 'POST']
@classmethod
def get_view_title(cls, **kwargs):
return lazy_gettext('Developer login')
@classmethod
def get_menu_title(cls, **kwargs):
return lazy_gettext('Login (dev)')
@property
def dbmodel(self):
return self.get_model(hawat.const.MODEL_USER)
@property
def search_by(self):
return self.dbmodel.login
def get_user_login(self):
form = LoginForm()
self.response_context.update(
form = form
)
if form.validate_on_submit():
return form.login.data
return None
class RegisterView(HTMLMixin, SQLAlchemyMixin, BaseRegisterView):
"""
View responsible for registering new user account into application.
"""
methods = ['GET', 'POST']
@classmethod
def get_menu_title(cls, **kwargs):
return lazy_gettext('Register (dev)')
@classmethod
def get_view_title(cls, **kwargs):
return lazy_gettext('User account registration (dev)')
@property
def dbmodel(self):
return self.get_model(hawat.const.MODEL_USER)
@property
def dbchlogmodel(self):
return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
@staticmethod
def get_item_form(item):
roles = list(
zip(
flask.current_app.config['ROLES'],
flask.current_app.config['ROLES']
)
)
locales = list(
flask.current_app.config['SUPPORTED_LOCALES'].items()
)
return RegisterUserAccountForm(
choices_roles = roles,
choices_locales = locales
)
#-------------------------------------------------------------------------------
class DevAuthBlueprint(VialBlueprint):
"""Pluggable module - developer authentication service (*auth_dev*)."""
@classmethod
def get_module_title(cls):
return lazy_gettext('Developer authentication service')
#-------------------------------------------------------------------------------
def get_blueprint():
"""
Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
must return a valid instance of :py:class:`vial.app.VialBlueprint` or
:py:class:`flask.Blueprint`.
"""
hbp = DevAuthBlueprint(
BLUEPRINT_NAME,
__name__,
template_folder = 'templates',
url_prefix = '/{}'.format(BLUEPRINT_NAME)
)
hbp.register_view_class(LoginView, '/login')
hbp.register_view_class(RegisterView, '/register')
return hbp
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Use of this source is governed by the MIT license, see LICENSE file.
#-------------------------------------------------------------------------------
"""
This module contains custom developer login form for Hawat.
"""
import wtforms
from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField
import flask
import flask_wtf
from flask_babel import lazy_gettext
import vial.forms
import vial.db
from vial.forms import check_login, check_unique_login, get_available_groups
from vial.blueprints.users.forms import BaseUserAccountForm
class LoginForm(flask_wtf.FlaskForm):
"""
Class representing developer authentication login form. This form provides
list of all currently existing user accounts in simple selectbox, so that
the developer can quickly login as different user.
"""
login = wtforms.SelectField(
lazy_gettext('User account:'),
validators = [
wtforms.validators.DataRequired(),
check_login
]
)
submit = wtforms.SubmitField(
lazy_gettext('Login')
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_choices()
def set_choices(self):
"""
Load list of all user accounts and populate the ``choices`` attribute of
the ``login`` selectbox.
"""
dbsess = vial.db.db_get().session
user_model = flask.current_app.get_model(hawat.const.MODEL_USER)
users = dbsess.query(user_model).order_by(user_model.login).all()
choices = []
for usr in users:
choices.append((usr.login, "{} ({}, #{})".format(usr.fullname, usr.login, usr.id)))
choices = sorted(choices, key=lambda x: x[1])
self.login.choices = choices
class RegisterUserAccountForm(BaseUserAccountForm):
"""
Class representing user account registration form.
"""
login = wtforms.StringField(
lazy_gettext('Login:'),
validators = [
wtforms.validators.DataRequired(),
wtforms.validators.Length(min = 3, max = 50),
check_login,
check_unique_login
]
)
memberships_wanted = QuerySelectMultipleField(
lazy_gettext('Requested group memberships:'),
query_factory = get_available_groups
)
justification = wtforms.TextAreaField(
lazy_gettext('Justification:'),
validators = [
wtforms.validators.DataRequired(),
wtforms.validators.Length(min = 10, max = 500)
]
)
{%- extends "_layout_login.html" %}
{%- block loginformfields %}
{{ macros_form.render_form_item_select(form.login) }}
{%- endblock loginformfields %}
{%- extends "_layout_registration.html" %}
{%- block registrationformfields %}
{{ macros_form.render_form_item_default(form.login) }}
{{ macros_form.render_form_item_default(form.fullname) }}
{{ macros_form.render_form_item_default(form.email) }}
{{ macros_form.render_form_item_default(form.justification) }}
<hr>
{{ macros_form.render_form_item_select(form.memberships_wanted) }}
<hr>
{{ macros_form.render_form_item_select(form.locale) }}
{{ macros_form.render_form_item_select(form.timezone) }}
<hr>
{%- endblock registrationformfields %}
{{ _('Dear administrator,') | wordwrap }}
{{ _('a new account "%(item_id)s" was just registered in %(app_name)s. Please review the following request and activate or delete the account:', item_id = account.login, app_name = vial_appname) | wordwrap }}
{{ '{:16s}'.format(_('Login:')) }} {{ account.login }}
{{ '{:16s}'.format(_('Full name:')) }} {{ account.fullname }}
{{ '{:16s}'.format(_('Email:')) }} {{ account.email }}
{%- if account.memberships_wanted %}
{{ _('User has requested membership in following groups:') | wordwrap }}
{%- for group in account.memberships_wanted %}
* {{ group.name }}
{%- endfor %}
{%- endif %}
{{ _('User has provided following justification to be given access to the system:') | wordwrap }}
{{ justification | wordwrap(width=75, break_long_words=False) | indent(width=4, first=True) }}
{{ _('Account details can be found here:') | wordwrap }}
{{ url_for('users.show', _external = True, item_id = account.id ) }}
{{ _('Have a nice day') | wordwrap }}
-- {{ vial_appname }}
{{ _('Dear group manager,') | wordwrap }}
{{ _('a new account "%(item_id)s" was just registered in %(app_name)s and user requested membership in your group "%(group_id)s". Please review the following information and approve or reject the request:', item_id = account.login, app_name = vial_appname, group_id = group.name) | wordwrap }}
{{ '{:16s}'.format(_('Login:')) }} {{ account.login }}
{{ '{:16s}'.format(_('Full name:')) }} {{ account.fullname }}
{{ '{:16s}'.format(_('Email:')) }} {{ account.email }}
{{ _('User has provided following justification to be given access to the system:') | wordwrap }}
{{ justification | wordwrap(width=75, break_long_words=False) | indent(width=4, first=True) }}
{{ _('Management page for your group can be found here:') | wordwrap }}
{{ url_for('groups.show', _external = True, item_id = group.id ) }}
{{ _('Have a nice day') | wordwrap }}
-- {{ vial_appname }}
{{ _('Dear user,') | wordwrap }}
{{ _('this email is a confirmation, that you have successfully registered your new user account "%(item_id)s" in %(app_name)s.', item_id = account.login, app_name = vial_appname) | wordwrap }}
{{ _('During the registration process you have provided following information:') | wordwrap }}
{{ '{:16s}'.format(_('Login:')) }} {{ account.login }}
{{ '{:16s}'.format(_('Full name:')) }} {{ account.fullname }}
{{ '{:16s}'.format(_('Email:')) }} {{ account.email }}
{%- if account.memberships_wanted %}
{{ _('You have requested membership in following groups:') | wordwrap }}
{%- for group in account.memberships_wanted %}
* {{ group.name }}
{%- endfor %}
{%- endif %}
{{ _('You have provided following justification to be given access to the system:') | wordwrap }}
{{ justification | wordwrap(width=75, break_long_words=False) | indent(width=4, first=True) }}
{{ _('Administrator was informed about registration of a new account. You will receive email confirmation when your account will be activated.') | wordwrap }}
{{ _('After successfull activation you will be able to login and start using the system:') | wordwrap }}
{{ url_for('auth.login', _external = True ) }}
{{ _('Have a nice day') | wordwrap }}
-- {{ vial_appname }}
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Use of this source is governed by the MIT license, see LICENSE file.
#-------------------------------------------------------------------------------
"""
Unit tests for :py:mod:`vial.blueprints.auth_dev`.
"""
import sys
import unittest
import hawat.const
import vial.test
import vial.db
from vial.test import RegistrationVialTestCase
from vial.test.runner import TestRunnerMixin
_IS_NOSE = sys.argv[0].endswith('nosetests')
@unittest.skipIf(_IS_NOSE, "broken under nosetest")
class AuthDevTestCase(TestRunnerMixin, RegistrationVialTestCase):
"""
Class for testing :py:mod:`vial.blueprints.auth_dev` blueprint.
"""
def test_01_login_user(self):
"""
Test login/logout with *auth_dev* module - user 'user'.
"""
response = self.login_dev(hawat.const.ROLE_USER)
self.assertEqual(response.status_code, 200)
self.assertTrue(b'You have been successfully logged in as' in response.data)
response = self.logout()
self.assertEqual(response.status_code, 200)
self.assertTrue(b'You have been successfully logged out' in response.data)
def test_02_login_developer(self):
"""
Test login/logout with *auth_dev* module - user 'developer'.
"""
response = self.login_dev(hawat.const.ROLE_DEVELOPER)
self.assertEqual(response.status_code, 200)
self.assertTrue(b'You have been successfully logged in as' in response.data)
response = self.logout()
self.assertEqual(response.status_code, 200)
self.assertTrue(b'You have been successfully logged out' in response.data)
def test_03_login_admin(self):
"""
Test login/logout with *auth_dev* module - user 'admin'.
"""
response = self.login_dev(hawat.const.ROLE_ADMIN)
self.assertEqual(response.status_code, 200)
self.assertTrue(b'You have been successfully logged in as' in response.data)
response = self.logout()
self.assertEqual(response.status_code, 200)
self.assertTrue(b'You have been successfully logged out' in response.data)
def test_04_register(self):
"""
Test registration with *auth_dev* module - new user 'test'.
"""
self.assertRegister(
'/auth_dev/register',
[
('submit', 'Register'),
('login', 'test'),
('fullname', 'Test User'),
('email', 'test.user@domain.org'),
('justification', 'I really want in.')
],
{
'txt': [
'Dear administrator,\n'
'\n'
'a new account "test" was just registered in Vial. Please review the '
'following\n'
'request and activate or delete the account:\n'
'\n'
' Login: test\n'
' Full name: Test User\n'
' Email: test.user@domain.org\n'
'\n'
'User has provided following justification to be given access to the system:\n'
'\n'
' I really want in.\n'
'\n'
'Account details can be found here:\n'
'\n'
' http://localhost/users/5/show\n'
'\n'
'Have a nice day\n'
'\n'
'-- Vial',
'Dear user,\n'
'\n'
'this email is a confirmation, that you have successfully registered your '
'new\n'
'user account "test" in Vial.\n'
'\n'
'During the registration process you have provided following information:\n'
'\n'
' Login: test\n'
' Full name: Test User\n'
' Email: test.user@domain.org\n'
'\n'
'You have provided following justification to be given access to the system:\n'
'\n'
' I really want in.\n'
'\n'
'Administrator was informed about registration of a new account. You will\n'
'receive email confirmation when your account will be activated.\n'
'\n'
'After successfull activation you will be able to login and start using the\n'
'system:\n'
'\n'
'\thttp://localhost/auth/login\n'
'\n'
'Have a nice day\n'
'\n'
'-- Vial'
],
'html': [
None,
None
]
}
)
def test_05_register_fail(self):
"""
Test registration with *auth_dev* module - existing user 'user'.
"""
self.assertRegisterFail(
'/auth_dev/register',
[
('submit', 'Register'),
('login', 'user'),
('fullname', 'Demo User'),
('email', 'demo.user@domain.org'),
('organization', 'TEST, org.'),
('justification', 'I really want in.')
]
)
#-------------------------------------------------------------------------------
if __name__ == "__main__":
unittest.main()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment