Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-backend / pithos / backends / lib / sqlalchemy / permissions.py @ 87835e94

History | View | Annotate | Download (10 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from sqlalchemy.sql import select, literal, or_, and_
35
from sqlalchemy.sql.expression import join, union
36

    
37
from xfeatures import XFeatures
38
from groups import Groups
39
from public import Public
40
from node import Node
41
from collections import defaultdict
42

    
43
from dbworker import ESCAPE_CHAR
44

    
45

    
46
READ = 0
47
WRITE = 1
48

    
49

    
50
class Permissions(XFeatures, Groups, Public, Node):
51

    
52
    def __init__(self, **params):
53
        XFeatures.__init__(self, **params)
54
        Groups.__init__(self, **params)
55
        Public.__init__(self, **params)
56
        Node.__init__(self, **params)
57

    
58
    def access_grant(self, path, access, members=()):
59
        """Grant members with access to path.
60
           Members can also be '*' (all),
61
           or some group specified as 'owner:group'."""
62

    
63
        if not members:
64
            return
65
        feature = self.xfeature_create(path)
66
        self.feature_setmany(feature, access, members)
67

    
68
    def access_set(self, path, permissions):
69
        """Set permissions for path. The permissions dict
70
           maps 'read', 'write' keys to member lists."""
71

    
72
        r = permissions.get('read', [])
73
        w = permissions.get('write', [])
74
        if not r and not w:
75
            self.xfeature_destroy(path)
76
            return
77
        feature = self.xfeature_create(path)
78
        self.feature_clear(feature, READ)
79
        self.feature_clear(feature, WRITE)
80
        if r:
81
            self.feature_setmany(feature, READ, r)
82
        if w:
83
            self.feature_setmany(feature, WRITE, w)
84

    
85
    def access_get_for_bulk(self, perms):
86
        """Get permissions for path."""
87
        allowed = None
88
        d = defaultdict(list)
89
        for value, feature_id, key in perms:
90
            d[key].append(value)
91
        permissions = d
92
        if READ in permissions:
93
            allowed = 0
94
            permissions['read'] = permissions[READ]
95
            del(permissions[READ])
96
        if WRITE in permissions:
97
            allowed = 1
98
            permissions['write'] = permissions[WRITE]
99
            del(permissions[WRITE])
100
        return (permissions, allowed)
101

    
102
    def access_get(self, path):
103
        """Get permissions for path."""
104

    
105
        feature = self.xfeature_get(path)
106
        if not feature:
107
            return {}
108
        permissions = self.feature_dict(feature)
109
        if READ in permissions:
110
            permissions['read'] = permissions[READ]
111
            del(permissions[READ])
112
        if WRITE in permissions:
113
            permissions['write'] = permissions[WRITE]
114
            del(permissions[WRITE])
115
        return permissions
116

    
117
    def access_members(self, path):
118
        feature = self.xfeature_get(path)
119
        if not feature:
120
            return []
121
        permissions = self.feature_dict(feature)
122
        members = set()
123
        members.update(permissions.get(READ, []))
124
        members.update(permissions.get(WRITE, []))
125
        for m in set(members):
126
            parts = m.split(':', 1)
127
            if len(parts) != 2:
128
                continue
129
            user, group = parts
130
            members.remove(m)
131
            members.update(self.group_members(user, group))
132
        return list(members)
133

    
134
    def access_clear(self, path):
135
        """Revoke access to path (both permissions and public)."""
136

    
137
        self.xfeature_destroy(path)
138
        self.public_unset(path)
139

    
140
    def access_clear_bulk(self, paths):
141
        """Revoke access to path (both permissions and public)."""
142

    
143
        self.xfeature_destroy_bulk(paths)
144
        self.public_unset_bulk(paths)
145

    
146
    def access_check(self, path, access, member):
147
        """Return true if the member has this access to the path."""
148

    
149
        feature = self.xfeature_get(path)
150
        if not feature:
151
            return False
152
        members = self.feature_get(feature, access)
153
        if member in members or '*' in members:
154
            return True
155
        for owner, group in self.group_parents(member):
156
            if owner + ':' + group in members:
157
                return True
158
        return False
159

    
160
    def access_check_bulk(self, paths, member):
161
        rows = None
162
        xfeatures_xfeaturevals = self.xfeaturevals.join(self.xfeatures,
163
                onclause=and_(self.xfeatures.c.feature_id ==
164
                    self.xfeaturevals.c.feature_id, self.xfeatures.c.path.in_(paths)))
165
        s = select([self.xfeatures.c.path,
166
                    self.xfeaturevals.c.value,
167
                    self.xfeaturevals.c.feature_id,
168
                    self.xfeaturevals.c.key], from_obj=[xfeatures_xfeaturevals])
169
        r = self.conn.execute(s)
170
        rows = r.fetchall()
171
        r.close()
172
        if rows:
173
            access_check_paths = {}
174
            for path, value, feature_id, key in rows:
175
                try:
176
                    access_check_paths[path].append((value, feature_id, key))
177
                except KeyError:
178
                    access_check_paths[path] = [(value, feature_id, key)]
179
            access_check_paths['group_parents'] = self.group_parents(member)
180
            return access_check_paths
181
        return None
182

    
183
    def access_inherit(self, path):
184
        """Return the paths influencing the access for path."""
185

    
186
#         r = self.xfeature_inherit(path)
187
#         if not r:
188
#             return []
189
#         # Compute valid.
190
#         return [x[0] for x in r if x[0] in valid]
191

    
192
        # Only keep path components.
193
        parts = path.rstrip('/').split('/')
194
        valid = []
195
        for i in range(1, len(parts)):
196
            subp = '/'.join(parts[:i + 1])
197
            valid.append(subp)
198
            if subp != path:
199
                valid.append(subp + '/')
200
        return [x for x in valid if self.xfeature_get(x)]
201

    
202
    def access_inherit_bulk(self, paths):
203
        """Return the paths influencing the access for path."""
204

    
205
        # Only keep path components.
206
        valid = []
207
        for path in paths:
208
            parts = path.rstrip('/').split('/')
209
            for i in range(1, len(parts)):
210
                subp = '/'.join(parts[:i + 1])
211
                valid.append(subp)
212
                if subp != path:
213
                    valid.append(subp + '/')
214
        valid = self.xfeature_get_bulk(valid)
215
        return [x[1] for x in valid]
216

    
217
    def access_list_paths(self, member, prefix=None, include_owned=False,
218
                          include_containers=True):
219
        """Return the list of paths granted to member.
220

221
        Keyword arguments:
222
        prefix -- return only paths starting with prefix (default None)
223
        include_owned -- return also paths owned by member (default False)
224
        include_containers -- return also container paths owned by member
225
                              (default True)
226

227
        """
228

    
229
        xfeatures_xfeaturevals = self.xfeatures.join(self.xfeaturevals)
230

    
231
        selectable = (self.groups.c.owner + ':' + self.groups.c.name)
232
        member_groups = select([selectable.label('value')],
233
                               self.groups.c.member == member)
234

    
235
        members = select([literal(member).label('value')])
236
        any = select([literal('*').label('value')])
237

    
238
        u = union(member_groups, members, any).alias()
239
        inner_join = join(xfeatures_xfeaturevals, u,
240
                          self.xfeaturevals.c.value == u.c.value)
241
        s = select([self.xfeatures.c.path], from_obj=[inner_join]).distinct()
242
        if prefix:
243
            like = lambda p: self.xfeatures.c.path.like(
244
                self.escape_like(p) + '%', escape=ESCAPE_CHAR)
245
            s = s.where(or_(*map(like,
246
                                 self.access_inherit(prefix) or [prefix])))
247
        r = self.conn.execute(s)
248
        l = [row[0] for row in r.fetchall()]
249
        r.close()
250

    
251
        if include_owned:
252
            container_nodes = select(
253
                [self.nodes.c.node],
254
                self.nodes.c.parent == self.node_lookup(member))
255
            condition = self.nodes.c.parent.in_(container_nodes)
256
            if include_containers:
257
                condition = or_(condition,
258
                                self.nodes.c.node.in_(container_nodes))
259
            s = select([self.nodes.c.path], condition)
260
            r = self.conn.execute(s)
261
            l += [row[0] for row in r.fetchall() if row[0] not in l]
262
            r.close()
263
        return l
264

    
265
    def access_list_shared(self, prefix=''):
266
        """Return the list of shared paths."""
267

    
268
        s = select([self.xfeatures.c.path])
269
        like = lambda p: self.xfeatures.c.path.like(
270
            self.escape_like(p) + '%', escape=ESCAPE_CHAR)
271
        s = s.where(or_(*map(like, self.access_inherit(prefix) or [prefix])))
272
        s = s.order_by(self.xfeatures.c.path.asc())
273
        r = self.conn.execute(s)
274
        l = [row[0] for row in r.fetchall()]
275
        r.close()
276
        return l