#4786 Add a collaborator level to projects.
Opened 2 months ago by pingou. Modified a month ago

@@ -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: d7589827abbb

+ 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 = 'd7589827abbb'

+ 

+ 

+ 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

@@ -364,6 +364,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

@@ -624,6 +624,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):

@@ -658,6 +662,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

@@ -120,7 +120,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"]

  

@@ -165,8 +165,9 @@ 

          _log.info("%s is not a contributor to this project" % remoteuser)

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

@@ -897,7 +897,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
+51 -6

@@ -141,7 +141,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:

@@ -430,6 +430,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",

@@ -466,6 +473,16 @@ 

          viewonly=True,

      )

  

+     collaborator_groups = relation(

+         "PagureGroup",

+         secondary="projects_groups",

+         primaryjoin="projects.c.id==projects_groups.c.project_id",

+         secondaryjoin="and_(pagure_group.c.id==projects_groups.c.group_id,\

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

+         order_by="PagureGroup.group_name.asc()",

+         viewonly=True,

+     )

+ 

      def __repr__(self):

          return (

              "Project(%s, name:%s, namespace:%s, url:%s, is_fork:%s, "

@@ -892,7 +909,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"

              )

@@ -902,6 +919,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:

@@ -911,11 +930,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

@@ -938,7 +966,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"

              )

@@ -948,6 +976,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:

@@ -957,11 +987,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):

@@ -976,6 +1013,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,

@@ -1015,6 +1055,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,

@@ -1152,6 +1195,7 @@ 

          ),

          nullable=False,

      )

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

  

      project = relation(

          "Project",

@@ -2674,6 +2718,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,

          ),

      )

@@ -2374,6 +2401,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectUser.access == "admin",

                      model.ProjectUser.access == "commit",

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

                  ),

              )

          )

@@ -2388,6 +2416,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

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

                  ),

              )

          )

@@ -2403,6 +2432,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

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

                  ),

              )

          )

@@ -2449,6 +2479,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectUser.access == "admin",

                      model.ProjectUser.access == "commit",

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

                  ),

              )

          )

@@ -2464,6 +2495,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

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

                  ),

              )

          )

@@ -2480,6 +2512,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

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

                  ),

              )

          )

@@ -2585,7 +2618,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

@@ -2079,6 +2079,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()

@@ -2239,6 +2240,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
+43

@@ -11,6 +11,7 @@ 

  from __future__ import unicode_literals, absolute_import

  

  import datetime

+ import fnmatch

  import logging

  import logging.config

  import os

@@ -263,6 +264,48 @@ 

      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:

+         return committer

+ 

+     import pagure.lib.query

+ 

+     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 group in repo_obj.collaborator_groups:

+         if group.group_name in usergroups:

+             for branch in group.branches.split(","):

+                 branch = branch.strip()

+                 if fnmatch.fnmatch(branch, refname):

+                     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:

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

                      "project": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],

@@ -456,11 +458,13 @@ 

                      "repo_from": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],

@@ -751,9 +755,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": [],

@@ -781,9 +791,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": [],

@@ -897,9 +913,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": [],

@@ -927,9 +949,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": [],

@@ -3000,9 +3028,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": [],

@@ -3030,9 +3064,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": [],

@@ -3125,9 +3165,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": [],

@@ -3155,9 +3201,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": [],

@@ -3472,9 +3524,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": [],

@@ -3502,9 +3560,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": [],

@@ -286,9 +292,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": [],

@@ -306,11 +318,13 @@ 

                      "parent": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],

@@ -396,9 +410,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": [],

@@ -426,9 +446,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": [],

@@ -446,11 +472,13 @@ 

                      "parent": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],

@@ -556,9 +584,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": [],

@@ -586,9 +620,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": [],

@@ -606,11 +646,13 @@ 

                      "parent": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],

@@ -283,11 +283,13 @@ 

                  {

                      "access_groups": {

                          "admin": ["some_group"],

+                         "collaborator": [],

                          "commit": [],

                          "ticket": [],

                      },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -353,11 +355,13 @@ 

                  {

                      "access_groups": {

                          "admin": ["some_group"],

+                         "collaborator": [],

                          "commit": [],

                          "ticket": [],

                      },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -415,11 +419,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(

@@ -312,9 +314,15 @@ 

              },

              "projects": [

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -524,9 +532,15 @@ 

              },

              "projects": [

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -581,9 +595,15 @@ 

              },

              "projects": [

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -610,9 +630,15 @@ 

                      "user": {"fullname": "PY C", "name": "pingou"},

                  },

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -639,9 +665,15 @@ 

                      "user": {"fullname": "PY C", "name": "pingou"},

                  },

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -696,9 +728,15 @@ 

              },

              "projects": [

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -725,9 +763,15 @@ 

                      "user": {"fullname": "PY C", "name": "pingou"},

                  },

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -754,9 +798,15 @@ 

                      "user": {"fullname": "PY C", "name": "pingou"},

                  },

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -807,9 +857,15 @@ 

              },

              "projects": [

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -860,9 +916,15 @@ 

              },

              "projects": [

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -929,9 +991,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": [],

@@ -1015,11 +1083,13 @@ 

          expected_data = {

              "access_groups": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": ["some_group"],

                  "ticket": [],

              },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["pingou"],

                  "ticket": [],

@@ -1075,9 +1145,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": [],

@@ -1136,9 +1212,15 @@ 

              },

              "projects": [

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -1165,9 +1247,15 @@ 

                      "user": {"fullname": "PY C", "name": "pingou"},

                  },

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -1194,9 +1282,15 @@ 

                      "user": {"fullname": "PY C", "name": "pingou"},

                  },

                  {

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

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -1261,9 +1355,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": [],

@@ -1415,9 +1515,15 @@ 

          data["date_created"] = "1496338274"

          data["date_modified"] = "1496338274"

          expected_output = {

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

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["foo"],

                  "ticket": [],

@@ -1469,9 +1575,15 @@ 

          data["date_created"] = "1496338274"

          data["date_modified"] = "1496338274"

          expected_output = {

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

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": ["pingou"],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["foo"],

                  "ticket": [],

@@ -1533,9 +1645,15 @@ 

          data["date_created"] = "1496338274"

          data["date_modified"] = "1496338274"

          expected_output = {

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

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": ["pingou"],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["foo"],

                  "ticket": [],

@@ -1589,9 +1707,15 @@ 

          data["date_created"] = "1496338274"

          data["date_modified"] = "1496338274"

          expected_output = {

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

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["foo"],

                  "ticket": [],

@@ -1647,9 +1771,15 @@ 

          data["date_created"] = "1496338274"

          data["date_modified"] = "1496338274"

          expected_output = {

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

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["foo"],

                  "ticket": [],

@@ -3322,7 +3452,8 @@ 

  

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

          self.assertEquals(

-             project.access_users, {"admin": [], "commit": [], "ticket": []}

+             project.access_users,

+             {"admin": [], "collaborator": [], "commit": [], "ticket": []},

          )

  

      def test_api_modify_acls_no_project(self):

@@ -3462,9 +3593,15 @@ 

          data["date_modified"] = "1510742566"

  

          expected_output = {

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

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": ["foo"],

                  "owner": ["pingou"],

                  "ticket": [],

@@ -3522,9 +3659,15 @@ 

          data["date_modified"] = "1510742566"

  

          expected_output = {

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

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": ["baz"],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["pingou"],

                  "ticket": [],

@@ -3559,7 +3702,8 @@ 

  

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

          self.assertEquals(

-             project.access_users, {"admin": [], "commit": [], "ticket": []}

+             project.access_users,

+             {"admin": [], "collaborator": [], "commit": [], "ticket": []},

          )

  

          data = {"user_type": "user", "name": "foo"}

@@ -3608,7 +3752,12 @@ 

          user_foo = pagure.lib.query.search_user(self.session, username="foo")

          self.assertEquals(

              project.access_users,

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

+             {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [user_foo],

+                 "ticket": [],

+             },

          )

  

          # Create an API token for `foo` for the project `test`

@@ -3637,9 +3786,15 @@ 

          data["date_modified"] = "1510742566"

  

          expected_output = {

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

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],