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
This commit is contained in:
Henrik Levkowetz 2020-04-15 17:22:06 +00:00
parent eedd48d455
commit 69a5d0817d
3 changed files with 29 additions and 19 deletions

View file

@ -21,7 +21,7 @@ from ietf.group.models import Group, Role, RoleName
from ietf.group.factories import GroupFactory, RoleFactory from ietf.group.factories import GroupFactory, RoleFactory
from ietf.ietfauth.htpasswd import update_htpasswd_file from ietf.ietfauth.htpasswd import update_htpasswd_file
from ietf.mailinglists.models import Subscribed 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.person.factories import PersonFactory, EmailFactory
from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory
from ietf.review.models import ReviewWish, UnavailablePeriod from ietf.review.models import ReviewWish, UnavailablePeriod
@ -531,18 +531,19 @@ class IetfAuthTests(TestCase):
self.assertContains(r, 'Endpoint') self.assertContains(r, 'Endpoint')
# Add 2 keys # 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}) r = self.client.post(url, {'endpoint': endpoint})
self.assertRedirects(r, urlreverse('ietf.ietfauth.views.apikey_index')) self.assertRedirects(r, urlreverse('ietf.ietfauth.views.apikey_index'))
# Check api key list content # Check api key list content
url = urlreverse('ietf.ietfauth.views.apikey_index') url = urlreverse('ietf.ietfauth.views.apikey_index')
r = self.client.get(url) r = self.client.get(url)
for endpoint, display in PERSON_API_KEY_ENDPOINTS: for endpoint, display in endpoints:
self.assertContains(r, endpoint) self.assertContains(r, endpoint)
q = PyQuery(r.content) q = PyQuery(r.content)
self.assertEqual(len(q('td code')), len(PERSON_API_KEY_ENDPOINTS)) # hash self.assertEqual(len(q('td code')), len(endpoints)) # hash
self.assertEqual(len(q('td a:contains("Disable")')), len(PERSON_API_KEY_ENDPOINTS)) self.assertEqual(len(q('td a:contains("Disable")')), len(endpoints))
# Get one of the keys # Get one of the keys
key = person.apikeys.first() key = person.apikeys.first()
@ -562,8 +563,8 @@ class IetfAuthTests(TestCase):
url = urlreverse('ietf.ietfauth.views.apikey_index') url = urlreverse('ietf.ietfauth.views.apikey_index')
r = self.client.get(url) r = self.client.get(url)
q = PyQuery(r.content) q = PyQuery(r.content)
self.assertEqual(len(q('td code')), len(PERSON_API_KEY_ENDPOINTS)) # key hash self.assertEqual(len(q('td code')), len(endpoints)) # key hash
self.assertEqual(len(q('td a:contains("Disable")')), len(PERSON_API_KEY_ENDPOINTS)-1) self.assertEqual(len(q('td a:contains("Disable")')), len(endpoints)-1)
def test_apikey_errors(self): def test_apikey_errors(self):
BAD_KEY = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" BAD_KEY = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
@ -577,7 +578,7 @@ class IetfAuthTests(TestCase):
login_testing_unauthorized(self, person.user.username, url) login_testing_unauthorized(self, person.user.username, url)
# Add keys # 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}) r = self.client.post(url, {'endpoint': endpoint})
self.assertRedirects(r, urlreverse('ietf.ietfauth.views.apikey_index')) self.assertRedirects(r, urlreverse('ietf.ietfauth.views.apikey_index'))
@ -620,7 +621,8 @@ class IetfAuthTests(TestCase):
login_testing_unauthorized(self, person.user.username, url) login_testing_unauthorized(self, person.user.username, url)
# Add keys # 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}) r = self.client.post(url, {'endpoint': endpoint})
self.assertRedirects(r, urlreverse('ietf.ietfauth.views.apikey_index')) self.assertRedirects(r, urlreverse('ietf.ietfauth.views.apikey_index'))
@ -639,7 +641,7 @@ class IetfAuthTests(TestCase):
cmd = Command() cmd = Command()
cmd.handle(verbosity=0, days=7) 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: for mail in outbox:
body = mail.get_payload(decode=True).decode('utf-8') body = mail.get_payload(decode=True).decode('utf-8')
self.assertIn("API key usage", mail['subject']) self.assertIn("API key usage", mail['subject'])

View file

@ -65,9 +65,9 @@ from ietf.ietfauth.forms import ( RegistrationForm, PasswordForm, ResetPasswordF
WhitelistForm, ChangePasswordForm, get_person_form, RoleEmailForm, WhitelistForm, ChangePasswordForm, get_person_form, RoleEmailForm,
NewEmailForm, ChangeUsernameForm, PersonPasswordForm) NewEmailForm, ChangeUsernameForm, PersonPasswordForm)
from ietf.ietfauth.htpasswd import update_htpasswd_file 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.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.models import ReviewerSettings, ReviewWish, ReviewAssignment
from ietf.review.utils import unavailable_periods_to_list, get_default_filter_re from ietf.review.utils import unavailable_periods_to_list, get_default_filter_re
from ietf.doc.fields import SearchableDocumentField from ietf.doc.fields import SearchableDocumentField
@ -650,7 +650,10 @@ def apikey_index(request):
@login_required @login_required
@person_required @person_required
def apikey_create(request): 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): class ApiKeyForm(forms.ModelForm):
endpoint = forms.ChoiceField(choices=endpoints)
class Meta: class Meta:
model = PersonalApiKey model = PersonalApiKey
fields = ['endpoint'] fields = ['endpoint']

View file

@ -234,6 +234,11 @@ class Person(models.Model):
ct1['ascii'] = self.ascii ct1['ascii'] = self.ascii
return ct1 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 @python_2_unicode_compatible
class Alias(models.Model): class Alias(models.Model):
"""This is used for alternative forms of a name. This is the """This is used for alternative forms of a name. This is the
@ -328,13 +333,13 @@ def salt():
return uuid.uuid4().bytes[:12] return uuid.uuid4().bytes[:12]
# Manual maintenance: List all endpoints that use @require_api_key here # Manual maintenance: List all endpoints that use @require_api_key here
PERSON_API_KEY_ENDPOINTS = [ PERSON_API_KEY_VALUES = [
("/api/iesg/position", "/api/iesg/position"), ("/api/iesg/position", "/api/iesg/position", "Area Director"),
# This requires secretariat role, and need not be listed generally: ("/api/v2/person/person", "/api/v2/person/person", "Secretariat"),
# ("/api/v2/person/person", "/api/v2/person/person"), ("/api/meeting/session/video/url", "/api/meeting/session/video/url", "Recording Manager"),
("/api/meeting/session/video/url", "/api/meeting/session/video/url"), ("/api/person/access/meetecho", "/api/person/access/meetecho", None),
("/api/v2/person/access/meetecho", "/api/v2/person/access/meetecho"),
] ]
PERSON_API_KEY_ENDPOINTS = [ (v, n) for (v, n, r) in PERSON_API_KEY_VALUES ]
@python_2_unicode_compatible @python_2_unicode_compatible
class PersonalApiKey(models.Model): class PersonalApiKey(models.Model):
@ -375,7 +380,7 @@ class PersonalApiKey(models.Model):
return self._cached_hash return self._cached_hash
def __str__(self): 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 = [ PERSON_EVENT_CHOICES = [
("apikey_login", "API key login"), ("apikey_login", "API key login"),