Added a new API endpoint to be used by the registration system, to trigger account creation.

- Legacy-Id: 17941
This commit is contained in:
Henrik Levkowetz 2020-06-08 19:51:10 +00:00
parent 2416a46f5e
commit ec5d159b4f
6 changed files with 135 additions and 18 deletions

View file

@ -23,6 +23,7 @@ from ietf.meeting.factories import MeetingFactory, SessionFactory
from ietf.meeting.test_data import make_meeting_test_data
from ietf.person.factories import PersonFactory
from ietf.person.models import PersonalApiKey
from ietf.utils.mail import outbox, get_payload_text
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
OMITTED_APPS = (
@ -31,7 +32,7 @@ OMITTED_APPS = (
'ietf.ipr',
)
class CustomApiTestCase(TestCase):
class CustomApiTests(TestCase):
# Using mock to patch the import functions in ietf.meeting.views, where
# api_import_recordings() are using them:
@ -197,6 +198,48 @@ class CustomApiTestCase(TestCase):
self.assertEqual(data['name'], person.name)
self.assertEqual(data['email'], person.email().address)
def test_api_new_meeting_registration(self):
meeting = MeetingFactory(type_id='ietf')
reg = {
'apikey': 'invalid',
'affiliation': "Alguma Corporação",
'country_code': 'PT',
'email': 'foo@example.pt',
'first_name': 'Foo',
'last_name': 'Bar',
'meeting': meeting.number,
'reg_type': 'hackathon',
'ticket_type': 'regular',
}
url = urlreverse('ietf.api.views.api_new_meeting_registration')
r = self.client.post(url, reg)
self.assertContains(r, 'Invalid apikey', status_code=400)
person = PersonFactory(user__is_staff=True)
# Make sure 'person' has an acceptable role
RoleFactory(name_id='robot', person=person, email=person.email(), group__acronym='secretariat')
key = PersonalApiKey.objects.create(person=person, endpoint=url)
reg['apikey'] = key.hash()
#
# Test valid POST
r = self.client.post(url, reg)
self.assertContains(r, "Accepted, New registration, Email sent", status_code=202)
#
# Check outgoing mail
self.assertEqual(len(outbox), 1)
body = get_payload_text(outbox[-1])
self.assertIn(reg['email'], outbox[-1]['To'] )
self.assertIn(reg['email'], body)
self.assertIn('account creation request', body)
#
# Test incomplete POST
drop_fields = ['affiliation', 'first_name', 'reg_type']
for field in drop_fields:
del reg[field]
r = self.client.post(url, reg)
self.assertContains(r, 'Missing parameters:', status_code=400)
err, fields = r.content.decode().split(':', 1)
missing_fields = [ f.strip() for f in fields.split(',') ]
self.assertEqual(set(missing_fields), set(drop_fields))
class TastypieApiTestCase(ResourceTestCaseMixin, TestCase):
def __init__(self, *args, **kwargs):

View file

@ -12,22 +12,30 @@ from ietf.utils.urls import url
api.autodiscover()
urlpatterns = [
# Top endpoint for Tastypie's REST API (this isn't standard Tastypie):
# General API help page
url(r'^$', api_views.api_help),
# Top endpoint for Tastypie's REST API (this isn't standard Tastypie):
url(r'^v1/?$', api_views.top_level),
# Custom API endpoints
url(r'^notify/meeting/import_recordings/(?P<number>[a-z0-9-]+)/?$', meeting_views.api_import_recordings),
url(r'^meeting/session/video/url$', meeting_views.api_set_session_video_url),
url(r'^submit/?$', submit_views.api_submit),
url(r'^iesg/position', views_ballot.api_set_position),
# GPRD: export of personal information for the logged-in person
url(r'^export/personal-information/$', api_views.PersonalInformationExportView.as_view()),
# For mailarchive use, requires secretariat role
url(r'^v2/person/person', api_views.ApiV2PersonExportView.as_view()),
# For meetecho access
url(r'^person/access/meetecho', api_views.person_access_meetecho),
#
# --- Custom API endpoints, sorted alphabetically ---
# GPRD: export of personal information for the logged-in person
url(r'^export/personal-information/$', api_views.PersonalInformationExportView.as_view()),
# Let IESG members set positions programmatically
url(r'^iesg/position', views_ballot.api_set_position),
# Let Meetecho set session video URLs
url(r'^meeting/session/video/url$', meeting_views.api_set_session_video_url),
# Let Meetecho trigger recording imports
url(r'^notify/meeting/import_recordings/(?P<number>[a-z0-9-]+)/?$', meeting_views.api_import_recordings),
# Let the registration system notify us about registrations
url(r'^notify/meeting/registration', api_views.api_new_meeting_registration),
# OpenID authentication provider
url(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')),
# For meetecho access
url(r'^person/access/meetecho', api_views.person_access_meetecho),
# Draft submission API
url(r'^submit/?$', submit_views.api_submit),
]
# Additional (standard) Tastypie endpoints

View file

@ -8,6 +8,9 @@ from jwcrypto.jwk import JWK
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
@ -23,11 +26,14 @@ from tastypie.serializers import Serializer
import debug # pyflakes:ignore
from ietf.person.models import Person
from ietf.person.models import Person, Email
from ietf.api import _api_list
from ietf.api.serializer import JsonExportMixin
from ietf.utils.decorators import require_api_key
from ietf.ietfauth.views import send_account_creation_email
from ietf.ietfauth.utils import role_required
from ietf.meeting.models import Meeting
from ietf.stats.models import MeetingRegistration
from ietf.utils.decorators import require_api_key
def top_level(request):
@ -108,3 +114,61 @@ def person_access_meetecho(request):
'secr': list(person.role_set.filter(name='secr', group__state__in=['active', 'bof', 'proposed']).values_list('group__acronym', flat=True)),
}
}), content_type='application/json')
@require_api_key
@role_required('Robot')
@csrf_exempt
def api_new_meeting_registration(request):
'''REST API to notify the datatracker about a new meeting registration'''
def err(code, text):
return HttpResponse(text, status=code, content_type='text/plain')
required_fields = [ 'meeting', 'first_name', 'last_name', 'affiliation', 'country_code', 'email', 'reg_type', 'ticket_type', ]
fields = required_fields + []
if request.method == 'POST':
# parameters:
# apikey:
# meeting
# name
# email
# reg_type (In Person, Remote, Hackathon Only)
# ticket_type (full_week, one_day, student)
#
data = {}
missing_fields = []
for item in fields:
value = request.POST.get(item, None)
if value is None and item in required_fields:
missing_fields.append(item)
data[item] = value
if missing_fields:
return err(400, "Missing parameters: %s" % ', '.join(missing_fields))
number = data['meeting']
try:
meeting = Meeting.objects.get(number=number)
except Meeting.DoesNotExist:
return err(400, "Invalid meeting value: '%s'" % (number, ))
email = data['email']
try:
validate_email(email)
except ValidationError:
return err(400, "Invalid email value: '%s'" % (email, ))
if request.POST.get('cancelled', 'false') == 'true':
MeetingRegistration.objects.filter(meeting_id=meeting.pk, email=email).delete()
return HttpResponse('OK', status=200, content_type='text/plain')
else:
object, created = MeetingRegistration.objects.get_or_create(meeting_id=meeting.pk, email=email)
try:
MeetingRegistration.objects.filter(id=object.id).update(**data)
object.save()
except ValueError as e:
return err(400, "Unexpected POST data: %s" % e)
response = "Accepted, New registration" if created else "Accepted, Updated registration"
if User.objects.filter(username=email).exists() or Email.objects.filter(address=email).exists():
pass
else:
send_account_creation_email(request, email)
response += ", Email sent"
return HttpResponse(response, status=202, content_type='text/plain')
else:
return HttpResponse(status=405)

