source: trunk/packages/python-afs/afs/pts.py @ 2955

Last change on this file since 2955 was 2599, checked in by broder, 15 years ago

Import python-afs.

Debathena should eventually be importing PyAFS for
<http://debathena.mit.edu/trac/ticket/395>, so hopefully this is only
temporary.

File size: 13.2 KB
RevLine 
[2599]1import collections
2from afs import _pts
3
4try:
5    SetMixin = collections.MutableSet
6except AttributeError:
7    SetMixin = object
8
9class 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
186class 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
325PTS_UNAUTH = 0
326PTS_AUTH = 1
327PTS_FORCEAUTH = 2
328PTS_ENCRYPT = 3
329
330
331class 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)
Note: See TracBrowser for help on using the repository browser.