Caucus Groups in C5

Charles Roth, 5/17/2005
Copyright (C) 2005 CaucusCare.  All rights reserved.

I. Introduction
The way groups are handled in Caucus is completely redone in C5, according to the following principles.

  1. Every collection of people is a group.  (Groups, conferences, manager privs, etc.)
  2. Groups can contain subgroups.
  3. Groups are defined by owner (userid, conf name, etc.) and group name.
  4. Groups define membership and access level.
  5. Groups must be usable in simple MySQL "joins" to determine who is in a group, and with what access level.
  6. Users may opt-out of access to a group.
  7. Groups may define opt-in access levels.

II. Opt-out and Opt-in
The last two points are tricky, and are driven by a design decision about conferences.  In Caucus 4, each conference has a userlist, and a membership list.  The userlist is the list of "invitees"; the membership list is the set of invitees who actually "showed up", so to speak.  While distinguishing between the two is useful, in practice it has led to endless confusion, and even more complicated work-arounds such as "force join" and "auto join" for cases where the organizer needs to make people be members.

So, in C5, the userlist and the membership become one: the conference group.  If your access to the conference group is readonly or higher, you are a member.  Period.

However, if you don't want to be a member, you can "opt out" of a group.  As implemented, it means you can add an opt-out rule to a group that you belong to.  If you change your mind (again), you can remove your opt-out rule, and get back your original access.

Opt-in is similar.  Whoever owns a group can define opt-in access for a set of people.  They are not members of the group, but they can choose to add themselves to the group, with whatever access level the owner originally specified.

III. The Formal Details
The rest of this document lays out the details and formal definitions of these principles.  Take a deep breath and read onward!

  1. Every collection of people is a group.  Groups are groups.  Conference user lists are groups.  Item access lists (new in C5) are groups.  Manager privileges are groups.  Etc.

  2. Groups contain userids, or other groups.  Each entry in a group is called a "rule".  E.g. a group G that contains userid A, userid B, and group X has three rules, one each for A and B, and one for X.  (For now, when a group is contained in another group, it is shown with a preceding "<" as in C4.)

  3. Userids in groups may have wildcards.  The MySQL "LIKE" syntax is used, e.g. "user%" or "%_class" or "abc%xyz".  Asterisks in C4 groups are translated to %'s.

  4. All groups are owned by someone or something.  Group names take the form:

      owner.name

    Owners can be:

    • "CONF".  E.g. CONF.demo is the userlist for conference "demo".
    • "MGR".  Special manager groups are owned by "MGR".
    • A userid.  E.g. "roth.special" is owned by userid "roth". 

    Note that all userids are lower-case, and all "special" owners are all upper-case.  Additional categories of special owners may be added later, such as the C4 "interface" groups that record who was registered under which interface.

    Currently, everyone can read or use all groups.  Userid groups (e.g. "roth.special") can be edited or deleted by the owning userid.  Conference groups (e.g. "CONF.demo") can be edited by the matching conference organizers.  All groups may be edited by members of "mgr.groups" (the managers with "edit groups" privilege).

  5. Rules define access.  Each rule selects a userid or group, and an access level, and whether that is "real" or "optional" access.  Access levels are represented internally by integers, and in the user interface by keywords.  There is an "out-of-the-box" list of standard access levels, and custom (site) levels may also be added.

    The standard access levels are:

      Keyword Value     Description
      organizer 40 Full management powers for whatever the group is used in
      instructor 30 (limited organizer powers)
      include 20
      readonly 10
      exclude 0 No access
      inherit -1 Inherit actual access level from subgroup

    If a rule lists a userid but no access level, then the default level of "include" is implied.  The precise implications of an access level depend on what a group is used for.  E.g. for a conference the levels are pretty clear, but for a group that defines manager privs, everything above "exclude" means you have that priv, and everything below means you do not have that priv.  Ditto emailing everyone in a group, and so on.

    If an access level is "optional", then users do not have any access, and are not considered members -- except that they have the ability to add a rule to the group with the "real" equivalent access.  (Optional exclude access is probably meaningless -- generally anyone with real access in a group can exclude themselves.  This may be prohibited separately at the interface level, e.g. a conference organizer may turn off the ability to for users to exclude themselves.)

  6. Rules may inherit or override.  When a rule in group G references another group X (i.e. a subgroup), it is interpreted in one of two ways:
    1. If the rule access in G is inherit, then the rules in X are inherited exactly as they are (i.e. as if those rules were copied directly into G).
    2. Otherwise, the rule in G applies to everyone in X who has readonly or higher access in X.

    Example:

      Group G
      <M include
      <F inherit
         Group M
      alfred readonly
      bob include
      charlie organizer
      dexter exclude
         Group F
      alice readonly
      betty include
      charlotte organizer
      debby exclude

    Group G's rules yield the following access:

    • Include: alfred, bob, charlie, betty
    • Readonly: alice
    • Organizer: charlotte

  7. Exclude wins.  Then highest access wins.  If more than one rule in a group applies to a userid (including rules from subgroups), then:
    • Any exclude rule for that userid wins.  (Opt-out acts like exclude.)
    • Otherwise, the highest (most privileged) access wins.

    Example: if we modified group G above to add two new rules:

      dexter include
      debby include

    Then dexter would have include access (because he is effectively not in group M), but debby would still be excluded, because the inherited exclude rule from F wins over the new include rule in G.

  8. Assume simplicity.  The group structure as presented is complex, if thorough.  But in most cases, the interface need only present the simplest aspects of the groups.  Only if a user chooses to invoke a more complex option (such as wildcards or inheritance or optional access) do the relevant interface options appear. 

    So, for example, most of the time the access rule "include" is assumed when including groups within groups.

    The "exclude" concept, in particular, adds a lot of complexity.  But in general it is only needed to counter the effects of a wild-card match, such as "give everyone access except certain people".  In most cases, neither wild-cards nor exclude will be used, and the interface can appear correspondingly simpler. 

  9. Group rules define JOINable groups table.  The implementation of the groups rules is done via two tables: 'grouprules' and 'groups'.  The rules as described above are recorded in 'grouprules'.  The 'groups' table records the relationship between every userid and every group.  The idea is that 'groups' can be used in simple MySQL "joins" when a query needs to be restricted by membership in a group.

    More precisely, each row in 'groups' has a structure something like:

       userid   varchar
       group    varchar
       access   int
    The values in 'groups' are totally derived from the rules in 'grouprules'.  Every time a group is changed and the relevant rows in 'grouprules' are changed, all rows in 'groups' that reference the changed group, either directly or through included layers of subgroups, are updated to match.  (This also means that any time a new userid is added, any wild-card group rules must be scanned for matches against the new userid, and the 'groups' table similarly updated.  Thus, determining group membership and access is very fast and simple, but changing group membership is expensive.)

    Thus, from our previous example, 'groups' would have a row for (userid='alfred', group='M'), and a row for (userid='alfred', group='G').

    This means it is possible (and easy!) to craft queries such as

       SELECT userid, access FROM groups WHERE group='G'
    and still get a list of all the userids that belong to group G, and their access level, no matter how many layers of included sub-groups are involved.