1 | import collections |
---|
2 | from afs import _pts |
---|
3 | |
---|
4 | try: |
---|
5 | SetMixin = collections.MutableSet |
---|
6 | except AttributeError: |
---|
7 | SetMixin = object |
---|
8 | |
---|
9 | class PTRelationSet(SetMixin): |
---|
10 | """Collection class for the groups/members of a PTEntry. |
---|
11 | |
---|
12 | This class, which acts like a set, is actually a view of the |
---|
13 | groups or members associated with a PTS Entry. Changes to this |
---|
14 | class are immediately reflected to the PRDB. |
---|
15 | |
---|
16 | Attributes: |
---|
17 | _ent: The PTEntry whose groups/members this instance |
---|
18 | represents |
---|
19 | _set: If defined, the set of either groups or members for this |
---|
20 | instance's PTEntry |
---|
21 | """ |
---|
22 | def __init__(self, ent): |
---|
23 | """Initialize a PTRelationSet class. |
---|
24 | |
---|
25 | Args: |
---|
26 | ent: The PTEntry this instance should be associated with. |
---|
27 | """ |
---|
28 | super(PTRelationSet, self).__init__() |
---|
29 | |
---|
30 | self._ent = ent |
---|
31 | |
---|
32 | def _loadSet(self): |
---|
33 | """Load the membership/groups for this instance's PTEntry. |
---|
34 | |
---|
35 | If they have not previously been loaded, this method updates |
---|
36 | self._set with the set of PTEntries that are either members of |
---|
37 | this group, or the groups that this entry is a member of. |
---|
38 | """ |
---|
39 | if not hasattr(self, '_set'): |
---|
40 | self._set = set(self._ent._pts.getEntry(m) for m in |
---|
41 | self._ent._pts._ListMembers(self._ent.id)) |
---|
42 | |
---|
43 | def _add(self, elt): |
---|
44 | """Add a new PTEntry to this instance's internal representation. |
---|
45 | |
---|
46 | This method adds a new entry to this instance's set of |
---|
47 | members/groups, but unlike PTRelationSet.add, it doesn't add |
---|
48 | itself to the other instance's set. |
---|
49 | |
---|
50 | Args: |
---|
51 | elt: The element to add. |
---|
52 | """ |
---|
53 | if hasattr(self, '_set'): |
---|
54 | self._set.add(self._ent._pts.getEntry(elt)) |
---|
55 | |
---|
56 | def _discard(self, elt): |
---|
57 | """Remove a PTEntry to this instance's internal representation. |
---|
58 | |
---|
59 | This method removes an entry from this instance's set of |
---|
60 | members/groups, but unlike PTRelationSet.discard, it doesn't |
---|
61 | remove itself from the other instance's set. |
---|
62 | |
---|
63 | Args: |
---|
64 | elt: The element to discard. |
---|
65 | """ |
---|
66 | if hasattr(self, '_set'): |
---|
67 | self._set.discard(self._ent._pts.getEntry(elt)) |
---|
68 | |
---|
69 | def __len__(self): |
---|
70 | """Count the members/groups in this set. |
---|
71 | |
---|
72 | Returns: |
---|
73 | The number of entities in this instance. |
---|
74 | """ |
---|
75 | self._loadSet() |
---|
76 | return len(self._set) |
---|
77 | |
---|
78 | def __iter__(self): |
---|
79 | """Iterate over members/groups in this set |
---|
80 | |
---|
81 | Returns: |
---|
82 | An iterator that loops over the members/groups of this |
---|
83 | set. |
---|
84 | """ |
---|
85 | self._loadSet() |
---|
86 | return iter(self._set) |
---|
87 | |
---|
88 | def __contains__(self, name): |
---|
89 | """Test if a PTEntry is connected to this instance. |
---|
90 | |
---|
91 | If the membership of the group hasn't already been loaded, |
---|
92 | this method takes advantage of the IsAMemberOf lookup to test |
---|
93 | for membership. |
---|
94 | |
---|
95 | This has the convenient advantage of working even when the |
---|
96 | user doens't have permission to enumerate the group's |
---|
97 | membership. |
---|
98 | |
---|
99 | Args: |
---|
100 | name: The element whose membership is being tested. |
---|
101 | |
---|
102 | Returns: |
---|
103 | True, if name is a member of self (or if self is a member |
---|
104 | of name); otherwise, False |
---|
105 | """ |
---|
106 | name = self._ent._pts.getEntry(name) |
---|
107 | if hasattr(self, '_set'): |
---|
108 | return name in self._set |
---|
109 | else: |
---|
110 | if self._ent.id < 0: |
---|
111 | return self._ent._pts._IsAMemberOf(name.id, self._ent.id) |
---|
112 | else: |
---|
113 | return self._ent._pts._IsAMemberOf(self._ent.id, name.id) |
---|
114 | |
---|
115 | def __repr__(self): |
---|
116 | self._loadSet() |
---|
117 | return repr(self._set) |
---|
118 | |
---|
119 | def add(self, elt): |
---|
120 | """Add one new entity to a group. |
---|
121 | |
---|
122 | This method will add a new user to a group, regardless of |
---|
123 | whether this instance represents a group or a user. The change |
---|
124 | is also immediately reflected to the PRDB. |
---|
125 | |
---|
126 | Raises: |
---|
127 | TypeError: If you try to add a grop group to a group, or a |
---|
128 | user to a user |
---|
129 | """ |
---|
130 | elt = self._ent._pts.getEntry(elt) |
---|
131 | if elt in self: |
---|
132 | return |
---|
133 | |
---|
134 | if self._ent.id < 0: |
---|
135 | if elt.id < 0: |
---|
136 | raise TypeError( |
---|
137 | "Adding group '%s' to group '%s' is not supported." % |
---|
138 | (elt, self._ent)) |
---|
139 | |
---|
140 | self._ent._pts._AddToGroup(elt.id, self._ent.id) |
---|
141 | |
---|
142 | elt.groups._add(self._ent) |
---|
143 | else: |
---|
144 | if elt.id > 0: |
---|
145 | raise TypeError( |
---|
146 | "Can't add user '%s' to user '%s'." % |
---|
147 | (elt, self._ent)) |
---|
148 | |
---|
149 | self._ent._pts._AddToGroup(self._ent.id, elt.id) |
---|
150 | |
---|
151 | elt.members._add(self._ent) |
---|
152 | |
---|
153 | self._add(elt) |
---|
154 | |
---|
155 | def discard(self, elt): |
---|
156 | """Remove one entity from a group. |
---|
157 | |
---|
158 | This method will remove a user from a group, regardless of |
---|
159 | whether this instance represents a group or a user. The change |
---|
160 | is also immediately reflected to the PRDB. |
---|
161 | """ |
---|
162 | elt = self._ent._pts.getEntry(elt) |
---|
163 | if elt not in self: |
---|
164 | return |
---|
165 | |
---|
166 | if self._ent.id < 0: |
---|
167 | self._ent._pts._RemoveFromGroup(elt.id, self._ent.id) |
---|
168 | elt.groups._discard(self._ent) |
---|
169 | else: |
---|
170 | self._ent._pts._RemoveFromGroup(self._ent.id, elt.id) |
---|
171 | elt.members._discard(self._ent) |
---|
172 | |
---|
173 | self._discard(elt) |
---|
174 | |
---|
175 | def remove(self, elt): |
---|
176 | """Remove an entity from a group; it must already be a member. |
---|
177 | |
---|
178 | If the entity is not a member, raise a KeyError. |
---|
179 | """ |
---|
180 | if elt not in self: |
---|
181 | raise KeyError(elt) |
---|
182 | |
---|
183 | self.discard(elt) |
---|
184 | |
---|
185 | |
---|
186 | class PTEntry(object): |
---|
187 | """An entry in the AFS protection database. |
---|
188 | |
---|
189 | PTEntry represents a user or group in the AFS protection |
---|
190 | database. Each PTEntry is associated with a particular connection |
---|
191 | to the protection database. |
---|
192 | |
---|
193 | PTEntry instances should not be created directly. Instead, use the |
---|
194 | "getEntry" method of the PTS object. |
---|
195 | |
---|
196 | If a PTS connection is authenticated, it should be possible to |
---|
197 | change most attributes on a PTEntry. These changes are immediately |
---|
198 | propogated to the protection database. |
---|
199 | |
---|
200 | Attributes: |
---|
201 | id: The PTS ID of the entry |
---|
202 | name: The username or group name of the entry |
---|
203 | count: For users, the number of groups they are a member of; for |
---|
204 | groups, the number of users in that group |
---|
205 | flags: An integer representation of the flags set on a given |
---|
206 | entry |
---|
207 | ngroups: The number of additional groups this entry is allowed |
---|
208 | to create |
---|
209 | nusers: Only meaningful for foreign-cell groups, where it |
---|
210 | indicates the ID of the next entry to be created from that |
---|
211 | cell. |
---|
212 | owner: A PTEntry object representing the owner of a given entry. |
---|
213 | creator: A PTEntry object representing the creator of a given |
---|
214 | entry. This field is read-only. |
---|
215 | |
---|
216 | groups: For users, this contains a collection class representing |
---|
217 | the set of groups the user is a member of. |
---|
218 | users: For groups, this contains a collection class representing |
---|
219 | the members of this group. |
---|
220 | """ |
---|
221 | _attrs = ('id', 'name', 'count', 'flags', 'ngroups', 'nusers') |
---|
222 | _entry_attrs = ('owner', 'creator') |
---|
223 | |
---|
224 | def __new__(cls, pts, id=None, name=None): |
---|
225 | if id is None: |
---|
226 | if name is None: |
---|
227 | raise TypeError('Must specify either a name or an id.') |
---|
228 | else: |
---|
229 | id = pts._NameToId(name) |
---|
230 | |
---|
231 | if id not in pts._cache: |
---|
232 | if name is None: |
---|
233 | name = pts._IdToName(id) |
---|
234 | |
---|
235 | inst = super(PTEntry, cls).__new__(cls) |
---|
236 | inst._pts = pts |
---|
237 | inst._id = id |
---|
238 | inst._name = name |
---|
239 | if id < 0: |
---|
240 | inst.members = PTRelationSet(inst) |
---|
241 | else: |
---|
242 | inst.groups = PTRelationSet(inst) |
---|
243 | pts._cache[id] = inst |
---|
244 | return pts._cache[id] |
---|
245 | |
---|
246 | def __repr__(self): |
---|
247 | if self.name != '': |
---|
248 | return '<PTEntry: %s>' % self.name |
---|
249 | else: |
---|
250 | return '<PTEntry: PTS ID %s>' % self.id |
---|
251 | |
---|
252 | def _get_id(self): |
---|
253 | return self._id |
---|
254 | def _set_id(self, val): |
---|
255 | del self._pts._cache[self._id] |
---|
256 | self._pts._ChangeEntry(self.id, newid=val) |
---|
257 | self._id = val |
---|
258 | self._pts._cache[val] = self |
---|
259 | id = property(_get_id, _set_id) |
---|
260 | |
---|
261 | def _get_name(self): |
---|
262 | return self._name |
---|
263 | def _set_name(self, val): |
---|
264 | self._pts._ChangeEntry(self.id, newname=val) |
---|
265 | self._name = val |
---|
266 | name = property(_get_name, _set_name) |
---|
267 | |
---|
268 | def _get_krbname(self): |
---|
269 | return self._pts._AfsToKrb5(self.name) |
---|
270 | def _set_krbname(self, val): |
---|
271 | self.name = self._pts._Krb5ToAfs(val) |
---|
272 | krbname = property(_get_krbname, _set_krbname) |
---|
273 | |
---|
274 | def _get_count(self): |
---|
275 | self._loadEntry() |
---|
276 | return self._count |
---|
277 | count = property(_get_count) |
---|
278 | |
---|
279 | def _get_flags(self): |
---|
280 | self._loadEntry() |
---|
281 | return self._flags |
---|
282 | def _set_flags(self, val): |
---|
283 | self._pts._SetFields(self.id, access=val) |
---|
284 | self._flags = val |
---|
285 | flags = property(_get_flags, _set_flags) |
---|
286 | |
---|
287 | def _get_ngroups(self): |
---|
288 | self._loadEntry() |
---|
289 | return self._ngroups |
---|
290 | def _set_ngroups(self, val): |
---|
291 | self._pts._SetFields(self.id, groups=val) |
---|
292 | self._ngroups = val |
---|
293 | ngroups = property(_get_ngroups, _set_ngroups) |
---|
294 | |
---|
295 | def _get_nusers(self): |
---|
296 | self._loadEntry() |
---|
297 | return self._nusers |
---|
298 | def _set_nusers(self, val): |
---|
299 | self._pts._SetFields(self.id, users=val) |
---|
300 | self._nusers = val |
---|
301 | nusers = property(_get_nusers, _set_nusers) |
---|
302 | |
---|
303 | def _get_owner(self): |
---|
304 | self._loadEntry() |
---|
305 | return self._owner |
---|
306 | def _set_owner(self, val): |
---|
307 | self._pts._ChangeEntry(self.id, newoid=self._pts.getEntry(val).id) |
---|
308 | self._owner = val |
---|
309 | owner = property(_get_owner, _set_owner) |
---|
310 | |
---|
311 | def _get_creator(self): |
---|
312 | self._loadEntry() |
---|
313 | return self._creator |
---|
314 | creator = property(_get_creator) |
---|
315 | |
---|
316 | def _loadEntry(self): |
---|
317 | if not hasattr(self, '_flags'): |
---|
318 | info = self._pts._ListEntry(self._id) |
---|
319 | for field in self._attrs: |
---|
320 | setattr(self, '_%s' % field, getattr(info, field)) |
---|
321 | for field in self._entry_attrs: |
---|
322 | setattr(self, '_%s' % field, self._pts.getEntry(getattr(info, field))) |
---|
323 | |
---|
324 | |
---|
325 | PTS_UNAUTH = 0 |
---|
326 | PTS_AUTH = 1 |
---|
327 | PTS_FORCEAUTH = 2 |
---|
328 | PTS_ENCRYPT = 3 |
---|
329 | |
---|
330 | |
---|
331 | class PTS(_pts.PTS): |
---|
332 | """A connection to an AFS protection database. |
---|
333 | |
---|
334 | This class represents a connection to the AFS protection database |
---|
335 | for a particular cell. |
---|
336 | |
---|
337 | Both the umax and gmax attributes can be changed if the connection |
---|
338 | was authenticated by a principal on system:administrators for the |
---|
339 | cell. |
---|
340 | |
---|
341 | For sufficiently privileged and authenticated connections, |
---|
342 | iterating over a PTS object will yield all entries in the |
---|
343 | protection database, in no particular order. |
---|
344 | |
---|
345 | Args: |
---|
346 | cell: The cell to connect to. If None (the default), PTS |
---|
347 | connects to the workstations home cell. |
---|
348 | sec: The security level to connect with: |
---|
349 | - PTS_UNAUTH: unauthenticated connection |
---|
350 | - PTS_AUTH: try authenticated, then fall back to |
---|
351 | unauthenticated |
---|
352 | - PTS_FORCEAUTH: fail if an authenticated connection can't be |
---|
353 | established |
---|
354 | - PTS_ENCRYPT: same as PTS_FORCEAUTH, plus encrypt all traffic |
---|
355 | to the protection server |
---|
356 | |
---|
357 | Attributes: |
---|
358 | realm: The Kerberos realm against which this cell authenticates |
---|
359 | umax: The maximum user ID currently assigned (the next ID |
---|
360 | assigned will be umax + 1) |
---|
361 | gmax: The maximum (actually minimum) group ID currently assigned |
---|
362 | (the next ID assigned will be gmax - 1, since group IDs are |
---|
363 | negative) |
---|
364 | """ |
---|
365 | def __init__(self, *args, **kwargs): |
---|
366 | self._cache = {} |
---|
367 | |
---|
368 | def __iter__(self): |
---|
369 | for pte in self._ListEntries(): |
---|
370 | yield self.getEntry(pte.id) |
---|
371 | |
---|
372 | def getEntry(self, ident): |
---|
373 | """Retrieve a particular PTEntry from this cell. |
---|
374 | |
---|
375 | getEntry accepts either a name or PTS ID as an argument, and |
---|
376 | returns a PTEntry object with that name or ID. |
---|
377 | """ |
---|
378 | if isinstance(ident, PTEntry): |
---|
379 | if ident._pts is not self: |
---|
380 | raise TypeError("Entry '%s' is from a different cell." % |
---|
381 | elt) |
---|
382 | return ident |
---|
383 | |
---|
384 | elif isinstance(ident, basestring): |
---|
385 | return PTEntry(self, name=ident) |
---|
386 | else: |
---|
387 | return PTEntry(self, id=ident) |
---|
388 | |
---|
389 | def getEntryFromKrbname(self, ident): |
---|
390 | """Retrieve a PTEntry matching a given Kerberos v5 principal. |
---|
391 | |
---|
392 | getEntryFromKrb accepts a krb5 principal, converts it to the |
---|
393 | equivalent AFS principal, and returns a PTEntry for that |
---|
394 | principal.""" |
---|
395 | return self.getEntry(self._Krb5ToAfs(ident)) |
---|
396 | |
---|
397 | def expire(self): |
---|
398 | """Flush the cache of PTEntry objects. |
---|
399 | |
---|
400 | This method will disconnect all PTEntry objects from this PTS |
---|
401 | object and flush the cache. |
---|
402 | """ |
---|
403 | for elt in self._cache.keys(): |
---|
404 | del self._cache[elt]._pts |
---|
405 | del self._cache[elt] |
---|
406 | |
---|
407 | def _get_umax(self): |
---|
408 | return self._ListMax()[0] |
---|
409 | def _set_umax(self, val): |
---|
410 | self._SetMaxUserId(val) |
---|
411 | umax = property(_get_umax, _set_umax) |
---|
412 | |
---|
413 | def _get_gmax(self): |
---|
414 | return self._ListMax()[1] |
---|
415 | def _set_gmax(self, val): |
---|
416 | self._SetMaxGroupId(val) |
---|
417 | gmax = property(_get_gmax, _set_gmax) |
---|