def check_string(string: str, name: str, legal_characters: str = None, max_len: int = None) -> None: ''' Check that a string meets a set of criteria: - it is not None or whitespace only - (optional) it is less than some specified maximum length - (optional) it contains only legal characters. :param string: the string to test. :param name: the name of the string to be used in error messages. :param legal_characters: a regex character class that matches legal characters in the string. Typical examples are a-zA-Z_0-9, a-z, etc. :param max_len: the maximum length of the string. :raises MissingParameterError: if the string is None or whitespace only. :raises IllegalParameterError: if the string is too long or contains illegal characters. ''' if not string or not string.strip(): raise MissingParameterError(name) if max_len and len(string) > max_len: raise IllegalParameterError( '{} {} exceeds maximum length of {}'.format(name, string, max_len)) if legal_characters: global _REGEX_CACHE if legal_characters not in _REGEX_CACHE: _REGEX_CACHE[legal_characters] = _re.compile('[^' + legal_characters + ']') match = _REGEX_CACHE[legal_characters].search(string) if match: raise IllegalParameterError( 'Illegal character in {} {}: {}'.format( name, string, match.group()))
def test_authsource_init_fail(): fail_authsource_init(None, MissingParameterError('authsource id')) fail_authsource_init(' \t \n ', MissingParameterError('authsource id')) fail_authsource_init('abcdefghijklmnopqrstu', IllegalParameterError( 'authsource id abcdefghijklmnopqrstu exceeds maximum length of 20')) fail_authsource_init('fooo1b&', IllegalParameterError('Illegal character in authsource id fooo1b&: 1'))
def test_namespace_id_init_fail(): fail_namespace_id_init(None, MissingParameterError('namespace id')) fail_namespace_id_init(' \t \n ', MissingParameterError('namespace id')) fail_namespace_id_init( 'a' * 257, IllegalParameterError('namespace id ' + ('a' * 257) + ' exceeds maximum length of 256')) fail_namespace_id_init( 'fooo1b&_*', IllegalParameterError( 'Illegal character in namespace id fooo1b&_*: &'))
def _get_object_id_list_from_json(request) -> List[str]: # flask has a built in get_json() method but the errors it throws suck. body = json.loads(request.get_data()) if not isinstance(body, dict): raise IllegalParameterError('Expected JSON mapping in request body') ids = body.get('ids') if not isinstance(ids, list): raise IllegalParameterError('Expected list at /ids in request body') if not ids: raise MissingParameterError('No ids supplied') for id_ in ids: if not id_ or not id_.strip(): raise MissingParameterError('null or whitespace-only id in list') return ids
def get_mappings(ns): """ Find mappings. """ ns_filter = request.args.get('namespace_filter') separate = request.args.get('separate') if ns_filter and ns_filter.strip(): ns_filter = [NamespaceID(n.strip()) for n in ns_filter.split(',')] else: ns_filter = [] ids = _get_object_id_list_from_json(request) if len(ids) > 1000: raise IllegalParameterError('A maximum of 1000 ids are allowed') ret = {} for id_ in ids: id_ = id_.strip() a, o = app.config[_APP].get_mappings( ObjectID(NamespaceID(ns), id_), ns_filter) if separate is not None: # empty string if in query with no value ret[id_] = { 'admin': _objids_to_jsonable(a), 'other': _objids_to_jsonable(o) } else: a.update(o) ret[id_] = {'mappings': _objids_to_jsonable(a)} return flask.jsonify(ret)
def test_object_id_init_fail(): ns = NamespaceID('foo') fail_object_id_init(None, 'o', TypeError('namespace_id cannot be None')) fail_object_id_init(ns, None, MissingParameterError('data id')) fail_object_id_init(ns, ' \t \n ', MissingParameterError('data id')) fail_object_id_init( ns, 'a' * 1001, IllegalParameterError('data id ' + ('a' * 1001) + ' exceeds maximum length of 1000'))
def test_check_string_fail(): fail_check_string(None, 'foo', None, None, MissingParameterError('foo')) fail_check_string(' \t \n ', 'foo', None, None, MissingParameterError('foo')) fail_check_string( 'bar', 'foo', None, 2, IllegalParameterError('foo bar exceeds maximum length of 2')) fail_check_string( 'b_ar&_1', 'foo', 'a-z_', None, IllegalParameterError('Illegal character in foo b_ar&_1: &')) # this is reaching into the implementation which is very naughty but I don't see a good way # to check the cache is actually working otherwise assert arg_check._REGEX_CACHE['a-z_'].pattern == '[^a-z_]' # test with cache fail_check_string( 'b_ar&_1', 'foo', 'a-z_', None, IllegalParameterError('Illegal character in foo b_ar&_1: &'))
def remove_mapping(admin_ns, other_ns): """ Remove a mapping. """ authsource, token = _get_auth(request) ids = _get_object_id_dict_from_json(request) if len(ids) > 10000: raise IllegalParameterError('A maximum of 10000 ids are allowed') for id_ in ids: app.config[_APP].remove_mapping( authsource, token, ObjectID(NamespaceID(admin_ns), id_.strip()), ObjectID(NamespaceID(other_ns), ids[id_].strip())) return ('', 204)
def _get_object_id_dict_from_json(request) -> Dict[str, str]: # flask has a built in get_json() method but the errors it throws suck. ids = json.loads(request.get_data()) if not isinstance(ids, dict): raise IllegalParameterError('Expected JSON mapping in request body') if not ids: raise MissingParameterError('No ids supplied') for id_ in ids: # json keys must be strings if not id_.strip(): raise MissingParameterError('whitespace only key in input JSON') val = ids[id_] if not isinstance(val, str): raise IllegalParameterError( 'value for key {} in input JSON is not string: {}'.format( id_, val)) if not val.strip(): raise MissingParameterError( 'value for key {} in input JSON is whitespace only'.format( id_)) return ids
def set_namespace_params(namespace): """ Change settings on a namespace. """ authsource, token = _get_auth(request) pubmap = request.args.get('publicly_mappable') if pubmap: # expand later if more settings are allowed if pubmap not in [_TRUE, _FALSE]: raise IllegalParameterError( "Expected value of 'true' or 'false' for publicly_mappable" ) app.config[_APP].set_namespace_publicly_mappable( authsource, token, NamespaceID(namespace), pubmap == _TRUE) else: raise MissingParameterError('No settings provided.') return ('', 204)
def _get_auth(request, required=True) -> Tuple[Optional[AuthsourceID], Optional[Token]]: """ :returns None if required is False and there is no authorization header. :raises NoTokenError: if required is True and there's no authorization header. :raises InvalidTokenError: if the authorization header is malformed. :raises IllegalParameterError: if the authsource is illegal. """ auth = request.headers.get('Authorization') if not auth: if required: raise NoTokenError() return (None, None) auth = auth.strip().split() if len(auth) != 2: raise IllegalParameterError('Expected authsource and token in header.') return AuthsourceID(auth[0]), Token(auth[1])