#4786 Add a collaborator level to projects.
Merged 4 years ago by pingou. Opened 4 years ago by pingou.

@@ -0,0 +1,30 @@ 

+ """Add branch info to projects_groups

+ 

+ Revision ID: 2b39a728a38f

+ Revises: 318a4793b360

+ Create Date: 2020-03-26 21:50:45.899760

+ 

+ """

+ 

+ from alembic import op

+ import sqlalchemy as sa

+ 

+ 

+ # revision identifiers, used by Alembic.

+ revision = '2b39a728a38f'

+ down_revision = '318a4793b360'

+ 

+ 

+ def upgrade():

+     ''' Add the column branches to the table projects_groups.

+     '''

+     op.add_column(

+         'projects_groups',

+         sa.Column('branches', sa.Text, nullable=True)

+     )

+ 

+ 

+ def downgrade():

+     ''' Drop the column branches from the table projects_groups.

+     '''

+     op.drop_column('projects_groups', 'branches')

@@ -0,0 +1,30 @@ 

+ """Add branch info to user_projects

+ 

+ Revision ID: 318a4793b360

+ Revises: 060b20d6d6e6

+ Create Date: 2020-03-26 21:49:17.632967

+ 

+ """

+ 

+ from alembic import op

+ import sqlalchemy as sa

+ 

+ 

+ # revision identifiers, used by Alembic.

+ revision = '318a4793b360'

+ down_revision = '060b20d6d6e6'

+ 

+ 

+ def upgrade():

+     ''' Add the column branches to the table user_projects.

+     '''

+     op.add_column(

+         'user_projects',

+         sa.Column('branches', sa.Text, nullable=True)

+     )

+ 

+ 

+ def downgrade():

+     ''' Drop the column branches from the table user_projects.

+     '''

+     op.drop_column('user_projects', 'branches')

file modified
+5
@@ -395,6 +395,11 @@ 

          flask.g.repo_obj = pygit2.Repository(flask.g.reponame)

          flask.g.repo_admin = pagure.utils.is_repo_admin(flask.g.repo)

          flask.g.repo_committer = pagure.utils.is_repo_committer(flask.g.repo)

+         if flask.g.authenticated and not flask.g.repo_committer:

+             flask.g.repo_committer = flask.g.fas_user.username in [

+                 u.user.username for u in flask.g.repo.collaborators

+             ]

+ 

          flask.g.repo_user = pagure.utils.is_repo_user(flask.g.repo)

          flask.g.branches = sorted(flask.g.repo_obj.listall_branches())

  

file modified
+8
@@ -632,6 +632,10 @@ 

          'Access Level <span class="error">*</span>',

          [wtforms.validators.DataRequired()],

      )

+     branches = wtforms.StringField(

+         'Git branches <span class="error">*</span>',

+         [wtforms.validators.Optional()],

+     )

  

  

  class AddUserToGroupForm(PagureForm):
@@ -666,6 +670,10 @@ 

          'Access Level <span class="error">*</span>',

          [wtforms.validators.DataRequired()],

      )

+     branches = wtforms.StringField(

+         'Git branches <span class="error">*</span>',

+         [wtforms.validators.Optional()],

+     )

  

  

  class ConfirmationForm(PagureForm):

file modified
+4 -3
@@ -126,7 +126,7 @@ 

  @PV.route("/ssh/checkaccess/", methods=["POST"])

  @internal_access_only

  def check_ssh_access():

-     """ Determines whether a user has any access to the requested repo. """

+     """ Determines whether a user has read access to the requested repo. """

      gitdir = flask.request.form["gitdir"]

      remoteuser = flask.request.form["username"]

      _auth_log.info(
@@ -192,8 +192,9 @@ 

          )

          return flask.jsonify({"access": False})

  

-     _log.info("Access granted to %s on: %s" % (remoteuser, project.fullname))

- 

