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