diff --git a/CHANGELOG.md b/CHANGELOG.md index ff1e048..d826dd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ Changelog for dila ================= -0.3.1 (unreleased) +0.4.0 (unreleased) ------------------ +- Add login page using ldap backend. - Preserve PO metadata. diff --git a/dila-development-compose/docker-compose.yml b/dila-development-compose/docker-compose.yml index 2e83373..c932676 100644 --- a/dila-development-compose/docker-compose.yml +++ b/dila-development-compose/docker-compose.yml @@ -13,7 +13,7 @@ db: POSTGRES_USER: dila POSTGRES_PASSWORD: dila ldap: - image: osixia/openldap:1.1.3 + image: osixia/openldap:1.1.8 volumes: - ../acceptance_tests/test.ldif:/scripts/test.ldif:ro environment: diff --git a/dila/frontend/flask/authenticate_views.py b/dila/frontend/flask/authenticate_views.py index 8e1de7a..3eeefff 100644 --- a/dila/frontend/flask/authenticate_views.py +++ b/dila/frontend/flask/authenticate_views.py @@ -1,15 +1,34 @@ import flask +from cached_property import cached_property from flask import views -from dila.frontend.flask import template_tools +from dila.frontend.flask import forms blueprint = flask.Blueprint('authenticate', __name__) -template_tools.setup_language_context(blueprint) class LoginView(views.MethodView): def get(self): return flask.render_template('login.html', **self.context) + def post(self): + if self.form.validate(): + username = self.form.username.data + flask.session['username'] = username + return flask.redirect(flask.url_for('main.home')) + else: + return self.get() + + + @property + def context(self): + return { + 'form': self.form, + } + + @cached_property + def form(self): + return forms.LoginForm() + blueprint.add_url_rule('/login/', view_func=LoginView.as_view('login')) diff --git a/dila/frontend/flask/forms.py b/dila/frontend/flask/forms.py index 0298ab5..486c543 100644 --- a/dila/frontend/flask/forms.py +++ b/dila/frontend/flask/forms.py @@ -72,3 +72,16 @@ class NewLanguageForm(flask_wtf.FlaskForm): new_language_name = wtforms.StringField('New language') new_language_short = wtforms.StringField('Language code') next = wtforms.HiddenField() + + +class LoginForm(flask_wtf.FlaskForm): + username = wtforms.StringField('Username') + password = wtforms.PasswordField('Password') + + def validate(self, *args, **kwargs): + if super().validate(*args, **kwargs): + if application.authenticate(self.username.data, self.password.data): + return True + else: + self.password.errors.append('Invalid login or password') + return False diff --git a/dila/frontend/flask/template_tools.py b/dila/frontend/flask/template_tools.py index 18f00fe..836aed5 100644 --- a/dila/frontend/flask/template_tools.py +++ b/dila/frontend/flask/template_tools.py @@ -13,6 +13,7 @@ def static_url(filename): return urllib.parse.urljoin(config.STATIC_URL, filename) return flask.url_for('static', filename=filename) + def setup_language_context(blueprint): @blueprint.context_processor def inject_languages_menu(): diff --git a/dila/frontend/flask/templates/login.html b/dila/frontend/flask/templates/login.html index c85555f..98fd21f 100644 --- a/dila/frontend/flask/templates/login.html +++ b/dila/frontend/flask/templates/login.html @@ -12,11 +12,33 @@ -
- Log in. +
+ {{ form.csrf_token }} +
+ {{ form.username.label }} {{ form.username(class_="form-control") }} + {% if form.username.errors %} +
    + {% for error in form.username.errors %} +
  • {{ error }}
  • + {% endfor %} +
+ {% endif %} +
+
+ {{ form.password.label }} {{ form.password(class_="form-control") }} + {% if form.password.errors %} +
    + {% for error in form.password.errors %} +
  • {{ error }}
  • + {% endfor %} +
+ {% endif %} +
+ +
- + \ No newline at end of file diff --git a/setup.py b/setup.py index 2f2b252..3a219fd 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='dila', - version='0.3.1.dev0', + version='0.4.0.dev0', description='Dila is a open source web-based translation platform for translators, content creators and developers.', author='Jakub Skiepko', author_email='it@socialwifi.com', diff --git a/test_image/Dockerfile b/test_image/Dockerfile index 975ddf5..0555bb4 100644 --- a/test_image/Dockerfile +++ b/test_image/Dockerfile @@ -1,4 +1,5 @@ FROM python:3.6 +RUN apt-get update && apt-get install -y libldap2-dev libsasl2-dev && rm -rf /var/lib/apt/lists/* COPY base_requirements.txt /package/ RUN pip install -r /package/base_requirements.txt COPY dila /package/dila diff --git a/test_image/test_config.py b/test_image/test_config.py index b4ae4d0..174e148 100644 --- a/test_image/test_config.py +++ b/test_image/test_config.py @@ -4,3 +4,5 @@ LDAP_SERVER_URI = 'ldap://ldap' LDAP_BIND_DN = 'cn=admin,dc=example,dc=com' LDAP_BIND_PASSWORD = 'admin_password' +LDAP_BASE_DN = 'ou=employees,dc=example,dc=com' +LDAP_USER_OBJECT_FILTER = "(|(uid=%(user)s)(mail=%(user)s))" diff --git a/tests/test_authentication_views.py b/tests/test_authentication_views.py new file mode 100644 index 0000000..347d3c8 --- /dev/null +++ b/tests/test_authentication_views.py @@ -0,0 +1,28 @@ +import re +from unittest import mock + + +def test_login_form(flask_client): + response = flask_client.get('/login/') + assert re.search('', + response.data.decode()) + assert re.search('', + response.data.decode()) + assert re.search('', response.data.decode()) + + +@mock.patch('dila.application.authenticate') +def test_post_login(authenticate, flask_client): + authenticate.return_value = True + response = flask_client.post('/login/', data={'username': 'songo', 'password': 'ssj4'}) + authenticate.assert_called_once_with('songo', 'ssj4') + assert response.status_code == 302 + assert response.location == 'http://localhost/' + + +@mock.patch('dila.application.authenticate') +def test_post_invalid_login(authenticate, flask_client): + authenticate.return_value = False + response = flask_client.post('/login/', data={'username': 'songo', 'password': 'ssj5'}) + authenticate.assert_called_once_with('songo', 'ssj5') + assert "Invalid login or password" in response.data.decode()