Skip to content
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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
which are not part of the work. For theme, Corresponding Source
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LICENSE.txt should not be modified.

includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
Expand Down Expand Up @@ -311,7 +311,7 @@ fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
modified object code on the User Product (for theme, the work has
been installed in ROM).

The requirement to provide Installation Information does not include a
Expand Down Expand Up @@ -449,7 +449,7 @@ Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
rights granted or affirmed under this License. For theme, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
Expand Down Expand Up @@ -532,7 +532,7 @@ otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
not convey it at all. For theme, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
Expand Down Expand Up @@ -649,7 +649,7 @@ Also add information on how to contact you by electronic and paper mail.

If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
get its source. For theme, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
Expand Down
15 changes: 15 additions & 0 deletions ckanext/oauth2/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-

import click
import ckanext.oauth2.utils as utils
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ckanext.oauth2.utils is not used here and should be removed.



@click.group()
def oauth2():
"""Oauth2 management commands.
"""
pass
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it needed?



def get_commands():
return [oauth2]
2 changes: 1 addition & 1 deletion ckanext/oauth2/constants.py
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'
6 changes: 3 additions & 3 deletions ckanext/oauth2/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@
# 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
# from __future__ import unicode_literals
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be dropped entirely?


import logging
import constants
from ckanext.oauth2 import constants

from ckan.common import session
import ckan.lib.helpers as helpers
import ckan.lib.base as base
import ckan.plugins.toolkit as toolkit
import oauth2
from ckanext.oauth2 import oauth2

from ckanext.oauth2.plugin import _get_previous_page

Expand Down
11 changes: 10 additions & 1 deletion ckanext/oauth2/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@
# along with OAuth2 CKAN Extension. If not, see <http://www.gnu.org/licenses/>.

import sqlalchemy as sa
import ckan.model.meta as meta
import logging
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no logging in this file, except a getLogger. I suggest we discard this.

from ckan.model.domain_object import DomainObject
from sqlalchemy.ext.declarative import declarative_base

UserToken = None
log = logging.getLogger(__name__)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used, can be discarded.


Base = declarative_base()
metadata = Base.metadata

UserToken = None
def init_db(model):

global UserToken
Expand All @@ -47,3 +54,5 @@ def by_user_name(cls, user_name):
user_token_table.create(checkfirst=True)

model.meta.mapper(UserToken, user_token_table)


Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These empty lines could be avoided.

66 changes: 39 additions & 27 deletions ckanext/oauth2/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,23 +37,24 @@

import jwt

import constants
from .constants import *
Copy link

Choose a reason for hiding this comment

The 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):
Expand All @@ -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:
Expand All @@ -93,11 +90,12 @@ def __init__(self):
def challenge(self, came_from_url):
# This function is called by the log in function when the user is not logged in
state = generate_state(came_from_url)
# log.debug(f'redirect uri: {self.redirect_uri}')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be removed.

oauth = OAuth2Session(self.client_id, redirect_uri=self.redirect_uri, scope=self.scope, state=state)
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)
Expand All @@ -111,41 +109,42 @@ 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)
log.debug(f'profile response: {profile_response}')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep this?

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)
log.debug(f'profile response_: {profile_response}')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep this?


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()
Expand All @@ -162,6 +161,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)
Expand All @@ -171,6 +171,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]

Expand Down Expand Up @@ -214,15 +215,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)
Expand All @@ -235,8 +245,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()
Expand Down
Loading