+     _log.info(

+         "Read access granted to %s on: %s" % (remoteuser, project.fullname)

+     )

      return flask.jsonify(

          {

              "access": True,

file modified
+4 -2
@@ -27,7 +27,7 @@ 

  import pagure.lib.query

  from pagure.config import config as pagure_config

  from pagure.lib import model

- from pagure.utils import is_repo_committer, lookup_deploykey

+ from pagure.utils import is_repo_collaborator, lookup_deploykey

  

  

  # logging.config.dictConfig(pagure_config.get('LOGGING') or {'version': 1})
@@ -901,7 +901,9 @@ 

              return False

  

          # Determine whether the current user is allowed to push

-         is_committer = is_repo_committer(project, username, session)

+         is_committer = is_repo_collaborator(

+             project, refname, username, session

+         )

          deploykey = lookup_deploykey(project, username)

          if deploykey is not None:

              self.info("Deploykey used. Push access: %s" % deploykey.pushaccess)

file modified
+48 -6
@@ -142,7 +142,7 @@ 

              session.rollback()

              _log.debug("ACL %s could not be added", acl)

  

-     for access in ["ticket", "commit", "admin"]:

+     for access in ["ticket", "collaborator", "commit", "admin"]:

          access_obj = AccessLevels(access=access)

          session.add(access_obj)

          try:
@@ -443,6 +443,13 @@ 

          viewonly=True,

      )

  

+     collaborators = relation(

+         "ProjectUser",

+         primaryjoin="and_(projects.c.id==user_projects.c.project_id,\

+                     user_projects.c.access=='collaborator')",

+         viewonly=True,

+     )

+ 

      groups = relation(

          "PagureGroup",

          secondary="projects_groups",
@@ -479,6 +486,13 @@ 

          viewonly=True,

      )

  

+     collaborator_groups = relation(

+         "ProjectGroup",

+         primaryjoin="and_(projects.c.id==projects_groups.c.project_id,\

+                     projects_groups.c.access=='collaborator')",

+         viewonly=True,

+     )

