api: discard obsolete registration endpoint (#83234)
gitea/authentic/pipeline/head This commit looks good Details

This commit is contained in:
Paul Marillonnet 2023-12-13 17:12:39 +01:00
parent 42e3920339
commit c649bc686b
4 changed files with 1 additions and 574 deletions

View File

@ -19,7 +19,6 @@ from django.urls import path, re_path
from . import api_views
urlpatterns = [
path('register/', api_views.register, name='a2-api-register'),
path('password-change/', api_views.password_change, name='a2-api-password-change'),
path('user/', api_views.user, name='a2-api-user'),
re_path(

View File

@ -28,7 +28,6 @@ from django.core.exceptions import MultipleObjectsReturned
from django.db import models, transaction
from django.shortcuts import get_object_or_404
from django.utils.dateparse import parse_datetime
from django.utils.encoding import force_str
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@ -192,118 +191,6 @@ class BaseRpcView(ExceptionHandlerMixin, RpcMixin, GenericAPIView):
pass
class Register(BaseRpcView):
"""Register the given email, send a mail to the user and return a
validation token.
A mail will be sent to the user to validate its email. On
validation of the mail the user will be logged and redirected to
`{return_url}?token={token}`. It's the durty of the requesting
service to finish the registration process on its side.
If email is unique and an account already exist the requesting
must enter in a process of registration through SSO, i.e. ask for
authentication of the user and then finish the registration
process for the received identity.
"""
permission_classes = (permissions.IsAuthenticated,)
serializer_class = RegistrationSerializer
def rpc(self, request, serializer):
validated_data = serializer.validated_data
if not request.user.has_ou_perm('custom_user.add_user', validated_data['ou']):
raise PermissionDenied(
'You do not have permission to create users in ou %s' % validated_data['ou'].slug
)
email = validated_data.get('email')
registration_data = {}
for field in ('first_name', 'last_name', 'password', 'username'):
if field in validated_data:
if isinstance(validated_data[field], models.Model):
registration_data[field] = validated_data[field].pk
else:
registration_data[field] = validated_data[field]
ctx = {
'registration_data': registration_data,
}
token = None
final_return_url = None
if validated_data.get('return_url'):
token = utils_misc.get_hex_uuid()[:16]
final_return_url = utils_misc.make_url(validated_data['return_url'], params={'token': token})
if email and not validated_data.get('no_email_validation'):
registration_template = ['authentic2/activation_email']
if validated_data['ou']:
registration_template.insert(0, 'authentic2/activation_email_%s' % validated_data['ou'].slug)
try:
utils_misc.send_registration_mail(
self.request,
email,
template_names=registration_template,
next_url=final_return_url,
ou=validated_data['ou'],
context=ctx,
**registration_data,
)
except smtplib.SMTPException as e:
response = {
'result': 0,
'errors': {'__all__': ['Mail sending failed']},
'exception': force_str(e),
}
response_status = status.HTTP_503_SERVICE_UNAVAILABLE
else:
response = {
'result': 1,
}
if token:
response['token'] = token
response_status = status.HTTP_202_ACCEPTED
else:
username = validated_data.get('username')
first_name = validated_data.get('first_name')
last_name = validated_data.get('last_name')
password = validated_data.get('password')
ou = validated_data.get('ou')
if not email and not username and not (first_name and last_name):
response = {
'result': 0,
'errors': {
'__all__': [
'You must set at least a username, an email or a first name and a last name'
]
},
}
response_status = status.HTTP_400_BAD_REQUEST
else:
new_user = User(
email=email, username=username, ou=ou, first_name=first_name, last_name=last_name
)
if password:
new_user.set_password(password)
new_user.save()
validated_data['uuid'] = new_user.uuid
response = {
'result': 1,
'user': BaseUserSerializer(new_user).data,
'token': token,
}
if email:
response['validation_url'] = utils_misc.build_activation_url(
request, email, next_url=final_return_url, ou=ou, **registration_data
)
if token:
response['token'] = token
response_status = status.HTTP_201_CREATED
return response, response_status
register = Register.as_view()
class PasswordChangeSerializer(serializers.Serializer):
'''Register RPC payload'''

View File

@ -22,7 +22,6 @@ from unittest import mock
import faker
import pytest
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import check_password
from django.contrib.contenttypes.models import ContentType
from django.core import mail
from django.test.utils import override_settings
@ -720,121 +719,6 @@ def test_api_users_create_force_password_reset(app, client, settings, superuser)
assert 'Password changed' in resp
def test_register_no_email_validation(settings, app, admin, django_user_model):
settings.A2_USERNAME_IS_UNIQUE = False
settings.A2_REGISTRATION_USERNAME_IS_UNIQUE = False
User = django_user_model
password = '12XYab'
username = 'john.doe'
email = 'john.doe@example.com'
first_name = 'John'
last_name = 'Doe'
return_url = 'http://sp.example.com/validate/'
# invalid payload
payload = {
'last_name': last_name,
'return_url': return_url,
}
headers = basic_authorization_header(admin)
assert len(mail.outbox) == 0
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers, status=400)
assert 'errors' in response.json
assert response.json['result'] == 0
assert response.json['errors'] == {
'__all__': ['You must set at least a username, an email or a first name and a last name'],
}
# valid payload
payload = {
'username': username,
'email': email,
'first_name': first_name,
'last_name': last_name,
'password': password,
'no_email_validation': True,
'return_url': return_url,
}
assert len(mail.outbox) == 0
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers)
assert len(mail.outbox) == 0
assert response.status_code == 201
assert response.json['result'] == 1
assert response.json['user']['username'] == username
assert response.json['user']['email'] == email
assert response.json['user']['first_name'] == first_name
assert response.json['user']['last_name'] == last_name
assert check_password(password, response.json['user']['password'])
assert response.json['token']
assert response.json['validation_url'].startswith('https://testserver/accounts/activate/')
assert User.objects.count() == 2
user = User.objects.latest('id')
assert user.ou == get_default_ou()
assert user.username == username
assert user.email == email
assert user.first_name == first_name
assert user.last_name == last_name
assert user.check_password(password)
def test_register_ou_no_email_validation(settings, app, admin, django_user_model):
settings.A2_USERNAME_IS_UNIQUE = False
settings.A2_REGISTRATION_USERNAME_IS_UNIQUE = False
User = django_user_model
password = '12XYab'
username = 'john.doe'
email = 'john.doe@example.com'
first_name = 'John'
last_name = 'Doe'
return_url = 'http://sp.example.com/validate/'
ou = 'default'
# invalid payload
payload = {
'last_name': last_name,
'return_url': return_url,
}
headers = basic_authorization_header(admin)
assert len(mail.outbox) == 0
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers, status=400)
assert 'errors' in response.json
assert response.json['result'] == 0
assert response.json['errors'] == {
'__all__': ['You must set at least a username, an email or a first name and a last name'],
}
# valid payload
payload = {
'username': username,
'email': email,
'first_name': first_name,
'last_name': last_name,
'password': password,
'no_email_validation': True,
'return_url': return_url,
'ou': ou,
}
assert len(mail.outbox) == 0
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers)
assert len(mail.outbox) == 0
assert response.status_code == 201
assert response.json['result'] == 1
assert response.json['user']['username'] == username
assert response.json['user']['email'] == email
assert response.json['user']['first_name'] == first_name
assert response.json['user']['last_name'] == last_name
assert check_password(password, response.json['user']['password'])
assert response.json['token']
assert response.json['validation_url'].startswith('https://testserver/accounts/activate/')
assert User.objects.count() == 2
user = User.objects.latest('id')
assert user.username == username
assert user.email == email
assert user.first_name == first_name
assert user.last_name == last_name
assert user.check_password(password)
def test_api_drf_authentication_class(app, admin, user_ou1, oidc_client):
from authentic2_idp_oidc.models import OIDCAuthorization, OIDCClient
@ -1449,29 +1333,6 @@ def test_api_users_create_user_delete(app, settings, admin):
assert resp.json['errors'] == {'email': ['email already used']}
def test_api_register_user_delete(app, settings, admin):
settings.A2_EMAIL_IS_UNIQUE = True
user = User.objects.create(username='foo', email='john.doe@example.com', ou=get_default_ou())
headers = basic_authorization_header(admin)
payload = {
'username': 'john.doe',
'email': 'john.doe@example.com',
'first_name': 'John',
'last_name': 'Doe',
'password': '12XYab',
'no_email_validation': True,
'return_url': 'http://sp.example.com/validate/',
'ou': 'default',
}
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers, status=400)
assert response.json['errors'] == {'__all__': ['Account already exists']}
user.delete()
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers, status=201)
def test_api_password_change_user_delete(app, settings, admin, ou1):
app.authorization = ('Basic', (admin.username, admin.username))
user1 = User.objects.create(username='john.doe', email='john.doe@example.com', ou=ou1)

