Skip to content

Commit ff7714a

Browse files
d-w-moorealanking
authored andcommitted
[#420][#422] present appropriate iRODSAccess.codes, in sorted order
(The sorting was an issue only in Python2, where the dict structure doesn't maintain keys in the order introduced.) For added convenience, we also add some smarts to the iRODSAccess class, giving it the ability to function as a dictionary: iRODSAccess.keys() -> ['null', ... 'own'] iRODSAccess['own'] -> 1200. A new iRODSSession property, available_permissions, returns either this class or the legacy _iRODSAccess_pre_4_3_0, thus session.available_permissions.keys() returns a list of the permission strings (sorted according to the numeric value, aka privilege level) which are applicable for the connected server.
1 parent fafd75a commit ff7714a

5 files changed

Lines changed: 102 additions & 13 deletions

File tree

README.rst

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,32 @@ users (those whose User.type is 'rodsadmin' or 'rodsuser'). The empty group ('em
11081108
members, so it doesn't show up in our final list.
11091109

11101110

1111+
iRODS Permissions (ACLs)
1112+
------------------------
1113+
1114+
The :code:`iRODSAccess` class offers a convenient dictionary interface mapping iRODS permission
1115+
strings to the corresponding integer codes:
1116+
1117+
>>> from irods.access import iRODSAccess
1118+
>>> iRODSAccess.keys()
1119+
['null', 'read_metadata', 'read_object', 'create_metadata', 'modify_metadata', 'delete_metadata', 'create_object', 'modify_object', 'delete_object', 'own']
1120+
>>> WRITE = iRODSAccess.to_int('modify_object')
1121+
1122+
Armed with that, we can then query on all data objects with ACL's that allow our user to write them:
1123+
1124+
>>> from irods.models import (DataObject, User, DataAccess)
1125+
>>> data_objects_writable = list(session.query(DataObject, User, DataAccess).filter(User.name == session.username, DataAccess.type >= WRITE))
1126+
1127+
Finally, we can also access the list of permissions available through a given session object via the :code:`available_permissions` property.
1128+
Note that -- in keeping with changes in iRODS server 4.3 -- the permissions list will be longer, as appropriate, for session objects connected to
1129+
the more recent servers; and also that the embedded spaces in some 4.2 permission strings will be replaced by underscores in 4.3 and later.
1130+
1131+
>>> session.server_version
1132+
(4, 2, 11)
1133+
>>> session.available_permissions.items()
1134+
[('null', 1000), ('read object', 1050), ('modify object', 1120), ('own', 1200)]
1135+
1136+
11111137
Getting and setting permissions
11121138
-------------------------------
11131139

@@ -1130,12 +1156,6 @@ If we then want to downgrade those permissions to read-only, we can do the follo
11301156
A call to :code:`session.acls.get(c)` -- with :code:`c` being the result of :code:`sessions.collections.get(c[Collection.name])` --
11311157
would then verify the desired change had taken place (as well as list all ACLs stored in the catalog for that collection).
11321158

1133-
We can also query on access type using its numeric value, which will seem more natural to some:
1134-
1135-
>>> OWN = 1200; MODIFY = 1120 ; READ = 1050
1136-
>>> from irods.models import DataAccess, DataObject, User
1137-
>>> data_objects_writable = list(session.query(DataObject,DataAccess,User)).filter(User.name=='alice', DataAccess.type >= MODIFY)
1138-
11391159
One last note on permissions: The older access manager, :code:`<session>.permissions`, produced inconsistent results when the :code:`get()`
11401160
method was invoked with the parameter :code:`report_raw_acls` set (or defaulting) to :code:`False`. Specifically, collections would exhibit the
11411161
"non-raw-ACL" behavior of reporting individual member users' permissions as a by-product of group ACLs, whereas data objects would not.

irods/access.py

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
11
import collections
2+
import copy
3+
import six
24

3-
class iRODSAccess(object):
5+
class _Access_LookupMeta(type):
6+
def __getitem__(self, key): return self.codes[key]
7+
def keys(self): return list(self.codes.keys())
8+
def values(self): return list(self.codes[k] for k in self.codes.keys())
9+
def items(self): return list(zip(self.keys(),self.values()))
410

5-
codes = { key:value for key,value in dict( # copied from iRODS server code:
6-
null = 1000, # server/core/include/irods/catalog_utilities.hpp
11+
class iRODSAccess(six.with_metaclass(_Access_LookupMeta)):
12+
13+
@classmethod
14+
def to_int(cls,key):
15+
return cls.codes[key]
16+
17+
@classmethod
18+
def to_string(cls,key):
19+
return cls.strings[key]
20+
21+
codes = collections.OrderedDict((key_,value_) for key_,value_ in sorted(dict(
22+
# copied from iRODS source code in
23+
# ./server/core/include/irods/catalog_utilities.hpp:
24+
null = 1000,
725
execute = 1010,
826
read_annotation = 1020,
927
read_system_metadata = 1030,
@@ -21,7 +39,7 @@ class iRODSAccess(object):
2139
delete_token = 1150,
2240
curate = 1160,
2341
own = 1200
24-
).items() if key in (
42+
).items(),key=lambda _:_[1]) if key_ in (
2543
# These are copied from ichmod help text.
2644
'own',
2745
'delete_object',
@@ -34,9 +52,9 @@ class iRODSAccess(object):
3452
'read_metadata',
3553
'null'
3654
)
37-
}
55+
)
3856

39-
strings = collections.OrderedDict(sorted((number,string) for string,number in codes.items()))
57+
strings = collections.OrderedDict((number,string) for string,number in codes.items())
4058

4159
def __init__(self, access_name, path, user_name='', user_zone='', user_type=None):
4260
self.access_name = access_name
@@ -45,10 +63,31 @@ def __init__(self, access_name, path, user_name='', user_zone='', user_type=None
4563
self.user_zone = user_zone
4664
self.user_type = user_type
4765

66+
def copy(self, decanonicalize = False):
67+
other = copy.deepcopy(self)
68+
if decanonicalize:
69+
replacement_string = { 'read object':'read',
70+
'read_object':'read',
71+
'modify object':'write',
72+
'modify_object':'write'}.get(self.access_name)
73+
other.access_name = replacement_string if replacement_string is not None \
74+
else self.access_name
75+
return other
76+
4877
def __repr__(self):
4978
object_dict = vars(self)
5079
access_name = self.access_name.replace(' ','_')
5180
user_type_hint = ("({user_type})" if object_dict.get('user_type') is not None else "").format(**object_dict)
5281
return "<iRODSAccess {0} {path} {user_name}{1} {user_zone}>".format(access_name,
5382
user_type_hint,
5483
**object_dict)
84+
85+
class _iRODSAccess_pre_4_3_0(iRODSAccess):
86+
codes = collections.OrderedDict(
87+
(key.replace('_',' '),value) for key,value in iRODSAccess.codes.items() if key in (
88+
'own',
89+
'write', 'modify_object',
90+
'read', 'read_object',
91+
'null'
92+
))
93+
strings = collections.OrderedDict((number,string) for string,number in codes.items())

irods/manager/access_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def set(self, acl, recursive=False, admin=False, **kw):
135135
userName_=acl.user_name
136136
zone_=acl.user_zone
137137
if acl.access_name.endswith('inherit'): zone_ = userName_ = ''
138-
138+
acl = acl.copy(decanonicalize = True)
139139
message_body = ModAclRequest(
140140
recursiveFlag=int(recursive),
141141
accessLevel='{prefix}{access_name}'.format(prefix=prefix, **vars(acl)),

irods/session.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ def auth_file (self):
5858
# method has a default parameter of report_raw_acls = True, so it enumerates
5959
# ACLs exactly in the manner of "ils -A".
6060

61+
@property
62+
def available_permissions(self):
63+
from irods.access import (iRODSAccess,_iRODSAccess_pre_4_3_0)
64+
try:
65+
self.__access
66+
except AttributeError:
67+
self.__access = _iRODSAccess_pre_4_3_0 if self.server_version < (4,3) else iRODSAccess
68+
return self.__access
69+
6170
@property
6271
def acls(self):
6372
class ACLs(self.permissions.__class__):

irods/test/access_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,27 @@ def test_set_inherit_acl(self):
8080
c = self.sess.collections.get(self.coll_path)
8181
self.assertFalse(c.inheritance)
8282

83+
def test_available_permissions__420_422(self):
84+
# Cycle through access levels (strings available via session.available_permissions) and test, with
85+
# a string compare, that the "set" access level matches the "get" access level.
86+
user = data = None
87+
try:
88+
user = self.sess.users.create('bob','rodsuser')
89+
data = self.sess.data_objects.create('{}/obj_422'.format(helpers.home_collection(self.sess)))
90+
permission_strings = self.sess.available_permissions.keys()
91+
for perm in permission_strings:
92+
access = iRODSAccess(perm, data.path, 'bob')
93+
self.sess.acls.set( access )
94+
a = [acl for acl in self.sess.acls.get( data ) if acl.user_name == 'bob']
95+
if perm == 'null':
96+
self.assertEqual(len(a),0)
97+
else:
98+
self.assertEqual(len(a),1)
99+
self.assertEqual(access.access_name,a[0].access_name)
100+
finally:
101+
if user: user.remove()
102+
if data: data.unlink(force=True)
103+
83104
def test_set_inherit_and_test_sub_objects (self):
84105
DEPTH = 3
85106
OBJ_PER_LVL = 1

0 commit comments

Comments
 (0)