This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
"""
Parts derived from socialregistration and authorized by: alen, pinda
Inspired by:
http://github.com/leah/python-oauth/blob/master/oauth/example/client.py
http://github.com/facebook/tornado/blob/master/tornado/auth.py
"""
import requests
from urllib.parse import parse_qsl, urlparse
from django.http import HttpResponseRedirect
from django.utils.http import urlencode
from django.utils.translation import gettext as _
from requests_oauthlib import OAuth1
from allauth.utils import build_absolute_uri, get_request_param
def get_token_prefix(url):
"""
Returns a prefix for the token to store in the session so we can hold
more than one single oauth provider's access key in the session.
Example:
The request token url ``http://twitter.com/oauth/request_token``
returns ``twitter.com``
"""
return urlparse(url).netloc
class OAuthError(Exception):
pass
class OAuthClient(object):
def __init__(
self,
request,
consumer_key,
consumer_secret,
request_token_url,
access_token_url,
callback_url,
parameters=None,
provider=None,
):
self.request = request
self.request_token_url = request_token_url
self.access_token_url = access_token_url
self.consumer_key = consumer_key
self.consumer_secret = consumer_secret
self.parameters = parameters
self.callback_url = callback_url
self.provider = provider
self.errors = []
self.request_token = None
self.access_token = None
def _get_request_token(self):
"""
Obtain a temporary request token to authorize an access token and to
sign the request to obtain the access token
"""
if self.request_token is None:
get_params = {}
if self.parameters:
get_params.update(self.parameters)
get_params["oauth_callback"] = build_absolute_uri(
self.request, self.callback_url
)
rt_url = self.request_token_url + "?" + urlencode(get_params)
oauth = OAuth1(self.consumer_key, client_secret=self.consumer_secret)
response = requests.post(url=rt_url, auth=oauth)
if response.status_code not in [200, 201]:
raise OAuthError(
_(
"Invalid response while obtaining request token"
' from "%s". Response was: %s.'
)
% (get_token_prefix(self.request_token_url), response.text)
)
self.request_token = dict(parse_qsl(response.text))
self.request.session[
"oauth_%s_request_token" % get_token_prefix(self.request_token_url)
] = self.request_token
return self.request_token
def get_access_token(self):
"""
Obtain the access token to access private resources at the API
endpoint.
"""
if self.access_token is None:
request_token = self._get_rt_from_session()
oauth = OAuth1(
self.consumer_key,
client_secret=self.consumer_secret,
resource_owner_key=request_token["oauth_token"],
resource_owner_secret=request_token["oauth_token_secret"],
)
at_url = self.access_token_url
# Passing along oauth_verifier is required according to:
# http://groups.google.com/group/twitter-development-talk/browse_frm/thread/472500cfe9e7cdb9#
# Though, the custom oauth_callback seems to work without it?
oauth_verifier = get_request_param(self.request, "oauth_verifier")
if oauth_verifier:
at_url = at_url + "?" + urlencode({"oauth_verifier": oauth_verifier})
response = requests.post(url=at_url, auth=oauth)
if response.status_code not in [200, 201]:
raise OAuthError(
_("Invalid response while obtaining access token" ' from "%s".')
% get_token_prefix(self.request_token_url)
)
self.access_token = dict(parse_qsl(response.text))
self.request.session[
"oauth_%s_access_token" % get_token_prefix(self.request_token_url)
] = self.access_token
return self.access_token
def _get_rt_from_session(self):
"""
Returns the request token cached in the session by
``_get_request_token``
"""
try:
return self.request.session[
"oauth_%s_request_token" % get_token_prefix(self.request_token_url)
]
except KeyError:
raise OAuthError(
_('No request token saved for "%s".')
% get_token_prefix(self.request_token_url)
)
def is_valid(self):
try:
self._get_rt_from_session()
self.get_access_token()
except OAuthError as e:
self.errors.append(e.args[0])
return False
return True
def get_redirect(self, authorization_url, extra_params):
"""
Returns a ``HttpResponseRedirect`` object to redirect the user
to the URL the OAuth provider handles authorization.
"""
request_token = self._get_request_token()
params = {
"oauth_token": request_token["oauth_token"],
"oauth_callback": self.request.build_absolute_uri(self.callback_url),
}
params.update(extra_params)
url = authorization_url + "?" + urlencode(params)
return HttpResponseRedirect(url)
class OAuth(object):
"""
Base class to perform oauth signed requests from access keys saved
in a user's session. See the ``OAuthTwitter`` class below for an
example.
"""
def __init__(self, request, consumer_key, secret_key, request_token_url):
self.request = request
self.consumer_key = consumer_key
self.secret_key = secret_key
self.request_token_url = request_token_url
def _get_at_from_session(self):
"""
Get the saved access token for private resources from the session.
"""
try:
return self.request.session[
"oauth_%s_access_token" % get_token_prefix(self.request_token_url)
]
except KeyError:
raise OAuthError(
_('No access token saved for "%s".')
% get_token_prefix(self.request_token_url)
)
def query(self, url, method="GET", params=None, headers=None):
"""
Request a API endpoint at ``url`` with ``params`` being either the
POST or GET data.
"""
access_token = self._get_at_from_session()
oauth = OAuth1(
self.consumer_key,
client_secret=self.secret_key,
resource_owner_key=access_token["oauth_token"],
resource_owner_secret=access_token["oauth_token_secret"],
)
response = getattr(requests, method.lower())(
url, auth=oauth, headers=headers, params=params
)
if response.status_code != 200:
raise OAuthError(
_('No access to private resources at "%s".')
% get_token_prefix(self.request_token_url)
)
return response.text