View file

@ -85,7 +85,7 @@ def has_role(user, role_names, *args, **kwargs):
"Reviewer": Q(person=person, name="reviewer", group__state="active"),
"Review Team Secretary": Q(person=person, name="secr", group__reviewteamsettings__isnull=False,group__state="active", ),
"IRSG Member": (Q(person=person, name="member", group__acronym="irsg") | Q(person=person, name="chair", group__acronym="irtf") | Q(person=person, name="atlarge", group__acronym="irsg")),
"Robot": Q(person=person, name="robot", group__acronym="secretariat"),
}
filter_expr = Q()

View file

@ -337,6 +337,7 @@ PERSON_API_KEY_VALUES = [
("/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),
("/api/notify/meeting/registration", "/api/notify/meeting/registration", "Robot"),
]
PERSON_API_KEY_ENDPOINTS = [ (v, n) for (v, n, r) in PERSON_API_KEY_VALUES ]

View file

@ -249,12 +249,13 @@ def get_meeting_registration_data(meeting):
address = registration['Email'].strip()
object, created = MeetingRegistration.objects.get_or_create(
meeting_id=meeting.pk,
first_name=first_name[:200],
last_name=last_name[:200],
affiliation=affiliation,
country_code=country_code,
email=address,
)
object.first_name=first_name[:200]
object.last_name=last_name[:200]
object.affiliation=affiliation
object.country_code=country_code
object.save()
# Add a Person object to MeetingRegistration object
# if valid email is available