diff --git a/ckanext/security/logic/action.py b/ckanext/security/logic/action.py index 8474f0c..bb541b3 100644 --- a/ckanext/security/logic/action.py +++ b/ckanext/security/logic/action.py @@ -1,7 +1,8 @@ from ckan.plugins.toolkit import ( get_action, chained_action, - check_access, get_or_bust) + check_access, get_or_bust, + config, asbool) from ckanext.security.authenticator import ( get_user_throttle, get_address_throttle, @@ -70,7 +71,8 @@ def user_update(up_func, context, data_dict): """ # (canada fork only): update the user update form schema for username field # TODO: upstream contrib?? - context['schema']['name'].append(validators.old_username_validator) + if asbool(config.get('ckanext.security.use_ivalidators', False)): + context['schema']['name'].append(validators.old_username_validator) rval = up_func(context, data_dict) get_action('security_throttle_user_reset')( dict(context, ignore_auth=True), {'user': rval['name']}) diff --git a/ckanext/security/plugin/__init__.py b/ckanext/security/plugin/__init__.py index bd1bff0..32ae33a 100644 --- a/ckanext/security/plugin/__init__.py +++ b/ckanext/security/plugin/__init__.py @@ -1,7 +1,9 @@ import logging import ckan.plugins as p +from ckanext.security import schema as ext_schema from ckan.plugins import toolkit as tk +from ckan.logic import schema as core_schema from ckanext.security.model import define_security_tables from ckanext.security.resource_upload_validator import ( validate_upload_type, validate_upload_presence @@ -33,8 +35,21 @@ class CkanSecurityPlugin(MixinPlugin, p.SingletonPlugin): def update_config(self, config): define_security_tables() # map security models to db schema - # (canada fork only): remove monkey patching + # (canada fork only): monkey patching behind config # TODO: upstream contrib?? + if not tk.asbool(config.get('ckanext.security.use_ivalidators', False)): + # Monkeypatching all user schemas in order to enforce a stronger + # password policy. I tried monkeypatching + # `ckan.logic.validators.user_password_validator` instead + # without success. + core_schema.default_user_schema = \ + ext_schema.default_user_schema + core_schema.user_new_form_schema = \ + ext_schema.user_new_form_schema + core_schema.user_edit_form_schema = \ + ext_schema.user_edit_form_schema + core_schema.default_update_user_schema = \ + ext_schema.default_update_user_schema tk.add_template_directory(config, '../templates') tk.add_resource('../fanstatic', 'security') @@ -46,6 +61,8 @@ def update_config(self, config): def get_validators(self): # (canada fork only): implement IValidators instead of monkey patching # TODO: upstream contrib?? + if not tk.asbool(tk.config.get('ckanext.security.use_ivalidators', False)): + return return { 'user_password_validator': validators.user_password_validator, } diff --git a/ckanext/security/schema.py b/ckanext/security/schema.py new file mode 100644 index 0000000..2bd88a3 --- /dev/null +++ b/ckanext/security/schema.py @@ -0,0 +1,75 @@ +# encoding: utf-8 + +import six + +from ckan.lib.navl.validators import ignore_missing, not_empty, ignore +from ckan.logic.validators import ( + name_validator, user_name_validator, user_password_not_empty, + user_passwords_match, ignore_not_sysadmin, user_about_validator, + user_both_passwords_entered +) +from ckanext.security import validators + +# The main purpose of this file is to modify CKAN's user-related schemas, and +# to replace the default password validators everywhere. We are also replacing +# the username validators for endpoints where username changes user to be +# allowed. + + +def default_user_schema(): + schema = { + 'id': [ignore_missing, six.text_type], + 'name': [not_empty, name_validator, user_name_validator, + six.text_type], + 'fullname': [ignore_missing, six.text_type], + 'password': [validators.user_password_validator, + user_password_not_empty, + ignore_missing, six.text_type], + 'password_hash': [ignore_missing, ignore_not_sysadmin, + six.text_type], + 'email': [not_empty, six.text_type], + 'about': [ignore_missing, user_about_validator, six.text_type], + 'created': [ignore], + 'openid': [ignore_missing], + 'sysadmin': [ignore_missing, ignore_not_sysadmin], + 'apikey': [ignore], + 'reset_key': [ignore], + 'activity_streams_email_notifications': [ignore_missing], + 'state': [ignore_missing], + } + return schema + + +def user_new_form_schema(): + schema = default_user_schema() + + schema['password1'] = [six.text_type, user_both_passwords_entered, + validators.user_password_validator, + user_passwords_match] + schema['password2'] = [six.text_type] + + return schema + + +def user_edit_form_schema(): + schema = default_user_schema() + + schema['name'] += [validators.old_username_validator] + schema['password'] = [ignore_missing] + schema['password1'] = [ignore_missing, six.text_type, + validators.user_password_validator, + user_passwords_match] + schema['password2'] = [ignore_missing, six.text_type] + + return schema + + +def default_update_user_schema(): + schema = default_user_schema() + + schema['name'] = [ignore_missing, name_validator, user_name_validator, + six.text_type] + schema['password'] = [validators.user_password_validator, + ignore_missing, six.text_type] + + return schema