+ 

      def __repr__(self):

          return (

              "Project(%s, name:%s, namespace:%s, url:%s, is_fork:%s, "
@@ -905,7 +919,7 @@ 

          :type combine: boolean

          """

  

-         if access not in ["admin", "commit", "ticket"]:

+         if access not in ["admin", "commit", "collaborator", "ticket"]:

              raise pagure.exceptions.AccessLevelNotFound(

                  "The access level does not exist"

              )
@@ -915,6 +929,8 @@ 

                  return self.admins

              elif access == "commit":

                  return self.committers

+             elif access == "collaborator":

+                 return [u.user for u in self.collaborators]

              elif access == "ticket":

                  return self.users

          else:
@@ -924,11 +940,20 @@ 

                  committers = set(self.committers)

                  admins = set(self.admins)

                  return list(committers - admins)

-             elif access == "ticket":

+             elif access == "collaborator":

+                 admins = set(self.admins)

                  committers = set(self.committers)

+                 return list(

+                     set([u.user for u in self.collaborators])

+                     - committers

+                     - admins

+                 )

+             elif access == "ticket":

                  admins = set(self.admins)

+                 committers = set(self.committers)

+                 collaborators = set([u.user for u in self.collaborators])

                  users = set(self.users)

-                 return list(users - committers - admins)

+                 return list(users - collaborators - committers - admins)

  

      def get_project_groups(self, access, combine=True):

          """ Returns the list of groups of the project according
@@ -951,7 +976,7 @@ 

          :type combine: boolean

          """

  

-         if access not in ["admin", "commit", "ticket"]:

+         if access not in ["admin", "commit", "collaborator", "ticket"]:

              raise pagure.exceptions.AccessLevelNotFound(

                  "The access level does not exist"

              )
@@ -961,6 +986,8 @@ 

                  return self.admin_groups

              elif access == "commit":

                  return self.committer_groups

+             elif access == "collaborator":

+                 return self.collaborator_groups

              elif access == "ticket":

                  return self.groups

          else:
@@ -970,11 +997,18 @@ 

                  committers = set(self.committer_groups)

                  admins = set(self.admin_groups)

                  return list(committers - admins)

+             elif access == "collaborator":

+                 committers = set(self.committer_groups)

+                 admins = set(self.admin_groups)

+                 return list(

+                     set(self.collaborator_groups) - committers - admins

+                 )

              elif access == "ticket":

                  committers = set(self.committer_groups)

                  admins = set(self.admin_groups)

+                 collaborators = set(self.collaborator_groups)

                  groups = set(self.groups)

-                 return list(groups - committers - admins)

+                 return list(groups - collaborators - committers - admins)

  

      @property

      def access_users(self):
@@ -989,6 +1023,9 @@ 

                  self.get_project_users(access="commit", combine=False),

                  key=lambda u: u.user,

              ),

+             "collaborator": sorted(

+                 self.get_project_users(access="collaborator", combine=False)

+             ),

              "ticket": sorted(

                  self.get_project_users(access="ticket", combine=False),

                  key=lambda u: u.user,
@@ -1028,6 +1065,9 @@ 

                  self.get_project_groups(access="commit", combine=False),

                  key=lambda x: x.group_name,

              ),

+             "collaborator": sorted(

+                 self.get_project_groups(access="collaborator", combine=False)

+             ),

              "ticket": sorted(

                  self.get_project_groups(access="ticket", combine=False),

                  key=lambda x: x.group_name,
@@ -1171,6 +1211,7 @@ 

          ),

          nullable=False,

      )

+     branches = sa.Column(sa.Text, nullable=True,)

  

      project = relation(

          "Project",
@@ -2707,6 +2748,7 @@ 

          ),

          nullable=False,

      )

+     branches = sa.Column(sa.Text, nullable=True,)

  

      project = relation(

          "Project",

file modified
+39 -6
@@ -1098,7 +1098,13 @@ 

  

  

  def add_user_to_project(

-     session, project, new_user, user, access="admin", required_groups=None

+     session,

+     project,

+     new_user,

+     user,

+     access="admin",

+     branches=None,

+     required_groups=None,

  ):

      """ Add a specified user to a specified project with a specified access

      """
@@ -1127,15 +1133,20 @@ 

      )

      users.add(project.user.user)

  

-     if new_user in users:

+     if new_user in users and access != "collaborator":

          raise pagure.exceptions.PagureException(

              "This user is already listed on this project with the same access"

          )

  

+     # Reset the branches to None if the user isn't a collaborator

+     if access != "collaborator":

+         branches = None

+ 

      # user has some access on project, so update to new access

      if new_user_obj in project.users:

          access_obj = get_obj_access(session, project, new_user_obj)

          access_obj.access = access

+         access_obj.branches = branches

          project.date_modified = datetime.datetime.utcnow()

          update_read_only_mode(session, project, read_only=True)

          session.add(access_obj)
@@ -1149,6 +1160,7 @@ 

                  project=project.to_json(public=True),

                  new_user=new_user_obj.username,

                  new_access=access,

+                 new_branches=branches,

                  agent=user_obj.username,

              ),

          )
@@ -1156,7 +1168,10 @@ 

          return "User access updated"

  

      project_user = model.ProjectUser(

-         project_id=project.id, user_id=new_user_obj.id, access=access

+         project_id=project.id,

+         user_id=new_user_obj.id,

+         access=access,

+         branches=branches,

      )

      project.date_modified = datetime.datetime.utcnow()

      session.add(project_user)
@@ -1173,6 +1188,7 @@ 

              project=project.to_json(public=True),

              new_user=new_user_obj.username,

              access=access,

+             branches=branches,

              agent=user_obj.username,

          ),

      )
@@ -1186,6 +1202,7 @@ 

      new_group,

      user,

      access="admin",

+     branches=None,

      create=False,

      is_admin=False,

  ):
@@ -1228,15 +1245,20 @@ 

          ]

      )

  

-     if new_group in groups:

+     if new_group in groups and access != "collaborator":

          raise pagure.exceptions.PagureException(

              "This group already has this access on this project"

          )

  

+     # Reset the branches to None if the group isn't a collaborator

+     if access != "collaborator":

+         branches = None

+ 

      # the group already has some access, update to new access

      if group_obj in project.groups:

          access_obj = get_obj_access(session, project, group_obj)

          access_obj.access = access

+         access_obj.branches = branches

          session.add(access_obj)

          project.date_modified = datetime.datetime.utcnow()

          update_read_only_mode(session, project, read_only=True)
@@ -1250,6 +1272,7 @@ 

                  project=project.to_json(public=True),

                  new_group=group_obj.group_name,

                  new_access=access,

+                 new_branches=branches,

                  agent=user,

              ),

          )
@@ -1257,7 +1280,10 @@ 

          return "Group access updated"

  

      project_group = model.ProjectGroup(

-         project_id=project.id, group_id=group_obj.id, access=access

+         project_id=project.id,

+         group_id=group_obj.id,

+         access=access,

+         branches=branches,

      )

      session.add(project_group)

      # Make sure we won't have SQLAlchemy error before we continue
@@ -1274,6 +1300,7 @@ 

              project=project.to_json(public=True),

              new_group=group_obj.group_name,

              access=access,

+             branches=branches,

              agent=user,

          ),

      )
@@ -2380,6 +2407,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectUser.access == "admin",

                      model.ProjectUser.access == "commit",

+                     model.ProjectUser.access == "collaborator",

                  ),

              )

          )
@@ -2394,6 +2422,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

+                     model.ProjectGroup.access == "collaborator",

                  ),

              )

          )
@@ -2409,6 +2438,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

+                     model.ProjectGroup.access == "collaborator",

                  ),

              )

          )
@@ -2455,6 +2485,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectUser.access == "admin",

                      model.ProjectUser.access == "commit",

+                     model.ProjectUser.access == "collaborator",

                  ),

              )

          )
@@ -2470,6 +2501,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

+                     model.ProjectGroup.access == "collaborator",

                  ),

              )

          )
@@ -2486,6 +2518,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

+                     model.ProjectGroup.access == "collaborator",

                  ),

              )

          )
@@ -2591,7 +2624,7 @@ 

      projects = session.query(sqlalchemy.distinct(model.Project.id))

  

      if acls is None:

-         acls = ["main admin", "admin", "commit", "ticket"]

+         acls = ["main admin", "admin", "collaborator", "commit", "ticket"]

  

      if username is not None:

  

@@ -0,0 +1,34 @@ 

+ <p class="center"> <strong>Access Levels</strong> </p>

+ <p class="justify">

+ <strong>Ticket</strong>: A user or a group with this level of access can only edit metadata

+   of an issue. This includes changing the status of an issue, adding/removing

+   tags from them, adding/removing assignees and every other option which can

+   be accessed when you click "Edit Metadata" button in an issue page. However,

+   this user can not "create" a new tag or "delete" an existing tag because,

+   that would involve access to settings page of the project which this user

+   won't have. It also won't be able to "delete" the issue because, it falls

+   outside of "Edit Metadata".

+ </p>

+ <p class="justify">

+ <strong>Collaborator</strong>: A user or a group with this level of access can do everything what

+   a user/group with ticket access can do + it can commit to some branches in the project.

+   These branches are defined here using their name or a pattern and needs to be comma separated. <br />

+   Some examples:

+     <ul>

+         <li>master,features/*</li>

+         <li>el*</li>

+         <li>master,f*</li>

+     </ul>

+ </p>

+ <p class="justify">

+ <strong>Commit</strong>: A user or a group with this level of access can do everything what

+   a user/group with ticket access can do + it can do everything on the project

+   which doesn't include access to settings page. It can "Edit Metadata" of an issue

+   just like a user with ticket access would do, can merge a pull request, can push

+   to the main repository directly, delete an issue, cancel a pull request etc.

+ </p>

+ <p class="justify">

+ <strong>Admin</strong>: The user/group with this access has access to everything on the project.

+   All the "users" of the project that have been added till now are having this access.

+   They can change the settings of the project, add/remove users/groups on the project.

+ </p>

@@ -33,6 +33,8 @@ 

              <option value="{{ access }}" id="{{ access }}"> {{ access }} </option>

            {% endfor %}

          </select>

+         <input class="form-control" name="branches" id="branches" class="hidden"

+           placeholder="A comma separated list of branches" value=""/>

        </fieldset>

  

        <p class="buttons indent">
@@ -41,29 +43,7 @@ 

          {{ form.csrf_token }}

        </p>

      </form>

-     <p class="center"> <strong>Access Levels</strong> </p>

-     <p class="justify">

-     <strong>Ticket</strong>: A user or a group with this level of access can only edit metadata

-       of an issue. This includes changing the status of an issue, adding/removing

-       tags from them, adding/removing assignees and every other option which can

-       be accessed when you click "Edit Metadata" button in an issue page. However,

-       this user can not "create" a new tag or "delete" an existing tag because,

-       that would involve access to settings page of the project which this user

-       won't have. It also won't be able to "delete" the issue because, it falls

-       outside of "Edit Metadata".

-     </p>

-     <p class="justify">

-     <strong>Commit</strong>: A user or a group with this level of access can do everything what

-       a user/group with ticket access can do + it can do everything on the project

-       which doesn't include access to settings page. It can "Edit Metadata" of an issue

-       just like a user with ticket access would do, can merge a pull request, can push

-       to the main repository directly, delete an issue, cancel a pull request etc.

-     </p>

-     <p class="justify">

-     <strong>Admin</strong>: The user/group with this access has access to everything on the project.

-       All the "users" of the project that have been added till now are having this access.

-       They can change the settings of the project, add/remove users/groups on the project.

-     </p>

+     {% include '_access_levels_descriptions.html' %}

      </div>

    </div>

  </div>
@@ -90,7 +70,10 @@ 

    );

  }

  

+ 

  $( document ).ready(function() {

+   $("#branches").hide();

+ 

    var group_to_update = "{{ group_to_update }}";

    if (!group_to_update || group_to_update === "None") {

      $('#group').selectize({
@@ -114,9 +97,24 @@ 

      if (group_access !== "None") {

        $("#" + "{{ group_access.access }}").attr("selected", "selected");

      }

+     $("#branches").val("{{ group_access.branches or ''}}");

      $("#card-topic").html("<strong>Update group access in {{repo.name}}</strong>");

      $("#add_update_button").attr("value", "Update");

-   }

+   };

+ 

+   if ($("#access").val() == "collaborator") {

+     $("#branches").show();

+   };

+ 

+   $("#access").on("change", function() {

+     var _acc = $("#access");

+     if (_acc.val() == "collaborator") {

+         $("#branches").show();

+     } else {

+         $("#branches").hide();

+     }

+   });

+ 

  });

  </script>

  {% endblock %}

file modified
+21 -23
@@ -31,6 +31,9 @@ 

              <option value="{{ access }}" id="{{ access }}"> {{ access }} </option>

            {% endfor %}

          </select>

+ 

+         <input class="form-control" name="branches" id="branches" class="hidden"

+           placeholder="A comma separated list of branches" value=""/>

        </fieldset>

  

        <p class="buttons indent">
@@ -39,29 +42,7 @@ 

          {{ form.csrf_token }}

        </p>

      </form>

-     <p class="center"> <strong>Access Levels</strong> </p>

-     <p class="justify">

-     <strong>Ticket</strong>: A user or a group with this level of access can only edit metadata

-       of an issue. This includes changing the status of an issue, adding/removing

-       tags from them, adding/removing assignees and every other option which can

-       be accessed when you click "Edit Metadata" button in an issue page. However,

-       this user can not "create" a new tag or "delete" an existing tag because,

-       that would involve access to settings page of the project which this user

-       won't have. It also won't be able to "delete" the issue because, it falls

-       outside of "Edit Metadata".

-     </p>

-     <p class="justify">

-     <strong>Commit</strong>: A user or a group with this level of access can do everything what

-       a user/group with ticket access can do + it can do everything on the project

-       which doesn't include access to settings page. It can "Edit Metadata" of an issue

-       just like a user with ticket access would do, can merge a pull request, can push

-       to the main repository directly, delete an issue, cancel a pull request etc.

-     </p>

-     <p class="justify">

-     <strong>Admin</strong>: The user/group with this access has access to everything on the project.

-       All the "users" of the project that have been added till now are having this access.

-       They can change the settings of the project, add/remove users/groups on the project.

-     </p>

+     {% include '_access_levels_descriptions.html' %}

      </div>

    </div>

  </div>
@@ -75,6 +56,8 @@ 

  

  <script type="text/javascript" nonce="{{ g.nonce }}">

  $( document ).ready(function() {

+   $("#branches").hide();

+ 

    var user_to_update = "{{ user_to_update }}";

    if (!user_to_update || user_to_update === "None") {

      $('#user').selectize({
@@ -99,13 +82,28 @@ 

      $("#user").attr("value", user_to_update);

      $("#user").attr("readonly", true);

      var user_access = "{{ user_access }}";

+     console.log("{{ user_access.branches }}");

      if (user_access !== "None") {

        $("#" + "{{ user_access.access }}").attr("selected", "selected");

      }

+     $("#branches").val("{{ user_access.branches or ''}}");

      $("#card-topic").html("<strong>Update user access in {{repo.name}}</strong>");

      $("#add_update_button").attr("value", "Update");

    }

  

+   if ($("#access").val() == "collaborator") {

+     $("#branches").show();

+   };

+ 

+   $("#access").on("change", function() {

+     var _acc = $("#access");

+     if (_acc.val() == "collaborator") {

+         $("#branches").show();

+     } else {

+         $("#branches").hide();

+     }

+   });

+ 

  });

  </script>

  {% endblock %}

file modified
+2
@@ -2081,6 +2081,7 @@ 

                  new_user=form.user.data,

                  user=flask.g.fas_user.username,

                  access=form.access.data,

+                 branches=form.branches.data,

                  required_groups=pagure_config.get("REQUIRED_GROUPS"),

              )

              flask.g.session.commit()
@@ -2241,6 +2242,7 @@ 

                  new_group=form.group.data,

                  user=flask.g.fas_user.username,

                  access=form.access.data,

+                 branches=form.branches.data,

                  create=pagure_config.get("ENABLE_GROUP_MNGT", False),

                  is_admin=pagure.utils.is_admin(),

              )

file modified
+59 -1
@@ -1,7 +1,7 @@ 

  # -*- coding: utf-8 -*-

  

  """

-  (c) 2017 - Copyright Red Hat Inc

+  (c) 2017-2020 - Copyright Red Hat Inc

  

   Authors:

     Pierre-Yves Chibon <pingou@pingoured.fr>
@@ -11,6 +11,7 @@ 

  from __future__ import unicode_literals, absolute_import

  

  import datetime

+ import fnmatch

  import logging

  import logging.config

  import os
@@ -269,6 +270,63 @@ 

      return False

  

  

+ def is_repo_collaborator(repo_obj, refname, username=None, session=None):

+     """ Return whether the user has commit on the specified branch of the

+     provided repo. """

+     committer = is_repo_committer(repo_obj, username=username, session=session)

+     if committer:

+         _log.debug("User is a committer")

+         return committer

+ 

+     import pagure.lib.query

+ 

+     if username is None:

+         if not authenticated():

+             return False

+         if is_admin():

+             return True

+         username = flask.g.fas_user.username

+         usergroups = set(flask.g.fas_user.groups)

+ 

+     if not session:

+         session = flask.g.session

+     try:

+         user = pagure.lib.query.get_user(session, username)

+         usergroups = set(user.groups)

+     except pagure.exceptions.PagureException:

+         return False

+ 

+     # If they are in the list of committers -> maybe

+     for user in repo_obj.collaborators:

+         if user.user.username == username:

+             # if branch is None when the user tries to read,

+             # so we'll allow that

+             if refname is None:

+                 return True

+             # If the branch is specified: the user is trying to write, we'll

+             # check if they are allowed to

+             for pattern in user.branches.split(","):

+                 pattern = "refs/heads/{}".format(pattern.strip())

+                 if fnmatch.fnmatch(refname, pattern):

+                     return True

+ 

+     # If they are in a group that has commit access -> maybe

+     for project_group in repo_obj.collaborator_groups:

+         if project_group.group.group_name in usergroups:

+             # if branch is None when the user tries to read,

+             # so we'll allow that

+             if refname is None:

+                 return True

+             # If the branch is specified: the user is trying to write, we'll

+             # check if they are allowed to

+             for pattern in project_group.branches.split(","):

+                 pattern = "refs/heads/{}".format(pattern.strip())

+                 if fnmatch.fnmatch(refname, pattern):

+                     return True

+ 

+     return False

+ 

+ 

  def is_repo_user(repo_obj, username=None):

      """ Return whether the user has some access in the provided repo. """

      if username:

file modified
+187
@@ -317,6 +317,193 @@ 

              output = pagure.utils.is_repo_committer(repo)

              self.assertFalse(output)

  

+     def test_is_repo_collaborator_logged_out(self):

+         """ Test is_repo_committer in pagure when there is no logged in user.

+         """

+         repo = pagure.lib.query._get_project(self.session, "test")

+         with self.app.application.app_context():

+             output = pagure.utils.is_repo_collaborator(repo, "master")

+         self.assertFalse(output)

+ 

+     def test_is_repo_collaborator_logged_in(self):

+         """ Test is_repo_collaborator in pagure with the appropriate user logged

+         in. """

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="pingou")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertTrue(output)

