async def move_content(context, request): results = [] async for ob in _iter_copyable_content(context, request): new_ob = await move(ob, context) results.append({ "source": get_object_url(ob), "target": get_object_url(new_ob) }) return results
async def test_object_utils(container_requester): async with container_requester as requester: response, status = await requester( 'POST', '/db/guillotina/', data=json.dumps({ "@type": "Item", "title": "Item1", "id": "item1" })) assert status == 201 request = get_mocked_request(requester.db) root = await get_root(request) txn = await request._tm.begin(request) container = await root.async_get('guillotina') ob = await utils.get_object_by_oid(response['@uid'], txn) assert ob is not None assert ob._p_oid == response['@uid'] ob2 = await utils.navigate_to(container, 'item1') assert ob2._p_oid == ob._p_oid url = utils.get_object_url(ob, request) assert url.endswith('item1') await request._tm.abort(txn=txn)
async def test_object_utils(container_requester): async with container_requester as requester: response, status = await requester("POST", "/db/guillotina/", data=json.dumps({ "@type": "Item", "title": "Item1", "id": "item1" })) assert status == 201 request = get_mocked_request(db=requester.db) root = await get_root(db=requester.db) tm = requester.db.get_transaction_manager() txn = await tm.begin() container = await root.async_get("guillotina") ob = await utils.get_object_by_uid(response["@uid"], txn) assert ob is not None assert ob.__uuid__ == response["@uid"] ob2 = await utils.navigate_to(container, "item1") assert ob2.__uuid__ == ob.__uuid__ url = utils.get_object_url(ob, request) assert url.endswith("item1") await tm.abort(txn=txn)
async def search(self, container: IContainer, query: ParsedQueryInfo): # type: ignore sql, arguments = self.build_query(container, query, ['id', 'zoid', 'json']) txn = get_transaction() if txn is None: raise TransactionNotFound() conn = await txn.get_connection() results = [] try: context_url = get_object_url(container) except RequestNotFound: context_url = get_content_path(container) logger.debug(f'Running search:\n{sql}\n{arguments}') for record in await conn.fetch(sql, *arguments): data = json.loads(record['json']) result = self.load_meatdata(query, data) result['@name'] = record['id'] result['@uid'] = record['zoid'] result['@id'] = data['@absolute_url'] = context_url + data['path'] results.append(result) # also do count... total = len(results) if total >= query['size'] or query['_from'] != 0: sql, arguments = self.build_count_query(container, query) logger.debug(f'Running search:\n{sql}\n{arguments}') records = await conn.fetch(sql, *arguments) total = records[0]['count'] return {'member': results, 'items_count': total}
async def check_content_moved(event): request = event.request try: get_current_container() except ContainerNotFound: return storage = utils.get_storage() if storage is None: return tail, _, view = '/'.join(event.tail).partition('/@') if view: view = '@' + view path = os.path.join( get_content_path(request.resource), tail) query = Query.from_(aliases_table).select( aliases_table.zoid ).where( (aliases_table.path == sqlq(path)) | (aliases_table.path == sqlq(path) + '/' + sqlq(view)) ) async with storage.pool.acquire() as conn: results = await conn.fetch(str(query)) if len(results) > 0: ob = await get_object_by_oid(results[0]['zoid']) url = get_object_url(ob) if view: url += '/' + view raise HTTPMovedPermanently(url)
async def test_object_utils(container_requester): async with container_requester as requester: response, status = await requester('POST', '/db/guillotina/', data=json.dumps({ "@type": "Item", "title": "Item1", "id": "item1" })) assert status == 201 request = get_mocked_request(requester.db) root = await get_root(request) txn = await request._tm.begin(request) container = await root.async_get('guillotina') ob = await utils.get_object_by_oid(response['@uid'], txn) assert ob is not None assert ob._p_oid == response['@uid'] ob2 = await utils.navigate_to(container, 'item1') assert ob2._p_oid == ob._p_oid url = utils.get_object_url(ob, request) assert url.endswith('item1') await request._tm.abort(txn=txn)
async def resolve_uid(context, request): uid = request.matchdict['uid'] ob = await get_object_by_oid(uid) if ob is None: return HTTPNotFound(content={'reason': f'Could not find uid: {uid}'}) interaction = IInteraction(request) if interaction.check_permission('guillotina.AccessContent', ob): return HTTPMovedPermanently(get_object_url(ob, request)) else: # if a user doesn't have access to it, they shouldn't know anything about it return HTTPNotFound(content={'reason': f'Could not find uid: {uid}'})
async def resolve_uid(context, request): uid = request.matchdict["uid"] try: ob = await get_object_by_uid(uid) except KeyError: return HTTPNotFound(content={"reason": f"Could not find uid: {uid}"}) policy = get_security_policy() if policy.check_permission("guillotina.AccessContent", ob): return HTTPMovedPermanently(get_object_url(ob, request)) else: # if a user doesn't have access to it, they shouldn't know anything about it return HTTPNotFound(content={"reason": f"Could not find uid: {uid}"})
async def get_all_files(context, request): children = [] async for _, obj in context.async_items(): children.append({ "type": obj.type_name, "url": get_object_url(obj), "path": get_content_path(obj) }) if obj.type_name == 'Directory': sub = await get_all_files(obj, request) children += sub return children
async def _query(self, context: IResource, query: ParsedQueryInfo, unrestricted: bool = False): sql, arguments = self.build_query(context, query, ["id", "zoid", "json"], unrestricted=unrestricted) txn = get_current_transaction() conn = await txn.get_connection() results = [] fullobjects = query["fullobjects"] container = find_container(context) if container is None: raise ContainerNotFound() try: context_url = get_object_url(container) request = get_current_request() except RequestNotFound: context_url = get_content_path(container) request = None logger.debug(f"Running search:\n{sql}\n{arguments}") async with txn.lock: records = await conn.fetch(sql, *arguments) for record in records: data = json.loads(record["json"]) if fullobjects and request is not None and txn is not None: # Get Object obj = await txn.get(data["uuid"]) # Serialize object view = DefaultGET(obj, request) result = await view() else: result = self.load_meatdata(query, data) result["@name"] = record["id"] result["@uid"] = record["zoid"] result["@id"] = data[ "@absolute_url"] = context_url + data["path"] results.append(result) # also do count... total = len(results) if total >= query["size"] or query["_from"] != 0: sql, arguments = self.build_count_query(context, query, unrestricted=unrestricted) logger.debug(f"Running search:\n{sql}\n{arguments}") async with txn.lock: records = await conn.fetch(sql, *arguments) total = records[0]["count"] return {"items": results, "items_total": total}
async def __call__(self): summary = json_compatible({ "@id": get_object_url(self.context, self.request), "@name": self.context.__name__, "@type": self.context.type_name, "@uid": self.context.uuid, }) return summary
async def move(context, request): try: data = await request.json() except Exception: data = {} try: await content.move(context, **data) except TypeError: raise ErrorResponse('RequiredParam', _("Invalid params"), reason=error_reasons.REQUIRED_PARAM_MISSING, status=412) return {'@url': get_object_url(context, request)}
async def move(context, request): try: data = await request.json() except Exception: data = {} try: await content.move( context, destination=data.get("destination"), new_id=data.get("new_id"), check_permission=True ) except TypeError: raise ErrorResponse( "RequiredParam", _("Invalid params"), reason=error_reasons.REQUIRED_PARAM_MISSING, status=412 ) return {"@url": get_object_url(context, request)}
async def translate_links(content, container=None) -> str: """ optimized url builder here so we don't pull full objects from database however, we lose caching. Would be great to move this into an implementation that worked with current cache/invalidation strategies """ req = None if container is None: container = find_container(content) container_url = get_object_url(container, req) dom = html.fromstring(content) contexts = {} for node in dom.xpath("//a") + dom.xpath("//img"): url = node.get("href", node.get("src", "")) if "resolveuid/" not in url: continue path = [] _, _, current_uid = url.partition("resolveuid/") current_uid = current_uid.split("/")[0].split("?")[0] error = False while current_uid != container.uuid: if current_uid not in contexts: # fetch from db result = await _get_id(current_uid) if result is not None: contexts[current_uid] = result else: # could not find, this should not happen error = True break path = [contexts[current_uid]["id"]] + path current_uid = contexts[current_uid]["parent"] if error: continue url = os.path.join(container_url, "/".join(path)) attr = node.tag.lower() == "a" and "href" or "src" node.attrib[attr] = url return html.tostring(dom).decode("utf-8")
async def create(self, payload: dict, in_: IResource = None) -> IResource: await self.get_transaction() if in_ is None: in_ = self.db view = get_multi_adapter((in_, self.request), app_settings["http_methods"]["POST"], name="") async def json(): return payload self.request.json = json resp = await view() await self.commit() path = resp.headers["Location"] if path.startswith("http://") or path.startswith("https://"): # strip off container prefix container_url = get_object_url(in_, self.request) # type: ignore path = path[len(container_url or "") :] return await navigate_to(in_, path.strip("/")) # type: ignore
async def create(self, payload: dict, in_: IResource=None) -> IResource: await self.get_transaction() if in_ is None: in_ = self.db view = get_multi_adapter( (in_, self.request), app_settings['http_methods']['POST'], name='') async def json(): return payload self.request.json = json resp = await view() await self.commit() path = resp.headers['Location'] if path.startswith('http://') or path.startswith('https://'): # strip off container prefix container_url = get_object_url(in_, self.request) # type: ignore path = path[len(container_url or ''):] return await navigate_to(in_, path.strip('/')) # type: ignore
async def _iter_copyable_content(context, request): policy = get_security_policy() data = await request.json() if 'source' not in data: raise HTTPPreconditionFailed(content={'reason': 'No source'}) source = data['source'] if not isinstance(source, list): source = [source] container = find_container(context) container_url = get_object_url(container) for item in source: if item.startswith(container_url): path = item[len(container_url):] ob = await navigate_to(container, path.strip('/')) if ob is None: raise HTTPPreconditionFailed(content={ 'reason': 'Could not find content', 'source': item }) elif '/' in item: ob = await navigate_to(container, item.strip('/')) if ob is None: raise HTTPPreconditionFailed(content={ 'reason': 'Could not find content', 'source': item }) else: try: ob = await get_object_by_uid(item) except KeyError: raise HTTPPreconditionFailed(content={ 'reason': 'Could not find content', 'source': item }) if not policy.check_permission('guillotina.DuplicateContent', ob): raise HTTPPreconditionFailed(content={ 'reason': 'Invalid permission', 'source': item }) yield ob
async def sharing_get(context, request): roleperm = IRolePermissionMap(context) prinperm = IPrincipalPermissionMap(context) prinrole = IPrincipalRoleMap(context) result = {'local': {}, 'inherit': []} result['local']['roleperm'] = roleperm._bycol result['local']['prinperm'] = prinperm._bycol result['local']['prinrole'] = prinrole._bycol for obj in iter_parents(context): roleperm = IRolePermissionMap(obj, None) url = get_object_url(obj, request) if roleperm is not None and url is not None: prinperm = IPrincipalPermissionMap(obj) prinrole = IPrincipalRoleMap(obj) result['inherit'].append({ '@id': url, 'roleperm': roleperm._bycol, 'prinperm': prinperm._bycol, 'prinrole': prinrole._bycol, }) await notify(ObjectPermissionsViewEvent(context)) return result
async def sharing_get(context, request): roleperm = IRolePermissionMap(context) prinperm = IPrincipalPermissionMap(context) prinrole = IPrincipalRoleMap(context) result = {"local": {}, "inherit": []} result["local"]["roleperm"] = roleperm._bycol result["local"]["prinperm"] = prinperm._bycol result["local"]["prinrole"] = prinrole._bycol for obj in iter_parents(context): roleperm = IRolePermissionMap(obj, None) url = get_object_url(obj, request) if roleperm is not None and url is not None: prinperm = IPrincipalPermissionMap(obj) prinrole = IPrincipalRoleMap(obj) result["inherit"].append({ "@id": url, "roleperm": roleperm._bycol, "prinperm": prinperm._bycol, "prinrole": prinrole._bycol, }) await notify(ObjectPermissionsViewEvent(context)) return result
def _get_items_from_result(self, container, request, result): items = [] container_url = get_object_url(container, request) for item in result["hits"]["hits"]: data = format_hit(item) data.update({ "@id": container_url + data.get("path", ""), "@type": data.get("type_name"), "@uid": item["_id"], "@name": data.get("id", data.get("path", "").split("/")[-1]), }) sort_value = item.get("sort") if sort_value: data.update({"sort": sort_value}) if "highlight" in item: data["@highlight"] = item["highlight"] items.append(data) return items
async def tus_create(self, *args, **kwargs): await self.dm.load() # This only happens in tus-java-client, redirect this POST to a PATCH if self.request.headers.get('X-HTTP-Method-Override') == 'PATCH': return await self.tus_patch() md5 = extension = size = None deferred_length = False if self.request.headers.get('Upload-Defer-Length') == '1': deferred_length = True if 'UPLOAD-LENGTH' in self.request.headers: size = int(self.request.headers['UPLOAD-LENGTH']) else: if not deferred_length: raise HTTPPreconditionFailed( content={'reason': 'We need upload-length header'}) if 'UPLOAD-MD5' in self.request.headers: md5 = self.request.headers['UPLOAD-MD5'] if 'UPLOAD-EXTENSION' in self.request.headers: extension = self.request.headers['UPLOAD-EXTENSION'] if 'TUS-RESUMABLE' not in self.request.headers: raise HTTPPreconditionFailed( content={'reason': 'TUS needs a TUS version'}) if 'X-UPLOAD-FILENAME' in self.request.headers: filename = self.request.headers['X-UPLOAD-FILENAME'] elif 'UPLOAD-FILENAME' in self.request.headers: filename = self.request.headers['UPLOAD-FILENAME'] elif 'UPLOAD-METADATA' not in self.request.headers: filename = uuid.uuid4().hex else: filename = self.request.headers['UPLOAD-METADATA'] filename = base64.b64decode(filename.split()[1]).decode('utf-8') if extension is None and '.' in filename: extension = filename.split('.')[-1] await self.dm.start() await self.dm.update(content_type=self.request.content_type, md5=md5, filename=filename, extension=extension, size=size, deferred_length=deferred_length, offset=0) await self.file_storage_manager.start(self.dm) await self.dm.save() if 'filename' in self.request.matchdict: location = posixpath.join( get_object_url(self.context, self.request), '@tusupload', self.field.__name__, self.request.matchdict['filename']) else: location = posixpath.join( get_object_url(self.context, self.request), '@tusupload', self.field.__name__) return Response( status=201, headers={ 'Location': location, # noqa 'Tus-Resumable': '1.0.0', 'Access-Control-Expose-Headers': 'Location,Tus-Resumable' })
async def __call__(self, include=None, omit=None): self.include = include or [] self.omit = omit or [] parent = self.context.__parent__ if parent is not None: # We render the summary of the parent try: parent_summary = await get_multi_adapter( (parent, self.request), IResourceSerializeToJsonSummary)() except ComponentLookupError: parent_summary = {} else: parent_summary = {} factory = get_cached_factory(self.context.type_name) behaviors = [] for behavior_schema in factory.behaviors or (): behaviors.append(behavior_schema.__identifier__) result = { "@id": get_object_url(self.context, self.request), "@type": self.context.type_name, "@name": self.context.__name__, "@uid": self.context.uuid, "@static_behaviors": behaviors, "parent": parent_summary, # should be @parent "is_folderish": IFolder.providedBy(self.context), # eek, should be @folderish? "creation_date": json_compatible(self.context.creation_date), "modification_date": json_compatible(self.context.modification_date), } main_schema = factory.schema await self.get_schema(main_schema, self.context, result, False) # include can be one of: # - <field name> on content schema # - namespace.IBehavior # - namespace.IBehavior.field_name included_ifaces = [name for name in self.include if "." in name] included_ifaces.extend( [name.rsplit(".", 1)[0] for name in self.include if "." in name]) for behavior_schema, behavior in await get_all_behaviors(self.context, load=False): if "*" not in self.include: dotted_name = behavior_schema.__identifier__ if dotted_name in self.omit or (len(included_ifaces) > 0 and dotted_name not in included_ifaces): # make sure the schema isn't filtered continue if not getattr(behavior, "auto_serialize", True) and dotted_name not in included_ifaces: continue if IAsyncBehavior.implementedBy(behavior.__class__): # providedBy not working here? await behavior.load(create=False) await self.get_schema(behavior_schema, behavior, result, True) for post_serialize_processors in app_settings["post_serialize"]: await apply_coroutine(post_serialize_processors, self.context, result) return result
async def __call__(self): """To create a content.""" data = await self.get_data() type_ = data.get('@type', None) id_ = data.get('id', None) behaviors = data.get('@behaviors', None) if not type_: raise ErrorResponse('RequiredParam', _("Property '@type' is required"), reason=error_reasons.REQUIRED_PARAM_MISSING, status=412) # Generate a temporary id if the id is not given new_id = None if not id_: generator = query_adapter(self.request, IIDGenerator) if generator is not None: new_id = generator(data) if isinstance(new_id, str) and not valid_id(new_id): raise ErrorResponse('PreconditionFailed', 'Invalid id: {}'.format(new_id), status=412, reason=error_reasons.INVALID_ID) else: if not isinstance(id_, str) or not valid_id(id_): raise ErrorResponse('PreconditionFailed', 'Invalid id: {}'.format(id_), status=412, reason=error_reasons.INVALID_ID) new_id = id_ user = get_authenticated_user_id() options = {'creators': (user, ), 'contributors': (user, )} if 'uid' in data: options['__uuid__'] = data.pop('uid') # Create object try: obj = await create_content_in_container(self.context, type_, new_id, **options) except ValueError as e: return ErrorResponse('CreatingObject', str(e), status=412) for behavior in behaviors or (): obj.add_behavior(behavior) # Update fields deserializer = query_multi_adapter((obj, self.request), IResourceDeserializeFromJson) if deserializer is None: return ErrorResponse('DeserializationError', 'Cannot deserialize type {}'.format( obj.type_name), status=412, reason=error_reasons.DESERIALIZATION_FAILED) await deserializer(data, validate_all=True, create=True) # Local Roles assign owner as the creator user get_owner = get_utility(IGetOwner) roleperm = IPrincipalRoleManager(obj) owner = await get_owner(obj, user) if owner is not None: roleperm.assign_role_to_principal('guillotina.Owner', owner) data['id'] = obj.id await notify(ObjectAddedEvent(obj, self.context, obj.id, payload=data)) headers = { 'Access-Control-Expose-Headers': 'Location', 'Location': get_object_url(obj, self.request) } serializer = query_multi_adapter((obj, self.request), IResourceSerializeToJsonSummary) response = await serializer() return Response(content=response, status=201, headers=headers)
async def tus_create(self, *args, **kwargs): await self.dm.load() # This only happens in tus-java-client, redirect this POST to a PATCH if self.request.headers.get("X-HTTP-Method-Override") == "PATCH": return await self.tus_patch() md5 = extension = size = None deferred_length = False if self.request.headers.get("Upload-Defer-Length") == "1": deferred_length = True if "UPLOAD-LENGTH" in self.request.headers: size = int(self.request.headers["UPLOAD-LENGTH"]) else: if not deferred_length: raise HTTPPreconditionFailed( content={"reason": "We need upload-length header"}) if "UPLOAD-MD5" in self.request.headers: md5 = self.request.headers["UPLOAD-MD5"] if "UPLOAD-EXTENSION" in self.request.headers: extension = self.request.headers["UPLOAD-EXTENSION"] if "TUS-RESUMABLE" not in self.request.headers: raise HTTPPreconditionFailed( content={"reason": "TUS needs a TUS version"}) if "X-UPLOAD-FILENAME" in self.request.headers: filename = self.request.headers["X-UPLOAD-FILENAME"] elif "UPLOAD-FILENAME" in self.request.headers: filename = self.request.headers["UPLOAD-FILENAME"] elif "UPLOAD-METADATA" not in self.request.headers: filename = uuid.uuid4().hex else: filename = self.request.headers["UPLOAD-METADATA"] filename = base64.b64decode(filename.split()[1]).decode("utf-8") if extension is None and "." in filename: extension = filename.split(".")[-1] await self.dm.start() await self.dm.update( content_type=self.request.content_type, md5=md5, filename=filename, extension=extension, size=size, deferred_length=deferred_length, offset=0, ) await self.file_storage_manager.start(self.dm) await self.dm.save() if "file_key" in self.request.matchdict: location = posixpath.join( get_object_url(self.context, self.request), "@tusupload", self.field.__name__, self.request.matchdict["file_key"], ) else: location = posixpath.join( get_object_url(self.context, self.request), "@tusupload", self.field.__name__) return Response( status=201, headers={ "Location": location, # noqa "Tus-Resumable": "1.0.0", "Access-Control-Expose-Headers": "Location,Tus-Resumable", }, )
async def __call__(self): """To create a content.""" data = await self.get_data() type_ = data.get("@type", None) id_ = data.get("id", None) behaviors = data.get("@behaviors", None) if not type_: raise ErrorResponse( "RequiredParam", _("Property '@type' is required"), reason=error_reasons.REQUIRED_PARAM_MISSING, status=412, ) id_checker = get_adapter(self.context, IIDChecker) # Generate a temporary id if the id is not given new_id = None if not id_: generator = query_adapter(self.request, IIDGenerator) if generator is not None: new_id = await apply_coroutine(generator, data) if isinstance(new_id, str) and not await id_checker(new_id, type_): raise ErrorResponse( "PreconditionFailed", "Invalid id: {}".format(new_id), status=412, reason=error_reasons.INVALID_ID, ) else: if not isinstance(id_, str) or not await id_checker(id_, type_): raise ErrorResponse( "PreconditionFailed", "Invalid id: {}".format(id_), status=412, reason=error_reasons.INVALID_ID, ) new_id = id_ user = get_authenticated_user_id() options = {"creators": (user,), "contributors": (user,)} if "uid" in data: options["__uuid__"] = data.pop("uid") # Create object try: obj = await create_content_in_container( self.context, type_, new_id, check_constraints=True, **options ) except ValueError as e: return ErrorResponse("CreatingObject", str(e), status=412) for behavior in behaviors or (): obj.add_behavior(behavior) # Update fields deserializer = query_multi_adapter((obj, self.request), IResourceDeserializeFromJson) if deserializer is None: return ErrorResponse( "DeserializationError", "Cannot deserialize type {}".format(obj.type_name), status=412, reason=error_reasons.DESERIALIZATION_FAILED, ) await deserializer(data, validate_all=True, create=True) # Local Roles assign owner as the creator user get_owner = IGetOwner(obj) roleperm = IPrincipalRoleManager(obj) owner = await get_owner(user) if owner is not None: roleperm.assign_role_to_principal("guillotina.Owner", owner) data["id"] = obj.id await notify(ObjectAddedEvent(obj, self.context, obj.id, payload=data)) headers = {"Access-Control-Expose-Headers": "Location", "Location": get_object_url(obj, self.request)} serializer = query_multi_adapter((obj, self.request), IResourceSerializeToJsonSummary) response = await serializer() return Response(content=response, status=201, headers=headers)