-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a45843c
Showing
15 changed files
with
1,218 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
syntax: glob | ||
*.pyc | ||
*.egg-info | ||
*.orig | ||
.coverage | ||
build | ||
*.egg | ||
.DS_Store | ||
dist | ||
development.ini | ||
*.sw? | ||
*~ | ||
node_modules | ||
*.project |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
include README.rst | ||
include LICENSE | ||
include requirements.txt | ||
recursive-include ckanext/keycloak *.html *.json *.js *.less *.css *.mo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
.. You should enable this project on travis-ci.org and coveralls.io to make | ||
these badges work. The necessary Travis and Coverage config files have been | ||
generated for you. | ||
.. .. image:: https://travis-ci.org/etri-sodas/ckanext-keycloak.svg?branch=master | ||
:target: https://travis-ci.org/etri-sodas/ckanext-keycloak | ||
.. .. image:: https://coveralls.io/repos/etri-sodas/ckanext-keycloak/badge.svg | ||
:target: https://coveralls.io/r/etri-sodas/ckanext-keycloak | ||
.. .. image:: https://pypip.in/download/ckanext-keycloak/badge.svg | ||
:target: https://pypi.python.org/pypi/etri-sodas/ckanext-keycloak/ | ||
:alt: Downloads | ||
.. .. image:: https://pypip.in/version/ckanext-keycloak/badge.svg | ||
:target: https://pypi.python.org/pypi/ckanext-keycloak/ | ||
:alt: Latest Version | ||
.. .. image:: https://pypip.in/py_versions/ckanext-keycloak/badge.svg | ||
:target: https://pypi.python.org/pypi/ckanext-keycloak/ | ||
:alt: Supported Python versions | ||
.. .. image:: https://pypip.in/status/ckanext-keycloak/badge.svg | ||
:target: https://pypi.python.org/pypi/ckanext-keycloak/ | ||
:alt: Development Status | ||
.. .. image:: https://pypip.in/license/ckanext-keycloak/badge.svg | ||
:target: https://pypi.python.org/pypi/ckanext-keycloak/ | ||
:alt: License | ||
=========================================================== | ||
ckanext-keycloak - Keycloak authentication extension | ||
=========================================================== | ||
|
||
.. Put a description of your extension here: | ||
What does it do? What features does it have? | ||
Consider including some screenshots or embedding a video! | ||
Ckanext-keycloak is a extension for enalbing the user authentication with Keycloak, an open source software product to allow single sign-on with Identity Management and Access Management aimed at modern applications and services. | ||
|
||
This extension provides an ability to let users use access-token from Keycloak server to access CKAN functions via CKAN REST Api. | ||
|
||
Notes: | ||
|
||
* A new user will be created automatically in ckan database for the corresponding keycloak user if it does not exist. | ||
* Original ckan authentication still works normally with this extension. | ||
|
||
------------ | ||
Requirements | ||
------------ | ||
|
||
This extension was developed and tested under CKAN-2.7.3 and Keycloak-2.5.5 | ||
|
||
------------ | ||
Installation | ||
------------ | ||
|
||
.. Add any additional install steps to the list below. | ||
For example installing any non-Python dependencies or adding any required | ||
config settings. | ||
To install ckanext-keycloak: | ||
|
||
1. Activate your CKAN virtual environment, for example:: | ||
|
||
. /usr/lib/ckan/default/bin/activate | ||
|
||
2. Install the ckanext-keycloak Python package into your virtual environment:: | ||
|
||
pip install ckanext-keycloak | ||
|
||
3. Add ``keycloak`` setting in your CKAN config file (by default the config file is located at ``/etc/ckan/default/production.ini``) as follows:: | ||
|
||
ckan.plugins = keycloak <other-plugins> | ||
ckan.keycloak.authorization_endpoint = | ||
ckan.keycloak.realm = | ||
ckan.keycloak.client_id = | ||
ckan.keycloak.client_secret = | ||
ckan.keycloak.sysadmin_group_name = | ||
ckan.keycloak.profile_group_field = | ||
ckan.keycloak.profile_username_field = | ||
ckan.keycloak.profile_email_field = | ||
ckan.keycloak.profile_fullname_field = | ||
|
||
4. Restart CKAN. For example if you've deployed CKAN with Apache on Ubuntu:: | ||
|
||
sudo service apache2 reload | ||
|
||
|
||
------------------------ | ||
Development Installation | ||
------------------------ | ||
|
||
To install ckanext-keycloak for development, activate your CKAN virtualenv and | ||
do:: | ||
|
||
git clone https://github.com/etri-odp/ckanext-keycloak.git | ||
cd ckanext-keycloak | ||
python setup.py develop | ||
pip install -r dev-requirements.txt | ||
|
||
|
||
----------------- | ||
Running the Tests | ||
----------------- | ||
|
||
To run the tests, do:: | ||
|
||
nosetests --nologcapture --with-pylons=test.ini | ||
|
||
To run the tests and produce a coverage report, first make sure you have | ||
coverage installed in your virtualenv (``pip install coverage``) then run:: | ||
|
||
nosetests --nologcapture --with-pylons=test.ini --with-coverage --cover-package=ckanext.keycloak --cover-inclusive --cover-erase --cover-tests | ||
|
||
|
||
---------------------------------------------- | ||
Registering ckanext-keycloak on PyPI | ||
---------------------------------------------- | ||
|
||
ckanext-keycloak should be availabe on PyPI as | ||
https://pypi.python.org/pypi/ckanext-keycloak. If that link doesn't work, then | ||
you can register the project on PyPI for the first time by following these | ||
steps: | ||
|
||
1. Create a source distribution of the project:: | ||
|
||
python setup.py sdist | ||
|
||
2. Register the project:: | ||
|
||
python setup.py register | ||
|
||
3. Upload the source distribution to PyPI:: | ||
|
||
python setup.py sdist upload | ||
|
||
4. Tag the first release of the project on GitHub with the version number from | ||
the ``setup.py`` file. For example if the version number in ``setup.py`` is | ||
0.0.1 then do:: | ||
|
||
git tag 0.0.1 | ||
git push --tags | ||
|
||
================ | ||
Acknowledgements | ||
================ | ||
|
||
This work was supported by Institute for Information & communications Technology Promotion (IITP) grant funded by the Korea government (MSIT) (No.2017-00253, Development of an Advanced Open Data Distribution Platform based on International Standards) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
#!/bin/bash | ||
set -e | ||
|
||
echo "This is travis-build.bash..." | ||
|
||
echo "Installing the packages that CKAN requires..." | ||
sudo apt-get update -qq | ||
sudo apt-get install postgresql-$PGVERSION solr-jetty libcommons-fileupload-java:amd64=1.2.2-1 | ||
|
||
echo "Installing CKAN and its Python dependencies..." | ||
git clone https://github.com/ckan/ckan | ||
cd ckan | ||
export latest_ckan_release_branch=`git branch --all | grep remotes/origin/release-v | sort -r | sed 's/remotes\/origin\///g' | head -n 1` | ||
echo "CKAN branch: $latest_ckan_release_branch" | ||
git checkout $latest_ckan_release_branch | ||
python setup.py develop | ||
pip install -r requirements.txt --allow-all-external | ||
pip install -r dev-requirements.txt --allow-all-external | ||
cd - | ||
|
||
echo "Creating the PostgreSQL user and database..." | ||
sudo -u postgres psql -c "CREATE USER ckan_default WITH PASSWORD 'pass';" | ||
sudo -u postgres psql -c 'CREATE DATABASE ckan_test WITH OWNER ckan_default;' | ||
|
||
echo "SOLR config..." | ||
# Solr is multicore for tests on ckan master, but it's easier to run tests on | ||
# Travis single-core. See https://github.com/ckan/ckan/issues/2972 | ||
sed -i -e 's/solr_url.*/solr_url = http:\/\/127.0.0.1:8983\/solr/' ckan/test-core.ini | ||
|
||
echo "Initialising the database..." | ||
cd ckan | ||
paster db init -c test-core.ini | ||
cd - | ||
|
||
echo "Installing ckanext-keycloak and its requirements..." | ||
python setup.py develop | ||
pip install -r dev-requirements.txt | ||
|
||
echo "Moving test.ini into a subdir..." | ||
mkdir subdir | ||
mv test.ini subdir | ||
|
||
echo "travis-build.bash is done." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/sh -e | ||
|
||
echo "NO_START=0\nJETTY_HOST=127.0.0.1\nJETTY_PORT=8983\nJAVA_HOME=$JAVA_HOME" | sudo tee /etc/default/jetty | ||
sudo cp ckan/ckan/config/solr/schema.xml /etc/solr/conf/schema.xml | ||
sudo service jetty restart | ||
nosetests --nologcapture --with-pylons=subdir/test.ini --with-coverage --cover-package=ckanext.keycloak --cover-inclusive --cover-erase --cover-tests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# encoding: utf-8 | ||
|
||
# this is a namespace package | ||
try: | ||
import pkg_resources | ||
pkg_resources.declare_namespace(__name__) | ||
except ImportError: | ||
import pkgutil | ||
__path__ = pkgutil.extend_path(__path__, __name__) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import ckan.model as model | ||
import json | ||
import logging | ||
import os | ||
from six.moves.urllib.parse import urljoin | ||
from base64 import b64encode, b64decode | ||
from pylons import config | ||
from keycloak.realm import KeycloakRealm | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
class KeycloakHelper(object): | ||
|
||
def __init__(self): | ||
self.authorization_endpoint = config.get('ckan.keycloak.authorization_endpoint', None) | ||
self.client_id = config.get('ckan.keycloak.client_id', None) | ||
self.client_secret = config.get('ckan.keycloak.client_secret', None) | ||
self.realm = config.get('ckan.keycloak.realm', 'ckan') | ||
self.profile_username_field = config.get('ckan.keycloak.profile_username_field', None) | ||
self.profile_fullname_field = config.get('ckan.keycloak.profile_fullname_field', None) | ||
self.profile_email_field = config.get('ckan.keycloak.profile_email_field', None) | ||
self.profile_group_field = config.get('ckan.keycloak.profile_group_field', None) | ||
self.sysadmin_group_name = config.get('ckan.keycloak.sysadmin_group_name', None) | ||
realm = KeycloakRealm(server_url=self.authorization_endpoint, realm_name=self.realm) | ||
self.oidc_client = realm.open_id_connect(client_id=self.client_id,client_secret=self.client_secret) | ||
|
||
def identify(self, token): | ||
user_token = self.oidc_client.userinfo(token) | ||
user_data = self.oidc_client.decode_token(user_token, '', options={ 'verify_signature': False }) | ||
try : email = user_data[self.profile_email_field] | ||
except : | ||
log.debug("Not Found Email.") | ||
try : user_name = user_data[self.profile_username_field] | ||
except : | ||
log.debug("Not Found User Name.") | ||
|
||
user = model.User.get(user_name) | ||
if user : | ||
return user.name | ||
user = None | ||
users = model.User.by_email(email) | ||
if len(users) == 1: | ||
user = users[0] | ||
if user is None: | ||
user = model.User(email=email) | ||
user.name = user_name | ||
if self.profile_fullname_field and self.profile_fullname_field in user_data: | ||
user.fullname = user_data[self.profile_fullname_field] | ||
if self.profile_group_field and self.profile_group_field in user_data: | ||
if self.sysadmin_group_name and self.sysadmin_group_name in user_data[self.profile_group_field]: | ||
user.sysadmin = True | ||
else: | ||
user.sysadmin = False | ||
model.Session.add(user) | ||
model.Session.commit() | ||
model.Session.remove() | ||
log.info('Add keycloak user into ckan database: %s'%user) | ||
return user.name |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
from __future__ import unicode_literals | ||
|
||
import logging | ||
import helper | ||
|
||
from ckan import plugins | ||
from ckan.plugins import toolkit | ||
from pylons import config | ||
from urlparse import urlparse | ||
|
||
import ckan.model as model | ||
from ckan.lib.helpers import redirect_to as redirect | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
class KeycloakPlugin(plugins.SingletonPlugin): | ||
plugins.implements(plugins.IAuthenticator, inherit=True) | ||
plugins.implements(plugins.IConfigurer) | ||
|
||
def __init__(self, name=None): | ||
self.keycloak_helper = helper.KeycloakHelper() | ||
|
||
def update_config(self,config): | ||
return None | ||
|
||
def configure(self, config): | ||
required_keys = ( | ||
'ckan.keycloak.authorization_endpoint', | ||
'ckan.keycloak.client_id', | ||
'ckan.keycloak.client_secret', | ||
'ckan.keycloak.realm', | ||
'ckan.keycloak.profile_username_field', | ||
'ckan.keycloak.profile_fullname_field', | ||
'ckan.keycloak.profile_email_field', | ||
'ckan.keycloak.profile_group_field', | ||
'ckan.keycloak.sysadmin_group_name' | ||
) | ||
for key in required_keys: | ||
if config.get(key) is None: | ||
raise RuntimeError('Required configuration option {0} not found.'.format(key)) | ||
|
||
def identify(self): | ||
if not getattr(toolkit.c, u'user', None): | ||
self._identify_user_default() | ||
if toolkit.c.user and not getattr(toolkit.c, u'userobj', None): | ||
toolkit.c.userobj = model.User.by_name(toolkit.c.user) | ||
|
||
def _identify_user_default(self): | ||
toolkit.c.user = toolkit.request.environ.get(u'REMOTE_USER', u'') | ||
if toolkit.c.user: | ||
toolkit.c.user = toolkit.c.user.decode(u'utf8') | ||
toolkit.c.userobj = model.User.by_name(toolkit.c.user) | ||
if toolkit.c.userobj is None or not toolkit.c.userobj.is_active(): | ||
ev = request.environ | ||
if u'repoze.who.plugins' in ev: | ||
pth = getattr(ev[u'repoze.who.plugins'][u'friendlyform'], | ||
u'logout_handler_path') | ||
redirect(pth) | ||
else: | ||
toolkit.c.userobj = self._get_user_info() | ||
if 'name' in dir(toolkit.c.userobj) : | ||
toolkit.c.user = toolkit.c.userobj.name | ||
toolkit.c.author = toolkit.c.userobj.name | ||
log.debug('toolkit.c.userobj.id :' + toolkit.c.userobj.id) | ||
log.debug('toolkit.c.userobj.name :' + toolkit.c.userobj.name) | ||
|
||
def _get_user_info(self): | ||
authorizationKey = toolkit.request.headers.get(u'Authorization', u'') | ||
if not authorizationKey: | ||
authorizationKey = toolkit.request.environ.get(u'Authorization', u'') | ||
if not authorizationKey: | ||
authorizationKey = toolkit.request.environ.get(u'HTTP_AUTHORIZATION', u'') | ||
if not authorizationKey: | ||
authorizationKey = toolkit.request.environ.get(u'Authorization', u'') | ||
if u' ' in authorizationKey: | ||
authorizationKey = u'' | ||
if not authorizationKey: | ||
return None | ||
authorizationKey = authorizationKey.decode(u'utf8', u'ignore') | ||
user = None | ||
query = model.Session.query(model.User) | ||
user = query.filter_by(apikey=authorizationKey).first() | ||
if user == None : | ||
try: | ||
user = self.keycloak_helper.identify(authorizationKey) | ||
user = query.filter_by(name=user).first() | ||
except Exception as e: | ||
log.error( e.message) | ||
return user |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
-r requirements.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
python-keycloak-client==0.1.3 |
Oops, something went wrong.