def test_collection(): coll1 = Collection.create("/", uuid.uuid4().hex) coll2 = Collection.create(coll1.path, uuid.uuid4().hex) meta_dict = {"meta": "val"} coll3 = Collection.create("/", uuid.uuid4().hex, metadata=meta_dict) assert coll3.get_cdmi_user_meta() == meta_dict coll4 = Collection.create("/", uuid.uuid4().hex, creator="test") # test if name ends with '/' coll5 = Collection.create("/", uuid.uuid4().hex + "/") with pytest.raises(NoSuchCollectionError): Collection.create("unknown", uuid.uuid4().hex) r = Resource.find("/g") with pytest.raises(ResourceConflictError): coll = Collection.create("/", "g") with pytest.raises(CollectionConflictError): Collection.create("/", coll1.name) coll1.delete() coll2.delete() coll3.delete() coll4.delete() coll5.delete()
def test_get_child(): # Create a new collection with a random name coll_name = uuid.uuid4().hex coll1 = Collection.create('/', coll_name) coll2 = Collection.create(coll1.path, uuid.uuid4().hex) coll3 = Collection.create(coll1.path, uuid.uuid4().hex) coll4 = Collection.create(coll1.path, uuid.uuid4().hex) resc1 = Resource.create(coll1.path, uuid.uuid4().hex, url="http://www.google.fr") resc2 = Resource.create(coll1.path, uuid.uuid4().hex, url="http://www.google.fr") coll_childs, resc_childs = coll1.get_child() assert set(coll_childs) == set([coll2.name, coll3.name, coll4.name]) assert set(resc_childs) == set([resc1.get_name(), resc2.get_name()]) assert coll1.get_child_resource_count() == 2 coll_root = Collection.find("/") # Test for a resource where the url has been lost somehow resc3 = Resource.create(coll_root.path, uuid.uuid4().hex) resc3.update(object_url=None) resc3 = Resource.find(resc3.path) coll_childs, resc_childs = coll_root.get_child() assert set(coll_childs) == set(["1/", "2/", coll1.name]) assert set(resc_childs) == set([resc3.get_name(), 'g']) coll1.delete()
def mkdir(self, args): "Create a new container." session = self.get_session() cwd = session.get('cwd', '/') path = args[ARG_PATH] # Collections names should end with a '/' if not path.endswith("/"): path += '/' if not path.startswith("/"): # relative path path = "{}{}".format(cwd, path) col = Collection.find(path) if col: self.print_error(MSG_COLL_EXIST.format(path)) return parent, name = split(path) if name.startswith("cdmi_"): self.print_error(MSG_COLL_WRONG_NAME.format(name)) return p_coll = Collection.find(parent) if not p_coll: self.print_error(MSG_COLL_NOT_EXIST.format(path)) return Collection.create(name=name, container=parent)
def delete_collection(request, path): """Display the page to delete a collection""" coll = Collection.find(path) if not coll: raise Http404 if not coll.user_can(request.user, "delete"): raise PermissionDenied if request.method == "POST": parent_coll = Collection.find(coll.path) if parent_coll: parent_path = parent_coll.container else: # Just in case parent_path = "" coll.delete(username=request.user.name) messages.add_message( request, messages.INFO, u"The collection '{}' has been deleted".format(coll.name), ) return redirect("archive:view", path=parent_path) return render(request, "archive/delete.html", {"collection": coll})
def test_find(): coll1 = Collection.create("/", "a") assert Collection.find("/a") == None assert Collection.find("/a/") != None assert Collection.find("/a/", 1) == None coll1.delete()
def delete_container(self, path): """Delete a container""" collection = Collection.find(path) if not collection: self.logger.info(u"Fail to delete collection at '{}'".format(path)) return Response(status=HTTP_404_NOT_FOUND) if not collection.user_can(self.user, "delete"): self.logger.warning( u"User {} tried to delete container '{}'".format( self.user, path)) return Response(status=HTTP_403_FORBIDDEN) Collection.delete_all(collection.path) self.logger.info( u"The container '{}' was successfully deleted".format(path)) return Response(status=HTTP_204_NO_CONTENT)
def create_root(): """Create the root container :return: The root collection object :rtype: :class:`radon.model.Collection`""" # get_root will create the root if it doesn't exist yet return Collection.get_root()
def test_size(): coll_name = uuid.uuid4().hex coll = Collection.create('/', coll_name) myFactory = Faker() content = myFactory.text() chk = hashlib.sha224(content.encode()).hexdigest() do = DataObject.create(content.encode()) resc_name = uuid.uuid4().hex resc = Resource.create(coll.path, resc_name, url="{}{}".format(cfg.protocol_cassandra, do.uuid)) resc = Resource.find(resc.path) assert resc.get_size() == len(content) resc.obj = None assert resc.get_size() == 0 resc.delete() # Size for a reference resc = Resource.create(coll.path, resc_name, url="http://www.google.fr", mimetype="text/plain") resc = Resource.find(resc.path) # Size stored in the tree entry assert resc.get_size() == 0 resc.delete() coll.delete()
def test_metadata(): # Create a new collection with a random name coll_name = uuid.uuid4().hex coll1 = Collection.create('/', coll_name) metadata = {"test": "val", "test_json": '["t", "e", "s", "t"]'} coll1.update(metadata=metadata) coll1 = Collection.find("/{}/".format(coll_name)) assert coll1.get_list_user_meta() == [('test', 'val'), ('test_json', '["t", "e", "s", "t"]') ] assert coll1.get_user_meta_key("test_json") == '["t", "e", "s", "t"]' sys_meta = coll1.get_cdmi_sys_meta() assert "radon_create_ts" in sys_meta assert "radon_modify_ts" in sys_meta
def test_dict(): coll_name = uuid.uuid4().hex coll = Collection.create("/", coll_name) myFactory = Faker() content = myFactory.text() # Read/Write resource stored in Cassandra do = DataObject.create(content.encode()) resc_name = uuid.uuid4().hex resc = Resource.create(coll.path, resc_name, url="{}{}".format(cfg.protocol_cassandra, do.uuid)) resc = Resource.find(resc.path) resc_dict = resc.full_dict(User.find(USR1_NAME)) assert resc_dict['size'] == len(content) assert resc_dict['can_read'] assert resc_dict['can_write'] assert resc_dict['uuid'] == resc.uuid resc_dict = resc.simple_dict(User.find(USR1_NAME)) assert resc_dict['name'] == resc_name assert resc_dict['is_reference'] == False assert resc_dict['can_read'] assert resc_dict['can_write'] assert resc_dict['id'] == resc.uuid assert resc.simple_dict() == resc.to_dict() resc.delete() coll.delete()
def test_chunk_content(): coll_name = uuid.uuid4().hex coll = Collection.create("/", coll_name) myFactory = Faker() content = myFactory.text() do = DataObject.create(content.encode()) resc_name = uuid.uuid4().hex resc = Resource.create(coll.path, resc_name, url="{}{}".format(cfg.protocol_cassandra, do.uuid)) data = [] for chk in resc.chunk_content(): data.append(chk) res = b"".join([s for s in data]) assert res == content.encode() resc.obj = None assert resc.chunk_content() == None TEST_URL = "http://www.google.fr" resc_name = uuid.uuid4().hex resc = Resource.create(coll.path, resc_name, url=TEST_URL) data = [] for chk in resc.chunk_content(): data.append(chk) res = b"".join(data) assert res coll.delete()
def test_to_dict(): # Create a new collection with a random name coll_name = uuid.uuid4().hex coll1 = Collection.create('/', coll_name) coll_dict = coll1.to_dict() assert coll_dict['id'] == coll1.uuid assert coll_dict['name'] == coll_name + '/' assert coll_dict['path'] == "/{}/".format(coll_name) # Specify a user to get ACL # user 1 is admin, he can do everything coll_dict = coll1.to_dict(User.find("user1")) assert coll_dict['can_read'] == True assert coll_dict["can_write"] == True assert coll_dict["can_edit"] == True assert coll_dict["can_delete"] == True # user 2 should have limited access coll_dict = coll1.to_dict(User.find("user2")) assert coll_dict['can_read'] == True assert coll_dict["can_write"] == False assert coll_dict["can_edit"] == False assert coll_dict["can_delete"] == False coll1.delete()
def change_dir(self, args): "Move into a different container." session = self.get_session() cwd = session.get('cwd', '/') if args[ARG_PATH]: path = args[ARG_PATH] else: path = "/" if not path.startswith("/"): # relative path path = "{}{}".format(cwd, path) if not path.endswith("/"): path = path + '/' col = Collection.find(path) if not col: self.print_error(MSG_COLL_NOT_EXIST.format(path)) return session['cwd'] = path # Save the client for persistent use self.save_session(session) return 0
def read_data_object(self, path): """Read a resource""" resource = Resource.find(path) if not resource: collection = Collection.find(path) if collection: self.logger.info( u"Fail to read a resource at '{}', test if it's a collection" .format(path)) return self.read_container(path) else: self.logger.info( u"Fail to read a resource at '{}'".format(path)) return Response(status=HTTP_404_NOT_FOUND) if not resource.user_can(self.user, "read"): self.logger.warning( u"User {} tried to read resource at '{}'".format( self.user, path)) return Response(status=HTTP_403_FORBIDDEN) cdmi_resource = CDMIResource(resource, self.api_root) if self.http_mode: if cdmi_resource.is_reference(): return self.read_data_object_reference(cdmi_resource) else: return self.read_data_object_http(cdmi_resource) else: return self.read_data_object_cdmi(cdmi_resource)
def get_authorized_actions(self, user): """" Get available actions for a user according to the groups it belongs :param user: The user we want to check :type user: :class:`radon.model.User` :return: the set of actions the user can do :rtype: Set[str] """ # Check permission on the parent container if there's no action # defined at this level acl = self.get_acl_dict() if not acl: from radon.model import Collection parent_container = Collection.find(self.container) return parent_container.get_authorized_actions(user) actions = set([]) for gid in user.groups + ["AUTHENTICATED@"]: if gid in acl: ace = acl[gid] level = acemask_to_str(ace.acemask, True) if level == "read": actions.add("read") elif level == "write": actions.add("write") actions.add("delete") actions.add("edit") elif level == "read/write": actions.add("read") actions.add("write") actions.add("delete") actions.add("edit") return actions
def delete_resource(request, path): """Display the page to delete a resource""" resource = Resource.find(path) if not resource: raise Http404 if not resource.user_can(request.user, "delete"): raise PermissionDenied container = Collection.find(resource.container) if request.method == "POST": resource.delete(username=request.user.name) messages.add_message( request, messages.INFO, "The resource '{}' has been deleted".format(resource.name), ) return redirect("archive:view", path=container.path) # Requires delete on resource ctx = { "resource": resource, "container": container, } return render(request, "archive/resource/delete.html", ctx)
def home(request): """Default view for Activities""" notifications = Notification.recent(10) activities = [] for notif in notifications: tmpl = template.Template(notif["tmpl"]) obj_uuid = notif["object_uuid"] obj = None if notif["object_type"] == OBJ_RESOURCE: obj = Resource.find(obj_uuid) if obj: object_dict = obj.to_dict() else: object_dict = {"name": obj_uuid} elif notif["object_type"] == OBJ_COLLECTION: obj = Collection.find(obj_uuid) if obj: object_dict = obj.to_dict() else: object_dict = {"name": obj_uuid} elif notif["object_type"] == OBJ_USER: obj = User.find(obj_uuid) if obj: object_dict = obj.to_dict() else: # User has been deleted it can't be find by uuid # look in payload of the message to get the name if notif["operation"] in [OP_CREATE, OP_UPDATE]: name = notif["payload"]["post"]["name"] else: # OP_DELETE name = notif["payload"]["pre"]["name"] object_dict = {"name": name} elif notif["object_type"] == OBJ_GROUP: obj = Group.find(obj_uuid) if obj: object_dict = obj.to_dict() else: # User has been deleted it can't be find by uuid # look in payload of the message to get the name if notif["operation"] in [OP_CREATE, OP_UPDATE]: name = notif["payload"]["post"]["name"] else: # OP_DELETE name = notif["payload"]["pre"]["name"] object_dict = {"uuid": obj_uuid, "name": name} user_dict = {} if notif["username"]: user = User.find(notif["username"]) if user: user_dict = user.to_dict() else: user_dict = { 'name': notif["username"], 'email': notif["username"]+ '@radon.org' } variables = {"user": user_dict, "when": notif["when"], "object": object_dict} ctx = template.Context(variables) activities.append({"html": tmpl.render(ctx)}) return render(request, "activity/index.html", {"activities": activities})
def get_parentID(self): """Conditional Object ID of the parent container object We don't support objects only accessible by ID so this is mandatory""" parent_path = self.collection.container if self.collection.is_root: parent_path = "/" parent = Collection.find(parent_path) return parent.uuid
def test_update(): # Create a new collection with a random name coll_name = uuid.uuid4().hex coll1 = Collection.create('/', coll_name) coll1.update(username="******") coll1.delete()
def put_data_object(self, path): """Put a data object to a specific collection""" # Check if a collection with the name exists collection = Collection.find(path) if collection: # Try to put a data_object when a collection of the same name # already exists self.logger.info( "Impossible to create a new resource, the collection '{}' already exists, try to update it" .format(path)) return self.put_container(path) parent, name = split(path) # Check if the resource already exists resource = Resource.find(path) # Check permissions if resource: # Update Resource if not resource.user_can(self.user, "edit"): self.logger.warning( "User {} tried to modify resource at '{}'".format( self.user, path)) return Response(status=HTTP_403_FORBIDDEN) else: # Create Resource parent_collection = Collection.find(parent) if not parent_collection: self.logger.info( "Fail to create a resource at '{}', collection doesn't exist" .format(path)) return Response(status=HTTP_404_NOT_FOUND) # Check if user can create a new resource in the collection if not parent_collection.user_can(self.user, "write"): self.logger.warning( "User {} tried to create new resource at '{}'".format( self.user, path)) return Response(status=HTTP_403_FORBIDDEN) # All permissions are checked, we can proceed to create/update if self.http_mode: return self.put_data_object_http(parent, name, resource) else: return self.put_data_object_cdmi(parent, name, resource)
def is_collection(path): """Check if the collection exists :param path: The path of the collection in Radon :type path: str :return: a boolean :rtype: bool """ from radon.model import Collection return Collection.find(path) is not None
def ls(self, args): """List a container.""" session = self.get_session() cwd = session.get('cwd', '/') if args[ARG_PATH]: path = args[ARG_PATH] if not path.startswith("/"): # relative path path = "{}{}".format(cwd, path) else: # Get the current working dir from the session file path = cwd # --v option specify the version we want to display if args["--v"]: version = int(args["--v"]) col = Collection.find(path, version) else: col = Collection.find(path) if not col: self.print_error(MSG_COLL_NOT_EXIST.format(path)) return # Display name of the collection if path == "/": print("Root:") else: print("{}:".format(col.path)) # Display Acl if args["-a"]: acl = col.get_acl_dict() if acl: for gid in acl: print(" ACL - {}: {}".format( gid, acl[gid])) else: print(" ACL: No ACE defined") # Display child c_colls, c_objs = col.get_child() for child in sorted(c_colls, key=methodcaller("lower")): print(self.terminal.blue(child)) for child in sorted(c_objs, key=methodcaller("lower")): print(child)
def edit_collection(request, path): """Display the form to edit an existing collection""" coll = Collection.find(path) if not coll: raise Http404 if not coll.user_can(request.user, "edit"): raise PermissionDenied if request.method == "POST": form = CollectionForm(request.POST) if form.is_valid(): metadata = {} for k, v in json.loads(form.cleaned_data["metadata"]): if k in metadata: if isinstance(metadata[k], list): metadata[k].append(v) else: metadata[k] = [metadata[k], v] else: metadata[k] = v try: data = form.cleaned_data coll.update(metadata=metadata, username=request.user.name) coll.create_acl_list(data["read_access"], data["write_access"]) return redirect("archive:view", path=coll.path) except CollectionConflictError: messages.add_message( request, messages.ERROR, "That name is in use in the current collection", ) else: md = coll.get_cdmi_user_meta() metadata = json.dumps(md) if not md: metadata = '{"":""}' read_access, write_access = coll.get_acl_list() initial_data = { "name": coll.name, "metadata": metadata, "read_access": read_access, "write_access": write_access, } form = CollectionForm(initial=initial_data) groups = Group.objects.all() return render( request, "archive/edit.html", {"form": form, "collection": coll, "groups": groups}, )
def test_update(): coll_name = uuid.uuid4().hex coll = Collection.create('/', coll_name) myFactory = Faker() content = myFactory.text() chk = hashlib.sha224(content.encode()).hexdigest() metadata = {"test": "val", "test_json": '["t", "e", "s", "t"]'} # Simple update do = DataObject.create(content.encode()) resc_name = uuid.uuid4().hex resc = Resource.create(coll.path, resc_name, url="{}{}".format(cfg.protocol_cassandra, do.uuid)) resc.update(mimetype="text/plain") resc = Resource.find(resc.path) assert resc.get_mimetype() == "text/plain" resc.delete() # update with metadata and a username for notification do = DataObject.create(content.encode()) resc_name = uuid.uuid4().hex resc = Resource.create(coll.path, resc_name, url="{}{}".format(cfg.protocol_cassandra, do.uuid)) resc.update(username="******", metadata=metadata) resc = Resource.find(resc.path) assert resc.get_cdmi_user_meta()['test'] == metadata['test'] resc.delete() # Update with a change of url (new dataObject) do = DataObject.create(content.encode()) resc_name = uuid.uuid4().hex resc = Resource.create(coll.path, resc_name, url="{}{}".format(cfg.protocol_cassandra, do.uuid)) DataObject.delete_id(do.uuid) do = DataObject.create(content.encode()) resc.update(object_url="{}{}".format(cfg.protocol_cassandra, do.uuid)) resc = Resource.find(resc.path) assert resc.get_size() == len(content) resc.delete() # Update for a reference resc = Resource.create(coll.path, resc_name, url="http://www.google.fr") resc.update(mimetype="text/plain") resc = Resource.find(resc.path) # Mimetype stored in the tree entry assert resc.get_mimetype() == "text/plain" resc.delete() coll.delete()
def test_metadata(): coll_name = uuid.uuid4().hex coll = Collection.create('/', coll_name) myFactory = Faker() content = myFactory.text() chk = hashlib.sha224(content.encode()).hexdigest() metadata = {"test": "val", "test_list": ['t', 'e', 's', 't']} # Checksum passed at creation do = DataObject.create(content.encode()) resc_name = uuid.uuid4().hex resc = Resource.create(coll.path, resc_name, url="{}{}".format(cfg.protocol_cassandra, do.uuid), metadata=metadata) resc = Resource.find(resc.path) meta_dict = resc.get_cdmi_user_meta() assert meta_dict['test'] == metadata['test'] assert meta_dict['test_list'] == metadata['test_list'] sys_meta = resc.get_cdmi_sys_meta() assert "radon_create_ts" in sys_meta assert "radon_modify_ts" in sys_meta resc.delete() # Checksum passed at creation do = DataObject.create(content.encode()) resc_name = uuid.uuid4().hex resc = Resource.create(coll.path, resc_name, url="{}{}".format(cfg.protocol_cassandra, do.uuid), mimetype="text/plain") resc = Resource.find(resc.path) assert resc.get_mimetype() == "text/plain" resc.delete() # Mimetype for a reference resc = Resource.create(coll.path, resc_name, url="http://www.google.fr", mimetype="text/plain") resc = Resource.find(resc.path) # Mimetype stored in the tree entry assert resc.get_mimetype() == "text/plain" resc.delete() coll.delete()
def crud_id(request, obj_id): """Call the correct method from a call with the uuid of a resource/collection""" # The URL should end with a '/' obj_id = obj_id.replace("/", "") collection = Collection.find_by_uuid(obj_id) if collection: return redirect("rest_cdmi:api_cdmi", path=collection.path()) else: resource = Resource.find_by_uuid(obj_id) if resource: return redirect("rest_cdmi:api_cdmi", path=resource.path()) else: return Response(status=HTTP_404_NOT_FOUND)
def setup_module(): cfg.dse_keyspace = TEST_KEYSPACE initialise() connect() create_tables() create_root() try: coll = Collection.create("/", "coll1") ref1 = Resource.create("/", "test.url", url=TEST_URL) resc = Resource.create("/coll1", "test.txt") ref2 = Resource.create("/coll1", "test.url", url=TEST_URL) except: # If collections or resources already exist pass
def view_resource(request, path): """Display the page for a resource in the archive""" resource = Resource.find(path) if not resource: raise Http404() if not resource.user_can(request.user, "read"): raise PermissionDenied container = Collection.find(resource.container) if not container: # TODO: the container has to be there. If not it may be a network # issue with Cassandra so we try again before raising an error to the # user container = Collection.find(resource.container) if not container: return HttpResponse( status=408, content="Unable to find parent container '{}'".format( resource.container ), ) paths = [] full = "/" for pth in container.path.split("/"): if not pth: continue full = u"{}{}/".format(full, pth) paths.append((pth, full)) ctx = { "resource": resource.full_dict(request.user), "container": container, "container_path": container.path, "collection_paths": paths, } return render(request, "archive/resource/view.html", ctx)
def view_collection(request, path='/'): """Display the page which shows the subcollections/resources of a collection""" if not path: path = "/" collection = Collection.find(path) if not collection: raise Http404() if not collection.user_can(request.user, "read") and not collection.is_root: # If the user can't read, then return 404 rather than 403 so that # we don't leak information. raise Http404() paths = [] full = "/" for p in collection.path.split("/"): if not p: continue full = u"{}{}/".format(full, p) paths.append((p, full)) children_c, children_r = collection.get_child(False) children_c.sort(key=lambda x: x.lower()) children_r.sort(key=lambda x: x.lower()) ctx = { "collection": collection.to_dict(request.user), "children_c": [ Collection.find(merge(path, c)).to_dict(request.user) for c in children_c ], "children_r": [ Resource.find(merge(path, c)).simple_dict(request.user) for c in children_r ], "collection_paths": paths, "empty": len(children_c) + len(children_r) == 0, } return render(request, "archive/index.html", ctx)
def test_create_acl_fail(mocker): list_read = ['grp1'] list_write = ['grp1'] user2 = User.find("user2") # Create a new collection with a random name coll_name = uuid.uuid4().hex + "/" coll = Collection.create('/', coll_name) mocker.patch('radon.model.collection.acemask_to_str', return_value="wrong_oper") coll.create_acl_list(list_read, list_write) coll = Collection.find('/{}'.format(coll_name)) # Test get_acl_list wrong operation name acl_list = coll.get_acl_list() assert acl_list == ([], []) coll.delete() # Check authorized actions for root coll = Collection.find("/") mocker.patch.object(Collection, 'get_acl_dict', return_value=None) assert coll.get_authorized_actions(user2) == set([])