| 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) | 
|---|