+ 

+     def test_is_repo_collaborator_invalid_username(self):

+         """ Test is_repo_collaborator in pagure with the appropriate user logged

+         in. """

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="invalid")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertFalse(output)

+ 

+     @mock.patch.dict("pagure.config.config", {"PAGURE_ADMIN_USERS": ["foo"]})

+     def test_is_repo_collaborator_admin_user(self):

+         """ Test is_repo_collaborator in pagure with the appropriate user logged

+         in. """

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="foo")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertTrue(output)

+ 

+     def test_is_repo_collaborator_not_in_project(self):

+         """ Test is_repo_collaborator in pagure with the appropriate user logged

+         in. """

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="foo")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertFalse(output)

+ 

+     def test_is_repo_collaborator_in_project(self):

+         """ Test is_repo_collaborator in pagure with the appropriate user logged

+         in. """

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         # Add user foo to project test

+         msg = pagure.lib.query.add_user_to_project(

+             self.session,

+             project=repo,

+             new_user="foo",

+             user="pingou",

+             access="collaborator",

+             branches="epel*",

+         )

+         self.session.commit()

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="foo")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             # Collaborator trying to read the project

+             output = pagure.utils.is_repo_collaborator(repo, None)

+             self.assertTrue(output)

+ 

+             # Collaborator trying to write to the project

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertFalse(output)

