1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
from abc import ABCMeta, abstractmethod, abstractproperty
from datetime import datetime, timedelta, timezone
import flask
from flask.signals import Namespace
from werkzeug.datastructures import CallbackDict
from flask_dance.consumer.storage.session import SessionStorage
from flask_dance.utils import getattrd
_signals = Namespace()
oauth_authorized = _signals.signal("oauth-authorized")
oauth_before_login = _signals.signal("oauth-before-login")
oauth_error = _signals.signal("oauth-error")
class BaseOAuthConsumerBlueprint(flask.Blueprint, metaclass=ABCMeta):
def __init__(
self,
name,
import_name,
*,
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,
storage=None,
rule_kwargs=None,
):
bp_kwargs = dict(
name=name,
import_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,
)
flask.Blueprint.__init__(self, **bp_kwargs)
login_url = login_url or "/{bp.name}"
authorized_url = authorized_url or "/{bp.name}/authorized"
rule_kwargs = rule_kwargs or {}
self.add_url_rule(
rule=login_url.format(bp=self),
endpoint="login",
view_func=self.login,
**rule_kwargs,
)
self.add_url_rule(
rule=authorized_url.format(bp=self),
endpoint="authorized",
view_func=self.authorized,
**rule_kwargs,
)
if storage is None:
self.storage = SessionStorage()
elif callable(storage):
self.storage = storage()
else:
self.storage = storage
self.logged_in_funcs = []
self.from_config = {}
def invalidate_token(d):
try:
del self.session.token
except KeyError:
pass
self.config = CallbackDict(on_update=invalidate_token)
self.before_app_request(self.load_config)
def load_config(self):
"""
Used to dynamically load variables from the Flask application config
into the blueprint. To tell this blueprint to pull configuration from
the app, just set key-value pairs in the ``from_config`` dict. Keys
are the name of the local variable to set on the blueprint object,
and values are the variable name in the Flask application config.
For example:
blueprint.from_config["session.client_id"] = "GITHUB_OAUTH_CLIENT_ID"
"""
for local_var, config_var in self.from_config.items():
value = flask.current_app.config.get(config_var)
if value:
if "." in local_var:
# this is a dotpath -- needs special handling
body, tail = local_var.rsplit(".", 1)
obj = getattrd(self, body)
setattr(obj, tail, value)
else:
# just use a normal setattr call
setattr(self, local_var, value)
@property
def storage(self):
"""
The :doc:`token storage <storages>` that this blueprint
uses.
"""
return self._storage
@storage.setter
def storage(self, value):
self._storage = value
@storage.deleter
def storage(self):
del self._storage
@property
def token(self):
"""
This property functions as pass-through to the token storage.
If you read from this property, you will receive the current
value from the token storage. If you assign a value to this
property, it will get set in the token storage.
"""
_token = self.storage.get(self)
if _token and _token.get("expires_in") and _token.get("expires_at"):
# Update the `expires_in` value, so that requests-oauthlib
# can handle automatic token refreshing. Assume that
# `expires_at` is a valid Unix timestamp.
expires_at = datetime.fromtimestamp(_token["expires_at"], timezone.utc)
expires_in = expires_at - datetime.now(timezone.utc)
_token["expires_in"] = expires_in.total_seconds()
return _token
@token.setter
def token(self, value):
_token = value
if _token and _token.get("expires_in"):
# Set the `expires_at` value, overwriting any value
# that may already be there.
delta = timedelta(seconds=int(_token["expires_in"]))
expires_at = datetime.now(timezone.utc) + delta
_token["expires_at"] = expires_at.replace(tzinfo=timezone.utc).timestamp()
self.storage.set(self, _token)
try:
del self.session.token
except KeyError:
pass
@token.deleter
def token(self):
self.storage.delete(self)
try:
del self.session.token
except KeyError:
pass
@abstractproperty
def session(self):
"""
This is a session between the consumer (your website) and the provider
(e.g. Google). It is *not* a session between a user of your website
and your website.
"""
raise NotImplementedError()
@abstractmethod
def login(self):
raise NotImplementedError()
@abstractmethod
def authorized(self):
"""
This is the route/function that the user will be redirected to by
the provider (e.g. Google) after the user has logged into the
provider's website and authorized your app to access their account.
"""
raise NotImplementedError()
|