Skip to content

Commit

Permalink
Fix all loginpass backends
Browse files Browse the repository at this point in the history
  • Loading branch information
lepture committed May 19, 2020
1 parent 7473230 commit 32b565e
Show file tree
Hide file tree
Showing 14 changed files with 127 additions and 160 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Authlib Loginpass
=================

Social connections powered by [Authlib][]. This library is a part of Authlib project.
It works well with Authlib v0.7+.
It works well with Authlib v0.14.3+.

[Authlib]: https://authlib.org/

Expand All @@ -14,8 +14,9 @@ It works well with Authlib v0.7+.

```python
from flask import Flask
from authlib.flask.client import OAuth
from loginpass import create_flask_blueprint, GitHub
from authlib.integrations.flask_client import OAuth
from loginpass import create_flask_blueprint
from loginpass import Twitter, GitHub, Google

app = Flask(__name__)
oauth = OAuth(app)
Expand All @@ -28,8 +29,9 @@ def handle_authorize(remote, token, user_info):
return user_page
raise some_error

github_bp = create_flask_blueprint(GitHub, oauth, handle_authorize)
app.register_blueprint(github_bp, url_prefix='/github')
backends = [Twitter, GitHub, Google]
bp = create_flask_blueprint(backends, oauth, handle_authorize)
app.register_blueprint(bp, url_prefix='')
```

Useful Links
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Authlib Loginpass
=================

Social connections powered by Authlib_. This library is a part of Authlib project.
It works well with Authlib v0.7+.
It works well with Authlib v0.14.3+.

.. _Authlib: https://authlib.org/

Expand Down
9 changes: 5 additions & 4 deletions django_example/website/logins.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@
from django.urls import include, path
from authlib.django.client import OAuth
from loginpass import create_django_urlpatterns
from loginpass import OAUTH_BACKENDS
from loginpass import Twitter, GitHub, Google

oauth = OAuth()
backends = [Twitter, GitHub, Google]


def handle_authorize(request, remote, token, user_info):
return JsonResponse(user_info)


urlpatterns = []
for backend in OAUTH_BACKENDS:
for backend in backends:
oauth_urls = create_django_urlpatterns(backend, oauth, handle_authorize)
urlpatterns.append(path(backend.OAUTH_NAME + '/', include(oauth_urls)))
urlpatterns.append(path(backend.NAME + '/', include(oauth_urls)))


def home(request):
tpl = '<li><a href="/{}/login">{}</a></li>'
lis = [tpl.format(b.OAUTH_NAME, b.OAUTH_NAME) for b in OAUTH_BACKENDS]
lis = [tpl.format(b.NAME, b.NAME) for b in backends]
html = '<ul>{}</ul>'.format(''.join(lis))
return HttpResponse(html)
14 changes: 8 additions & 6 deletions flask_example/app.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
from flask import Flask, jsonify
from authlib.integrations.flask_client import OAuth
from loginpass import create_flask_blueprint
from loginpass import OAUTH_BACKENDS
from loginpass import Twitter, GitHub, Google

app = Flask(__name__)
app.config.from_pyfile('config.py')


oauth = OAuth(app)

#: you can customize this part
backends = [Twitter, GitHub, Google]


@app.route('/')
def index():
tpl = '<li><a href="/{}/login">{}</a></li>'
lis = [tpl.format(b.NAME, b.NAME) for b in OAUTH_BACKENDS]
tpl = '<li><a href="/login/{}">{}</a></li>'
lis = [tpl.format(b.NAME, b.NAME) for b in backends]
return '<ul>{}</ul>'.format(''.join(lis))


def handle_authorize(remote, token, user_info):
return jsonify(user_info)


for backend in OAUTH_BACKENDS:
bp = create_flask_blueprint(backend, oauth, handle_authorize)
app.register_blueprint(bp, url_prefix='/{}'.format(backend.NAME))
bp = create_flask_blueprint(backends, oauth, handle_authorize)
app.register_blueprint(bp, url_prefix='')
29 changes: 4 additions & 25 deletions loginpass/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from ._flask import create_flask_blueprint
from ._django import create_django_urlpatterns
from ._consts import version, homepage
# from .azure import Azure, create_azure_backend
from .azure import Azure, create_azure_backend
from .battlenet import BattleNet, create_battlenet_backend
from .google import Google, GoogleServiceAccount
from .github import GitHub
# from .facebook import Facebook
from .facebook import Facebook
from .instagram import Instagram
from .twitter import Twitter
from .dropbox import Dropbox
Expand All @@ -25,34 +25,14 @@
from .hydra import create_hydra_backend


