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:
david-sarah 2010-05-31 21:54:28 -07:00
parent 027e7701bd
commit 53f7d2c7fe
2 changed files with 86 additions and 51 deletions

View File

@ -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

View File

@ -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()