-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
flask and python3 conversion #42
base: master
Are you sure you want to change the base?
Changes from 4 commits
e33ff06
390bdb6
3633240
223175f
9ff9977
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import click | ||
|
||
@click.group() | ||
def oauth2(): | ||
"""Oauth2 management commands. | ||
""" | ||
pass | ||
|
||
|
||
def get_commands(): | ||
return [oauth2] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
CAME_FROM_FIELD = 'came_from' | ||
INITIAL_PAGE = '/dashboard' | ||
INITIAL_PAGE = '/' | ||
REDIRECT_URL = 'oauth2/callback' |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,11 +19,10 @@ | |
# along with OAuth2 CKAN Extension. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
|
||
from __future__ import unicode_literals | ||
|
||
import base64 | ||
import ckan.model as model | ||
import db | ||
from ckanext.oauth2.db import UserToken | ||
import ckanext.oauth2.db as db | ||
import json | ||
import logging | ||
from six.moves.urllib.parse import urljoin | ||
|
@@ -38,23 +37,24 @@ | |
|
||
import jwt | ||
|
||
import constants | ||
from .constants import * | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wildcard import are a bit dangerous and potentially confusing, but they are not a big issue. |
||
from flask import Flask, request, redirect, session, url_for, jsonify | ||
|
||
|
||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
def generate_state(url): | ||
return b64encode(bytes(json.dumps({constants.CAME_FROM_FIELD: url}))) | ||
return b64encode(bytes(json.dumps({CAME_FROM_FIELD: url}).encode())) | ||
|
||
|
||
def get_came_from(state): | ||
return json.loads(b64decode(state)).get(constants.CAME_FROM_FIELD, '/') | ||
return json.loads(b64decode(state)).get(CAME_FROM_FIELD, '/') | ||
|
||
|
||
REQUIRED_CONF = ("authorization_endpoint", "token_endpoint", "client_id", "client_secret", "profile_api_url", "profile_api_user_field", "profile_api_mail_field") | ||
|
||
|
||
class OAuth2Helper(object): | ||
|
||
def __init__(self): | ||
|
@@ -79,10 +79,7 @@ def __init__(self): | |
self.profile_api_groupmembership_field = six.text_type(os.environ.get('CKAN_OAUTH2_PROFILE_API_GROUPMEMBERSHIP_FIELD', toolkit.config.get('ckan.oauth2.profile_api_groupmembership_field', ''))).strip() | ||
self.sysadmin_group_name = six.text_type(os.environ.get('CKAN_OAUTH2_SYSADMIN_GROUP_NAME', toolkit.config.get('ckan.oauth2.sysadmin_group_name', ''))).strip() | ||
|
||
self.redirect_uri = urljoin(urljoin(toolkit.config.get('ckan.site_url', 'http://localhost:5000'), toolkit.config.get('ckan.root_path')), constants.REDIRECT_URL) | ||
|
||
# Init db | ||
db.init_db(model) | ||
self.redirect_uri = urljoin(urljoin(toolkit.config.get('ckan.site_url', 'http://localhost:5000'), toolkit.config.get('ckan.root_path')), REDIRECT_URL) | ||
|
||
missing = [key for key in REQUIRED_CONF if getattr(self, key, "") == ""] | ||
if missing: | ||
|
@@ -97,7 +94,7 @@ def challenge(self, came_from_url): | |
auth_url, _ = oauth.authorization_url(self.authorization_endpoint) | ||
log.debug('Challenge: Redirecting challenge to page {0}'.format(auth_url)) | ||
# CKAN 2.6 only supports bytes | ||
return toolkit.redirect_to(auth_url.encode('utf-8')) | ||
return toolkit.redirect_to(auth_url)#.encode('utf-8')) | ||
|
||
def get_token(self): | ||
oauth = OAuth2Session(self.client_id, redirect_uri=self.redirect_uri, scope=self.scope) | ||
|
@@ -111,41 +108,40 @@ def get_token(self): | |
if self.legacy_idm: | ||
# This is only required for Keyrock v6 and v5 | ||
headers['Authorization'] = 'Basic %s' % base64.urlsafe_b64encode( | ||
'%s:%s' % (self.client_id, self.client_secret) | ||
(f'{self.client_id}:{self.client_secret}').encode() | ||
) | ||
|
||
try: | ||
log.debug(f'authorization_response: {toolkit.request.url}') | ||
token = oauth.fetch_token(self.token_endpoint, | ||
headers=headers, | ||
client_id=self.client_id, | ||
client_secret=self.client_secret, | ||
authorization_response=toolkit.request.url, | ||
verify=self.verify_https) | ||
authorization_response=toolkit.request.url.replace('http:', 'https:', 1)) | ||
except requests.exceptions.SSLError as e: | ||
# TODO search a better way to detect invalid certificates | ||
if "verify failed" in six.text_type(e): | ||
raise InsecureTransportError() | ||
else: | ||
raise | ||
|
||
return token | ||
|
||
def identify(self, token): | ||
|
||
if self.jwt_enable: | ||
|
||
log.debug('jwt_enabled') | ||
access_token = bytes(token['access_token']) | ||
user_data = jwt.decode(access_token, verify=False) | ||
user = self.user_json(user_data) | ||
else: | ||
|
||
else: | ||
try: | ||
if self.legacy_idm: | ||
profile_response = requests.get(self.profile_api_url + '?access_token=%s' % token['access_token'], verify=self.verify_https) | ||
else: | ||
oauth = OAuth2Session(self.client_id, token=token) | ||
profile_response = oauth.get(self.profile_api_url, verify=self.verify_https) | ||
profile_response = oauth.get(self.profile_api_url) | ||
|
||
except requests.exceptions.SSLError as e: | ||
log.debug('exception identify oauth2') | ||
# TODO search a better way to detect invalid certificates | ||
if "verify failed" in six.text_type(e): | ||
raise InsecureTransportError() | ||
|
@@ -162,6 +158,7 @@ def identify(self, token): | |
else: | ||
user_data = profile_response.json() | ||
user = self.user_json(user_data) | ||
log.debug(f'user: {user}') | ||
|
||
# Save the user in the database | ||
model.Session.add(user) | ||
|
@@ -171,6 +168,7 @@ def identify(self, token): | |
return user.name | ||
|
||
def user_json(self, user_data): | ||
log.debug(f'user_data: {user_data}') | ||
email = user_data[self.profile_api_mail_field] | ||
user_name = user_data[self.profile_api_user_field] | ||
|
||
|
@@ -214,15 +212,24 @@ def remember(self, user_name): | |
rememberer = self._get_rememberer(environ) | ||
identity = {'repoze.who.userid': user_name} | ||
headers = rememberer.remember(environ, identity) | ||
response = jsonify() | ||
for header, value in headers: | ||
toolkit.response.headers.add(header, value) | ||
response.headers[header] = value | ||
return response | ||
|
||
def redirect_from_callback(self): | ||
def redirect_from_callback(self, resp_remember): | ||
'''Redirect to the callback URL after a successful authentication.''' | ||
state = toolkit.request.params.get('state') | ||
came_from = get_came_from(state) | ||
toolkit.response.status = 302 | ||
toolkit.response.location = came_from | ||
|
||
response = jsonify() | ||
response.status_code = 302 | ||
for header, value in resp_remember.headers: | ||
response.headers[header] = value | ||
response.headers['location'] = came_from | ||
response.autocorrect_location_header = False | ||
return response | ||
|
||
|
||
def get_stored_token(self, user_name): | ||
user_token = db.UserToken.by_user_name(user_name=user_name) | ||
|
@@ -235,8 +242,10 @@ def get_stored_token(self, user_name): | |
} | ||
|
||
def update_token(self, user_name, token): | ||
|
||
user_token = db.UserToken.by_user_name(user_name=user_name) | ||
try: | ||
user_token = db.UserToken.by_user_name(user_name=user_name) | ||
except AttributeError as e: | ||
user_token = None | ||
# Create the user if it does not exist | ||
if not user_token: | ||
user_token = db.UserToken() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,17 +18,18 @@ | |
# You should have received a copy of the GNU Affero General Public License | ||
# along with OAuth2 CKAN Extension. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
from __future__ import unicode_literals | ||
|
||
import logging | ||
import oauth2 | ||
from .oauth2 import * | ||
import os | ||
|
||
from functools import partial | ||
from ckan import plugins | ||
from ckan.common import g | ||
from ckan.plugins import toolkit | ||
from urlparse import urlparse | ||
import ckanext.oauth2.db as db | ||
import urllib.parse | ||
from ckanext.oauth2.views import get_blueprints | ||
from ckanext.oauth2.cli import get_commands | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
@@ -62,79 +63,50 @@ def request_reset(context, data_dict): | |
return _no_permissions(context, msg) | ||
|
||
|
||
def _get_previous_page(default_page): | ||
if 'came_from' not in toolkit.request.params: | ||
came_from_url = toolkit.request.headers.get('Referer', default_page) | ||
else: | ||
came_from_url = toolkit.request.params.get('came_from', default_page) | ||
|
||
came_from_url_parsed = urlparse(came_from_url) | ||
class _OAuth2Plugin(plugins.SingletonPlugin): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why split the plugin into two? |
||
plugins.implements(plugins.IBlueprint) | ||
plugins.implements(plugins.IClick) | ||
|
||
# Avoid redirecting users to external hosts | ||
if came_from_url_parsed.netloc != '' and came_from_url_parsed.netloc != toolkit.request.host: | ||
came_from_url = default_page | ||
# IBlueprint | ||
|
||
# When a user is being logged and REFERER == HOME or LOGOUT_PAGE | ||
# he/she must be redirected to the dashboard | ||
pages = ['/', '/user/logged_out_redirect'] | ||
if came_from_url_parsed.path in pages: | ||
came_from_url = default_page | ||
def get_blueprint(self): | ||
return get_blueprints() | ||
|
||
return came_from_url | ||
# IClick | ||
|
||
def get_commands(self): | ||
return get_commands() | ||
|
||
class OAuth2Plugin(plugins.SingletonPlugin): | ||
|
||
class OAuth2Plugin(_OAuth2Plugin, plugins.SingletonPlugin): | ||
plugins.implements(plugins.IAuthenticator, inherit=True) | ||
plugins.implements(plugins.IAuthFunctions, inherit=True) | ||
plugins.implements(plugins.IRoutes, inherit=True) | ||
# plugins.implements(plugins.IRoutes, inherit=True) | ||
plugins.implements(plugins.IConfigurer) | ||
|
||
|
||
def __init__(self, name=None): | ||
'''Store the OAuth 2 client configuration''' | ||
log.debug('Init OAuth2 extension') | ||
|
||
self.oauth2helper = oauth2.OAuth2Helper() | ||
|
||
def before_map(self, m): | ||
log.debug('Setting up the redirections to the OAuth2 service') | ||
|
||
m.connect('/user/login', | ||
controller='ckanext.oauth2.controller:OAuth2Controller', | ||
action='login') | ||
db.init_db(model) | ||
log.debug(f'Creating UserToken...') | ||
self.oauth2helper = OAuth2Helper() | ||
|
||
# We need to handle petitions received to the Callback URL | ||
# since some error can arise and we need to process them | ||
m.connect('/oauth2/callback', | ||
controller='ckanext.oauth2.controller:OAuth2Controller', | ||
action='callback') | ||
|
||
# Redirect the user to the OAuth service register page | ||
if self.register_url: | ||
m.redirect('/user/register', self.register_url) | ||
|
||
# Redirect the user to the OAuth service reset page | ||
if self.reset_url: | ||
m.redirect('/user/reset', self.reset_url) | ||
|
||
# Redirect the user to the OAuth service reset page | ||
if self.edit_url: | ||
m.redirect('/user/edit/{user}', self.edit_url) | ||
|
||
return m | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Various redirect have been dropped here, but the redirect configuration and test are still here. |
||
|
||
def identify(self): | ||
log.debug('identify') | ||
|
||
def _refresh_and_save_token(user_name): | ||
new_token = self.oauth2helper.refresh_token(user_name) | ||
if new_token: | ||
toolkit.c.usertoken = new_token | ||
toolkit.g.usertoken = new_token | ||
|
||
environ = toolkit.request.environ | ||
apikey = toolkit.request.headers.get(self.authorization_header, '') | ||
user_name = None | ||
|
||
|
||
if self.authorization_header == "authorization": | ||
if apikey.startswith('Bearer '): | ||
apikey = apikey[7:].strip() | ||
|
@@ -146,7 +118,10 @@ def _refresh_and_save_token(user_name): | |
try: | ||
token = {'access_token': apikey} | ||
user_name = self.oauth2helper.identify(token) | ||
except Exception: | ||
log.debug(f'user_name1: {user_name}') | ||
except Exception as e: | ||
log.debug(f'Auth error:') | ||
log.debug(e) | ||
pass | ||
|
||
# If the authentication via API fails, we can still log in the user using session. | ||
|
@@ -157,11 +132,12 @@ def _refresh_and_save_token(user_name): | |
# If we have been able to log in the user (via API or Session) | ||
if user_name: | ||
g.user = user_name | ||
toolkit.c.user = user_name | ||
toolkit.c.usertoken = self.oauth2helper.get_stored_token(user_name) | ||
toolkit.c.usertoken_refresh = partial(_refresh_and_save_token, user_name) | ||
toolkit.g.user = user_name | ||
toolkit.g.usertoken = self.oauth2helper.get_stored_token(user_name) | ||
toolkit.g.usertoken_refresh = partial(_refresh_and_save_token, user_name) | ||
else: | ||
g.user = None | ||
toolkit.g.user = None | ||
log.warn('The user is not currently logged...') | ||
|
||
def get_auth_functions(self): | ||
|
@@ -175,6 +151,7 @@ def get_auth_functions(self): | |
|
||
def update_config(self, config): | ||
# Update our configuration | ||
log.debug('update config...') | ||
self.register_url = os.environ.get("CKAN_OAUTH2_REGISTER_URL", config.get('ckan.oauth2.register_url', None)) | ||
self.reset_url = os.environ.get("CKAN_OAUTH2_RESET_URL", config.get('ckan.oauth2.reset_url', None)) | ||
self.edit_url = os.environ.get("CKAN_OAUTH2_EDIT_URL", config.get('ckan.oauth2.edit_url', None)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it needed?