+ 

+             output = pagure.utils.is_repo_collaborator(repo, "refs/heads/epel")

+             self.assertTrue(output)

+ 

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/epel8"

+             )

+             self.assertTrue(output)

+ 

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/epel8-sig-foobar"

+             )

+             self.assertTrue(output)

+ 

+     def test_is_repo_collaborator_logged_in_in_group(self):

+         """ Test is_repo_committer in pagure with the appropriate user logged

+         in. """

+         # Create group

+         msg = pagure.lib.query.add_group(

+             self.session,

+             group_name="packager",

+             display_name="packager",

+             description="The Fedora packager groups",

+             group_type="user",

+             user="pingou",

+             is_admin=False,

+             blacklist=[],

+         )

+         self.session.commit()

+         self.assertEqual(msg, "User `pingou` added to the group `packager`.")

+ 

+         # Add user to group

+         group = pagure.lib.query.search_groups(

+             self.session, group_name="packager"

+         )

+         msg = pagure.lib.query.add_user_to_group(

+             self.session,

+             username="foo",

+             group=group,

+             user="pingou",

+             is_admin=True,

+         )

+         self.session.commit()

+         self.assertEqual(msg, "User `foo` added to the group `packager`.")

+ 

+         # Add group packager to project test

+         project = pagure.lib.query._get_project(self.session, "test")

