Skip to content

Commit

Permalink
Merge pull request #181 from wagnerdelima/investigation
Browse files Browse the repository at this point in the history
merge: merge investigation into master.
  • Loading branch information
wagnerdelima authored Apr 30, 2023
2 parents 2c9dca4 + ae1c041 commit d55c2f3
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 60 deletions.
23 changes: 23 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
Change log
==========

2.1.1 - 2023-04-26
------------------

## What's Changed
* 175 create readthedocs documentation by @wagnerdelima in https://github.com/wagnerdelima/drf-social-oauth2/pull/176
* Running convert-token for the second time returns html headers with user environment variables by @wagnerdelima in https://github.com/wagnerdelima/drf-social-oauth2/pull/177

**Full Changelog**: https://github.com/wagnerdelima/drf-social-oauth2/compare/2.1.0...2.1.1

2.1.0 - 2023-04-24
------------------

## What's Changed
* chore: 👷 fix codecov.yml by @wagnerdelima in https://github.com/wagnerdelima/drf-social-oauth2/pull/165
* doc: 📝 add CONTRIBUTING.md file. by @wagnerdelima in https://github.com/wagnerdelima/drf-social-oauth2/pull/166
* Create SECURITY.md by @wagnerdelima in https://github.com/wagnerdelima/drf-social-oauth2/pull/167
* 104 use serializers for views by @wagnerdelima in https://github.com/wagnerdelima/drf-social-oauth2/pull/170
* docs: 📝 add missing documentation about invalidate refresh tokens. by @wagnerdelima in https://github.com/wagnerdelima/drf-social-oauth2/pull/173
* Update CHANGELOG.rst by @wagnerdelima in https://github.com/wagnerdelima/drf-social-oauth2/pull/174

**Full Changelog**: https://github.com/wagnerdelima/drf-social-oauth2/compare/2.0.0...2.1.0


2.0.0 - 2023-04-16
------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
project = 'drf-social-oauth2'
copyright = '2023, Wagner de Lima'
author = 'Wagner de Lima'
release = '2.1.1'
release = '2.1.2'

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
2 changes: 1 addition & 1 deletion docs/source/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ This framework is published at the PyPI, install it with pip:

.. code-block:: console
$ pip install drf_social_oauth2==2.1.1
$ pip install drf_social_oauth2==2.1.2
To enable OAuth2 social authentication support for your Django REST Framework application, you need to install
Expand Down
2 changes: 1 addition & 1 deletion drf_social_oauth2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
and a ton more!
"""

__version__ = '2.1.1'
__version__ = '2.1.2'

try:
from secrets import SystemRandom
Expand Down
58 changes: 35 additions & 23 deletions drf_social_oauth2/authentication.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from typing import List, Union, Callable
from functools import wraps

try:
from django.urls import reverse
except ImportError: # Will be removed in Django 2.0
Expand All @@ -13,6 +16,30 @@
from social_core.utils import requests


def validator(function: Callable):
@wraps(function)
def wrapper_validation(*args, **kwargs):
request = args[1]
auth_header = get_authorization_header(request).decode(HTTP_HEADER_ENCODING)
auth: Union[List[str], List[str, str, str]] = auth_header.split()

if not auth or auth[0].lower() != 'bearer':
return None

if len(auth) == 1:
raise AuthenticationFailed('Invalid token header. No backend provided.')
elif len(auth) == 2:
raise AuthenticationFailed('Invalid token header. No credentials provided.')
elif len(auth) > 3:
raise AuthenticationFailed(
'Invalid token header. Token string should not contain spaces.'
)

return function(*args, backend=auth[1], token=auth[2], **kwargs)

return wrapper_validation


class SocialAuthentication(BaseAuthentication):
"""
Authentication backend using `python-social-auth`
Expand All @@ -27,37 +54,22 @@ class SocialAuthentication(BaseAuthentication):

www_authenticate_realm = 'api'

def authenticate(self, request):
@validator
def authenticate(self, request, **kwargs):
"""
Returns two-tuple of (user, token) if authentication succeeds,
or None otherwise.
"""
auth_header = get_authorization_header(request).decode(HTTP_HEADER_ENCODING)
auth = auth_header.split()

if not auth or auth[0].lower() != 'bearer':
return None

if len(auth) == 1:
message = 'Invalid token header. No backend provided.'
raise AuthenticationFailed(message)
elif len(auth) == 2:
message = 'Invalid token header. No credentials provided.'
raise AuthenticationFailed(message)
elif len(auth) > 3:
message = 'Invalid token header. Token string should not contain spaces.'
raise AuthenticationFailed(message)

token = auth[2]
backend = auth[1]

token: str = kwargs['token']
backend: str = kwargs['backend']
strategy = load_strategy(request=request)

try:
backend = load_backend(
strategy, backend, reverse(f'{NAMESPACE}:complete', args=(backend,)),
strategy,
backend,
reverse(f"{NAMESPACE}:complete", args=(backend,)),
)

user = backend.do_auth(access_token=token)
except MissingBackend:
message = 'Invalid token header. Invalid backend.'
Expand All @@ -73,4 +85,4 @@ def authenticate_header(self, request):
"""
Bearer is the only finalized type currently
"""
return 'Bearer backend realm="%s"' % self.www_authenticate_realm
return f'Bearer backend realm="{self.www_authenticate_realm}"'
4 changes: 2 additions & 2 deletions drf_social_oauth2/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ class DjangoOAuth2(BaseOAuth2):