View File

@@ -0,0 +1,38 @@
from urllib.parse import parse_qsl
from django.urls import reverse
from django.utils.http import urlencode
from allauth.socialaccount.providers.base import Provider
class OAuthProvider(Provider):
def get_login_url(self, request, **kwargs):
url = reverse(self.id + "_login")
if kwargs:
url = url + "?" + urlencode(kwargs)
return url
def get_auth_params(self, request, action):
settings = self.get_settings()
ret = dict(settings.get("AUTH_PARAMS", {}))
dynamic_auth_params = request.GET.get("auth_params", None)
if dynamic_auth_params:
ret.update(dict(parse_qsl(dynamic_auth_params)))
return ret
def get_auth_url(self, request, action):
# TODO: This is ugly. Move authorization_url away from the
# adapter into the provider. Hmpf, the line between
# adapter/provider is a bit too thin here.
return None
def get_scope(self, request):
settings = self.get_settings()
scope = settings.get("SCOPE")
if scope is None:
scope = self.get_default_scope()
return scope
def get_default_scope(self):
return []

View File

@@ -0,0 +1,15 @@
from django.urls import include, path
from allauth.utils import import_attribute
def default_urlpatterns(provider):
login_view = import_attribute(provider.get_package() + ".views.oauth_login")
callback_view = import_attribute(provider.get_package() + ".views.oauth_callback")
urlpatterns = [
path("login/", login_view, name=provider.id + "_login"),
path("login/callback/", callback_view, name=provider.id + "_callback"),
]
return [path(provider.get_slug() + "/", include(urlpatterns))]

View File

@@ -0,0 +1,130 @@
from __future__ import absolute_import
import logging
from django.urls import reverse
from allauth.socialaccount.adapter import get_adapter
from allauth.socialaccount.helpers import (
complete_social_login,
render_authentication_error,
)
from allauth.socialaccount.models import SocialLogin, SocialToken
from allauth.socialaccount.providers.base.constants import (
AuthAction,
AuthError,
)
from allauth.socialaccount.providers.base.mixins import OAuthLoginMixin
from allauth.socialaccount.providers.oauth.client import (
OAuthClient,
OAuthError,
)
logger = logging.getLogger(__name__)
class OAuthAdapter(object):
def __init__(self, request):
self.request = request
def complete_login(self, request, app):
"""
Returns a SocialLogin instance
"""
raise NotImplementedError
def get_provider(self):
adapter = get_adapter(self.request)
app = adapter.get_app(self.request, provider=self.provider_id)
return app.get_provider(self.request)
class OAuthView(object):
@classmethod
def adapter_view(cls, adapter):
def view(request, *args, **kwargs):
self = cls()
self.request = request
self.adapter = adapter(request)
return self.dispatch(request, *args, **kwargs)
return view
def _get_client(self, request, callback_url):
provider = self.adapter.get_provider()
app = provider.app
scope = " ".join(provider.get_scope(request))
parameters = {}
if scope:
parameters["scope"] = scope
client = OAuthClient(
request,
app.client_id,
app.secret,
self.adapter.request_token_url,
self.adapter.access_token_url,
callback_url,
parameters=parameters,
provider=provider,
)
return client
class OAuthLoginView(OAuthLoginMixin, OAuthView):
def login(self, request, *args, **kwargs):
callback_url = reverse(self.adapter.provider_id + "_callback")
SocialLogin.stash_state(request)
action = request.GET.get("action", AuthAction.AUTHENTICATE)
provider = self.adapter.get_provider()
auth_url = provider.get_auth_url(request, action) or self.adapter.authorize_url
auth_params = provider.get_auth_params(request, action)
client = self._get_client(request, callback_url)
try:
return client.get_redirect(auth_url, auth_params)
except OAuthError as e:
logger.error("OAuth authentication error", exc_info=True)
return render_authentication_error(
request, self.adapter.provider_id, exception=e
)
class OAuthCallbackView(OAuthView):
def dispatch(self, request):
"""
View to handle final steps of OAuth based authentication where the user
gets redirected back to from the service provider
"""
login_done_url = reverse(self.adapter.provider_id + "_callback")
client = self._get_client(request, login_done_url)
if not client.is_valid():
if "denied" in request.GET:
error = AuthError.CANCELLED
else:
error = AuthError.UNKNOWN
extra_context = dict(oauth_client=client)
return render_authentication_error(
request,
self.adapter.provider_id,
error=error,
extra_context=extra_context,
)
app = self.adapter.get_provider().app
try:
access_token = client.get_access_token()
token = SocialToken(
app=app,
token=access_token["oauth_token"],
# .get() -- e.g. Evernote does not feature a secret
token_secret=access_token.get("oauth_token_secret", ""),
)
login = self.adapter.complete_login(
request, app, token, response=access_token
)
login.token = token
login.state = SocialLogin.unstash_state(request)
return complete_social_login(request, login)
except OAuthError as e:
return render_authentication_error(
request, self.adapter.provider_id, exception=e
)