mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-21 05:53:12 +00:00
dirnode.py: Fix #1034 (MetadataSetter does not enforce restriction on setting 'tahoe' subkeys), and expose the metadata updater for use by SFTP. Also, support diminishing a child cap to read-only if 'no-write' is set in the metadata.
This commit is contained in:
parent
027e7701bd
commit
53f7d2c7fe
@ -24,76 +24,11 @@ from pycryptopp.cipher.aes import AES
|
|||||||
from allmydata.util.dictutil import AuxValueDict
|
from allmydata.util.dictutil import AuxValueDict
|
||||||
|
|
||||||
|
|
||||||
# TODO: {Deleter,MetadataSetter,Adder}.modify all start by unpacking the
|
def update_metadata(metadata, new_metadata, now):
|
||||||
# contents and end by repacking them. It might be better to apply them to
|
"""Updates 'metadata' in-place with the information in 'new_metadata'.
|
||||||
# the unpacked contents.
|
Timestamps are set according to the time 'now'."""
|
||||||
|
|
||||||
class Deleter:
|
if metadata is None:
|
||||||
def __init__(self, node, name, must_exist=True):
|
|
||||||
self.node = node
|
|
||||||
self.name = name
|
|
||||||
self.must_exist = True
|
|
||||||
def modify(self, old_contents, servermap, first_time):
|
|
||||||
children = self.node._unpack_contents(old_contents)
|
|
||||||
if self.name not in children:
|
|
||||||
if first_time and self.must_exist:
|
|
||||||
raise NoSuchChildError(self.name)
|
|
||||||
self.old_child = None
|
|
||||||
return None
|
|
||||||
self.old_child, metadata = children[self.name]
|
|
||||||
del children[self.name]
|
|
||||||
new_contents = self.node._pack_contents(children)
|
|
||||||
return new_contents
|
|
||||||
|
|
||||||
|
|
||||||
class MetadataSetter:
|
|
||||||
def __init__(self, node, name, metadata):
|
|
||||||
self.node = node
|
|
||||||
self.name = name
|
|
||||||
self.metadata = metadata
|
|
||||||
|
|
||||||
def modify(self, old_contents, servermap, first_time):
|
|
||||||
children = self.node._unpack_contents(old_contents)
|
|
||||||
if self.name not in children:
|
|
||||||
raise NoSuchChildError(self.name)
|
|
||||||
children[self.name] = (children[self.name][0], self.metadata)
|
|
||||||
new_contents = self.node._pack_contents(children)
|
|
||||||
return new_contents
|
|
||||||
|
|
||||||
|
|
||||||
class Adder:
|
|
||||||
def __init__(self, node, entries=None, overwrite=True):
|
|
||||||
self.node = node
|
|
||||||
if entries is None:
|
|
||||||
entries = {}
|
|
||||||
precondition(isinstance(entries, dict), entries)
|
|
||||||
self.entries = entries
|
|
||||||
self.overwrite = overwrite
|
|
||||||
|
|
||||||
def set_node(self, name, node, metadata):
|
|
||||||
precondition(isinstance(name, unicode), name)
|
|
||||||
precondition(IFilesystemNode.providedBy(node), node)
|
|
||||||
self.entries[name] = (node, metadata)
|
|
||||||
|
|
||||||
def modify(self, old_contents, servermap, first_time):
|
|
||||||
children = self.node._unpack_contents(old_contents)
|
|
||||||
now = time.time()
|
|
||||||
for (name, (child, new_metadata)) in self.entries.iteritems():
|
|
||||||
precondition(isinstance(name, unicode), name)
|
|
||||||
precondition(IFilesystemNode.providedBy(child), child)
|
|
||||||
|
|
||||||
# Strictly speaking this is redundant because we would raise the
|
|
||||||
# error again in pack_children.
|
|
||||||
child.raise_error()
|
|
||||||
|
|
||||||
if name in children:
|
|
||||||
if not self.overwrite:
|
|
||||||
raise ExistingChildError("child '%s' already exists" % name)
|
|
||||||
|
|
||||||
if self.overwrite == "only-files" and IDirectoryNode.providedBy(children[name][0]):
|
|
||||||
raise ExistingChildError("child '%s' already exists" % name)
|
|
||||||
metadata = children[name][1].copy()
|
|
||||||
else:
|
|
||||||
metadata = {"ctime": now,
|
metadata = {"ctime": now,
|
||||||
"mtime": now,
|
"mtime": now,
|
||||||
"tahoe": {
|
"tahoe": {
|
||||||
@ -130,10 +65,97 @@ class Adder:
|
|||||||
sysmd["linkcrtime"] = now
|
sysmd["linkcrtime"] = now
|
||||||
sysmd["linkmotime"] = now
|
sysmd["linkmotime"] = now
|
||||||
|
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: {Deleter,MetadataSetter,Adder}.modify all start by unpacking the
|
||||||
|
# contents and end by repacking them. It might be better to apply them to
|
||||||
|
# the unpacked contents.
|
||||||
|
|
||||||
|
class Deleter:
|
||||||
|
def __init__(self, node, name, must_exist=True):
|
||||||
|
self.node = node
|
||||||
|
self.name = name
|
||||||
|
self.must_exist = True
|
||||||
|
def modify(self, old_contents, servermap, first_time):
|
||||||
|
children = self.node._unpack_contents(old_contents)
|
||||||
|
if self.name not in children:
|
||||||
|
if first_time and self.must_exist:
|
||||||
|
raise NoSuchChildError(self.name)
|
||||||
|
self.old_child = None
|
||||||
|
return None
|
||||||
|
self.old_child, metadata = children[self.name]
|
||||||
|
del children[self.name]
|
||||||
|
new_contents = self.node._pack_contents(children)
|
||||||
|
return new_contents
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataSetter:
|
||||||
|
def __init__(self, node, name, metadata, create_readonly_node=None):
|
||||||
|
self.node = node
|
||||||
|
self.name = name
|
||||||
|
self.metadata = metadata
|
||||||
|
self.create_readonly_node = create_readonly_node
|
||||||
|
|
||||||
|
def modify(self, old_contents, servermap, first_time):
|
||||||
|
children = self.node._unpack_contents(old_contents)
|
||||||
|
name = self.name
|
||||||
|
if name not in children:
|
||||||
|
raise NoSuchChildError(name)
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
metadata = update_metadata(children[name][1].copy(), self.metadata, now)
|
||||||
|
child = children[name][0]
|
||||||
|
if self.create_readonly_node and metadata and metadata.get('no-write', False):
|
||||||
|
child = self.create_readonly_node(child, name)
|
||||||
|
|
||||||
children[name] = (child, metadata)
|
children[name] = (child, metadata)
|
||||||
new_contents = self.node._pack_contents(children)
|
new_contents = self.node._pack_contents(children)
|
||||||
return new_contents
|
return new_contents
|
||||||
|
|
||||||
|
|
||||||
|
class Adder:
|
||||||
|
def __init__(self, node, entries=None, overwrite=True, create_readonly_node=None):
|
||||||
|
self.node = node
|
||||||
|
if entries is None:
|
||||||
|
entries = {}
|
||||||
|
precondition(isinstance(entries, dict), entries)
|
||||||
|
self.entries = entries
|
||||||
|
self.overwrite = overwrite
|
||||||
|
self.create_readonly_node = create_readonly_node
|
||||||
|
|
||||||
|
def set_node(self, name, node, metadata):
|
||||||
|
precondition(isinstance(name, unicode), name)
|
||||||
|
precondition(IFilesystemNode.providedBy(node), node)
|
||||||
|
self.entries[name] = (node, metadata)
|
||||||
|
|
||||||
|
def modify(self, old_contents, servermap, first_time):
|
||||||
|
children = self.node._unpack_contents(old_contents)
|
||||||
|
now = time.time()
|
||||||
|
for (name, (child, new_metadata)) in self.entries.iteritems():
|
||||||
|
precondition(isinstance(name, unicode), name)
|
||||||
|
precondition(IFilesystemNode.providedBy(child), child)
|
||||||
|
|
||||||
|
# Strictly speaking this is redundant because we would raise the
|
||||||
|
# error again in pack_children.
|
||||||
|
child.raise_error()
|
||||||
|
|
||||||
|
metadata = None
|
||||||
|
if name in children:
|
||||||
|
if not self.overwrite:
|
||||||
|
raise ExistingChildError("child '%s' already exists" % name)
|
||||||
|
|
||||||
|
if self.overwrite == "only-files" and IDirectoryNode.providedBy(children[name][0]):
|
||||||
|
raise ExistingChildError("child '%s' already exists" % name)
|
||||||
|
metadata = children[name][1].copy()
|
||||||
|
|
||||||
|
if self.create_readonly_node and metadata and metadata.get('no-write', False):
|
||||||
|
child = self.create_readonly_node(child, name)
|
||||||
|
|
||||||
|
children[name] = (child, update_metadata(metadata, new_metadata, now))
|
||||||
|
new_contents = self.node._pack_contents(children)
|
||||||
|
return new_contents
|
||||||
|
|
||||||
def _encrypt_rw_uri(filenode, rw_uri):
|
def _encrypt_rw_uri(filenode, rw_uri):
|
||||||
assert isinstance(rw_uri, str)
|
assert isinstance(rw_uri, str)
|
||||||
writekey = filenode.get_writekey()
|
writekey = filenode.get_writekey()
|
||||||
@ -248,6 +270,11 @@ class DirectoryNode:
|
|||||||
node.raise_error()
|
node.raise_error()
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
def _create_readonly_node(self, node, name):
|
||||||
|
if not node.is_unknown() and node.is_readonly():
|
||||||
|
return node
|
||||||
|
return self._create_and_validate_node(None, node.get_readonly_uri(), name=name)
|
||||||
|
|
||||||
def _unpack_contents(self, data):
|
def _unpack_contents(self, data):
|
||||||
# the directory is serialized as a list of netstrings, one per child.
|
# the directory is serialized as a list of netstrings, one per child.
|
||||||
# Each child is serialized as a list of four netstrings: (name, ro_uri,
|
# Each child is serialized as a list of four netstrings: (name, ro_uri,
|
||||||
@ -407,7 +434,8 @@ class DirectoryNode:
|
|||||||
if self.is_readonly():
|
if self.is_readonly():
|
||||||
return defer.fail(NotWriteableError())
|
return defer.fail(NotWriteableError())
|
||||||
assert isinstance(metadata, dict)
|
assert isinstance(metadata, dict)
|
||||||
s = MetadataSetter(self, name, metadata)
|
s = MetadataSetter(self, name, metadata,
|
||||||
|
create_readonly_node=self._create_readonly_node)
|
||||||
d = self._node.modify(s.modify)
|
d = self._node.modify(s.modify)
|
||||||
d.addCallback(lambda res: self)
|
d.addCallback(lambda res: self)
|
||||||
return d
|
return d
|
||||||
@ -463,7 +491,8 @@ class DirectoryNode:
|
|||||||
|
|
||||||
def set_children(self, entries, overwrite=True):
|
def set_children(self, entries, overwrite=True):
|
||||||
# this takes URIs
|
# this takes URIs
|
||||||
a = Adder(self, overwrite=overwrite)
|
a = Adder(self, overwrite=overwrite,
|
||||||
|
create_readonly_node=self._create_readonly_node)
|
||||||
for (name, e) in entries.iteritems():
|
for (name, e) in entries.iteritems():
|
||||||
assert isinstance(name, unicode)
|
assert isinstance(name, unicode)
|
||||||
if len(e) == 2:
|
if len(e) == 2:
|
||||||
@ -498,7 +527,8 @@ class DirectoryNode:
|
|||||||
return defer.fail(NotWriteableError())
|
return defer.fail(NotWriteableError())
|
||||||
assert isinstance(name, unicode)
|
assert isinstance(name, unicode)
|
||||||
assert IFilesystemNode.providedBy(child), child
|
assert IFilesystemNode.providedBy(child), child
|
||||||
a = Adder(self, overwrite=overwrite)
|
a = Adder(self, overwrite=overwrite,
|
||||||
|
create_readonly_node=self._create_readonly_node)
|
||||||
a.set_node(name, child, metadata)
|
a.set_node(name, child, metadata)
|
||||||
d = self._node.modify(a.modify)
|
d = self._node.modify(a.modify)
|
||||||
d.addCallback(lambda res: child)
|
d.addCallback(lambda res: child)
|
||||||
@ -508,7 +538,8 @@ class DirectoryNode:
|
|||||||
precondition(isinstance(entries, dict), entries)
|
precondition(isinstance(entries, dict), entries)
|
||||||
if self.is_readonly():
|
if self.is_readonly():
|
||||||
return defer.fail(NotWriteableError())
|
return defer.fail(NotWriteableError())
|
||||||
a = Adder(self, entries, overwrite=overwrite)
|
a = Adder(self, entries, overwrite=overwrite,
|
||||||
|
create_readonly_node=self._create_readonly_node)
|
||||||
d = self._node.modify(a.modify)
|
d = self._node.modify(a.modify)
|
||||||
d.addCallback(lambda res: self)
|
d.addCallback(lambda res: self)
|
||||||
return d
|
return d
|
||||||
@ -551,7 +582,8 @@ class DirectoryNode:
|
|||||||
d = self._nodemaker.create_immutable_directory(initial_children)
|
d = self._nodemaker.create_immutable_directory(initial_children)
|
||||||
def _created(child):
|
def _created(child):
|
||||||
entries = {name: (child, None)}
|
entries = {name: (child, None)}
|
||||||
a = Adder(self, entries, overwrite=overwrite)
|
a = Adder(self, entries, overwrite=overwrite,
|
||||||
|
create_readonly_node=self._create_readonly_node)
|
||||||
d = self._node.modify(a.modify)
|
d = self._node.modify(a.modify)
|
||||||
d.addCallback(lambda res: child)
|
d.addCallback(lambda res: child)
|
||||||
return d
|
return d
|
||||||
|
@ -894,11 +894,14 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
|||||||
|
|
||||||
d.addCallback(lambda res:
|
d.addCallback(lambda res:
|
||||||
n.set_metadata_for(u"child",
|
n.set_metadata_for(u"child",
|
||||||
{"tags": ["web2.0-compatible"]}))
|
{"tags": ["web2.0-compatible"], "tahoe": {"bad": "mojo"}}))
|
||||||
d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
|
d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
|
||||||
d.addCallback(lambda metadata:
|
def _check_metadata(md):
|
||||||
self.failUnlessEqual(metadata,
|
self.failUnless("tags" in md, md)
|
||||||
{"tags": ["web2.0-compatible"]}))
|
self.failUnlessEqual(md["tags"], ["web2.0-compatible"])
|
||||||
|
self.failUnless("tahoe" in md, md)
|
||||||
|
self.failIf("bad" in md["tahoe"], md)
|
||||||
|
d.addCallback(_check_metadata)
|
||||||
|
|
||||||
def _start(res):
|
def _start(res):
|
||||||
self._start_timestamp = time.time()
|
self._start_timestamp = time.time()
|
||||||
|
Loading…
Reference in New Issue
Block a user