+         msg = pagure.lib.query.add_group_to_project(

+             self.session,

+             project=project,

+             new_group="packager",

+             user="pingou",

+             access="collaborator",

+             branches="epel*",

+         )

+         self.session.commit()

+         self.assertEqual(msg, "Group added")

+ 

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="foo")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             # Collaborator in the group trying to read the project

+             output = pagure.utils.is_repo_collaborator(repo, None)

+             self.assertTrue(output)

+ 

+             # Collaborator in the group trying to write to the project

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertFalse(output)

+ 

+             output = pagure.utils.is_repo_collaborator(repo, "refs/heads/epel")

+             self.assertTrue(output)

+ 

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/epel8"

+             )

+             self.assertTrue(output)

+ 

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/epel8-sig-foobar"

+             )

+             self.assertTrue(output)

+ 

  

  if __name__ == "__main__":

      unittest.main(verbosity=2)

@@ -422,11 +422,13 @@ 

                      "project": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],
@@ -460,11 +462,13 @@ 

                      "repo_from": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],
@@ -763,9 +767,15 @@ 

              "initial_comment": None,

              "last_updated": "1431414800",

              "project": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],
@@ -797,9 +807,15 @@ 

              },

              "remote_git": None,

              "repo_from": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],
@@ -921,9 +937,15 @@ 

              "initial_comment": None,

              "last_updated": "1431414800",

              "project": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],
