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