From: Colin Patrick McCabe Date: Tue, 26 Apr 2011 23:27:14 +0000 (-0700) Subject: obsync: use lxml to parse XML ACL X-Git-Tag: v0.28~139^2~2^2~3 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=512ab30759426a7acacda2281d03a370307606c9;p=ceph.git obsync: use lxml to parse XML ACL Signed-off-by: Colin McCabe --- diff --git a/src/obsync/obsync.py b/src/obsync/obsync.py index f0b7ae60429..ff430ad9b3e 100755 --- a/src/obsync/obsync.py +++ b/src/obsync/obsync.py @@ -20,12 +20,14 @@ from boto.s3.connection import S3Connection from boto.s3.key import Key from optparse import OptionParser from sys import stderr -import boto +from lxml import etree import base64 +import boto import errno import hashlib import mimetypes import os +from StringIO import StringIO import rados import re import shutil @@ -144,6 +146,147 @@ def get_local_acl_file_name(local_name): return os.path.dirname(local_name) + "/." + \ os.path.basename(local_name) + "$acl" +###### ACLs ####### + +# for buckets: allow list +# for object: allow grantee to read object data and metadata +READ = 1 + +# for buckets: allow create, overwrite, or deletion of any object in the bucket +WRITE = 2 + +# for buckets: allow grantee to read the bucket ACL +# for objects: allow grantee to read the object ACL +READ_ACP = 4 + +# for buckets: allow grantee to write the bucket ACL +# for objects: allow grantee to write the object ACL +WRITE_ACP = 8 + +# all of the above +FULL_CONTROL = READ | WRITE | READ_ACP | WRITE_ACP + +ACL_TYPE_CANON_USER = "canon:" +ACL_TYPE_EMAIL_USER = "email:" +ACL_TYPE_GROUP = "group:" + +S3_GROUP_AUTH_USERS = ACL_TYPE_GROUP + "AuthenticatedUsers" +S3_GROUP_ALL_USERS = ACL_TYPE_GROUP + "AllUsers" +S3_GROUP_LOG_DELIVERY = ACL_TYPE_GROUP + "LogDelivery" + +NS = "http://s3.amazonaws.com/doc/2006-03-01/" +NS2 = "http://www.w3.org/2001/XMLSchema-instance" + +class AclGrant(object): + def __init__(self, user_id, display_name, permission): + self.user_id = user_id + self.display_name = display_name + self.permission = permission + +def get_user_type(utype): + for ut in [ ACL_TYPE_CANON_USER, ACL_TYPE_EMAIL_USER, ACL_TYPE_GROUP ]: + if utype[:len(ut)] == ut: + return ut + raise Exception("unknown user type for user %s" % utype) + +def strip_user_type(utype): + for ut in [ ACL_TYPE_CANON_USER, ACL_TYPE_EMAIL_USER, ACL_TYPE_GROUP ]: + if utype[:len(ut)] == ut: + return utype[len(ut):] + raise Exception("unknown user type for user %s" % utype) + +def grantee_attribute_to_user_type(utype): + if (utype == "Canonical User"): + return ACL_TYPE_CANON_USER + elif (utype == "CanonicalUser"): + return ACL_TYPE_CANON_USER + elif (utype == "Group"): + return ACL_TYPE_GROUP + elif (utype == "Email User"): + return ACL_TYPE_EMAIL_USER + elif (utype == "EmailUser"): + return ACL_TYPE_EMAIL_USER + else: + raise Exception("unknown user type for user %s" % utype) + +def user_type_to_attr(t): + if (t == ACL_TYPE_CANON_USER): + return "CanonicalUser" + elif (t == ACL_TYPE_GROUP): + return "Group" + elif (t == ACL_TYPE_EMAIL_USER): + return "EmailUser" + else: + raise Exception("unknown user type %s" % t) + +class AclPolicy(object): + def __init__(self, owner_id, owner_display_name, grants): + self.owner_id = owner_id + self.owner_display_name = owner_display_name + self.grants = grants # list of ACLGrant + @staticmethod + def from_xml(s): + root = etree.parse(StringIO(s)) + owner_id = root.find("{%s}Owner/{%s}ID" % (NS,NS)).text + owner_display_name = root.find("{%s}Owner/{%s}DisplayName" \ + % (NS,NS)).text + grantlist = root.findall("{%s}AccessControlList/{%s}Grant" \ + % (NS,NS)) + grants = [ ] + for g in grantlist: + grantee = g.find("{%s}Grantee" % NS) + user_id = grantee.find("{%s}ID" % NS).text + user_type = grantee.attrib["{%s}type" % NS2] + display_name = grantee.find("{%s}DisplayName" % NS).text + permission = g.find("{%s}Permission" % NS).text + g_user_id = grantee_attribute_to_user_type(user_type) + user_id + grants.append(AclGrant(g_user_id, display_name, permission)) + return AclPolicy(owner_id, owner_display_name, grants) + def to_xml(self): + root = etree.Element("AccessControlPolicy", nsmap={None: NS}) + owner = etree.SubElement(root, "Owner") + id_elem = etree.SubElement(owner, "ID") + id_elem.text = self.owner_id + display_name_elem = etree.SubElement(owner, "DisplayName") + display_name_elem.text = self.owner_display_name + access_control_list = etree.SubElement(root, "AccessControlList") + for g in self.grants: + grant_elem = etree.SubElement(access_control_list, "Grant") + grantee_elem = etree.SubElement(grant_elem, "{%s}Grantee" % NS, + nsmap={None: NS, "xsi" : NS2}) + grantee_elem.set("type", user_type_to_attr(get_user_type(g.user_id))) + user_id_elem = etree.SubElement(grantee_elem, "{%s}ID" % NS) + user_id_elem.text = strip_user_type(g.user_id) + display_name_elem = etree.SubElement(grantee_elem, "{%s}DisplayName" % NS) + display_name_elem.text = g.display_name + permission_elem = etree.SubElement(grant_elem, "{%s}Permission" % NS) + permission_elem.text = g.permission + return etree.tostring(root, encoding="UTF-8") + +def compare_xml(xml1, xml2): + tree1 = etree.parse(StringIO(xml1)) + out1 = etree.tostring(tree1, encoding="UTF-8") + tree2 = etree.parse(StringIO(xml2)) + out2 = etree.tostring(tree2, encoding="UTF-8") + out1 = out1.replace("xsi:type", "type") + out2 = out2.replace("xsi:type", "type") + if out1 != out2: + print "out1 = %s" % out1 + print "out2 = %s" % out2 + raise Exception("compare xml failed") + +# +def test_acl_policy(): + test1_xml = \ +"" + \ +"fooMrFoo" + \ +"*** Owner-Canonical-User-ID ***" + \ +"display-name" + \ +"FULL_CONTROL" + test1 = AclPolicy.from_xml(test1_xml) + compare_xml(test1_xml, test1.to_xml()) + ###### Object ####### class Object(object): def __init__(self, name, md5, size): @@ -632,7 +775,12 @@ parser.add_option("-v", "--verbose", action="store_true", \ dest="verbose", help="be verbose") parser.add_option("-V", "--more-verbose", action="store_true", \ dest="more_verbose", help="be really, really verbose (developer mode)") +parser.add_option("--unit", action="store_true", \ + dest="run_unit_tests", help="run unit tests and quit") (opts, args) = parser.parse_args() +if (opts.run_unit_tests): + test_acl_policy() + sys.exit(0) opts.preserve_acls = not opts.no_preserve_acls if (opts.create and opts.dry_run): raise Exception("You can't run with both --create-dest and --dry-run! \