diff --git a/iam/api/models.py b/iam/api/models.py index 6846b39f..8ddea4e9 100644 --- a/iam/api/models.py +++ b/iam/api/models.py @@ -180,7 +180,7 @@ 'check': "iterate-list", 'check-list': [ { - 'check': 'dict-keys-exact', + 'check': 'dict-keys-exist', 'keys': ['event_id', 'title'] }, { @@ -210,6 +210,25 @@ 'range': [1, 254] } ] + }, + { + 'check': 'index-check-list', + 'index': 'title_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.values()]) + }, + ] } ] } diff --git a/iam/authmethods/m_dnie.py b/iam/authmethods/m_dnie.py index 5a8d679b..c2f6f095 100644 --- a/iam/authmethods/m_dnie.py +++ b/iam/authmethods/m_dnie.py @@ -14,17 +14,26 @@ # along with iam. If not, see . import json +import logging from . import register_method from django.shortcuts import get_object_or_404, redirect from django.conf.urls import url from django.http import Http404 -from authmethods.utils import check_pipeline, give_perms +from authmethods.utils import ( + check_pipeline, + give_perms, + stack_trace_str +) from api.models import AuthEvent from utils import json_response +from contracts.base import check_contract, JsonTypeEncoder +from contracts import CheckException + +LOGGER = logging.getLogger('iam') def testview(request): req = request.GET @@ -112,6 +121,70 @@ class DNIE: ) dni_definition = { "name": "dni", "type": "text", "required": True, "min": 2, "max": 200, "required_on_authentication": True } + CONFIG_CONTRACT = [ + { + 'check': 'isinstance', + 'type': dict + }, + { + 'check': 'index-check-list', + 'index': 'msg_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 200 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'subject_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 1024 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'html_message_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 5000 for k in d.values()]) + }, + ] + } + ] + def error( self, msg, auth_event=None, error_codename=None, internal_error=None ): @@ -135,7 +208,26 @@ def authenticate(self, ae, request): return d def check_config(self, config): - return '' + """ Check config when create auth-event. """ + if config is None: + return '' + try: + check_contract(self.CONFIG_CONTRACT, config) + LOGGER.debug(\ + "Dnie.check_config success\n"\ + "config '%r'\n"\ + "returns ''\n"\ + "Stack trace: \n%s",\ + config, stack_trace_str()) + return '' + except CheckException as e: + LOGGER.error(\ + "Dnie.check_config error\n"\ + "error '%r'\n"\ + "config '%r'\n"\ + "Stack trace: \n%s",\ + e.data, config, stack_trace_str()) + return json.dumps(e.data, cls=JsonTypeEncoder) def census(self, ae, request): req = json.loads(request.body.decode('utf-8')) diff --git a/iam/authmethods/m_email.py b/iam/authmethods/m_email.py index ac389901..f1df7ec1 100644 --- a/iam/authmethods/m_email.py +++ b/iam/authmethods/m_email.py @@ -129,6 +129,63 @@ class Email: 'check': 'dict-keys-exist', 'keys': ['msg', 'subject', 'registration-action', 'authentication-action'] }, + { + 'check': 'index-check-list', + 'index': 'msg_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 200 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'subject_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 1024 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'html_message_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 5000 for k in d.values()]) + }, + ] + }, { 'check': 'index-check-list', 'index': 'msg', diff --git a/iam/authmethods/m_email_otp.py b/iam/authmethods/m_email_otp.py index 8c3e4770..bd17348d 100644 --- a/iam/authmethods/m_email_otp.py +++ b/iam/authmethods/m_email_otp.py @@ -133,6 +133,44 @@ class EmailOtp: 'check': 'dict-keys-exist', 'keys': ['msg', 'subject', 'registration-action', 'authentication-action'] }, + { + 'check': 'index-check-list', + 'index': 'msg_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 200 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'subject_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 1024 for k in d.values()]) + }, + ] + }, { 'check': 'index-check-list', 'index': 'msg', @@ -162,6 +200,25 @@ class EmailOtp: } ] }, + { + 'check': 'index-check-list', + 'index': 'html_message_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 5000 for k in d.values()]) + }, + ] + }, { 'check': 'index-check-list', 'index': 'subject', diff --git a/iam/authmethods/m_emailpwd.py b/iam/authmethods/m_emailpwd.py index c658acdc..8eeabc27 100644 --- a/iam/authmethods/m_emailpwd.py +++ b/iam/authmethods/m_emailpwd.py @@ -44,6 +44,9 @@ authenticate_otl ) +from contracts.base import check_contract, JsonTypeEncoder +from contracts import CheckException + LOGGER = logging.getLogger('iam') @@ -85,8 +88,91 @@ class EmailPassword: "required_on_authentication": True } + CONFIG_CONTRACT = [ + { + 'check': 'isinstance', + 'type': dict + }, + { + 'check': 'index-check-list', + 'index': 'msg_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 200 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'subject_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 1024 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'html_message_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 5000 for k in d.values()]) + }, + ] + } + ] + def check_config(self, config): - return '' + """ Check config when create auth-event. """ + if config is None: + return '' + try: + check_contract(self.CONFIG_CONTRACT, config) + LOGGER.debug(\ + "EmailPwd.check_config success\n"\ + "config '%r'\n"\ + "returns ''\n"\ + "Stack trace: \n%s",\ + config, stack_trace_str()) + return '' + except CheckException as e: + LOGGER.error(\ + "EmailPwd.check_config error\n"\ + "error '%r'\n"\ + "config '%r'\n"\ + "Stack trace: \n%s",\ + e.data, config, stack_trace_str()) + return json.dumps(e.data, cls=JsonTypeEncoder) def census(self, auth_event, request): req = json.loads(request.body.decode('utf-8')) diff --git a/iam/authmethods/m_openidconnect.py b/iam/authmethods/m_openidconnect.py index 0206c57d..0d7ef2f0 100644 --- a/iam/authmethods/m_openidconnect.py +++ b/iam/authmethods/m_openidconnect.py @@ -50,6 +50,9 @@ from oic.utils.authn.client import CLIENT_AUTHN_METHOD from oic.utils.time_util import utc_time_sans_frac +from contracts.base import check_contract, JsonTypeEncoder +from contracts import CheckException + LOGGER = logging.getLogger('iam') @@ -90,6 +93,70 @@ class OpenIdConnect(object): "required_on_authentication": True } + CONFIG_CONTRACT = [ + { + 'check': 'isinstance', + 'type': dict + }, + { + 'check': 'index-check-list', + 'index': 'msg_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 200 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'subject_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 1024 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'html_message_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 5000 for k in d.values()]) + }, + ] + } + ] + PROVIDERS = dict() def __init__(self): @@ -122,7 +189,26 @@ def __init__(self): ) def check_config(self, config): - return '' + """ Check config when create auth-event. """ + if config is None: + return '' + try: + check_contract(self.CONFIG_CONTRACT, config) + LOGGER.debug(\ + "OpenId.check_config success\n"\ + "config '%r'\n"\ + "returns ''\n"\ + "Stack trace: \n%s",\ + config, stack_trace_str()) + return '' + except CheckException as e: + LOGGER.error(\ + "OpenId.check_config error\n"\ + "error '%r'\n"\ + "config '%r'\n"\ + "Stack trace: \n%s",\ + e.data, config, stack_trace_str()) + return json.dumps(e.data, cls=JsonTypeEncoder) def census(self, ae, request): return {'status': 'ok'} diff --git a/iam/authmethods/m_pwd.py b/iam/authmethods/m_pwd.py index 9fc64bf6..4abd04c6 100644 --- a/iam/authmethods/m_pwd.py +++ b/iam/authmethods/m_pwd.py @@ -45,6 +45,9 @@ authenticate_otl ) +from contracts.base import check_contract, JsonTypeEncoder +from contracts import CheckException + LOGGER = logging.getLogger('iam') @@ -86,8 +89,92 @@ class Password: "required_on_authentication": True } + CONFIG_CONTRACT = [ + { + 'check': 'isinstance', + 'type': dict + }, + { + 'check': 'index-check-list', + 'index': 'msg_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 200 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'subject_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 1024 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'html_message_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 5000 for k in d.values()]) + }, + ] + } + ] + def check_config(self, config): - return '' + """ Check config when create auth-event. """ + if config is None: + return '' + try: + check_contract(self.CONFIG_CONTRACT, config) + LOGGER.debug(\ + "OpenId.check_config success\n"\ + "config '%r'\n"\ + "returns ''\n"\ + "Stack trace: \n%s",\ + config, stack_trace_str()) + return '' + except CheckException as e: + LOGGER.error(\ + "OpenId.check_config error\n"\ + "error '%r'\n"\ + "config '%r'\n"\ + "Stack trace: \n%s",\ + e.data, config, stack_trace_str()) + return json.dumps(e.data, cls=JsonTypeEncoder) + def census(self, auth_event, request): req = json.loads(request.body.decode('utf-8')) diff --git a/iam/authmethods/m_smart_link.py b/iam/authmethods/m_smart_link.py index 66abc054..45870dff 100644 --- a/iam/authmethods/m_smart_link.py +++ b/iam/authmethods/m_smart_link.py @@ -90,6 +90,63 @@ class SmartLink: 'check': 'isinstance', 'type': dict }, + { + 'check': 'index-check-list', + 'index': 'msg_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 200 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'subject_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 1024 for k in d.values()]) + }, + ] + }, + { + 'check': 'index-check-list', + 'index': 'html_message_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 5000 for k in d.values()]) + }, + ] + }, { 'check': 'lambda', 'lambda': lambda data: ( diff --git a/iam/authmethods/m_sms.py b/iam/authmethods/m_sms.py index ce86dd52..2fc56ed7 100644 --- a/iam/authmethods/m_sms.py +++ b/iam/authmethods/m_sms.py @@ -140,6 +140,25 @@ class Sms: 'check': 'dict-keys-exist', 'keys': ['msg', 'registration-action', 'authentication-action'] }, + { + 'check': 'index-check-list', + 'index': 'msg_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 200 for k in d.values()]) + }, + ] + }, { 'check': 'index-check-list', 'index': 'msg', @@ -169,6 +188,25 @@ class Sms: } ] }, + { + 'check': 'index-check-list', + 'index': 'html_message_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 5000 for k in d.values()]) + }, + ] + }, { 'check': 'index-check-list', 'index': 'registration-action', diff --git a/iam/authmethods/m_sms_otp.py b/iam/authmethods/m_sms_otp.py index ab88d5a7..3097f54f 100644 --- a/iam/authmethods/m_sms_otp.py +++ b/iam/authmethods/m_sms_otp.py @@ -141,6 +141,25 @@ class SmsOtp: 'check': 'dict-keys-exist', 'keys': ['msg', 'registration-action', 'authentication-action'] }, + { + 'check': 'index-check-list', + 'index': 'msg_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 200 for k in d.values()]) + }, + ] + }, { 'check': 'index-check-list', 'index': 'msg', @@ -170,6 +189,25 @@ class SmsOtp: } ] }, + { + 'check': 'index-check-list', + 'index': 'html_message_i18n', + 'optional': True, + 'check-list': [ + { + 'check': 'isinstance', + 'type': dict + }, + { # keys are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) for k in d.keys()]) + }, + { # values are strings + 'check': 'lambda', + 'lambda': lambda d: all([isinstance(k, str) and len(k) > 0 and len(k) <= 5000 for k in d.values()]) + }, + ] + }, { 'check': 'index-check-list', 'index': 'registration-action', diff --git a/iam/utils.py b/iam/utils.py index 45660d97..95df0d0a 100644 --- a/iam/utils.py +++ b/iam/utils.py @@ -1062,7 +1062,9 @@ def send_codes( # CHECKERS AUTHEVENT VALID_FIELDS = ( 'name', + 'name_i18n', 'help', + 'help_i18n', 'type', 'required', 'autofill', @@ -1241,6 +1243,19 @@ def check_pipeline(pipe): msg += "Invalid pipeline functions: %s not possible.\n" % func return msg +def check_translation_field(key, value, prefix): + msg = '' + if not isinstance(value, dict): + msg += "%s bad %s.\n" % (prefix, key) + else: + for k, v in value: + if not isinstance(k, str) or not isinstance(v, str) or \ + len(k) > settings.MAX_SIZE_NAME_EXTRA_FIELD or len(k) < 1 or \ + len(v) > settings.MAX_SIZE_NAME_EXTRA_FIELD or len(v) < 1: + msg += "%s bad %s.\n" % (prefix, key) + break + return msg + def check_extra_field(key, value): """ Check fields in extra_fields when create auth-event. """ from sys import maxsize @@ -1248,6 +1263,8 @@ def check_extra_field(key, value): if key == 'name' or key == 'help': if len(value) > settings.MAX_SIZE_NAME_EXTRA_FIELD or len(value) < 1: msg += "Invalid extra_fields: bad %s.\n" % key + elif key == 'name_i18n' or key == 'help_i18n': + msg += check_translation_field(key, value, "Invalid extra_fields:") elif key == 'type': if not value in VALID_TYPE_FIELDS: msg += "Invalid extra_fields: bad %s.\n" % key