From b84937db76fcfe91f392fc3cb0ee526a3e9c26ea Mon Sep 17 00:00:00 2001 From: Paul Marillonnet Date: Tue, 2 Oct 2018 16:12:26 +0200 Subject: [PATCH] add 'get or create' users api call (#22376) --- src/authentic2/api_views.py | 7 ++++++ src/authentic2/app_settings.py | 1 + src/authentic2/utils.py | 41 +++++++++++++++++++++++++++++++++- tests/test_api.py | 31 +++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/authentic2/api_views.py b/src/authentic2/api_views.py index 46074d88..68b9ee1e 100644 --- a/src/authentic2/api_views.py +++ b/src/authentic2/api_views.py @@ -571,6 +571,13 @@ class UsersAPI(HookMixin, ExceptionHandlerMixin, ModelViewSet): def put(self, request, *args, **kwargs): return self.patch(request, *args, **kwargs) + def list(self, request, *args, **kwargs): + get_or_create_kwargs = utils.user_get_or_create_kwargs(request) + if get_or_create_kwargs: + payload, status = utils.user_get_or_create(request, get_or_create_kwargs) + return Response(payload, status) + return super(UsersAPI, self).list(request, *args, **kwargs) + def check_perm(self, perm, ou): if ou: if not self.request.user.has_ou_perm(perm, ou): diff --git a/src/authentic2/app_settings.py b/src/authentic2/app_settings.py index 19154118..59b70495 100644 --- a/src/authentic2/app_settings.py +++ b/src/authentic2/app_settings.py @@ -206,6 +206,7 @@ default_settings = dict( A2_ACCOUNTS_URL=Setting(default=None, definition='IdP has no account page, redirect to this one.'), A2_CACHE_ENABLED=Setting(default=True, definition='Disable all cache decorators for testing purpose.'), A2_ACCEPT_EMAIL_AUTHENTICATION=Setting(default=True, definition='Enable authentication by email'), + A2_USERS_API_GET_OR_CREATE_WHITELIST_FIELDS=Setting(default=['email', 'username', 'ou__slug'], definition='Whitelist fields for "get or create" operations in the users API.'), ) diff --git a/src/authentic2/utils.py b/src/authentic2/utils.py index d32a5a67..9b8609e0 100644 --- a/src/authentic2/utils.py +++ b/src/authentic2/utils.py @@ -16,7 +16,8 @@ from importlib import import_module from django.conf import settings from django.http import HttpResponseRedirect, HttpResponse -from django.core.exceptions import ImproperlyConfigured, PermissionDenied +from django.core.exceptions import (ImproperlyConfigured, PermissionDenied, + ObjectDoesNotExist, MultipleObjectsReturned) from django.http.request import QueryDict from django.contrib.auth import (REDIRECT_FIELD_NAME, login as auth_login, SESSION_KEY, HASH_SESSION_KEY, BACKEND_SESSION_KEY, authenticate, @@ -1073,3 +1074,41 @@ def get_user_flag(user, name, default=None): if ou_value is not None: return ou_value return default + + +def user_get_or_create(request, get_or_create_kwargs): + from django_rbac.utils import get_ou_model + + OrganizationalUnit = get_ou_model() + ou_slug = get_or_create_kwargs.get('ou__slug') + + try : + ou = OrganizationalUnit.objects.get(slug=ou_slug) + except (ObjectDoesNotExist, MultipleObjectsReturned) as e: + return ({'reason': 'OrganizationalUnit %s: %s' % (ou_slug, e)}, 400) + + if not request.user.has_ou_perm('custom_user.add_user', ou): + return ({'reason': 'Permission denied'}, 403) + + get_or_create_kwargs.update({'ou': ou}) + try: + user, created = get_user_model().objects.get_or_create(**get_or_create_kwargs) + except MultipleObjectsReturned as e: + return ({'reason': 'Multiple objects returned'}, 400) + + return (user.to_json(), 200+created) + + +def user_get_or_create_kwargs(request): + from authentic2.a2_rbac.utils import get_default_ou + + kwargs_dict = dict() + for key, value in request.GET.items(): + if key.startswith('get_or_create_'): + fieldkey = key.split('get_or_create_')[-1] + if fieldkey in app_settings.A2_USERS_API_GET_OR_CREATE_WHITELIST_FIELDS: + kwargs_dict.update({fieldkey: value}) + # provide default values for missing fields when possible: + if kwargs_dict and not kwargs_dict.get('ou__slug'): + kwargs_dict['ou__slug'] = get_default_ou().slug + return kwargs_dict diff --git a/tests/test_api.py b/tests/test_api.py index f1c82e16..81553f09 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -934,3 +934,34 @@ def test_validate_password_regex(app, settings): assert response.json['checks'][3]['result'] is True assert response.json['checks'][4]['label'] == 'must contain "ok"' assert response.json['checks'][4]['result'] is True + + +def test_api_users_get_or_create(app, superuser, simple_user, ou1): + User = get_user_model() + app.authorization = ('Basic', (superuser.username, superuser.username)) + url = u'/api/users/?get_or_create_email=john.doe@nowhere.null&' \ + 'get_or_create_username=jdoe&get_or_create_ou__slug=%s' % ou1.slug + + assert not len(User.objects.filter(email='john.doe@nowhere.null', + username='jdoe', ou__slug=ou1.slug)) + + response = app.get(url, status=201) # created + assert len(User.objects.filter(email='john.doe@nowhere.null', + username='jdoe', ou__slug=ou1.slug)) == 1 + + john = User.objects.get(email='john.doe@nowhere.null') + uuid = john.uuid + response = app.get(url, status=200) # not created + assert len(User.objects.filter(email='john.doe@nowhere.null', + username='jdoe', ou__slug=ou1.slug)) == 1 + assert uuid == User.objects.get(email='john.doe@nowhere.null').uuid + + wrong_ou_url = u'/api/users/?get_or_create_email=john.doe@nowhere.null&' \ + 'get_or_create_username=jdoe&get_or_create_ou__slug=wrongwrongwrong' + response = app.get(wrong_ou_url, status=400) # bad request + + app.authorization = ('Basic', (superuser.username, superuser.username+'nope')) + response = app.get(url, status=401) # unauthorized + + app.authorization = ('Basic', (simple_user.username, simple_user.username)) + response = app.get(url, status=403) # permission denied -- 2.19.0