Registry/TeamParticipationUsage

Not logged in - Log In / Register

Team Participation Usage

Author: StuartBishop and CelsoProvidelo
Status: ImplementedSpecification, FoafSpecification
Created: 2005-02-09
Contributors: SteveAlexander

Introduction

This Specification aims to describe correct TeamParticipation usage in accordance with the FOAF approach.

Rationale

Everywhere we use a Person's reference we might be referring to a person or a team. We need to make sure that, when we query the database to see if a the user has a permission, we take into account the fact that the user might indirectly have that permission by virtue of his membership in a team.

In general, whenever you are checking if a person has a permission, or is the owner of an object, you need to make sure that your code is team-aware. This means using TeamParticipation!

Essential Team Participation

As we know the Person table stores both people and teams within the Launchpad System, and they differ only by the extra team-related fields such as teamdescription and teamowner which are filled when the entry refers to a team.

The TeamMembership table includes all the information related to each first level member of this team, including the fields

These are the direct memberships of a team. There is one entry in TeamMembership for every person who has ever requested to join that team (as discussed in TeamMembership). But since teams can be members of teams, we need a separate data structure to map from a team to the complete set of people who are current members of a team.

The Team``Participation table

That data structure is the TeamParticipation table, which stores the precise set of people that are members of a particular team. For every person who is a member of a team, or a member of a team which is in turn a member of that team, there will be a participation entry that expresses that persons membership in the team. So TeamParticipation expresses both direct AND indirect membership in a team. Note that TeamParticipation is entirely automatically managed. Only the TeamMembership table is administered, adding and removing members. The results of those operations are exploded out and stored in TeamParticipation.

TeamParticipation stores 3 important relationships. Consider the following structure of nested teams and persons:

T2(P4, T3(P1))
Team 2 contains both Person 4 and Team 3 (which contains Person 1).

P4
Person 4 exists, and is not in any Team.

   | id | team | person | 
   |  1 |   2  |    4   | -> compiled team 4 membership
   |  2 |   3  |    1   | -> compiled team 3 membership 

   | id | team | person | 
   |  3 |   2  |    1   | -> exploded team 3 member of team 4

   | id | team | person | 
   |  4 |   1  |    1   | -> refers to person 1
   |  5 |   4  |    4   | -> refers to person 4

This behaviour allows us to optimize permission checks to use a single quick database query.

Assumptions

We assume that the following Actions are aware about the existence of TeamParticipation and maintain the table correctly:

An immediate member of a team is one that is directly in that team. So, if Team B is a member of Team A, and Person C is a member of Team B, then Person C is in Team A, but is not an immediate member of team A.

There are no use cases in the application code for knowing if a person is an immediate member of a particular team.

Note that when checking if a person is an admin of a given team, we are concerned only whether they (or the team they are in) are the immediate admin of that given team. It does not matter whether they are the admin of another team, where that other team is a member of the given team.

Use Cases

The most probable use case is Ownership handling, like:

In all of these cases the security machinery will need to be able to check if the currently authenticated user is a member of the given role (as per TeamsAndRoles).

Implementation

Suggested Security Implementation:

   1     #!python
   2     class ICrowd(Interface):
   3 
   4         def __contains__(person_or_team):
   5             """Return True if the given person_or_team is in the crowd."""
   6 
   7         def __add__(crowd):
   8             """Return a new ICrowd that is this crowd added to the given crowd.
   9 
  10             The returned crowd contains the person or teams in
  11             both this crowd and the given crowd.
  12             """

   1 class SQLCrowd:
   2 
   3     implements(ICrowd)
   4 
   5     def __init__(self, person_or_team):
   6         if person is not None:
   7             person = IPerson(person_or_team)
   8             self.person_ids = sets.Set([person.id])
   9         self.crowds = []  # list because crowds may be not hashable.
  10 
  11     def __contains__(self, person_or_team):
  12         # Look in the non-SQLCrowds before hitting the database.
  13         for crowd in self.crowds:
  14             if person_or_team in crowd:
  15                 return True
  16         # If we have None passed in, the return False at this stage.
  17         if not IPerson.providedBy(person_or_team):
  18             raise TypeError, person_or_team
  19         string_ids = [str(person_id) for person_id in self.person_ids]
  20         sql = 'SELECT COUNT(*) FROM TeamParticipation WHERE person=%d and team in (%s)' % (
  21             person_or_team.id, ', '.join(string_ids)
  22             )
  23         rv = execute(sql)
  24         return rv[0][0] >= 1
  25 
  26     def _copy(self):
  27         new_crowd = SQLCrowd(None)
  28         new_crowd.persons = sets.Set(self.persons)
  29         new_crowd.crowds = list(self.crowds)
  30         return new_crowd
  31 
  32     def __add__(self, crowd):
  33         new_crowd = self._copy()
  34         if isinstance(crowd, SQLCrowd):
  35             new_crowd.persons |= crowd.persons
  36             new_crowd.crowds += crowd.crowds
  37         else:
  38             new_crowd.crowds.append(crowd)
  39         return new_crowd

Usage example

   1     class EditByOwnerOrAssignee(AuthorizationBase):
   2         permission = 'launchpad.Edit'
   3         usedfor = IBugTask
   4 
   5         def getAllowedCrowd(self):
   6             # Note that this is using the new API from CrowdControl.
   7             return ICrowd(self.obj.owner) + ICrowd(self.obj.assignee)

Unresolved Issues

Registry/TeamParticipationUsage (last edited 2009-07-31 14:14:13 by bac)