@@ -955,9 +977,15 @@ 

              },

              "remote_git": None,

              "repo_from": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],
@@ -3036,9 +3064,15 @@ 

                  "initial_comment": "Nothing much, the changes speak for themselves",

                  "last_updated": "1516348115",

                  "project": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -3070,9 +3104,15 @@ 

                  },

                  "remote_git": None,

                  "repo_from": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -3173,9 +3213,15 @@ 

                  "initial_comment": None,

                  "last_updated": "1516348115",

                  "project": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -3207,9 +3253,15 @@ 

                  },

                  "remote_git": None,

                  "repo_from": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -3532,9 +3584,15 @@ 

              "initial_comment": "Nothing much, the changes speak for themselves",

              "last_updated": "1516348115",

              "project": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],
@@ -3566,9 +3624,15 @@ 

              },

              "remote_git": None,

              "repo_from": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],

@@ -256,9 +256,15 @@ 

                  "initial_comment": "Edited initial comment",

                  "last_updated": "1551276261",

                  "project": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -290,9 +296,15 @@ 

                  },

                  "remote_git": None,

                  "repo_from": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -310,11 +322,13 @@ 

                      "parent": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],
@@ -412,9 +426,15 @@ 

                  "initial_comment": "",

                  "last_updated": "1551276261",

                  "project": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -446,9 +466,15 @@ 

                  },

                  "remote_git": None,

                  "repo_from": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -466,11 +492,13 @@ 

                      "parent": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],