OAUTH_BACKENDS = [
BattleNet,
Twitter,
# Facebook,
Google,
GitHub,
Dropbox,
Instagram,
Reddit,
Gitlab,
Slack,
Discord,
StackOverflow,
Bitbucket,
Strava,
Spotify,
Yandex,
Twitch, VK
]

__all__ = [
'create_flask_blueprint',
'create_django_urlpatterns',
# 'Azure', 'create_azure_backend',
'Azure', 'create_azure_backend',
'BattleNet', 'create_battlenet_backend',
'Google', 'GoogleServiceAccount',
'GitHub',
# 'Facebook',
'Facebook',
'Twitter',
'Dropbox',
'LinkedIn',
Expand All @@ -69,7 +49,6 @@
'VK',
'ORCiD',
'create_hydra_backend',
'OAUTH_BACKENDS',
]

__version__ = version
Expand Down
6 changes: 5 additions & 1 deletion loginpass/_django.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@

def create_django_urlpatterns(backend, oauth, handle_authorize):
from authlib.integrations.django_client import DjangoRemoteApp
from django.urls import path

class RemoteApp(backend, DjangoRemoteApp):
OAUTH_APP_CONFIG = backend.OAUTH_CONFIG

token_name = '_loginpass_{}_token'.format(backend.NAME)
auth_route_name = 'loginpass_{}_auth'.format(backend.NAME)
login_route_name = 'loginpass_{}_login'.format(backend.NAME)
Expand All @@ -10,7 +14,7 @@ def create_django_urlpatterns(backend, oauth, handle_authorize):
backend.NAME,
overwrite=True,
fetch_token=lambda request: getattr(request, token_name, None),
**backend.OAUTH_CONFIG
client_cls=RemoteApp,
)

auth = create_auth_endpoint(remote, handle_authorize)
Expand Down
56 changes: 38 additions & 18 deletions loginpass/_flask.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
def create_flask_blueprint(backend, oauth, handle_authorize):

def create_flask_blueprint(backends, oauth, handle_authorize):
"""Create a Flask blueprint that you can register it directly to Flask
app. The blueprint contains two route: ``/auth`` and ``/login``::
app. The blueprint contains two route: ``/auth/<name>`` and
``/login/<name>``::
from flask import Flask
from authlib.integrations.flask_client import OAuth
Expand All @@ -18,25 +20,31 @@ def handle_authorize(remote, token, user_info):
return user_page
raise some_error
github_bp = create_flask_blueprint(GitHub, oauth, handle_authorize)
app.register_blueprint(github_bp, url_prefix='/github')
account_bp = create_flask_blueprint(
oauth, [GitHub, Google], handle_authorize)
app.register_blueprint(account_bp, url_prefix='/account')
# visit /github/login
# callback /github/auth
# visit /account/login/github
# callback /account/auth/github
:param backend: An OAuthBackend
:param oauth: Authlib Flask OAuth instance
:param backends: A list of configured backends
:param handle_authorize: A function to handle authorized response
:return: Flask Blueprint instance
"""
from flask import Blueprint, request, url_for, current_app
from flask import Blueprint, request, url_for, current_app, abort

for b in backends:
register_to(oauth, b)

oauth.register(backend.NAME, overwrite=True, **backend.OAUTH_CONFIG)
bp = Blueprint('loginpass_' + backend.NAME, __name__)
bp = Blueprint('loginpass', __name__)

@bp.route('/auth/<name>', methods=('GET', 'POST'))
def auth(name):
remote = oauth.create_client(name)
if remote is None:
abort(404)

@bp.route('/auth', methods=('GET', 'POST'))
def auth():
remote = oauth.create_client(backend.NAME)
id_token = request.values.get('id_token')
if request.values.get('code'):
token = remote.authorize_access_token()
Expand All @@ -57,12 +65,24 @@ def auth():
user_info = remote.userinfo(token=token)
return handle_authorize(remote, token, user_info)