View File

@ -14,31 +14,24 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import json
import unittest.mock
import urllib.parse
import pytest
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.backends.cache import SessionStore
from django.core import mail
from django.core.serializers.json import DjangoJSONEncoder
from django.test import TestCase
from django.test.client import Client
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.translation import gettext as _
from rest_framework import status, test
from authentic2 import attribute_kinds, models
from authentic2.a2_rbac.models import OrganizationalUnit, Role
from authentic2.utils import misc as utils_misc
from authentic2.utils.misc import continue_to_next_url, login_require, make_url, redirect, redirect_to_login
from .utils import Authentic2TestCase, assert_event, get_link_from_mail, get_response_form
from .utils import Authentic2TestCase, assert_event, get_response_form
class SerializerTests(TestCase):
@ -387,319 +380,6 @@ class AttributeKindsTest(TestCase):
str(AttributeKindForm().as_p())
class APITest(TestCase):
def setUp(self):
User = get_user_model()
ct_user = ContentType.objects.get_for_model(User)
self.ou = OrganizationalUnit.objects.create(
slug='ou', name='OU', email_is_unique=True, username_is_unique=True
)
self.reguser1 = User.objects.create(username='reguser1')
self.reguser1.set_password('password')
self.reguser1.save()
cred = '%s:%s' % (self.reguser1.username, 'password')
cred = cred.encode('utf-8')
self.reguser1_cred = base64.b64encode(cred).decode('ascii')
self.user_admin_role = Role.objects.get_admin_role(
instance=ct_user, name='user admin', slug='user-admin'
)
self.reguser1.roles.add(self.user_admin_role)
self.reguser2 = User.objects.create(username='reguser2', password='password')
self.reguser2.set_password('password')
self.reguser2.save()
cred = '%s:%s' % (self.reguser2.username, 'password')
cred = cred.encode('utf-8')
self.reguser2_cred = base64.b64encode(cred).decode('ascii')
self.ou_user_admin_role = Role.objects.get_admin_role(
instance=ct_user, name='user admin', slug='user-admin', ou=self.ou
)
self.ou_user_admin_role.members.add(self.reguser2)
self.reguser3 = User.objects.create(username='reguser3', password='password', is_superuser=True)
self.reguser3.set_password('password')
self.reguser3.save()
cred = '%s:%s' % (self.reguser3.username, 'password')
cred = cred.encode('utf-8')
self.reguser3_cred = base64.b64encode(cred).decode('ascii')
def test_register_reguser1(self):
self.register_with_user(self.reguser1, self.reguser1_cred)
def test_register_reguser2(self):
self.register_with_user(self.reguser2, self.reguser2_cred)
def test_register_reguser3(self):
self.register_with_user(self.reguser3, self.reguser3_cred)
@override_settings(A2_REQUIRED_FIELDS=['username'])
def register_with_user(self, user, cred):
# disable existing attributes
models.Attribute.objects.update(disabled=True)
User = get_user_model()
user_count = User.objects.count()
client = test.APIClient()
password = '12=XY=ab'
username = 'john.doe'
email = 'john.doe@example.com'
return_url = 'http://sp.org/register/'
payload = {
'email': email,
'username': username,
'ou': self.ou.slug,
'password': password,
'return_url': return_url,
}
outbox_level = len(mail.outbox)
client.credentials(HTTP_AUTHORIZATION='Basic %s' % cred)
response = client.post(
reverse('a2-api-register'), content_type='application/json', data=json.dumps(payload)
)
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
self.assertIn('result', response.data)
self.assertEqual(response.data['result'], 1)
self.assertIn('token', response.data)
token = response.data['token']
self.assertEqual(len(mail.outbox), outbox_level + 1)
# User side
client = Client()
activation_url = get_link_from_mail(mail.outbox[-1])
response = client.get(activation_url, follow=True)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert utils_misc.make_url(return_url, params={'token': token}) in force_str(response.content)
self.assertEqual(User.objects.count(), user_count + 1)
response = client.get(reverse('auth_homepage'))
self.assertContains(response, username)
last_user = User.objects.order_by('id').last()
self.assertEqual(last_user.username, username)
self.assertEqual(last_user.email, email)
self.assertEqual(last_user.ou.slug, self.ou.slug)
self.assertTrue(last_user.check_password(password))
# Test email is unique with case change
client = test.APIClient()
client.credentials(HTTP_AUTHORIZATION='Basic %s' % cred)
payload = {
'email': email.upper(),
'username': username + '1',
'ou': self.ou.slug,
'password': password,
'return_url': return_url,
}
response = client.post(
reverse('a2-api-register'), content_type='application/json', data=json.dumps(payload)
)
self.assertEqual(response.data['errors']['__all__'], [_('Account already exists in this ou')])
# Username is required
payload = {
'email': '1' + email,
'ou': self.ou.slug,
'password': password,
'return_url': return_url,
}
response = client.post(
reverse('a2-api-register'), content_type='application/json', data=json.dumps(payload)
)
self.assertEqual(response.data['errors']['__all__'], [_('Username is required')])
# Test username is unique
payload = {
'email': '1' + email,
'username': username,
'ou': self.ou.slug,
'password': password,
'return_url': return_url,
}
response = client.post(
reverse('a2-api-register'), content_type='application/json', data=json.dumps(payload)
)
self.assertEqual(response.data['errors']['__all__'], [_('Account already exists')])
def test_register_reguser2_wrong_ou(self):
client = test.APIClient()
password = '12=XY=ab'
username = 'john.doe'
email = 'john.doe@example.com'
return_url = 'http://sp.org/register/'
payload = {
'email': email,
'username': username,
'ou': 'default',
'password': password,
'return_url': return_url,
}
client.credentials(HTTP_AUTHORIZATION='Basic %s' % self.reguser2_cred)
response = client.post(
reverse('a2-api-register'), content_type='application/json', data=json.dumps(payload)
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('errors', response.data)
@override_settings(A2_REQUIRED_FIELDS=['username'])
def test_email_is_unique_double_registration(self):
# disable existing attributes
models.Attribute.objects.update(disabled=True)
cred = self.reguser3_cred
User = get_user_model()
user_count = User.objects.count()
client = test.APIClient()
password = '12=XY=ab'
username = 'john.doe'
email = 'john.doe@example.com'
return_url = 'http://sp.org/register/'
payload = {
'email': email,
'username': username,
'ou': self.ou.slug,
'password': password,
'return_url': return_url,
}
outbox_level = len(mail.outbox)
client.credentials(HTTP_AUTHORIZATION='Basic %s' % cred)
response = client.post(
reverse('a2-api-register'), content_type='application/json', data=json.dumps(payload)
)
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
self.assertIn('result', response.data)
self.assertEqual(response.data['result'], 1)
self.assertIn('token', response.data)
token = response.data['token']
self.assertEqual(len(mail.outbox), outbox_level + 1)
outbox_level = len(mail.outbox)
# User side - user click on email
client = Client()
activation_url = get_link_from_mail(mail.outbox[0])
response = client.get(activation_url, follow=True)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert utils_misc.make_url(return_url, params={'token': token}) in force_str(response.content)
self.assertEqual(User.objects.count(), user_count + 1)
response = client.get(reverse('auth_homepage'))
self.assertContains(response, username)
last_user = User.objects.order_by('id').last()
self.assertEqual(last_user.username, username)
self.assertEqual(last_user.email, email)
self.assertEqual(last_user.ou.slug, self.ou.slug)
self.assertTrue(last_user.check_password(password))
# Test email is unique with case change
client = test.APIClient()
client.credentials(HTTP_AUTHORIZATION='Basic %s' % cred)
payload = {
'email': email.upper(),
'username': username + '1',
'ou': self.ou.slug,
'password': password,
'return_url': return_url,
}
response = client.post(
reverse('a2-api-register'), content_type='application/json', data=json.dumps(payload)
)
self.assertEqual(response.data['errors']['__all__'], [_('Account already exists in this ou')])
# Username is required
payload = {
'email': '1' + email,
'ou': self.ou.slug,
'password': password,
'return_url': return_url,
}
response = client.post(
reverse('a2-api-register'), content_type='application/json', data=json.dumps(payload)
)
self.assertEqual(response.data['errors']['__all__'], [_('Username is required')])
# Test username is unique
payload = {
'email': '1' + email,
'username': username,
'ou': self.ou.slug,
'password': password,
'return_url': return_url,
}
response = client.post(
reverse('a2-api-register'), content_type='application/json', data=json.dumps(payload)
)
self.assertEqual(response.data['errors']['__all__'], [_('Account already exists')])
@override_settings(A2_REQUIRED_FIELDS=['username'])
def test_email_username_is_unique_double_registration(self):
# disable existing attributes
models.Attribute.objects.update(disabled=True)
cred = self.reguser3_cred
User = get_user_model()
user_count = User.objects.count()
client = test.APIClient()
password = '12=XY=ab'
username = 'john.doe'
email = 'john.doe@example.com'
return_url = 'http://sp.org/register/'
payload = {
'email': email,
'username': username,
'ou': self.ou.slug,
'password': password,
'return_url': return_url,
}
outbox_level = len(mail.outbox)
client.credentials(HTTP_AUTHORIZATION='Basic %s' % cred)
response = client.post(
reverse('a2-api-register'), content_type='application/json', data=json.dumps(payload)
)
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
self.assertIn('result', response.data)
self.assertEqual(response.data['result'], 1)
self.assertIn('token', response.data)
token = response.data['token']
self.assertEqual(len(mail.outbox), outbox_level + 1)
outbox_level = len(mail.outbox)
# Second registration
payload['email'] = 'john.doe2@example.com'
response2 = client.post(
reverse('a2-api-register'), content_type='application/json', data=json.dumps(payload)
)
self.assertEqual(response2.status_code, status.HTTP_202_ACCEPTED)
self.assertIn('result', response2.data)
self.assertEqual(response2.data['result'], 1)
self.assertIn('token', response2.data)
self.assertEqual(len(mail.outbox), outbox_level + 1)
activation_mail1 = mail.outbox[0]
activation_mail2 = mail.outbox[1]
# User side - user click on first email
client = Client()
activation_url = get_link_from_mail(activation_mail1)
response = client.get(activation_url, follow=True)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert utils_misc.make_url(return_url, params={'token': token}) in force_str(response.content)
self.assertEqual(User.objects.count(), user_count + 1)
response = client.get(reverse('auth_homepage'))
self.assertContains(response, username)
last_user = User.objects.order_by('id').last()
self.assertEqual(last_user.username, username)
self.assertEqual(last_user.email, email)
self.assertEqual(last_user.ou.slug, self.ou.slug)
self.assertTrue(last_user.check_password(password))
# User click on second email
client = Client()
activation_url = get_link_from_mail(activation_mail2)
response = client.get(activation_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.status_code, 200)
self.assertFormError(
response,
'form',
'username',
_('This username is already in use. Please supply a different username.'),
)
def test_slug_from_name_default():
from authentic2.api_views import SlugFromNameDefault