@@ -588,9 +616,15 @@ 

                  "fixes #2 \n\nThanks",

                  "last_updated": "1551276261",

                  "project": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -622,9 +656,15 @@ 

                  },

                  "remote_git": None,

                  "repo_from": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -642,11 +682,13 @@ 

                      "parent": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],

@@ -290,11 +290,13 @@ 

                  {

                      "access_groups": {

                          "admin": ["some_group"],

+                         "collaborator": [],

                          "commit": [],

                          "ticket": [],

                      },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -368,11 +370,13 @@ 

                  {

                      "access_groups": {

                          "admin": ["some_group"],

+                         "collaborator": [],

                          "commit": [],

                          "ticket": [],

                      },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -438,11 +442,13 @@ 

                  {

                      "access_groups": {

                          "admin": ["some_group"],

+                         "collaborator": [],

                          "commit": [],

                          "ticket": [],

                      },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -36,6 +36,8 @@ 

  class PagureFlaskApiProjecttests(tests.Modeltests):

      """ Tests for the flask API of pagure for issue """

  

+     maxDiff = None

+ 

      def setUp(self):

          super(PagureFlaskApiProjecttests, self).setUp()

          self.gga_patcher = patch(
@@ -225,9 +227,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -441,9 +449,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -502,9 +516,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -535,9 +555,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -568,9 +594,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -629,9 +661,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -662,9 +700,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -695,9 +739,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -752,9 +802,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -809,9 +865,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -882,9 +944,15 @@ 

          data["date_created"] = "1436527638"

          data["date_modified"] = "1436527638"

          expected_data = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["pingou"],

                  "ticket": [],
@@ -972,11 +1040,13 @@ 

          expected_data = {

              "access_groups": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": ["some_group"],

                  "ticket": [],

              },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["pingou"],

                  "ticket": [],
@@ -1036,9 +1106,15 @@ 

          data["date_created"] = "1436527638"

          data["date_modified"] = "1436527638"

          expected_data = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["pingou"],

                  "ticket": [],
@@ -1101,9 +1177,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -1134,9 +1216,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -1167,9 +1255,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -1238,9 +1332,15 @@ 

              "pagination": {"next": None, "page": 2, "pages": 2, "per_page": 2},

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -1396,9 +1496,15 @@ 

          data["date_created"] = "1496338274"