-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OAuth 1.0a authentication #678
Closed
Closed
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
1062d71
add tests for OAuth authentication
swistakm ced22db
add django-oauth-plus & oauth2 to installed apps in runtests settings.py
swistakm 1aed9c1
add OAuthAuthentication class
swistakm e2b11a2
add django-oauth-plus & oauth2 to .travis.yml
swistakm cfce455
add django-oauth-plus & oauth2 to optionals.txt
swistakm 5d9ed34
add OAuthAuthentication documentation stub
swistakm 59a6f5f
Move oauth2 and django-oauth-plus imports to compat and fix some mino…
swistakm d84c2cf
OAuth tests now are skipped unless django-oauth-plus and oauth2 are i…
swistakm a430445
runtest.settings fixed if django-oauth-plus or oauth2 are not installed
swistakm dd355d5
oauth2 & django-oauth-plus installed only on 2.x
swistakm 55ea5b9
import compat version of unittest
swistakm 2eabc5c
rfc5849 link with anchor
swistakm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,5 @@ markdown>=2.1.0 | |
PyYAML>=3.10 | ||
defusedxml>=0.3 | ||
django-filter>=0.5.4 | ||
django-oauth-plus>=2.0 | ||
oauth2>=1.5.211 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,12 +2,16 @@ | |
from django.contrib.auth.models import User | ||
from django.http import HttpResponse | ||
from django.test import Client, TestCase | ||
from rest_framework import HTTP_HEADER_ENCODING | ||
from django.utils import unittest | ||
import time | ||
from rest_framework import HTTP_HEADER_ENCODING, status | ||
from rest_framework import permissions | ||
from rest_framework.authtoken.models import Token | ||
from rest_framework.authentication import TokenAuthentication, BasicAuthentication, SessionAuthentication | ||
from rest_framework.authentication import TokenAuthentication, BasicAuthentication, SessionAuthentication, OAuthAuthentication | ||
from rest_framework.compat import patterns | ||
from rest_framework.views import APIView | ||
from rest_framework.compat import oauth | ||
from rest_framework.compat import oauth_provider | ||
import json | ||
import base64 | ||
|
||
|
@@ -21,11 +25,15 @@ def post(self, request): | |
def put(self, request): | ||
return HttpResponse({'a': 1, 'b': 2, 'c': 3}) | ||
|
||
def get(self, request): | ||
return HttpResponse({'a': 1, 'b': 2, 'c': 3}) | ||
|
||
urlpatterns = patterns('', | ||
(r'^session/$', MockView.as_view(authentication_classes=[SessionAuthentication])), | ||
(r'^basic/$', MockView.as_view(authentication_classes=[BasicAuthentication])), | ||
(r'^token/$', MockView.as_view(authentication_classes=[TokenAuthentication])), | ||
(r'^auth-token/$', 'rest_framework.authtoken.views.obtain_auth_token'), | ||
(r'^oauth/$', MockView.as_view(authentication_classes=[OAuthAuthentication])) | ||
) | ||
|
||
|
||
|
@@ -186,3 +194,158 @@ def test_token_login_form(self): | |
{'username': self.username, 'password': self.password}) | ||
self.assertEqual(response.status_code, 200) | ||
self.assertEqual(json.loads(response.content.decode('ascii'))['token'], self.key) | ||
|
||
class OAuthTests(TestCase): | ||
"""OAuth 1.0a authentication""" | ||
urls = 'rest_framework.tests.authentication' | ||
|
||
def setUp(self): | ||
# these imports are here because oauth is optional and hiding them in try..except block or compat | ||
# could obscure problems if something breaks | ||
from oauth_provider.models import Consumer, Resource | ||
from oauth_provider.models import Token as OAuthToken | ||
from oauth_provider import consts | ||
|
||
self.consts = consts | ||
|
||
self.csrf_client = Client(enforce_csrf_checks=True) | ||
self.username = 'john' | ||
self.email = '[email protected]' | ||
self.password = 'password' | ||
self.user = User.objects.create_user(self.username, self.email, self.password) | ||
|
||
self.CONSUMER_KEY = 'consumer_key' | ||
self.CONSUMER_SECRET = 'consumer_secret' | ||
self.TOKEN_KEY = "token_key" | ||
self.TOKEN_SECRET = "token_secret" | ||
|
||
self.consumer = Consumer.objects.create(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, | ||
name='example', user=self.user, status=self.consts.ACCEPTED) | ||
|
||
|
||
self.resource = Resource.objects.create(name="resource name", url="api/") | ||
self.token = OAuthToken.objects.create(user=self.user, consumer=self.consumer, resource=self.resource, | ||
token_type=OAuthToken.ACCESS, key=self.TOKEN_KEY, secret=self.TOKEN_SECRET, is_approved=True | ||
) | ||
|
||
|
||
def _create_authorization_header(self): | ||
params = { | ||
'oauth_version': "1.0", | ||
'oauth_nonce': oauth.generate_nonce(), | ||
'oauth_timestamp': int(time.time()), | ||
'oauth_token': self.token.key, | ||
'oauth_consumer_key': self.consumer.key | ||
} | ||
|
||
req = oauth.Request(method="GET", url="http://example.com", parameters=params) | ||
|
||
signature_method = oauth.SignatureMethod_PLAINTEXT() | ||
req.sign_request(signature_method, self.consumer, self.token) | ||
|
||
return req.to_header()["Authorization"] | ||
|
||
def _create_authorization_url_parameters(self): | ||
params = { | ||
'oauth_version': "1.0", | ||
'oauth_nonce': oauth.generate_nonce(), | ||
'oauth_timestamp': int(time.time()), | ||
'oauth_token': self.token.key, | ||
'oauth_consumer_key': self.consumer.key | ||
} | ||
|
||
req = oauth.Request(method="GET", url="http://example.com", parameters=params) | ||
|
||
signature_method = oauth.SignatureMethod_PLAINTEXT() | ||
req.sign_request(signature_method, self.consumer, self.token) | ||
return dict(req) | ||
|
||
@unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') | ||
@unittest.skipUnless(oauth, 'oauth2 not installed') | ||
def test_post_form_passing_oauth(self): | ||
"""Ensure POSTing form over OAuth with correct credentials passes and does not require CSRF""" | ||
auth = self._create_authorization_header() | ||
response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) | ||
self.assertEqual(response.status_code, 200) | ||
|
||
@unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') | ||
@unittest.skipUnless(oauth, 'oauth2 not installed') | ||
def test_post_form_repeated_nonce_failing_oauth(self): | ||
"""Ensure POSTing form over OAuth with repeated auth (same nonces and timestamp) credentials fails""" | ||
auth = self._create_authorization_header() | ||
response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) | ||
self.assertEqual(response.status_code, 200) | ||
|
||
# simulate reply attack auth header containes already used (nonce, timestamp) pair | ||
response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) | ||
self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) | ||
|
||
@unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') | ||
@unittest.skipUnless(oauth, 'oauth2 not installed') | ||
def test_post_form_token_removed_failing_oauth(self): | ||
"""Ensure POSTing when there is no OAuth access token in db fails""" | ||
self.token.delete() | ||
auth = self._create_authorization_header() | ||
response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) | ||
self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) | ||
|
||
@unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') | ||
@unittest.skipUnless(oauth, 'oauth2 not installed') | ||
def test_post_form_consumer_status_not_accepted_failing_oauth(self): | ||
"""Ensure POSTing when consumer status is anything other than ACCEPTED fails""" | ||
for consumer_status in (self.consts.CANCELED, self.consts.PENDING, self.consts.REJECTED): | ||
self.consumer.status = consumer_status | ||
self.consumer.save() | ||
|
||
auth = self._create_authorization_header() | ||
response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) | ||
self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) | ||
|
||
@unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') | ||
@unittest.skipUnless(oauth, 'oauth2 not installed') | ||
def test_post_form_with_request_token_failing_oauth(self): | ||
"""Ensure POSTing with unauthorized request token instead of access token fails""" | ||
self.token.token_type = self.token.REQUEST | ||
self.token.save() | ||
|
||
auth = self._create_authorization_header() | ||
response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) | ||
self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) | ||
|
||
@unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') | ||
@unittest.skipUnless(oauth, 'oauth2 not installed') | ||
def test_post_form_with_urlencoded_parameters(self): | ||
"""Ensure POSTing with x-www-form-urlencoded auth parameters passes""" | ||
params = self._create_authorization_url_parameters() | ||
response = self.csrf_client.post('/oauth/', params) | ||
self.assertEqual(response.status_code, 200) | ||
|
||
@unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') | ||
@unittest.skipUnless(oauth, 'oauth2 not installed') | ||
def test_get_form_with_url_parameters(self): | ||
"""Ensure GETing with auth in url parameters passes""" | ||
params = self._create_authorization_url_parameters() | ||
response = self.csrf_client.get('/oauth/', params) | ||
self.assertEqual(response.status_code, 200) | ||
|
||
@unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') | ||
@unittest.skipUnless(oauth, 'oauth2 not installed') | ||
def test_post_hmac_sha1_signature_passes(self): | ||
"""Ensure POSTing using HMAC_SHA1 signature method passes""" | ||
params = { | ||
'oauth_version': "1.0", | ||
'oauth_nonce': oauth.generate_nonce(), | ||
'oauth_timestamp': int(time.time()), | ||
'oauth_token': self.token.key, | ||
'oauth_consumer_key': self.consumer.key | ||
} | ||
|
||
req = oauth.Request(method="POST", url="http://testserver/oauth/", parameters=params) | ||
|
||
signature_method = oauth.SignatureMethod_HMAC_SHA1() | ||
req.sign_request(signature_method, self.consumer, self.token) | ||
auth = req.to_header()["Authorization"] | ||
|
||
response = self.csrf_client.post('/oauth/', HTTP_AUTHORIZATION=auth) | ||
self.assertEqual(response.status_code, 200) | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests should be excluded unless both
oauth2 and django-oauth-plus
are installed.grep for
skipUnless
in some of the other tests to see some examples.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now are skipped.