Add symbolic names for the PTS encryption levels.
[invirt/packages/python-afs.git] / afs / pts.py
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)