@bp.route('/login')
def login():
remote = oauth.create_client(backend.NAME)
redirect_uri = url_for('.auth', _external=True)
conf_key = '{}_AUTHORIZE_PARAMS'.format(backend.NAME.upper())
@bp.route('/login/<name>')
def login(name):
remote = oauth.create_client(name)
if remote is None:
abort(404)

redirect_uri = url_for('.auth', name=name, _external=True)
conf_key = '{}_AUTHORIZE_PARAMS'.format(name.upper())
params = current_app.config.get(conf_key, {})
return remote.authorize_redirect(redirect_uri, **params)

return bp


def register_to(oauth, backend_cls):
from authlib.integrations.flask_client import FlaskRemoteApp

class RemoteApp(backend_cls, FlaskRemoteApp):
OAUTH_APP_CONFIG = backend_cls.OAUTH_CONFIG

oauth.register(RemoteApp.NAME, overwrite=True, client_cls=RemoteApp)
77 changes: 17 additions & 60 deletions loginpass/azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,77 +8,34 @@
:license: BSD, see LICENSE for more details.
"""

from ._core import UserInfo, OAuthBackend, parse_id_token

_BASE_URL = 'https://login.microsoftonline.com/'
def create_azure_backend(name, tenant, version=2):


def create_azure_backend(name, tenant, version=1, claims_options=None):
base_url = 'https://login.microsoftonline.com/common/'
if version == 1:
authorize_url = '{}{}/oauth2/authorize'.format(_BASE_URL, tenant)
token_url = '{}{}/oauth2/token'.format(_BASE_URL, tenant)
issuer_url = 'https://sts.windows.net/{}/'.format(tenant)
if claims_options is None:
claims_options = {
'iss': {
'values': [issuer_url]
}
}

metadata_url = base_url + '.well-known/openid-configuration'
elif version == 2:
authorize_url = '{}{}/oauth2/v2.0/authorize'.format(_BASE_URL, tenant)
token_url = '{}{}/oauth2/v2.0/token'.format(_BASE_URL, tenant)
issuer_url = '{}{}/v2.0'.format(_BASE_URL, tenant)

if claims_options is None:

def validate_iss(claims, value):
iss = 'https://login.microsoftonline.com/{}/v2.0'.format(claims['tid'])
return iss == value

claims_options = {
'iss': {
'essential': True,
'validate': validate_iss,
}
}

metadata_url = base_url + 'v2.0/.well-known/openid-configuration'
else:
raise ValueError('Invalid version')
raise ValueError('Invalid version value')

class AzureAD(OAuthBackend):
OAUTH_TYPE = '2.0,oidc'
OAUTH_NAME = name
class AzureAD(object):
NAME = name
OAUTH_CONFIG = {
'api_base_url': 'graph.microsoft.com',
'access_token_url': token_url,
'authorize_url': authorize_url,
'api_base_url': 'https://graph.microsoft.com/',
'server_metadata_url': metadata_url,
'client_kwargs': {'scope': 'openid email profile'},
}
JWK_SET_URL = '{}{}/discovery/keys'.format(_BASE_URL, tenant)

def profile(self, **kwargs):
url = '{}{}/openid/userinfo'.format(_BASE_URL, tenant)
resp = self.get(url, **kwargs)
resp.raise_for_status()
return UserInfo(**resp.json())

def parse_openid(self, token, nonce=None):
return parse_id_token(
self, token['id_token'], claims_options,
token.get('access_token'), nonce
)

class AzureADv2(AzureAD):
JWK_SET_URL = '{}{}/discovery/v2.0/keys'.format(_BASE_URL, tenant)
def load_server_metadata(self):
metadata = super(AzureAD, self).load_server_metadata()
# fix issuer value
issuer = metadata['issuer']
issuer = issuer.replace('{tenantid}', tenant)
metadata['issuer'] = issuer
return metadata

def profile(self, **kwargs):
return self.parse_openid(**kwargs)

if version == 2:
return AzureADv2
else:
return AzureAD
return AzureAD


Azure = create_azure_backend('azure', 'common')
Loading

0 comments on commit 32b565e

Please sign in to comment.