Source code for flask_dance.consumer.oauth2

from __future__ import unicode_literals, print_function
import json

import logging
from lazy import lazy
import flask
from flask import request, url_for, redirect
from oauthlib.oauth2 import MissingCodeError
from .base import (
    BaseOAuthConsumerBlueprint, oauth_authorized, oauth_error
)
from .requests import OAuth2Session

log = logging.getLogger(__name__)


[docs]class OAuth2ConsumerBlueprint(BaseOAuthConsumerBlueprint): """ A subclass of :class:`flask.Blueprint` that sets up OAuth 2 authentication. """
[docs] def __init__(self, name, import_name, client_id=None, client_secret=None, client=None, auto_refresh_url=None, auto_refresh_kwargs=None, scope=None, state=None, static_folder=None, static_url_path=None, template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, root_path=None, login_url=None, authorized_url=None, base_url=None, authorization_url=None, authorization_url_params=None, token_url=None, token_url_params=None, redirect_url=None, redirect_to=None, session_class=None, backend=None, **kwargs): """ Most of the constructor arguments are forwarded either to the :class:`flask.Blueprint` constructor or the :class:`requests_oauthlib.OAuth2Session` construtor, including ``**kwargs`` (which is forwarded to :class:`~requests_oauthlib.OAuth2Session`). Only the arguments that are relevant to Flask-Dance are documented here. Args: base_url: The base URL of the OAuth provider. If specified, all URLs passed to this instance will be resolved relative to this URL. authorization_url: The URL specified by the OAuth provider for obtaining an `authorization grant <http://tools.ietf.org/html/rfc6749#section-1.3>`_. This can be an fully-qualified URL, or a path that is resolved relative to the ``base_url``. authorization_url_params (dict): A dict of extra key-value pairs to include in the query string of the ``authorization_url``, beyond those necessary for a standard OAuth 2 authorization grant request. token_url: The URL specified by the OAuth provider for obtaining an `access token <http://tools.ietf.org/html/rfc6749#section-1.4>`_. This can be an fully-qualified URL, or a path that is resolved relative to the ``base_url``. token_url_params (dict): A dict of extra key-value pairs to include in the query string of the ``token_url``, beyond those necessary for a standard OAuth 2 access token request. login_url: The URL route for the ``login`` view that kicks off the OAuth dance. This string will be :ref:`formatted <python:formatstrings>` with the instance so that attributes can be interpolated. Defaults to ``/{bp.name}``, so that the URL is based on the name of the blueprint. authorized_url: The URL route for the ``authorized`` view that completes the OAuth dance. This string will be :ref:`formatted <python:formatstrings>` with the instance so that attributes can be interpolated. Defaults to ``/{bp.name}/authorized``, so that the URL is based on the name of the blueprint. redirect_url: When the OAuth dance is complete, redirect the user to this URL. redirect_to: When the OAuth dance is complete, redirect the user to the URL obtained by calling :func:`~flask.url_for` with this argument. If you do not specify either ``redirect_url`` or ``redirect_to``, the user will be redirected to the root path (``/``). session_class: The class to use for creating a Requests session. Defaults to :class:`~flask_dance.consumer.requests.OAuth2Session`. backend: A storage backend class, or an instance of a storage backend class, to use for this blueprint. Defaults to :class:`~flask_dance.consumer.backend.session.SessionBackend`. """ BaseOAuthConsumerBlueprint.__init__( self, name, import_name, static_folder=static_folder, static_url_path=static_url_path, template_folder=template_folder, url_prefix=url_prefix, subdomain=subdomain, url_defaults=url_defaults, root_path=root_path, login_url=login_url, authorized_url=authorized_url, backend=backend, ) self.base_url = base_url self.session_class = session_class or OAuth2Session # passed to OAuth2Session() self._client_id = client_id self.client = client self.auto_refresh_url = auto_refresh_url self.auto_refresh_kwargs = auto_refresh_kwargs self.scope = scope self.state = state self.kwargs = kwargs self.client_secret = client_secret # used by view functions self.authorization_url = authorization_url self.authorization_url_params = authorization_url_params or {} self.token_url = token_url self.token_url_params = token_url_params or {} self.redirect_url = redirect_url self.redirect_to = redirect_to self.teardown_app_request(self.teardown_session)
@property def client_id(self): return self.session.client_id @client_id.setter def client_id(self, value): self.session.client_id = value # due to a bug in requests-oauthlib, we need to set this manually self.session._client.client_id = value @lazy
[docs] def session(self): ret = self.session_class( client_id=self._client_id, client=self.client, auto_refresh_url=self.auto_refresh_url, auto_refresh_kwargs=self.auto_refresh_kwargs, scope=self.scope, state=self.state, blueprint=self, base_url=self.base_url, **self.kwargs ) def token_updater(token): self.token = token ret.token_updater = token_updater return self.session_created(ret)
def session_created(self, session): return session def teardown_session(self, exception=None): lazy.invalidate(self, "session") def login(self): log.debug("client_id = %s", self.client_id) self.session.redirect_uri = url_for( ".authorized", next=request.args.get('next'), _external=True, ) url, state = self.session.authorization_url( self.authorization_url, state=self.state, **self.authorization_url_params ) state_key = "{bp.name}_oauth_state".format(bp=self) flask.session[state_key] = state log.debug("state = %s", state) log.debug("redirect URL = %s", url) return redirect(url) def authorized(self): if "next" in request.args: next_url = request.args["next"] elif self.redirect_url: next_url = self.redirect_url elif self.redirect_to: next_url = url_for(self.redirect_to) else: next_url = "/" log.debug("next_url = %s", next_url) # check for error in request args error = request.args.get("error") if error: error_desc = request.args.get("error_description") error_uri = request.args.get("error_uri") log.warning( "OAuth 2 authorization error: %s description: %s uri: %s", error, error_desc, error_uri, ) oauth_error.send(self, error=error, error_description=error_desc, error_uri=error_uri, ) return redirect(next_url) state_key = "{bp.name}_oauth_state".format(bp=self) if state_key not in flask.session: # can't validate state, so redirect back to login view log.info("state not found, redirecting user to login") return redirect(url_for(".login")) state = flask.session[state_key] log.debug("state = %s", state) self.session._state = state del flask.session[state_key] self.session.redirect_uri = url_for( ".authorized", next=request.args.get('next'), _external=True, ) log.debug("client_id = %s", self.client_id) log.debug("client_secret = %s", self.client_secret) try: token = self.session.fetch_token( self.token_url, authorization_response=request.url, client_secret=self.client_secret, **self.token_url_params ) except MissingCodeError as e: e.args = ( e.args[0], "The redirect request did not contain the expected parameters. Instead I got: {}".format( json.dumps(request.args) ) ) raise results = oauth_authorized.send(self, token=token) or [] if not any(ret == False for func, ret in results): self.token = token return redirect(next_url)