From 69a5d0817da6b70361a42d633baf5257316163ad Mon Sep 17 00:00:00 2001 From: Henrik Levkowetz Date: Wed, 15 Apr 2020 17:22:06 +0000 Subject: [PATCH] Refined the GUI for personal API endpoints so that endpoints for which one does not have the right Roles do not show in the GUI, and added a supporting method on Person objects. Updated tests accordingly. - Legacy-Id: 17643 --- ietf/ietfauth/tests.py | 22 ++++++++++++---------- ietf/ietfauth/views.py | 7 +++++-- ietf/person/models.py | 19 ++++++++++++------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index e381893d6..a78a62854 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -21,7 +21,7 @@ from ietf.group.models import Group, Role, RoleName from ietf.group.factories import GroupFactory, RoleFactory from ietf.ietfauth.htpasswd import update_htpasswd_file from ietf.mailinglists.models import Subscribed -from ietf.person.models import Person, Email, PersonalApiKey, PERSON_API_KEY_ENDPOINTS +from ietf.person.models import Person, Email, PersonalApiKey from ietf.person.factories import PersonFactory, EmailFactory from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory from ietf.review.models import ReviewWish, UnavailablePeriod @@ -531,18 +531,19 @@ class IetfAuthTests(TestCase): self.assertContains(r, 'Endpoint') # Add 2 keys - for endpoint, display in PERSON_API_KEY_ENDPOINTS: + endpoints = person.available_api_endpoints() + for endpoint, display in endpoints: r = self.client.post(url, {'endpoint': endpoint}) self.assertRedirects(r, urlreverse('ietf.ietfauth.views.apikey_index')) # Check api key list content url = urlreverse('ietf.ietfauth.views.apikey_index') r = self.client.get(url) - for endpoint, display in PERSON_API_KEY_ENDPOINTS: + for endpoint, display in endpoints: self.assertContains(r, endpoint) q = PyQuery(r.content) - self.assertEqual(len(q('td code')), len(PERSON_API_KEY_ENDPOINTS)) # hash - self.assertEqual(len(q('td a:contains("Disable")')), len(PERSON_API_KEY_ENDPOINTS)) + self.assertEqual(len(q('td code')), len(endpoints)) # hash + self.assertEqual(len(q('td a:contains("Disable")')), len(endpoints)) # Get one of the keys key = person.apikeys.first() @@ -562,8 +563,8 @@ class IetfAuthTests(TestCase): url = urlreverse('ietf.ietfauth.views.apikey_index') r = self.client.get(url) q = PyQuery(r.content) - self.assertEqual(len(q('td code')), len(PERSON_API_KEY_ENDPOINTS)) # key hash - self.assertEqual(len(q('td a:contains("Disable")')), len(PERSON_API_KEY_ENDPOINTS)-1) + self.assertEqual(len(q('td code')), len(endpoints)) # key hash + self.assertEqual(len(q('td a:contains("Disable")')), len(endpoints)-1) def test_apikey_errors(self): BAD_KEY = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" @@ -577,7 +578,7 @@ class IetfAuthTests(TestCase): login_testing_unauthorized(self, person.user.username, url) # Add keys - for endpoint, display in PERSON_API_KEY_ENDPOINTS: + for endpoint, display in person.available_api_endpoints(): r = self.client.post(url, {'endpoint': endpoint}) self.assertRedirects(r, urlreverse('ietf.ietfauth.views.apikey_index')) @@ -620,7 +621,8 @@ class IetfAuthTests(TestCase): login_testing_unauthorized(self, person.user.username, url) # Add keys - for endpoint, display in PERSON_API_KEY_ENDPOINTS: + endpoints = person.available_api_endpoints() + for endpoint, display in endpoints: r = self.client.post(url, {'endpoint': endpoint}) self.assertRedirects(r, urlreverse('ietf.ietfauth.views.apikey_index')) @@ -639,7 +641,7 @@ class IetfAuthTests(TestCase): cmd = Command() cmd.handle(verbosity=0, days=7) - self.assertEqual(len(outbox), len(PERSON_API_KEY_ENDPOINTS)) + self.assertEqual(len(outbox), len(endpoints)) for mail in outbox: body = mail.get_payload(decode=True).decode('utf-8') self.assertIn("API key usage", mail['subject']) diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py index c12275bf1..129123b8f 100644 --- a/ietf/ietfauth/views.py +++ b/ietf/ietfauth/views.py @@ -65,9 +65,9 @@ from ietf.ietfauth.forms import ( RegistrationForm, PasswordForm, ResetPasswordF WhitelistForm, ChangePasswordForm, get_person_form, RoleEmailForm, NewEmailForm, ChangeUsernameForm, PersonPasswordForm) from ietf.ietfauth.htpasswd import update_htpasswd_file -from ietf.ietfauth.utils import role_required +from ietf.ietfauth.utils import role_required, has_role from ietf.mailinglists.models import Subscribed, Whitelisted -from ietf.person.models import Person, Email, Alias, PersonalApiKey +from ietf.person.models import Person, Email, Alias, PersonalApiKey, PERSON_API_KEY_VALUES from ietf.review.models import ReviewerSettings, ReviewWish, ReviewAssignment from ietf.review.utils import unavailable_periods_to_list, get_default_filter_re from ietf.doc.fields import SearchableDocumentField @@ -650,7 +650,10 @@ def apikey_index(request): @login_required @person_required def apikey_create(request): + endpoints = [('', '----------')] + [ (v, n) for (v, n, r) in PERSON_API_KEY_VALUES if r==None or has_role(request.user, r) ] class ApiKeyForm(forms.ModelForm): + endpoint = forms.ChoiceField(choices=endpoints) + class Meta: model = PersonalApiKey fields = ['endpoint'] diff --git a/ietf/person/models.py b/ietf/person/models.py index 2cdbc4240..f9349dc47 100644 --- a/ietf/person/models.py +++ b/ietf/person/models.py @@ -234,6 +234,11 @@ class Person(models.Model): ct1['ascii'] = self.ascii return ct1 + def available_api_endpoints(self): + from ietf.ietfauth.utils import has_role + return [ (v, n) for (v, n, r) in PERSON_API_KEY_VALUES if r==None or has_role(self.user, r) ] + + @python_2_unicode_compatible class Alias(models.Model): """This is used for alternative forms of a name. This is the @@ -328,13 +333,13 @@ def salt(): return uuid.uuid4().bytes[:12] # Manual maintenance: List all endpoints that use @require_api_key here -PERSON_API_KEY_ENDPOINTS = [ - ("/api/iesg/position", "/api/iesg/position"), -# This requires secretariat role, and need not be listed generally: -# ("/api/v2/person/person", "/api/v2/person/person"), - ("/api/meeting/session/video/url", "/api/meeting/session/video/url"), - ("/api/v2/person/access/meetecho", "/api/v2/person/access/meetecho"), +PERSON_API_KEY_VALUES = [ + ("/api/iesg/position", "/api/iesg/position", "Area Director"), + ("/api/v2/person/person", "/api/v2/person/person", "Secretariat"), + ("/api/meeting/session/video/url", "/api/meeting/session/video/url", "Recording Manager"), + ("/api/person/access/meetecho", "/api/person/access/meetecho", None), ] +PERSON_API_KEY_ENDPOINTS = [ (v, n) for (v, n, r) in PERSON_API_KEY_VALUES ] @python_2_unicode_compatible class PersonalApiKey(models.Model): @@ -375,7 +380,7 @@ class PersonalApiKey(models.Model): return self._cached_hash def __str__(self): - return "%s (%s): %s ..." % (self.endpoint, self.created.strftime("%Y-%m-%d %H:%M"), self.hash()[:16]) + return "%s %-24s %-32s(%6d): %s ..." % (self.created.strftime("%Y-%m-%d %H:%M"), self.person.name, self.endpoint, self.count, self.hash()[:16]) PERSON_EVENT_CHOICES = [ ("apikey_login", "API key login"),