mirror of
https://github.com/ansible/awx.git
synced 2024-10-31 15:21:13 +03:00
Added support for token authentication (with test).
This commit is contained in:
parent
09cca99c69
commit
9dbbf330e8
@ -14,8 +14,10 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Ansible Commander. If not, see <http://www.gnu.org/licenses/>.
|
# along with Ansible Commander. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models, DatabaseError
|
||||||
from django.db.models import CASCADE, SET_NULL, PROTECT
|
from django.db.models import CASCADE, SET_NULL, PROTECT
|
||||||
|
from django.db.models.signals import post_save
|
||||||
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
@ -23,6 +25,7 @@ from django.utils.timezone import now
|
|||||||
import exceptions
|
import exceptions
|
||||||
from jsonfield import JSONField
|
from jsonfield import JSONField
|
||||||
from djcelery.models import TaskMeta
|
from djcelery.models import TaskMeta
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
# TODO: jobs and events model TBD
|
# TODO: jobs and events model TBD
|
||||||
# TODO: reporting model TBD
|
# TODO: reporting model TBD
|
||||||
@ -814,3 +817,13 @@ class LaunchJobStatusEvent(models.Model):
|
|||||||
# FIXME: Connect host based on event_data.
|
# FIXME: Connect host based on event_data.
|
||||||
|
|
||||||
# TODO: reporting (MPD)
|
# TODO: reporting (MPD)
|
||||||
|
|
||||||
|
@receiver(post_save, sender=User)
|
||||||
|
def create_auth_token_for_user(sender, **kwargs):
|
||||||
|
instance = kwargs.get('instance', None)
|
||||||
|
if instance:
|
||||||
|
try:
|
||||||
|
Token.objects.get_or_create(user=instance)
|
||||||
|
except DatabaseError:
|
||||||
|
pass # Only fails when creating a new superuser from syncdb on a
|
||||||
|
# new database (before migrate has been called).
|
||||||
|
@ -38,6 +38,7 @@ class BaseTestMixin(object):
|
|||||||
django_user = DjangoUser.objects.create_superuser(username, "%s@example.com", password)
|
django_user = DjangoUser.objects.create_superuser(username, "%s@example.com", password)
|
||||||
else:
|
else:
|
||||||
django_user = DjangoUser.objects.create_user(username, "%s@example.com", password)
|
django_user = DjangoUser.objects.create_user(username, "%s@example.com", password)
|
||||||
|
self.assertTrue(django_user.auth_token)
|
||||||
return django_user
|
return django_user
|
||||||
|
|
||||||
def make_organizations(self, created_by, count=1):
|
def make_organizations(self, created_by, count=1):
|
||||||
@ -98,7 +99,10 @@ class BaseTestMixin(object):
|
|||||||
assert data is not None
|
assert data is not None
|
||||||
client = Client()
|
client = Client()
|
||||||
if auth:
|
if auth:
|
||||||
client.login(username=auth[0], password=auth[1])
|
if isinstance(auth, (list, tuple)):
|
||||||
|
client.login(username=auth[0], password=auth[1])
|
||||||
|
elif isinstance(auth, basestring):
|
||||||
|
client = Client(HTTP_AUTHORIZATION='Token %s' % auth)
|
||||||
method = getattr(client,method)
|
method = getattr(client,method)
|
||||||
response = None
|
response = None
|
||||||
if data is not None:
|
if data is not None:
|
||||||
|
@ -47,6 +47,33 @@ class UsersTest(BaseTest):
|
|||||||
self.post(url, expect=201, data=new_user2, auth=self.get_normal_credentials())
|
self.post(url, expect=201, data=new_user2, auth=self.get_normal_credentials())
|
||||||
self.post(url, expect=400, data=new_user2, auth=self.get_normal_credentials())
|
self.post(url, expect=400, data=new_user2, auth=self.get_normal_credentials())
|
||||||
|
|
||||||
|
def test_auth_token_login(self):
|
||||||
|
auth_token_url = '/api/v1/authtoken/'
|
||||||
|
|
||||||
|
# Always returns a 405 for any GET request, regardless of credentials.
|
||||||
|
self.get(auth_token_url, expect=405, auth=None)
|
||||||
|
self.get(auth_token_url, expect=405, auth=self.get_invalid_credentials())
|
||||||
|
self.get(auth_token_url, expect=405, auth=self.get_normal_credentials())
|
||||||
|
|
||||||
|
# Posting without username/password fields or invalid username/password
|
||||||
|
# returns a 400 error.
|
||||||
|
data = {}
|
||||||
|
self.post(auth_token_url, data, expect=400)
|
||||||
|
data = dict(zip(('username', 'password'), self.get_invalid_credentials()))
|
||||||
|
self.post(auth_token_url, data, expect=400)
|
||||||
|
|
||||||
|
# A valid username/password should give us an auth token.
|
||||||
|
data = dict(zip(('username', 'password'), self.get_normal_credentials()))
|
||||||
|
result = self.post(auth_token_url, data, expect=200, auth=None)
|
||||||
|
self.assertTrue('token' in result)
|
||||||
|
self.assertEqual(result['token'], self.normal_django_user.auth_token.key)
|
||||||
|
auth_token = result['token']
|
||||||
|
|
||||||
|
# Verify we can access our own user information with the auth token.
|
||||||
|
data = self.get('/api/v1/me/', expect=200, auth=auth_token)
|
||||||
|
self.assertEquals(data['results'][0]['username'], 'normal')
|
||||||
|
self.assertEquals(data['count'], 1)
|
||||||
|
|
||||||
def test_ordinary_user_can_modify_some_fields_about_himself_but_not_all_and_passwords_work(self):
|
def test_ordinary_user_can_modify_some_fields_about_himself_but_not_all_and_passwords_work(self):
|
||||||
|
|
||||||
detail_url = '/api/v1/users/%s/' % self.other_django_user.pk
|
detail_url = '/api/v1/users/%s/' % self.other_django_user.pk
|
||||||
|
@ -26,10 +26,18 @@ from rest_framework import generics
|
|||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
from rest_framework.settings import api_settings
|
||||||
|
from rest_framework.authtoken.views import ObtainAuthToken
|
||||||
import exceptions
|
import exceptions
|
||||||
import datetime
|
import datetime
|
||||||
from base_views import *
|
from base_views import *
|
||||||
|
|
||||||
|
class AuthTokenView(ObtainAuthToken):
|
||||||
|
|
||||||
|
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
|
||||||
|
# FIXME: Show a better form for HTML view
|
||||||
|
# FIXME: How to make this view discoverable?
|
||||||
|
|
||||||
class OrganizationsList(BaseList):
|
class OrganizationsList(BaseList):
|
||||||
|
|
||||||
model = Organization
|
model = Organization
|
||||||
|
@ -43,6 +43,7 @@ REST_FRAMEWORK = {
|
|||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +139,7 @@ INSTALLED_APPS = (
|
|||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'south',
|
'south',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
|
'rest_framework.authtoken',
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
'djcelery',
|
'djcelery',
|
||||||
'kombu.transport.django',
|
'kombu.transport.django',
|
||||||
|
@ -18,6 +18,9 @@ from django.conf import settings
|
|||||||
from django.conf.urls import *
|
from django.conf.urls import *
|
||||||
import lib.main.views as views
|
import lib.main.views as views
|
||||||
|
|
||||||
|
# auth token
|
||||||
|
views_AuthTokenView = views.AuthTokenView.as_view()
|
||||||
|
|
||||||
# organizations service
|
# organizations service
|
||||||
views_OrganizationsList = views.OrganizationsList.as_view()
|
views_OrganizationsList = views.OrganizationsList.as_view()
|
||||||
views_OrganizationsDetail = views.OrganizationsDetail.as_view()
|
views_OrganizationsDetail = views.OrganizationsDetail.as_view()
|
||||||
@ -87,6 +90,9 @@ views_CredentialsDetail = views.CredentialsDetail.as_view()
|
|||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
|
|
||||||
|
# obtain auth token
|
||||||
|
url(r'^api/v1/authtoken/$', views_AuthTokenView),
|
||||||
|
|
||||||
# organizations vice
|
# organizations vice
|
||||||
url(r'^api/v1/organizations/$', views_OrganizationsList),
|
url(r'^api/v1/organizations/$', views_OrganizationsList),
|
||||||
url(r'^api/v1/organizations/(?P<pk>[0-9]+)/$', views_OrganizationsDetail),
|
url(r'^api/v1/organizations/(?P<pk>[0-9]+)/$', views_OrganizationsDetail),
|
||||||
|
Loading…
Reference in New Issue
Block a user