def default_bucket(request): if request.method.lower() == 'options': path = request.path.replace('default', 'unknown') subrequest = build_request(request, { 'method': 'OPTIONS', 'path': path }) return request.invoke_subrequest(subrequest) if Authenticated not in request.effective_principals: # Pass through the forbidden_view_config raise httpexceptions.HTTPForbidden() settings = request.registry.settings if asbool(settings['readonly']): raise httpexceptions.HTTPMethodNotAllowed() bucket_id = request.default_bucket_id path = request.path.replace('/buckets/default', '/buckets/%s' % bucket_id) querystring = request.url[(request.url.index(request.path) + len(request.path)):] try: # If 'id' is provided as 'default', replace with actual bucket id. body = request.json body['data']['id'] = body['data']['id'].replace('default', bucket_id) except: body = request.body # Make sure bucket exists create_bucket(request, bucket_id) # Make sure the collection exists create_collection(request, bucket_id) subrequest = build_request(request, { 'method': request.method, 'path': path + querystring, 'body': body, }) subrequest.bound_data = request.bound_data try: response = request.invoke_subrequest(subrequest) except httpexceptions.HTTPException as error: is_redirect = error.status_code < 400 if error.content_type == 'application/json' or is_redirect: response = reapply_cors(subrequest, error) else: # Ask the upper level to format the error. raise error return response
def default_bucket(request): if request.method.lower() == 'options': path = request.path.replace('default', 'unknown') subrequest = build_request(request, { 'method': 'OPTIONS', 'path': path }) return request.invoke_subrequest(subrequest) if Authenticated not in request.effective_principals: # Pass through the forbidden_view_config raise httpexceptions.HTTPForbidden() settings = request.registry.settings if asbool(settings['readonly']): raise httpexceptions.HTTPMethodNotAllowed() bucket_id = request.default_bucket_id # Implicit object creations. # Make sure bucket exists create_bucket(request, bucket_id) # Make sure the collection exists create_collection(request, bucket_id) path = request.path.replace('/buckets/default', '/buckets/{}'.format(bucket_id)) querystring = request.url[(request.url.index(request.path) + len(request.path)):] try: # If 'id' is provided as 'default', replace with actual bucket id. body = request.json body['data']['id'] = body['data']['id'].replace('default', bucket_id) except: body = request.body or {"data": {}} subrequest = build_request(request, { 'method': request.method, 'path': path + querystring, 'body': body, }) subrequest.bound_data = request.bound_data try: response = request.invoke_subrequest(subrequest) except httpexceptions.HTTPException as error: is_redirect = error.status_code < 400 if error.content_type == 'application/json' or is_redirect: response = reapply_cors(subrequest, error) else: # Ask the upper level to format the error. raise error return response
def default_bucket(request): if request.method.lower() == "options": path = request.path.replace("default", "unknown") subrequest = build_request(request, { "method": "OPTIONS", "path": path }) return request.invoke_subrequest(subrequest) if Authenticated not in request.effective_principals: # Pass through the forbidden_view_config raise httpexceptions.HTTPForbidden() settings = request.registry.settings if asbool(settings["readonly"]): raise httpexceptions.HTTPMethodNotAllowed() bucket_id = request.default_bucket_id # Implicit object creations. # Make sure bucket exists create_bucket(request, bucket_id) # Make sure the collection exists create_collection(request, bucket_id) path = request.path.replace("/buckets/default", f"/buckets/{bucket_id}") querystring = request.url[(request.url.index(request.path) + len(request.path)):] try: # If 'id' is provided as 'default', replace with actual bucket id. body = request.json body["data"]["id"] = body["data"]["id"].replace("default", bucket_id) except Exception: body = request.body or {"data": {}} subrequest = build_request(request, { "method": request.method, "path": path + querystring, "body": body }) subrequest.bound_data = request.bound_data try: response = request.invoke_subrequest(subrequest) except httpexceptions.HTTPException as error: is_redirect = error.status_code < 400 if error.content_type == "application/json" or is_redirect: response = reapply_cors(subrequest, error) else: # Ask the upper level to format the error. raise error return response
def notify_resource_event(request, request_options, matchdict, resource_name, parent_id, record, action, old=None): """Helper that triggers resource events as real requests. """ fakerequest = build_request(request, request_options) fakerequest.matchdict = matchdict fakerequest.bound_data = request.bound_data fakerequest.authn_type, fakerequest.selected_userid = PLUGIN_USERID.split( ":") fakerequest.current_resource_name = resource_name # When kinto-signer copies record from one place to another, # it simulates a resource event. Since kinto-attachment # prevents from updating attachment fields, it throws an error. # The following flag will disable the kinto-attachment check. # See https://github.com/Kinto/kinto-signer/issues/256 # and https://bugzilla.mozilla.org/show_bug.cgi?id=1470812 has_changed_attachment = (resource_name == "record" and action == ACTIONS.UPDATE and "attachment" in old and old["attachment"] != record.get("attachment")) if has_changed_attachment: fakerequest._attachment_auto_save = True fakerequest.notify_resource_event(parent_id=parent_id, timestamp=record[FIELD_LAST_MODIFIED], data=record, action=action, old=old)
def notify_resource_event(request, request_options, matchdict, resource_name, parent_id, obj, action, old=None): """Helper that triggers resource events as real requests. """ fakerequest = build_request(request, request_options) fakerequest.matchdict = matchdict fakerequest.bound_data = request.bound_data fakerequest.authn_type, fakerequest.selected_userid = PLUGIN_USERID.split(":") fakerequest.current_resource_name = resource_name # When kinto-signer copies record from one place to another, # it simulates a resource event. Since kinto-attachment # prevents from updating attachment fields, it throws an error. # The following flag will disable the kinto-attachment check. # See https://github.com/Kinto/kinto-signer/issues/256 # and https://bugzilla.mozilla.org/show_bug.cgi?id=1470812 has_changed_attachment = ( resource_name == "record" and action == ACTIONS.UPDATE and "attachment" in old and old["attachment"] != obj.get("attachment")) if has_changed_attachment: fakerequest._attachment_auto_save = True fakerequest.notify_resource_event(parent_id=parent_id, timestamp=obj[FIELD_LAST_MODIFIED], data=obj, action=action, old=old)
def default_bucket(request): if request.method.lower() == "options": path = request.path.replace("default", "unknown") subrequest = build_request(request, {"method": "OPTIONS", "path": path}) return request.invoke_subrequest(subrequest) if Authenticated not in request.effective_principals: # Pass through the forbidden_view_config raise httpexceptions.HTTPForbidden() settings = request.registry.settings if asbool(settings["readonly"]): raise httpexceptions.HTTPMethodNotAllowed() bucket_id = request.default_bucket_id # Implicit object creations. # Make sure bucket exists create_bucket(request, bucket_id) # Make sure the collection exists create_collection(request, bucket_id) path = request.path.replace("/buckets/default", "/buckets/{}".format(bucket_id)) querystring = request.url[(request.url.index(request.path) + len(request.path)) :] try: # If 'id' is provided as 'default', replace with actual bucket id. body = request.json body["data"]["id"] = body["data"]["id"].replace("default", bucket_id) except Exception: body = request.body or {"data": {}} subrequest = build_request( request, {"method": request.method, "path": path + querystring, "body": body} ) subrequest.bound_data = request.bound_data try: response = request.invoke_subrequest(subrequest) except httpexceptions.HTTPException as error: is_redirect = error.status_code < 400 if error.content_type == "application/json" or is_redirect: response = reapply_cors(subrequest, error) else: # Ask the upper level to format the error. raise error return response
def resource_create_object(request, resource_cls, uri): """Implicitly create a resource (or fail silently). In the default bucket, the bucket and collection are implicitly created. This helper creates one of those resources using a simulated request and context that is appropriate for the resource. Also runs create events as though the resource were created in a subrequest. If the resource already exists, do nothing. """ resource_name, matchdict = view_lookup(request, uri) # Build a fake request, mainly used to populate the create events that # will be triggered by the resource. fakerequest = build_request(request, { 'method': 'PUT', 'path': uri, }) fakerequest.matchdict = matchdict fakerequest.bound_data = request.bound_data fakerequest.authn_type = request.authn_type fakerequest.selected_userid = request.selected_userid fakerequest.errors = request.errors fakerequest.current_resource_name = resource_name obj_id = matchdict['id'] # Fake context, required to instantiate a resource. context = RouteFactory(fakerequest) context.resource_name = resource_name resource = resource_cls(fakerequest, context) # Check that provided id is valid for this resource. if not resource.model.id_generator.match(obj_id): error_details = { 'location': 'path', 'description': 'Invalid {} id'.format(resource_name) } raise_invalid(resource.request, **error_details) data = {'id': obj_id} try: obj = resource.model.create_record(data) except UnicityError as e: # The record already exists; skip running events return e.record # Since the current request is not a resource (but a straight Service), # we simulate a request on a resource. # This will be used in the resource event payload. resource.postprocess(obj, action=ACTIONS.CREATE) return obj
def post_batch(request): requests = request.validated['body']['requests'] request.log_context(batch_size=len(requests)) limit = request.registry.settings['batch_max_requests'] if limit and len(requests) > int(limit): error_msg = 'Number of requests is limited to {}'.format(limit) request.errors.add('body', 'requests', error_msg) return if any([batch.path in req['path'] for req in requests]): error_msg = 'Recursive call on {} endpoint is forbidden.'.format(batch.path) request.errors.add('body', 'requests', error_msg) return responses = [] for subrequest_spec in requests: subrequest = build_request(request, subrequest_spec) log_context = {**request.log_context(), 'path': subrequest.path, 'method': subrequest.method} try: # Invoke subrequest without individual transaction. resp, subrequest = request.follow_subrequest(subrequest, use_tweens=False) except httpexceptions.HTTPException as e: # Since some request in the batch failed, we need to stop the parent request # through Pyramid's transaction manager. 5XX errors are already caught by # pyramid_tm's commit_veto # https://github.com/Kinto/kinto/issues/624 if e.status_code == 409: request.tm.abort() if e.content_type == 'application/json': resp = e else: # JSONify raw Pyramid errors. resp = errors.http_error(e) subrequest_logger.info('subrequest.summary', extra=log_context) dict_resp = build_response(resp, subrequest) responses.append(dict_resp) return { 'responses': responses }
def post_batch(request): requests = request.validated["body"]["requests"] request.log_context(batch_size=len(requests)) limit = request.registry.settings["batch_max_requests"] if limit and len(requests) > int(limit): error_msg = f"Number of requests is limited to {limit}" request.errors.add("body", "requests", error_msg) return if any([batch.path in req["path"] for req in requests]): error_msg = f"Recursive call on {batch.path} endpoint is forbidden." request.errors.add("body", "requests", error_msg) return responses = [] for subrequest_spec in requests: subrequest = build_request(request, subrequest_spec) log_context = { **request.log_context(), "path": subrequest.path, "method": subrequest.method, } try: # Invoke subrequest without individual transaction. resp, subrequest = request.follow_subrequest(subrequest, use_tweens=False) except httpexceptions.HTTPException as e: # Since some request in the batch failed, we need to stop the parent request # through Pyramid's transaction manager. 5XX errors are already caught by # pyramid_tm's commit_veto # https://github.com/Kinto/kinto/issues/624 if e.status_code == 409: request.tm.abort() if e.content_type == "application/json": resp = e else: # JSONify raw Pyramid errors. resp = errors.http_error(e) subrequest_logger.info("subrequest.summary", extra=log_context) dict_resp = build_response(resp, subrequest) responses.append(dict_resp) return {"responses": responses}
def post_batch(request): requests = request.validated['body']['requests'] batch_size = len(requests) limit = request.registry.settings['batch_max_requests'] if limit and len(requests) > int(limit): error_msg = 'Number of requests is limited to %s' % limit request.errors.add('body', 'requests', error_msg) return if any([batch.path in req['path'] for req in requests]): error_msg = 'Recursive call on %s endpoint is forbidden.' % batch.path request.errors.add('body', 'requests', error_msg) return responses = [] sublogger = logger.new() for subrequest_spec in requests: subrequest = build_request(request, subrequest_spec) sublogger.bind(path=subrequest.path, method=subrequest.method) try: # Invoke subrequest without individual transaction. resp, subrequest = request.follow_subrequest(subrequest, use_tweens=False) except httpexceptions.HTTPException as e: if e.content_type == 'application/json': resp = e else: # JSONify raw Pyramid errors. resp = errors.http_error(e) sublogger.bind(code=resp.status_code) sublogger.info('subrequest.summary') dict_resp = build_response(resp, subrequest) responses.append(dict_resp) # Rebing batch request for summary logger.bind(path=batch.path, method=request.method, batch_size=batch_size, agent=request.headers.get('User-Agent'),) return { 'responses': responses }
def post_batch(request): requests = request.validated['requests'] batch_size = len(requests) limit = request.registry.settings['batch_max_requests'] if limit and len(requests) > int(limit): error_msg = 'Number of requests is limited to %s' % limit request.errors.add('body', 'requests', error_msg) return if any([batch.path in req['path'] for req in requests]): error_msg = 'Recursive call on %s endpoint is forbidden.' % batch.path request.errors.add('body', 'requests', error_msg) return responses = [] sublogger = logger.new() for subrequest_spec in requests: subrequest = build_request(request, subrequest_spec) sublogger.bind(path=subrequest.path, method=subrequest.method) try: # Invoke subrequest without individual transaction. resp, subrequest = request.follow_subrequest(subrequest, use_tweens=False) except httpexceptions.HTTPException as e: if e.content_type == 'application/json': resp = e else: # JSONify raw Pyramid errors. resp = errors.http_error(e) sublogger.bind(code=resp.status_code) sublogger.info('subrequest.summary') dict_resp = build_response(resp, subrequest) responses.append(dict_resp) # Rebing batch request for summary logger.bind(path=batch.path, method=request.method, batch_size=batch_size, agent=request.headers.get('User-Agent'),) return { 'responses': responses }
def notify_resource_event(request, request_options, matchdict, resource_name, parent_id, record, action, old=None): """Private helper that triggers resource events when the updater modifies the source and destination objects. """ fakerequest = build_request(request, request_options) fakerequest.matchdict = matchdict fakerequest.bound_data = request.bound_data fakerequest.selected_userid = "kinto-signer" fakerequest.authn_type = "plugin" fakerequest.current_resource_name = resource_name fakerequest.notify_resource_event(parent_id=parent_id, timestamp=record[FIELD_LAST_MODIFIED], data=record, action=action, old=old)
def resource_create_object(request, resource_cls, uri): """In the default bucket, the bucket and collection are implicitly created. This helper instantiate the resource and simulate a request with its RootFactory on the instantiated resource. :returns: the created object :rtype: dict """ resource_name, matchdict = view_lookup(request, uri) # Build a fake request, mainly used to populate the create events that # will be triggered by the resource. fakerequest = build_request(request, { 'method': 'PUT', 'path': uri, }) fakerequest.matchdict = matchdict fakerequest.bound_data = request.bound_data fakerequest.authn_type = request.authn_type fakerequest.selected_userid = request.selected_userid fakerequest.errors = request.errors fakerequest.current_resource_name = resource_name obj_id = matchdict['id'] # Fake context, required to instantiate a resource. context = RouteFactory(fakerequest) context.resource_name = resource_name resource = resource_cls(fakerequest, context) # Check that provided id is valid for this resource. if not resource.model.id_generator.match(obj_id): error_details = { 'location': 'path', 'description': "Invalid %s id" % resource_name } raise_invalid(resource.request, **error_details) data = {'id': obj_id} try: obj = resource.model.create_record(data) # Since the current request is not a resource (but a straight Service), # we simulate a request on a resource. # This will be used in the resource event payload. resource.postprocess(data, action=ACTIONS.CREATE) except storage_exceptions.UnicityError as e: obj = e.record return obj
def post_batch(request): requests = request.validated['body']['requests'] request.log_context(batch_size=len(requests)) limit = request.registry.settings['batch_max_requests'] if limit and len(requests) > int(limit): error_msg = 'Number of requests is limited to {}'.format(limit) request.errors.add('body', 'requests', error_msg) return if any([batch.path in req['path'] for req in requests]): error_msg = 'Recursive call on {} endpoint is forbidden.'.format( batch.path) request.errors.add('body', 'requests', error_msg) return responses = [] for subrequest_spec in requests: subrequest = build_request(request, subrequest_spec) log_context = { 'path': subrequest.path, 'method': subrequest.method, **request.log_context() } try: # Invoke subrequest without individual transaction. resp, subrequest = request.follow_subrequest(subrequest, use_tweens=False) except httpexceptions.HTTPException as e: if e.content_type == 'application/json': resp = e else: # JSONify raw Pyramid errors. resp = errors.http_error(e) subrequest_logger.info('subrequest.summary', extra=log_context) dict_resp = build_response(resp, subrequest) responses.append(dict_resp) return {'responses': responses}
def test_built_request_has_kinto_core_custom_methods(self): original = build_real_request({'PATH_INFO': '/foo'}) request = build_request(original, {"path": "bar"}) self.assertTrue(hasattr(request, 'current_service'))
def test_built_request_has_kinto_core_custom_methods(self): original = build_real_request({"PATH_INFO": "/foo"}) request = build_request(original, {"path": "bar"}) self.assertTrue(hasattr(request, "current_service"))