name = DRFSO2_PROPRIETARY_BACKEND_NAME
AUTHORIZATION_URL = reverse(
DRFSO2_URL_NAMESPACE + ':authorize' if DRFSO2_URL_NAMESPACE else 'authorize'
f'{DRFSO2_URL_NAMESPACE}:authorize' if DRFSO2_URL_NAMESPACE else 'authorize'
)
ACCESS_TOKEN_URL = reverse(
DRFSO2_URL_NAMESPACE + ':token' if DRFSO2_URL_NAMESPACE else 'token'
f'{DRFSO2_URL_NAMESPACE}:token' if DRFSO2_URL_NAMESPACE else 'token'
)


Expand Down
4 changes: 2 additions & 2 deletions drf_social_oauth2/oauth2_grants.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import logging
from logging import getLogger

try:
from django.urls import reverse
Expand All @@ -16,7 +16,7 @@
from drf_social_oauth2.settings import DRFSO2_URL_NAMESPACE


log = logging.getLogger(__name__)
log = getLogger(__name__)


class SocialTokenGrant(RefreshTokenGrant):
Expand Down
5 changes: 4 additions & 1 deletion drf_social_oauth2/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
'django.contrib.messages',
'django.contrib.staticfiles',
'oauth2_provider',
'social_django',
'drf_social_oauth2',
'rest_framework',
'rest_framework.authtoken',
]

MIDDLEWARE = [
Expand Down Expand Up @@ -72,7 +76,6 @@


DRFSO2_PROPRIETARY_BACKEND_NAME = 'Django'

DRFSO2_URL_NAMESPACE = 'drf'

ROOT_URLCONF = 'drf_social_oauth2.urls'
Expand Down
14 changes: 5 additions & 9 deletions drf_social_oauth2/urls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
try:
from django.conf.urls import url, include

except ImportError:
from django.urls import re_path, include

from django.urls import path

from oauth2_provider.views import AuthorizationView
from social_django.views import complete


from drf_social_oauth2.views import (
ConvertTokenView,
Expand All @@ -18,10 +15,9 @@
InvalidateRefreshTokens,
)

app_name = 'drfso2'

app_name = 'drf'

urlpatterns = [path('complete/<str:backend>/', complete, name='complete')]
urlpatterns = []

try:
urlpatterns += [
Expand All @@ -36,7 +32,7 @@
name='invalidate_sessions',
),
url(
r'invalidate-refresh-tokens/?$',
r'^ invalidate-refresh-tokens/?$',
InvalidateRefreshTokens.as_view(),
name='invalidate_refresh_tokens',
),
Expand All @@ -59,7 +55,7 @@
name='invalidate_sessions',
),
re_path(
r'invalidate-refresh-tokens/?$',
r'^invalidate-refresh-tokens/?$',
InvalidateRefreshTokens.as_view(),
name='invalidate_refresh_tokens',
),
Expand Down
2 changes: 1 addition & 1 deletion drf_social_oauth2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ class RevokeTokenView(CsrfExemptMixin, OAuthLibMixin, APIView):
server_class = oauth2_settings.OAUTH2_SERVER_CLASS
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS
permission_classes = (AllowAny,)
permission_classes = (IsAuthenticated,)

def post(self, request: Request, *args, **kwargs):
serializer = RevokeTokenSerializer(data=request.data)
Expand Down
36 changes: 35 additions & 1 deletion tests/drf_social_oauth2/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ def test_authenticate_no_auth_header_fail():


def test_authenticate_no_backend_fail():
request = create_request('JWT')
authenticated = SocialAuthentication()

assert not authenticated.authenticate(request)


def test_authenticate_no_bearer_token_type():
request = create_request('Bearer')
authenticated = SocialAuthentication()

Expand Down Expand Up @@ -61,7 +68,7 @@ def test_authenticate(mocker):
authenticated = SocialAuthentication()
user, token = authenticated.authenticate(request)
assert user
assert token
assert token == '401f7ac837da42b97f613d789819ff93537bee6a'


def test_authenticate_missing_backend():
Expand All @@ -71,3 +78,30 @@ def test_authenticate_missing_backend():
authenticated = SocialAuthentication()
with raises(AuthenticationFailed):
authenticated.authenticate(request)


def test_authenticate_user_not_found(mocker):
token = 'Bearer facebook 401f7ac837da42b97f613d789819ff93537bee6a'

request = mocker.patch('django.http.request.HttpRequest')
request.session = None
request.META = {'HTTP_AUTHORIZATION': token}

load_backend_mocker = mocker.patch('drf_social_oauth2.authentication.load_backend')
load_backend_mocker.return_value.do_auth.return_value = None

authenticated = SocialAuthentication()
with raises(AuthenticationFailed):
authenticated.authenticate(request)


def test_authenticate_header(mocker):
token = 'Bearer facebook 401f7ac837da42b97f613d789819ff93537bee6a'

request = mocker.patch('django.http.request.HttpRequest')
request.session = None
request.META = {'HTTP_AUTHORIZATION': token}

authenticated = SocialAuthentication()
text = authenticated.authenticate_header(request)
assert text == 'Bearer backend realm="api"'
Loading

0 comments on commit d55c2f3

Please sign in to comment.