diff --git a/docs/settings.rst b/docs/settings.rst index 6fc46da46..4e5b05110 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -38,6 +38,32 @@ The import string of the class (model) representing your applications. Overwrite this value if you wrote your own implementation (subclass of ``oauth2_provider.models.Application``). +APPLICATION_REGISTRATION_PERMISSIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Defines which permissions are required to be able to register an application. +By default every authenticated user is able to register applications. + +A common setup for non public sites might be to only allow superusers and users +with the ```oauth2_provider | application | Can add application``` permission to be allowed +to register applications. + +This can be done with the following configuration:: + + OAUTH2_PROVIDER = { + ... + 'APPLICATION_REGISTRATION_PERMISSIONS': { + 'all': ('oauth2_provider.add_application', ), + }, + ... + } + +Following Django's auth system you can now either grant the ```oauth2_provider | application | Can add application``` +the a specific user or group to allow access to the application registration page. + +For more information about setting more complex permissions see +`MultiplePermissionsRequiredMixin `_. + + AUTHORIZATION_CODE_EXPIRE_SECONDS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The number of seconds an authorization code remains valid. Requesting an access diff --git a/oauth2_provider/settings.py b/oauth2_provider/settings.py index db5768686..49d0e3f7f 100644 --- a/oauth2_provider/settings.py +++ b/oauth2_provider/settings.py @@ -41,6 +41,7 @@ 'AUTHORIZATION_CODE_EXPIRE_SECONDS': 60, 'ACCESS_TOKEN_EXPIRE_SECONDS': 36000, 'APPLICATION_MODEL': getattr(settings, 'OAUTH2_PROVIDER_APPLICATION_MODEL', 'oauth2_provider.Application'), + 'APPLICATION_REGISTRATION_PERMISSIONS': None, 'REQUEST_APPROVAL_PROMPT': 'force', 'ALLOWED_REDIRECT_URI_SCHEMES': ['http', 'https'], diff --git a/oauth2_provider/tests/test_application_views.py b/oauth2_provider/tests/test_application_views.py index 32495ca27..cd278b788 100644 --- a/oauth2_provider/tests/test_application_views.py +++ b/oauth2_provider/tests/test_application_views.py @@ -1,8 +1,11 @@ from __future__ import unicode_literals from django.core.urlresolvers import reverse +from django.contrib.auth.models import Permission +from django.contrib.contenttypes.models import ContentType from django.test import TestCase +from ..settings import oauth2_settings from ..models import get_application_model from ..compat import get_user_model @@ -15,6 +18,10 @@ def setUp(self): self.foo_user = UserModel.objects.create_user("foo_user", "test@user.com", "123456") self.bar_user = UserModel.objects.create_user("bar_user", "dev@user.com", "123456") + application_content_type = ContentType.objects.get_for_model(Application) + add_application_permission = Permission.objects.get(content_type=application_content_type, codename='add_application') + self.foo_user.user_permissions.add(add_application_permission) + def tearDown(self): self.foo_user.delete() self.bar_user.delete() @@ -40,6 +47,58 @@ def test_application_registration_user(self): self.assertEqual(app.user.username, "foo_user") +class TestApplicationRegistrationViewPermissions(BaseTest): + def setUp(self): + super(TestApplicationRegistrationViewPermissions, self).setUp() + oauth2_settings.APPLICATION_REGISTRATION_PERMISSIONS = { + 'all': ('oauth2_provider.add_application', ), + } + + def tearDown(self): + super(TestApplicationRegistrationViewPermissions, self).tearDown() + oauth2_settings.APPLICATION_REGISTRATION_PERMISSIONS = None + + def test_application_registration_user_with_permission(self): + self.client.login(username="foo_user", password="123456") + + form_data = { + 'name': 'Foo app', + 'client_id': 'client_id', + 'client_secret': 'client_secret', + 'client_type': Application.CLIENT_CONFIDENTIAL, + 'redirect_uris': 'http://example.com', + 'authorization_grant_type': Application.GRANT_AUTHORIZATION_CODE + } + + response = self.client.post(reverse('oauth2_provider:register'), form_data) + self.assertEqual(response.status_code, 302) + self.assertEqual(response.has_header('Location'), True) + self.assertEqual(response['Location'].endswith("/o/applications/1/"), True) + + app = Application.objects.get(name="Foo app") + self.assertEqual(app.user.username, "foo_user") + + def test_application_registration_user_without_permission(self): + self.client.login(username="bar_user", password="123456") + + form_data = { + 'name': 'Bar app', + 'client_id': 'client_id', + 'client_secret': 'client_secret', + 'client_type': Application.CLIENT_CONFIDENTIAL, + 'redirect_uris': 'http://example.com', + 'authorization_grant_type': Application.GRANT_AUTHORIZATION_CODE + } + + response = self.client.post(reverse('oauth2_provider:register'), form_data) + self.assertEqual(response.status_code, 302) + self.assertEqual(response.has_header('Location'), True) + self.assertEqual(response['Location'].endswith("/accounts/login/?next=/o/applications/register/"), True) + + with self.assertRaises(Application.DoesNotExist): + app = Application.objects.get(name="Bar app") + + class TestApplicationViews(BaseTest): def _create_application(self, name, user): app = Application.objects.create( diff --git a/oauth2_provider/views/application.py b/oauth2_provider/views/application.py index 0a3598709..15fc62cfb 100644 --- a/oauth2_provider/views/application.py +++ b/oauth2_provider/views/application.py @@ -1,10 +1,11 @@ from django.core.urlresolvers import reverse_lazy from django.views.generic import CreateView, DetailView, DeleteView, ListView, UpdateView -from braces.views import LoginRequiredMixin +from braces.views import LoginRequiredMixin, MultiplePermissionsRequiredMixin from ..forms import RegistrationForm from ..models import get_application_model +from ..settings import oauth2_settings class ApplicationOwnerIsUserMixin(LoginRequiredMixin): @@ -17,13 +18,19 @@ def get_queryset(self): return get_application_model().objects.filter(user=self.request.user) -class ApplicationRegistration(LoginRequiredMixin, CreateView): +class ApplicationRegistration(LoginRequiredMixin, MultiplePermissionsRequiredMixin, CreateView): """ View used to register a new Application for the request.user """ form_class = RegistrationForm + permissions = oauth2_settings.APPLICATION_REGISTRATION_PERMISSIONS template_name = "oauth2_provider/application_registration_form.html" + def check_permissions(self, request): + if getattr(self, 'permissions', None) is None: + return True + return super(ApplicationRegistration, self).check_permissions(request) + def form_valid(self, form): form.instance.user = self.request.user return super(ApplicationRegistration, self).form_valid(form)