[2599] | 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) |
---|