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