Branch: Tag:

2013-08-09

2013-08-09 10:15:27 by Henrik Grubbström (Grubba) <grubba@grubba.org>

Add support for group-based access control.

This patch adds support for a group file ".git/info/group"
containing a mapping from group names to member users.

Note that the first listed member of a group gets full
access and the following basic access. Users not listed
get no access. If multiple users need full access, this
can be achieved by having multiple entries for the group.

Also cleans up the access level handling code somewhat
by introducing an enum AccessLevel.

338:    static array(string) files_to_commit;    GitAttributes attrs;    +  private string git_dir; +  +  string get_git_dir() +  { +  return git_dir || +  (git_dir = String.trim_all_whites(run_git("rev-parse", "--git-dir"))); +  } +     string get_file(string filename, int|void allow_empty);    string get_old_file(string filename, int|void allow_empty);    int entry_is_new(string filename) { return 0; }
542:   {    inherit CommitHookUtilsRepo;    +  enum AccessLevel { +  ACCESS_NONE = 0, +  ACCESS_BASIC = 1, +  ACCESS_FULL = 2, // rebase/delete branch, move/delete tag, etc. +  }; +  +  static mapping(string:AccessLevel) groups = ([ +  "scratch": ACCESS_FULL, +  ]); +     static array(string) commits_to_check = ({});    -  int check_access(string ref_name, string user) +  static void parse_groups(string user)    { -  /* Return 0 for no access, 1 for basic access, and 2 for full -  access (including rebase/delete branch, and move/delete tag) */ +  string git_dir = get_git_dir(); +  +  groups[user] = ACCESS_FULL; +  +  string group_data = Stdio.read_bytes(combine_path(git_dir, "info/group")); +  +  if (!group_data) return; +  +  foreach(replace(group_data, "\r", "\n")/"\n", string line) { +  array(string) fields = map(line/":", String.trim_all_whites); +  +  // NB: We currently only care about fields 0 (group name) +  // and 3 (member list). The first member of a group is +  // considered the primary member, and has full access. +  +  if ((sizeof(fields) != 4) || (fields[0] == "")) continue; +  array(string) members = map(fields[3]/",", String.trim_all_whites); +  if (members[0] == user) { +  // Primary member. +  groups[fields[0]] = ACCESS_FULL; +  continue; +  } +  if (groups[fields[0]]) continue; // Already a member. +  if (has_value(members, user)) { +  groups[fields[0]] = ACCESS_BASIC; +  continue; +  } +  groups[fields[0]] = ACCESS_NONE; +  } +  } +  +  AccessLevel check_access(string ref_name, string user) +  { +  /* Return ACCESS_NONE (0) for no access, +  * ACCESS_BASIC (1) for basic access, and +  * ACCESS_FULL (2) for full access +  * (including rebase/delete branch, and move/delete tag) +  */    string shortref = ref_name;    sscanf(shortref, "refs/%*[^/]/%s", shortref); -  if (has_prefix(shortref, "scratch/") || has_prefix(shortref, user+"/")) -  return 2; +  foreach(groups; string group; AccessLevel ac) { +  if (has_prefix(shortref, group + "/")) +  return ac; +  }    if (search(ref_name, "/x-") >= 0) {    write("The ref %s can only be modified by its owner\n", ref_name); -  return 0; +  return ACCESS_NONE;    } -  return 1; +  return ACCESS_BASIC;    }       int check_tag_push(string old_sha, string new_sha, string ref_name, -  int access_level) +  AccessLevel access_level)    { -  if (access_level >= 2) +  if (access_level >= ACCESS_FULL)    return 0;       string oldtag =
583:    }       int check_branch_push(string old_sha, string new_sha, string ref_name, -  int access_level) +  AccessLevel access_level)    {    if (old_sha == "0"*40) {    // New branch, check if the name is allowed...
604:    }    return 0;    } else { -  if (access_level >= 2) +  if (access_level >= ACCESS_FULL)    /* Skip checks */    return 0;   
630:    }    }    -  int check_push(string old_sha, string new_sha, string ref_name) +  int check_push(string git_user, +  string old_sha, string new_sha, string ref_name)    { -  string git_user = getenv("GIT_USER")||getenv("USER")||"nobody"; -  int access_level; +  AccessLevel access_level;    if(!(access_level = check_access(ref_name, git_user))) return 1;    if (has_prefix(ref_name, "refs/tags/")) {    return check_tag_push(old_sha, new_sha, ref_name, access_level);
647:       int hook()    { +  string git_user = getenv("GIT_USER")||getenv("USER")||"nobody"; +  parse_groups(git_user);    foreach(split_lf(Stdio.stdin->read()), string line) {    array(string) args = line / " ";    if(sizeof(args) != 3)    fail("Unexpected input line to pre-receive hook: %s\n", line); -  if(check_push(@args)) +  if(check_push(git_user, @args))    return 1;    }    foreach(Array.uniq(commits_to_check), string sha)