From patchwork Mon Aug 2 15:27:29 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Axtens X-Patchwork-Id: 1512498 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=112.213.38.117; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=axtens.net header.i=@axtens.net header.a=rsa-sha256 header.s=google header.b=pxVncD7P; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [112.213.38.117]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4Gdhhv5ZZSz9sRN for ; Tue, 3 Aug 2021 01:27:59 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4Gdhhv4ch2z30F1 for ; Tue, 3 Aug 2021 01:27:59 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=axtens.net header.i=@axtens.net header.a=rsa-sha256 header.s=google header.b=pxVncD7P; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=axtens.net (client-ip=2607:f8b0:4864:20::1031; helo=mail-pj1-x1031.google.com; envelope-from=dja@axtens.net; receiver=) Authentication-Results: lists.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=axtens.net header.i=@axtens.net header.a=rsa-sha256 header.s=google header.b=pxVncD7P; dkim-atps=neutral Received: from mail-pj1-x1031.google.com (mail-pj1-x1031.google.com [IPv6:2607:f8b0:4864:20::1031]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4Gdhhq06K5z3bXH for ; Tue, 3 Aug 2021 01:27:54 +1000 (AEST) Received: by mail-pj1-x1031.google.com with SMTP id s22-20020a17090a1c16b0290177caeba067so6447796pjs.0 for ; Mon, 02 Aug 2021 08:27:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=axtens.net; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=qFyMrJ/B1uPnlCGnC9TDoyOAZgRd6ucHRECxLdhd1R8=; b=pxVncD7PypNM4K4z3p2cBwQa+MTlAMck1jJXtuz5s0vke0h9C1avxdr4kvs1i602TU OsIo7nomX05jCmmNxV9Q5ioLVPry3IMR2IyFTZaUtT6l7IIGFZMNFtdlGr2HRK5Db2UT jbqAOyRkg9a1kJZQp461/SdOYRbFWj0iJtNqI= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=qFyMrJ/B1uPnlCGnC9TDoyOAZgRd6ucHRECxLdhd1R8=; b=lVpsPUEZSWNo/GjoQxzeridlLbtVUEMDkLt7C7YwFWqPIbckNc7GFPWlzqM2YtJSTq +2Yh3+sleuc1VzCDLOydL2qQOnbieoUpirHVm7ODuF36be7pIT6oflmsaJjMQoBkW3zs AL8iFdmqGliR4VmKUq2KiQ+DquG3FQ8dTTWDDLfG/cR7icWp9fERFmTUignZlCORhiDQ xzC65n/6OfKIC88jSi8ZmOWz8rFSJ3xc0c8OwXeqlyrhvGkI1fsecmFTeDSsDrZZYXPj 1szAj9pkI1hHeEkMheScpCV4HNZ2V9cNJ9dIZf2mXRe7sI6uuzFKsCy0gl2bkDF8uo9N pUhA== X-Gm-Message-State: AOAM530sQ73+YK8vHiAZTSOnHA77ZymLjLccr7EhBaEMYypcX26IUw6T 2XBobvQbGpIK/V7xz/LFCePaXZvcayPi0g== X-Google-Smtp-Source: ABdhPJzgrBGYtARjM8Wg+q8m84dx47amm/0wp7lnE8Q2t5CpRtpKaAhYyZAK02Cb/xq85O/eywVnWw== X-Received: by 2002:a17:90a:4e4e:: with SMTP id t14mr17967921pjl.8.1627918072072; Mon, 02 Aug 2021 08:27:52 -0700 (PDT) Received: from localhost (ip-145-57.yless4u.com.au. [103.22.145.57]) by smtp.gmail.com with ESMTPSA id w2sm10990742pjt.14.2021.08.02.08.27.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Aug 2021 08:27:51 -0700 (PDT) From: Daniel Axtens To: patchwork@lists.ozlabs.org Subject: [PATCH 3/3] REST: Allow a project to restrict submitter state changes Date: Tue, 3 Aug 2021 01:27:29 +1000 Message-Id: <20210802152729.2110734-4-dja@axtens.net> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210802152729.2110734-1-dja@axtens.net> References: <20210802152729.2110734-1-dja@axtens.net> MIME-Version: 1.0 X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" As with xmlrpc and the UI. Signed-off-by: Daniel Axtens --- patchwork/api/patch.py | 10 +++++ patchwork/tests/api/test_patch.py | 70 +++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/patchwork/api/patch.py b/patchwork/api/patch.py index 9d222754412e..b8d0d5e17749 100644 --- a/patchwork/api/patch.py +++ b/patchwork/api/patch.py @@ -122,6 +122,16 @@ class PatchListSerializer(BaseHyperlinkedModelSerializer): "'%s'" % (value, self.instance.project)) return value + def validate_state(self, value): + """Check that the users is authorised to set this state.""" + user = self.context.get('request').user + if not self.instance.can_set_state(user): + raise ValidationError( + "User '%s' is not permitted to set state '%s' on this patch." % + (user, value.name)) + + return value + def to_representation(self, instance): # NOTE(stephenfin): This is here to ensure our API looks the same even # after we changed the series-patch relationship from M:N to 1:N. It diff --git a/patchwork/tests/api/test_patch.py b/patchwork/tests/api/test_patch.py index da2dd6e9084b..9de7b0d105f4 100644 --- a/patchwork/tests/api/test_patch.py +++ b/patchwork/tests/api/test_patch.py @@ -11,6 +11,9 @@ from django.conf import settings from django.urls import reverse from patchwork.models import Patch +from patchwork.models import Person +from patchwork.models import Project +from patchwork.models import State from patchwork.tests.api import utils from patchwork.tests.utils import create_maintainer from patchwork.tests.utils import create_patch @@ -409,3 +412,70 @@ class TestPatchAPI(utils.APITestCase): self.client.force_authenticate(user=user) resp = self.client.delete(self.api_url(patch.id)) self.assertEqual(status.HTTP_405_METHOD_NOT_ALLOWED, resp.status_code) + + +@unittest.skipUnless(settings.ENABLE_REST_API, 'requires ENABLE_REST_API') +class TestPatchStateChecks(utils.APITestCase): + fixtures = ['default_tags'] + + @staticmethod + def api_url(item=None): + kwargs = {'pk': item} + return reverse('api-patch-detail', kwargs=kwargs) + + def setUp(self): + self.projects = {} + self.maintainers = {} + self.delegates = {} + self.submitters = {} + self.patches = {} + + for project_type in (Project.SUBMITTER_NO_STATE_CHANGES, + Project.SUBMITTER_ALL_STATE_CHANGES): + project = create_project( + submitter_state_change_rules=project_type) + self.projects[project_type] = project + self.maintainers[project_type] = create_maintainer(project) + submitter = create_user(project) + self.submitters[project_type] = submitter + delegate = create_user(project) + self.delegates[project_type] = delegate + + patch = create_patch(project=project, + submitter=Person.objects.get( + user=submitter), + delegate=delegate) + self.patches[project_type] = patch + + create_state(name="New") + create_state(name="RFC") + + def can_set_state(self, patch, user): + new_state = State.objects.get(name="New") + rfc_state = State.objects.get(name="RFC") + patch.state = new_state + patch.save() + + self.client.force_authenticate(user=user) + resp = self.client.patch(self.api_url(patch.id), + {'state': rfc_state.slug}) + + if resp.status_code != status.HTTP_200_OK: + return False + + self.assertEqual(Patch.objects.get(id=patch.id).state, rfc_state) + return True + + def test_allset(self): + project = Project.SUBMITTER_ALL_STATE_CHANGES + patch = self.patches[project] + self.assertTrue(self.can_set_state(patch, self.maintainers[project])) + self.assertTrue(self.can_set_state(patch, self.delegates[project])) + self.assertTrue(self.can_set_state(patch, self.submitters[project])) + + def test_noset(self): + project = Project.SUBMITTER_NO_STATE_CHANGES + patch = self.patches[project] + self.assertTrue(self.can_set_state(patch, self.maintainers[project])) + self.assertTrue(self.can_set_state(patch, self.delegates[project])) + self.assertFalse(self.can_set_state(patch, self.submitters[project]))