Skip to content
Snippets Groups Projects
Commit 5da13a4a authored by Neuwirth, Daniel's avatar Neuwirth, Daniel
Browse files

Merge pull request #32 in TPSH/ckanext-odsh from odpsh-548-password-validation to dev

* commit '2346c35f':
  adds unit tests, bugfix for test_checksum.py
  added validation error
  implements password validation
parents 1cff83aa 2346c35f
No related branches found
No related tags found
No related merge requests found
"use strict";
function isPasswordValid(password) {
if (password.length == 0) return true;
var minimumLength = 8;
var isValid =
(password.length >= minimumLength) &&
(atLeastOneUpperCaseLetter(password)) &&
(atLeastOneLowerCaseLetter(password)) &&
(atLeastOneNoneLetter(password));
return isValid
function atLeastOneUpperCaseLetter(password) {
return (password !== password.toLowerCase())
}
function atLeastOneLowerCaseLetter(password) {
return (password !== password.toUpperCase())
}
function atLeastOneNoneLetter(password) {
return /[\W\d_]/.test(password)
}
}
function showPasswordStatus(isValid, inputElement) {
if (isValid) {
messageText = '';
} else {
messageText = 'Passwörter müssen länger als 8 Zeichen sein und Großbuchstaben, Kleinbuchstaben und andere Zeichen enthalten.'
}
get_error_element(inputElement).innerHTML = messageText;
function get_error_element(inputElement) {
// assumes that there is an element after input_element's parent that
// contains a class "inline-error"
var currentNode = inputElement.parentNode
do {
currentNode = currentNode.nextElementSibling;
} while (
(currentNode !== null) &&
!(currentNode.classList.contains('inline-error'))
)
return currentNode
}
}
function setSubmitButtonState(isPasswordValid) {
var submitButton = document.getElementsByName('save')[0];
submitButton.disabled = !isPasswordValid;
}
ckan.module('tpsh_validate_password', function ($) {
return {
initialize: function () {
$.proxyAll(this, /_on/);
this.el.on('input', this._onChange);
},
_onChange: function(event) {
var inputElement = event.target;
var newPassword = inputElement.value;
var isValid = isPasswordValid(newPassword);
showPasswordStatus(isValid, inputElement);
setSubmitButtonState(isValid);
}
};
});
\ No newline at end of file
import logging import logging
import ckan.logic as logic
from ckan.logic.action.update import user_update
from ckan.logic.action.create import package_create, user_create, group_member_create from ckan.logic.action.create import package_create, user_create, group_member_create
import ckan.model as model import ckan.model as model
import ckan.lib.dictization.model_dictize as model_dictize import ckan.lib.dictization.model_dictize as model_dictize
...@@ -44,13 +46,33 @@ def munge_increment_name(data_dict): ...@@ -44,13 +46,33 @@ def munge_increment_name(data_dict):
data_dict['name'] = name data_dict['name'] = name
def check_password(password):
return (len(password) >= 8 and
any(c.islower() for c in password) and
any(c.isupper() for c in password) and
any((c.isalpha()==False) for c in password)) #Number or Special character
def odsh_user_create(context, data_dict): def odsh_user_create(context, data_dict):
model = context['model'] model = context['model']
password = data_dict.get('password')
if check_password(password):
user = user_create(context, data_dict) user = user_create(context, data_dict)
groups = toolkit.get_action('group_list')(data_dict={'all_fields': False}) groups = toolkit.get_action('group_list')(data_dict={'all_fields': False})
for group in groups: for group in groups:
group_member_create(context, {'id': group, 'username': user.get('name'), 'role': 'member'}) group_member_create(context, {'id': group, 'username': user.get('name'), 'role': 'member'})
return model_dictize.user_dictize(model.User.get(user.get('name')), context) return model_dictize.user_dictize(model.User.get(user.get('name')), context)
else:
raise logic.ValidationError({'security': ['Passwort muss mindestens acht Zeichen, einen gross, einen klein Buchstaben und entweder eine Zahl oder Sondernzeichen enthalten!']})
def tpsh_user_update(context, data_dict):
password = data_dict.get('password')
if password and not check_password(password):
raise logic.ValidationError({'security': ['Passwort muss mindestens acht Zeichen, einen gross, einen klein Buchstaben und entweder eine Zahl oder Sondernzeichen enthalten!']})
return user_update(context, data_dict)
@toolkit.side_effect_free @toolkit.side_effect_free
......
...@@ -50,6 +50,7 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm ...@@ -50,6 +50,7 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
def get_actions(self): def get_actions(self):
return {'package_create': action.odsh_package_create, return {'package_create': action.odsh_package_create,
'user_update':action.tpsh_user_update,
'user_create': action.odsh_user_create} 'user_create': action.odsh_user_create}
......
{% import 'macros/form.html' as form %}
{% resource 'odsh/tpsh_validate_password.js' %}
<form id="user-edit-form" class="dataset-form form-horizontal" method="post" action="{{ action }}">
{{ form.errors(error_summary) }}
<fieldset>
<legend>{{ _('Change details') }}</legend>
{{ form.input('name', label=_('Username'), id='field-username', value=data.name, error=errors.name, classes=['control-medium'], is_required=true) }}
{{ form.input('fullname', label=_('Full name'), id='field-fullname', value=data.fullname, error=errors.fullname, placeholder=_('eg. Joe Bloggs'), classes=['control-medium']) }}
{{ form.input('email', label=_('Email'), id='field-email', type='email', value=data.email, error=errors.email, placeholder=_('eg. joe@example.com'), classes=['control-medium'], is_required=true) }}
{{ form.markdown('about', label=_('About'), id='field-about', value=data.about, error=errors.about, placeholder=_('A little information about yourself')) }}
{% if c.show_email_notifications %}
{% call form.checkbox('activity_streams_email_notifications', label=_('Subscribe to notification emails'), id='field-activity-streams-email-notifications', value=True, checked=c.userobj.activity_streams_email_notifications) %}
{% set helper_text = _("You will receive notification emails from {site_title}, e.g. when you have new activities on your dashboard."|string) %}
{{ form.info(helper_text.format(site_title=g.site_title), classes=['info-help-tight']) }}
{% endcall %}
{% endif %}
</fieldset>
<fieldset>
<legend>{{ _('Change password') }}</legend>
{{ form.input('old_password',
type='password',
label=_('Sysadmin Password') if c.is_sysadmin else _('Old Password'),
id='field-password',
value=data.oldpassword,
error=errors.oldpassword,
classes=['control-medium'],
attrs={'autocomplete': 'off'}
) }}
{{ form.input(
'password1', type='password', label=_('Password'), id='field-password', value=data.password1, error=errors.password1, classes=['control-medium'], attrs={'autocomplete': 'off', 'data-module': 'tpsh_validate_password'} ) }}
{{ form.input('password2', type='password', label=_('Confirm Password'), id='field-password-confirm', value=data.password2, error=errors.password2, classes=['control-medium'], attrs={'autocomplete': 'off'}) }}
</fieldset>
<div class="form-actions">
{% block delete_button %}
{% if h.check_access('user_delete', {'id': data.id}) %}
<a class="btn btn-danger pull-left" href="{% url_for controller='user', action='delete', id=data.id %}" data-module="confirm-action" data-module-content="{{ _('Are you sure you want to delete this User?') }}">{% block delete_button_text %}{{ _('Delete') }}{% endblock %}</a>
{% endif %}
{% endblock %}
{% block generate_button %}
{% if h.check_access('user_generate_apikey', {'id': data.id}) %}
<a class="btn btn-warning" href="{% url_for controller='user', action='generate_apikey', id=data.id %}" data-module="confirm-action" data-module-content="{{ _('Are you sure you want to regenerate the API key?') }}">{% block generate_button_text %}{{ _('Regenerate API Key') }}{% endblock %}</a>
{% endif %}
{% endblock %}
{{ form.required_message() }}
<button class="btn btn-primary" type="submit" name="save">{{ _('Update Profile') }}</button>
</div>
</form>
\ No newline at end of file
from mock import patch, mock_open from mock import patch, mock_open
import nose.tools as nt import nose.tools as nt
from ckanext.odsh.lib.uploader import _raise_validation_error_if_hash_values_differ, _calculate_hash from ckanext.odsh.lib.uploader import _raise_validation_error_if_hash_values_differ, calculate_hash
import ckantoolkit as ct import ckantoolkit as ct
import ckan.logic as logic import ckan.logic as logic
import hashlib import hashlib
...@@ -51,7 +51,7 @@ class testHashException(object): ...@@ -51,7 +51,7 @@ class testHashException(object):
# md5sum test.pdf # md5sum test.pdf
expected_hash_pdf = '66123edf64fabf1c073fc45478bf4a57' expected_hash_pdf = '66123edf64fabf1c073fc45478bf4a57'
with open(dir_path + '/resources/test.pdf') as f: with open(dir_path + '/resources/test.pdf') as f:
hash = _calculate_hash(f) hash = calculate_hash(f)
nt.assert_equal(hash, expected_hash_pdf) nt.assert_equal(hash, expected_hash_pdf)
......
# encoding: utf-8
import nose.tools as nt
from ckanext.odsh.logic.action import check_password
class Test_PasswordValidation(object):
@staticmethod
def assert_password_invalid(password):
assert not check_password(password)
@staticmethod
def assert_password_valid(password):
assert check_password(password)
def test_valid_password(self):
self.assert_password_valid('Passwort1 :) :P :D')
def test_umlaute(self):
self.assert_password_valid('Pässword')
def test_no_uppercase(self):
self.assert_password_invalid('passwort1')
def test_no_lowercase(self):
self.assert_password_invalid('PASSWORT1')
def test_no_letters(self):
self.assert_password_invalid('37459073245!!?===))/=$§äüöÄÜÖ')
def test_only_letters(self):
self.assert_password_invalid('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
def test_to_short(self):
self.assert_password_invalid('Pw123')
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment