#4355 Add the possibility to block all actions of an user on a project
Merged 6 years ago by pingou. Opened 6 years ago by pingou.

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

+ """Add the _block_users field

+ 

+ Revision ID: 1a510f2216c0

+ Revises: 003fcd9e8860

+ Create Date: 2019-03-14 12:24:32.139377

+ 

+ """

+ 

+ from alembic import op

+ import sqlalchemy as sa

+ 

+ 

+ # revision identifiers, used by Alembic.

+ revision = '1a510f2216c0'

+ down_revision = '003fcd9e8860'

+ 

+ 

+ def upgrade():

+     ''' Add the column _block_users to the table projects.

+     '''

+     op.add_column(

+         'projects',

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

+     )

+ 

+ 

+ def downgrade():

+     ''' Drop the column _block_users from the table projects.

+     '''

+     op.drop_column('projects', '_block_users')

file modified
+34
@@ -121,6 +121,7 @@ 

      )

      ETRACKERREADONLY = "The issue tracker of this project is read-only"

      ENOPRSTATS = "No statistics could be computed for this PR"

+     EUBLOCKED = "You have been blocked from this project"

  

  

  def get_authorized_api_project(session, repo, user=None, namespace=None):
@@ -151,6 +152,37 @@ 

              response = check_api_acls(acls)

              if response:

                  return response

+ 

+             # Block all POST request from blocked users

+             if flask.request.method == "POST":

+                 # Retrieve the variables in the URL

+                 url_args = flask.request.view_args or {}

+                 # Check if there is a `repo` and an `username`

+                 repo = url_args.get("repo")

+                 username = url_args.get("username")

+                 namespace = url_args.get("namespace")

+ 

+                 if repo:

+                     flask.g.repo = pagure.lib.query.get_authorized_project(

+                         flask.g.session,

+                         repo,

+                         user=username,

+                         namespace=namespace,

+                     )

+ 

+                     if (

+                         flask.g.repo

+                         and flask.g.fas_user.username

+                         in flask.g.repo.block_users

+                     ):

+                         output = {

+                             "error": APIERROR.EUBLOCKED.value,

+                             "error_code": APIERROR.EUBLOCKED.name,

+                         }

+                         response = flask.jsonify(output)

+                         response.status_code = 403

+                         return response

+ 

              return function(*args, **kwargs)

  

          return decorated_function
@@ -531,6 +563,7 @@ 

      api_modify_project_options_doc = load_doc(

          project.api_modify_project_options

      )

+     api_project_block_user_doc = load_doc(project.api_project_block_user)

  

      issues = []

      if pagure_config.get("ENABLE_TICKETS", True):
@@ -620,6 +653,7 @@ 

              api_update_project_watchers_doc,

              api_get_project_options_doc,

              api_modify_project_options_doc,

+             api_project_block_user_doc,

          ],

          issues=issues,

          requests=[

file modified
+117 -112
@@ -1,7 +1,7 @@ 

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

  

  """

-  (c) 2015-2018 - Copyright Red Hat Inc

+  (c) 2015-2019 - Copyright Red Hat Inc

  

   Authors:

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

      get_page,

      get_per_page,

  )

+ from pagure.api.utils import _get_repo, _check_token

  from pagure.config import config as pagure_config

  

  
@@ -96,11 +97,7 @@ 

          flask.request.values.get("with_commits", False)

      )

  

-     repo = get_authorized_api_project(

-         flask.g.session, repo, user=username, namespace=namespace

-     )

-     if repo is None:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

+     repo = _get_repo(repo, username, namespace)

  

      tags = pagure.lib.git.get_git_tags(repo, with_commits=with_commits)

  
@@ -144,11 +141,7 @@ 

              }

          }

      """

-     repo = get_authorized_api_project(

-         flask.g.session, repo, user=username, namespace=namespace

-     )

-     if repo is None:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

+     repo = _get_repo(repo, username, namespace)

  

      implicit_watch_users = set([repo.user.username])

      for access_type in repo.access_users:
@@ -235,13 +228,9 @@ 

              }

          }

      """

-     repo = get_authorized_api_project(

-         flask.g.session, repo, user=username, namespace=namespace

-     )

-     if repo is None:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

-     git_urls = {}

+     repo = _get_repo(repo, username, namespace)

  

+     git_urls = {}

      git_url_ssh = pagure_config.get("GIT_URL_SSH")

      if pagure.utils.api_authenticated() and git_url_ssh:

          try:
@@ -293,11 +282,7 @@ 

          }

  

      """

-     repo = get_authorized_api_project(

-         flask.g.session, repo, user=username, namespace=namespace

-     )

-     if repo is None:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

+     repo = _get_repo(repo, username, namespace)

  

      branches = pagure.lib.git.get_git_branches(repo)

  
@@ -632,17 +617,12 @@ 

          }

  

      """

-     repo = get_authorized_api_project(

-         flask.g.session, repo, user=username, namespace=namespace

-     )

+     repo = _get_repo(repo, username, namespace)

  

      expand_group = pagure.utils.is_true(

          flask.request.values.get("expand_group", False)

      )

  

-     if repo is None:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

- 

      output = repo.to_json(api=True, public=True)

  

      if expand_group:
@@ -904,14 +884,8 @@ 

          }

  

      """

-     project = get_authorized_api_project(

-         flask.g.session, repo, namespace=namespace

-     )

-     if not project:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

- 

-     if flask.g.token.project and project != flask.g.token.project:

-         raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)

+     project = _get_repo(repo, namespace=namespace)

+     _check_token(project, project_token=False)

  

      is_site_admin = pagure.utils.is_admin()

      admins = [u.username for u in project.get_project_users("admin")]
@@ -1145,14 +1119,8 @@ 

          }

  

      """

-     project = get_authorized_api_project(

-         flask.g.session, repo, namespace=namespace

-     )

-     if not project:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

- 

-     if flask.g.token.project and project != flask.g.token.project:

-         raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)

+     project = _get_repo(repo, username, namespace)

+     _check_token(project, project_token=False)

  

      # Check if it's JSON or form data

      if flask.request.headers.get("Content-Type") == "application/json":
@@ -1230,14 +1198,8 @@ 

          }

  

      """

-     project = get_authorized_api_project(

-         flask.g.session, repo, namespace=namespace

-     )

-     if not project:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

- 

-     if flask.g.token.project and project != flask.g.token.project:

-         raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)

+     project = _get_repo(repo, username, namespace)

+     _check_token(project, project_token=False)

  

      # Check if it's JSON or form data

      if flask.request.headers.get("Content-Type") == "application/json":
@@ -1341,11 +1303,7 @@ 

          }

  

      """

-     repo = get_authorized_api_project(

-         flask.g.session, repo, user=username, namespace=namespace

-     )

-     if repo is None:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

+     repo = _get_repo(repo, username, namespace)

  

      reponame = pagure.utils.get_repo_path(repo)

      repo_obj = Repository(reponame)
@@ -1476,18 +1434,11 @@ 

  

      """  # noqa

  

-     repo = get_authorized_api_project(

-         flask.g.session, repo, user=username, namespace=namespace

-     )

+     repo = _get_repo(repo, username, namespace)

+     _check_token(repo, project_token=False)

  

      output = {}

  

-     if repo is None:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

- 

-     if flask.g.token.project and repo != flask.g.token.project:

-         raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)

- 

      reponame = pagure.utils.get_repo_path(repo)

      repo_obj = Repository(reponame)

      try:
@@ -1618,14 +1569,8 @@ 

          }

      """

  

-     project = get_authorized_api_project(

-         flask.g.session, repo, namespace=namespace

-     )

-     if not project:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

- 

-     if flask.g.token.project and project != flask.g.token.project:

-         raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)

+     project = _get_repo(repo, username, namespace)

+     _check_token(project)

  

      # Get the input submitted

      data = get_request_data()
@@ -1773,14 +1718,9 @@ 

  

      """

      output = {}

-     project = get_authorized_api_project(

-         flask.g.session, repo, namespace=namespace

-     )

-     if not project:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

  

-     if flask.g.token.project and project != flask.g.token.project:

-         raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)

+     project = _get_repo(repo, username, namespace)

+     _check_token(project, project_token=False)

  

      form = pagure.forms.ModifyACLForm(csrf_enabled=False)

      if form.validate_on_submit():
@@ -1956,14 +1896,8 @@ 

          }

  

      """

-     project = get_authorized_api_project(

-         flask.g.session, repo, namespace=namespace

-     )

-     if not project:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

- 

-     if flask.g.token.project and project != flask.g.token.project:

-         raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)

+     project = _get_repo(repo, username, namespace)

+     _check_token(project, project_token=False)

  

      return flask.jsonify({"settings": project.settings, "status": "ok"})

  
@@ -2015,14 +1949,8 @@ 

          }

  

      """

-     project = get_authorized_api_project(

-         flask.g.session, repo, namespace=namespace

-     )

-     if not project:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

- 

-     if flask.g.token.project and project != flask.g.token.project:

-         raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)

+     project = _get_repo(repo, username, namespace)

+     _check_token(project, project_token=False)

  

      authorized_users = [project.user.username]

      authorized_users.extend(
@@ -2105,14 +2033,8 @@ 

          }

  

      """

-     project = get_authorized_api_project(

-         flask.g.session, repo, namespace=namespace

-     )

-     if not project:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

- 

-     if flask.g.token.project and project != flask.g.token.project:

-         raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)

+     project = _get_repo(repo, username, namespace)

+     _check_token(project, project_token=False)

  

      settings = {}

      for key in flask.request.form:
@@ -2194,14 +2116,9 @@ 

  

      """

      output = {}

-     project = get_authorized_api_project(

-         flask.g.session, repo, namespace=namespace

-     )

-     if not project:

-         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

  

-     if flask.g.token.project and project != flask.g.token.project:

-         raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)

+     project = _get_repo(repo, username, namespace)

+     _check_token(project, project_token=False)

  

      authorized_users = [project.user.username]

      authorized_users.extend(
@@ -2229,3 +2146,91 @@ 

  

      jsonout = flask.jsonify(output)

      return jsonout

+ 

+ 

+ @API.route("/<repo>/blockuser", methods=["POST"])

+ @API.route("/<namespace>/<repo>/blockuser", methods=["POST"])

+ @API.route("/fork/<username>/<repo>/blockuser", methods=["POST"])

+ @API.route("/fork/<username>/<namespace>/<repo>/blockuser", methods=["POST"])

+ @api_login_required(acls=["modify_project"])

+ @api_method

+ def api_project_block_user(repo, namespace=None, username=None):

+     """

+     Block an user from a project

+     ----------------------------

+     Block an user from interacting with the project

+ 

+     This is restricted to project admins.

+ 

+     ::

+ 

+         POST /api/0/<repo>/blockuser

+         POST /api/0/<namespace>/<repo>/blockuser

+ 

+     ::

+ 

+         POST /api/0/fork/<username>/<repo>/blockuser

+         POST /api/0/fork/<username>/<namespace>/<repo>/blockuser

+ 

+ 

+     Input

+     ^^^^^

+ 

+     +------------------+---------+---------------+---------------------------+

+     | Key              | Type    | Optionality   | Description               |

+     +==================+=========+===============+===========================+

+     | ``username``     | String  | optional      | The username of the user  |

+     |                  |         |               | to block on this project  |

+     +------------------+---------+---------------+---------------------------+

+ 

+     Beware that this API endpoint updates **all** the users blocked in the

+     project, so if you are updating this list, do not submit just one username,

+     submit the updated list.

+ 

+ 

+     Sample response

+     ^^^^^^^^^^^^^^^

+ 

+     ::

+ 

+         {"message": "User(s) blocked"}

+ 

+     """

+     output = {}

+ 

+     project = _get_repo(repo, username, namespace)

+     _check_token(project)

+ 

+     authorized_users = [project.user.username]

+     authorized_users.extend(

+         [user.user for user in project.access_users["admin"]]

+     )

+     if flask.g.fas_user.username not in authorized_users:

+         raise pagure.exceptions.APIError(

+             401, error_code=APIERROR.ENOTHIGHENOUGH

+         )

+ 

+     usernames = flask.request.form.getlist("username")

+ 

+     try:

+         users = set()

+         for user in usernames:

+             user = user.strip()

+             if user:

+                 pagure.lib.query.get_user(flask.g.session, user)

+                 users.add(user)

+         project.block_users = list(users)

+         flask.g.session.add(project)

+         flask.g.session.commit()

+         output = {"message": "User(s) blocked"}

+     except pagure.exceptions.PagureException as err:

+         raise pagure.exceptions.APIError(

+             400, error_code=APIERROR.ENOCODE, error=str(err)

+         )

+     except SQLAlchemyError as err:  # pragma: no cover

+         flask.g.session.rollback()

+         _log.exception(err)

+         raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)

+ 

+     jsonout = flask.jsonify(output)

+     return jsonout

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

                  flask.g.session, flask.g.repo, user=flask.g.fas_user.username

              )

  

+             # Block all POST request from blocked users

+             if flask.g.repo and flask.request.method != "GET":

+                 if flask.g.fas_user.username in flask.g.repo.block_users:

+                     flask.abort(403, "You have been blocked from this project")

+ 

          if (

              not flask.g.repo

              and namespace

file modified
+18
@@ -377,6 +377,7 @@ 

      _reports = sa.Column(sa.Text, nullable=True)

      _notifications = sa.Column(sa.Text, nullable=True)

      _close_status = sa.Column(sa.Text, nullable=True)

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

      mirrored_from = sa.Column(sa.Text, nullable=True)

      mirrored_from_last_log = sa.Column(sa.Text, nullable=True)

  
@@ -721,6 +722,23 @@ 

          self._priorities = json.dumps(priorities)

  

      @property

+     def block_users(self):

+         """ Return the dict stored as string in the database as an actual

+         dict object.

+         """

+         block_users = []

+ 

+         if self._block_users:

+             block_users = json.loads(self._block_users)

+ 

+         return block_users

+ 

+     @block_users.setter

+     def block_users(self, block_users):

+         """ Ensures the block_users are properly saved. """

+         self._block_users = json.dumps(block_users)

+ 

+     @property

      def quick_replies(self):

          """ Return a list of quick replies available for pull requests and

          issues.

@@ -79,6 +79,11 @@ 

            <a class="nav-item nav-link" id="regen" data-toggle="tab"

                  href="#regen-tab" role="tab" aria-controls="regen">Regenerate Repos</a>

  

+           {% if repo.user.user == g.fas_user.username or pagure_admin %}

+           <a class="nav-item nav-link" id="blockusers" data-toggle="tab"

+             href="#blockusers-tab" role="tab" aria-controls="blockusers">Block Users</a>

+           {% endif %}

+ 

            {% if config.get('ENABLE_GIVE_PROJECTS', True)

              and (repo.user.user == g.fas_user.username or pagure_admin)

              and not repo.is_fork %}
@@ -1034,6 +1039,10 @@ 

                </div>

            </div>

  

+           <div class="tab-pane fade" id="blockusers-tab" role="tabpanel" aria-labelledby="blockusers-tab">

+             {% include 'settings_block_users.html' %}

+           </div>

+ 

            {% if config.get('ENABLE_GIVE_PROJECTS', True)

            and (repo.user.user == g.fas_user.username or pagure_admin)

            and not repo.is_fork %}
@@ -1418,6 +1427,39 @@ 

    }

  });

  {% endif %}

+ 

+ $('.ajaxed').click(function(e) {

+   _form = $(this).closest('form')

+   $.ajax({

+       url: _form.prop('action') ,

+       type: 'POST',

+       data: _form.serialize(),

+       dataType: 'json',

+       success: function(res) {

+         console.log(res);

+         if ( res.message ) {

+           var _html = '<div class="container pt-2">'

+               + '  <div class="alert alert-info border border-secondary bg-white alert-dismissible" role="alert">'

+               + '      <button type="button" class="close" data-dismiss="alert" aria-label="Close">'

+               + '      <span aria-hidden="true">×</span>'

+               + '      <span class="sr-only">Close</span>'

+               + '    </button>'

+               + '    <div class="text-info font-weight-bold">'

+               + '      <i class="fa fa-fw fa-info-circle"></i>' + res.message

+               + '    </div>'

+               + '  </div>'

+               + '</div>';

+           $('.bodycontent').prepend(_html)

+         }

+       },

+       error: function(res) {

+         console.log(res);

+         alert('Request failed');

+       }

+   });

+   return false;

+ });

+ 

  </script>

  

  <script type="text/javascript">

@@ -0,0 +1,58 @@ 

+ 

+ <h3 class="font-weight-bold mb-3">

+     Block users

+ </h3>

+ <p>

+   You can block any users from interacting with your project. They will be able

+   to view but nothing more.

+ </p>

+ <form action="{{ url_for(

+   'api_ns.api_project_block_user',

+   repo=repo.name,

+   username=username,

+   namespace=repo.namespace) }}"

+     method="post" class="icon">

+     <div class="row">

+       <div class="col-sm-12">

+         <strong>Users</strong>

+       </div>

+     </div>

+   <div class="form-group settings-field-rows" id="blockusers-list">

+     <div class="row hidden blank-field">

+       <div class="col-sm-11" >

+         <input type="text" name="username"

+                 value="" class="form-control"/>

+       </div>

+ 

+       <div class="col-sm-1">

+         <a href="javascript:void(0)" class="btn btn-outline-danger remove-settings-field-row"><i class="fa fa-trash"></i></a>

+       </div>

+     </div>

+     {% for user in repo.block_users | sort %}

+       <div class="row {{'hidden blank-field' if status == ''}}">

+         <div class="col-sm-11" >

+           <input type="text" name="username"

+                  value="{{ user }}" class="form-control"/>

+         </div>

+ 

+         <div class="col-sm-1">

+           <a href="javascript:void(0)" class="btn btn-outline-danger remove-settings-field-row"><i class="fa fa-trash"></i></a>

+         </div>

+       </div>

+     {% endfor %}

+   </div>

+   <a href="javascript:void(0)" class="btn btn-secondary pt-2 btn-sm btn-block add-settings-field-row" data-target="#blockusers-list">

+       <i class="fa fa-plus"></i> Add new user

+   </a>

+ 

+   <div class="row p-t-1">

+   </div>

+   <div class="row p-t-1">

+     <div class="col-sm-12">

+       <button class="btn btn-primary float-right mt-3 ajaxed" type="submit"

+           title="Update the blocked user">

+         Update

+       </button>

+     </div>

+   </div>

+ </form>

@@ -237,10 +237,10 @@ 

          output = self.app.get('/api/0/-/error_codes')

          self.assertEqual(output.status_code, 200)

          data = json.loads(output.get_data(as_text=True))

-         self.assertEqual(len(data), 35)

+         self.assertEqual(len(data), 36)

          self.assertEqual(

              sorted(data.keys()),

-             [

+             sorted([

                  'EDATETIME',

                  'EDBERROR',

                  'EGITERROR',
@@ -276,7 +276,8 @@ 

                  'ETIMESTAMP',

                  'ETRACKERDISABLED',

                  'ETRACKERREADONLY',

-             ]

+                 'EUBLOCKED',

+             ])

          )

  

      @patch("pagure.lib.tasks.get_result")

@@ -0,0 +1,264 @@ 

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

+ 

+ """

+  (c) 2019 - Copyright Red Hat Inc

+ 

+  Authors:

+    Pierre-Yves Chibon <pingou@pingoured.fr>

+ 

+ """

+ 

+ from __future__ import unicode_literals, absolute_import

+ 

+ import arrow

+ import copy

+ import datetime

+ import unittest

+ import shutil

+ import sys

+ import time

+ import os

+ 

+ import flask

+ import json

+ import munch

+ from mock import patch, MagicMock

+ from sqlalchemy.exc import SQLAlchemyError

+ 

+ sys.path.insert(

+     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")

+ )

+ 

+ import pagure.lib.query

+ import tests

+ 

+ 

+ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest):

+     """ Tests for the flask API of pagure for assigning a PR """

+ 

+     maxDiff = None

+ 

+     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))

+     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))

+     def setUp(self):

+         """ Set up the environnment, ran before every tests. """

+         super(PagureFlaskApiProjectBlockuserTests, self).setUp()

+ 

+         tests.create_projects(self.session)

+         tests.create_projects_git(

+             os.path.join(self.path, 'repos'), bare=True)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

+ 

+         item = pagure.lib.model.Token(

+             id="aaabbbcccdddeee",

+             user_id=2,

+             project_id=1,

+             expiration=datetime.datetime.utcnow()

+             + datetime.timedelta(days=30),

+         )

+         self.session.add(item)

+         self.session.commit()

+         tests.create_tokens_acl(self.session, token_id="aaabbbcccdddeee")

+ 

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

+         self.assertEqual(project.block_users, [])

+         self.blocked_users = []

+ 

+         project = pagure.lib.query.get_authorized_project(self.session, "test2")

+         project.block_users = ["foo"]

+         self.session.add(project)

+         self.session.commit()

+ 

+     def tearDown(self):

+         """ Tears down the environment at the end of the tests. """

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

+         self.assertEqual(project.block_users, self.blocked_users)

+ 

+         super(PagureFlaskApiProjectBlockuserTests, self).tearDown()

+ 

+     def test_api_blockuser_no_token(self):

+         """ Test api_project_block_user method when no token is provided.

+         """

+ 

+         # No token

+         output = self.app.post("/api/0/test/blockuser")

+         self.assertEqual(output.status_code, 401)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": "Invalid or expired token. Please visit "

+                 "http://localhost.localdomain/settings#api-keys to "

+                 "get or renew your API token.",

+                 "error_code": "EINVALIDTOK",

+                 "errors": "Invalid token",

+             },

+         )

+ 

+     def test_api_blockuser_invalid_token(self):

+         """ Test api_project_block_user method when the token provided is invalid.

+         """

+ 

+         headers = {"Authorization": "token aaabbbcccd"}

+ 

+         # Invalid token

+         output = self.app.post("/api/0/test/blockuser", headers=headers)

+         self.assertEqual(output.status_code, 401)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": "Invalid or expired token. Please visit "

+                 "http://localhost.localdomain/settings#api-keys to "

+                 "get or renew your API token.",

+                 "error_code": "EINVALIDTOK",

+                 "errors": "Invalid token",

+             },

+         )

+ 

+     def test_api_blockuser_no_data(self):

+         """ Test api_project_block_user method when no data is provided.

+         """

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+ 

+         # No user blocked

+         output = self.app.post("/api/0/test/blockuser", headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, {"message": "User(s) blocked"})

+ 

+     def test_api_blockuser_invalid_user(self):

+         """ Test api_project_block_user method when the data provided includes

+         an invalid username.

+         """

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {"username": ["invalid"]}

+ 

+         # No user blocked

+         output = self.app.post(

+             "/api/0/test/blockuser", headers=headers, data=data

+         )

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data, {"error": 'No user "invalid" found', "error_code": "ENOCODE"}

+         )

+ 

+     def test_api_blockuser_insufficient_rights(self):

+         """ Test api_project_block_user method when the user doing the action

+         does not have admin priviledges.

+         """

+ 

+         headers = {"Authorization": "token aaabbbcccdddeee"}

+         data = {"username": ["invalid"]}

+ 

+         # No user blocked

+         output = self.app.post(

+             "/api/0/test/blockuser", headers=headers, data=data

+         )

+         self.assertEqual(output.status_code, 401)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": "You do not have sufficient permissions to perform "

+                 "this action",

+                 "error_code": "ENOTHIGHENOUGH",

+             },

+         )

+ 

+     def test_api_blockuser_with_data(self):

+         """ Test api_pull_request_assign method when the project doesn't exist.

+         """

+         self.blocked_users = ["foo"]

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {"username": ["foo"]}

+ 

+         # user blocked

+         output = self.app.post(

+             "/api/0/test/blockuser", headers=headers, data=data

+         )

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, {"message": "User(s) blocked"})

+ 

+         # Second request, no changes

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {"username": ["foo"]}

+ 

+         output = self.app.post(

+             "/api/0/test/blockuser", headers=headers, data=data

+         )

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, {"message": "User(s) blocked"})

+ 

+     def test_api_blockeduser_api(self):

+         """ Test doing a POST request to the API when the user is blocked.

+         """

+         self.blocked_users = ["pingou"]

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {"username": ["pingou"]}

+ 

+         # user blocked

+         output = self.app.post(

+             "/api/0/test/blockuser", headers=headers, data=data

+         )

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, {"message": "User(s) blocked"})

+ 

+         # Second request, but user is blocked

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {"username": ["foo"]}

+ 

+         output = self.app.post(

+             "/api/0/test/blockuser", headers=headers, data=data

+         )

+         self.assertEqual(output.status_code, 403)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error":"You have been blocked from this project",

+                 "error_code":"EUBLOCKED"

+             }

+         )

+ 

+     def test_ui_new_issue_user_blocked(self):

+         """ Test doing a POST request to the UI when the user is blocked.

+         """

+ 

+         user = tests.FakeUser(username="foo")

+         with tests.user_set(self.app.application, user):

+ 

+             output = self.app.get('/test2/new_issue')

+             self.assertEqual(output.status_code, 200)

+             self.assertIn(

+                 'New Issue',

+                 output.get_data(as_text=True))

+ 

+             csrf_token = self.get_csrf(output=output)

+ 

+             data = {

+                 'title': 'Test issue',

+                 'issue_content': 'We really should improve on this issue',

+                 'status': 'Open',

+                 'csrf_token': csrf_token,

+             }

+ 

+             output = self.app.post('/test2/new_issue', data=data)

+             self.assertEqual(output.status_code, 403)

+             output_text = output.get_data(as_text=True)

+             self.assertIn(

+                 '<p>You have been blocked from this project</p>',

+                 output_text)

+ 

+ 

+ if __name__ == "__main__":

+     unittest.main(verbosity=2)