login function redirecting to idp and callback view added

This commit is contained in:
Serghei Mihai 2015-02-04 15:24:41 +01:00
parent b169c79741
commit c8204b7344
3 changed files with 346 additions and 1 deletions

View File

@ -0,0 +1,54 @@
PORT = 8666
#BASE = "https://lingon.ladok.umu.se:" + str(PORT) + "/"
BASE = "http://ckan.dev.entrouvert.org"
# If BASE is https these has to be specified
SERVER_CERT = "certs/server.crt"
SERVER_KEY = "certs/server.key"
CA_BUNDLE = None
VERIFY_SSL = False
# information used when registering the client, this may be the same for all OPs
ME = {
"application_type": "web",
"application_name": "idpproxy",
"contacts": ["ops@example.com"],
"redirect_uris": ["%sauthz_cb" % BASE],
"post_logout_redirect_uris": ["%slogout" % BASE],
"response_types": ["code"]
}
BEHAVIOUR = {
"response_type": "code",
"scope": ["openid", "profile", "email", "address", "phone"],
}
ACR_VALUES = ["SAML"]
# The keys in this dictionary are the OPs short userfriendly name
# not the issuer (iss) name.
CLIENTS = {
# The ones that support webfinger, OP discovery and client registration
# This is the default, any client that is not listed here is expected to
# support dynamic discovery and registration.
# Supports OP information lookup but not client registration
"ozwillo": {
"srv_discovery_url": "https://accounts.ozwillo-preprod.eu/",
"client_registration": {
"client_id": "64a1002e-3149-4e1d-a374-6ff08b79dae6",
"client_secret": "RCjT6YTN7CY0l8UAbGUOtSOrAKZKW4XXzK1ZWi7u0nE",
"redirect_uris": ["https://ckan.dev.entrouvert.org/openid/callback"],
},
"behaviour": {
"response_type": "code",
"scope": ["openid", "profile"]
},
"allow": {
"issuer_mismatch": True
}
}
}

234
ckanext/ozwillo_pyoidc/oidc.py Executable file
View File

@ -0,0 +1,234 @@
from oic.utils.http_util import Redirect
from oic.exception import MissingAttribute
from oic import oic
from oic.oauth2 import rndstr, ErrorResponse
from oic.oic import ProviderConfigurationResponse, AuthorizationResponse
from oic.oic import RegistrationResponse
from oic.oic import AuthorizationRequest
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
__author__ = 'roland'
import logging
logger = logging.getLogger(__name__)
class OIDCError(Exception):
pass
class Client(oic.Client):
def __init__(self, client_id=None, ca_certs=None,
client_prefs=None, client_authn_method=None, keyjar=None,
verify_ssl=True, behaviour=None):
oic.Client.__init__(self, client_id, ca_certs, client_prefs,
client_authn_method, keyjar, verify_ssl)
if behaviour:
self.behaviour = behaviour
def create_authn_request(self, session, acr_value=None):
session["state"] = rndstr()
session["nonce"] = rndstr()
request_args = {
"response_type": self.behaviour["response_type"],
"scope": self.behaviour["scope"],
"state": session["state"],
# "nonce": session["nonce"],
"redirect_uri": self.registration_response["redirect_uris"][0]
}
if acr_value is not None:
request_args["acr_values"] = acr_value
cis = self.construct_AuthorizationRequest(request_args=request_args)
logger.debug("request: %s" % cis)
url, body, ht_args, cis = self.uri_and_body(AuthorizationRequest, cis,
method="GET",
request_args=request_args)
logger.debug("body: %s" % body)
logger.info("URL: %s" % url)
logger.debug("ht_args: %s" % ht_args)
return str(url), ht_args
def callback(self, response):
"""
This is the method that should be called when an AuthN response has been
received from the OP.
:param response: The URL returned by the OP
:return:
"""
authresp = self.parse_response(AuthorizationResponse, response,
sformat="dict", keyjar=self.keyjar)
if isinstance(authresp, ErrorResponse):
return OIDCError("Access denied")
try:
self.id_token[authresp["state"]] = authresp["id_token"]
except KeyError:
pass
if self.behaviour["response_type"] == "code":
# get the access token
try:
args = {
"grant_type": "authorization_code",
"code": authresp["code"],
"redirect_uri": self.registration_response[
"redirect_uris"][0],
"client_id": self.client_id,
"client_secret": self.client_secret
}
atresp = self.do_access_token_request(
scope="openid", state=authresp["state"], request_args=args,
authn_method=self.registration_response["token_endpoint_auth_method"])
except Exception as err:
logger.error("%s" % err)
raise
if isinstance(atresp, ErrorResponse):
raise OIDCError("Invalid response %s." % atresp["error"])
inforesp = self.do_user_info_request(state=authresp["state"])
if isinstance(inforesp, ErrorResponse):
raise OIDCError("Invalid response %s." % inforesp["error"])
userinfo = inforesp.to_dict()
logger.debug("UserInfo: %s" % inforesp)
return userinfo
class OIDCClients(object):
def __init__(self, config):
"""
:param config: Imported configuration module
:return:
"""
self.client = {}
self.client_cls = Client
self.config = config
for key, val in config.CLIENTS.items():
if key == "":
continue
else:
self.client[key] = self.create_client(**val)
def create_client(self, userid="", **kwargs):
"""
Do an instantiation of a client instance
:param userid: An identifier of the user
:param: Keyword arguments
Keys are ["srv_discovery_url", "client_info", "client_registration",
"provider_info"]
:return: client instance
"""
_key_set = set(kwargs.keys())
args = {}
for param in ["verify_ssl"]:
try:
args[param] = kwargs[param]
except KeyError:
pass
else:
_key_set.discard(param)
client = self.client_cls(client_authn_method=CLIENT_AUTHN_METHOD,
behaviour=kwargs["behaviour"], verify_ssl=self.config.VERIFY_SSL, **args)
# The behaviour parameter is not significant for the election process
_key_set.discard("behaviour")
for param in ["allow"]:
try:
setattr(client, param, kwargs[param])
except KeyError:
pass
else:
_key_set.discard(param)
if _key_set == set(["client_info"]): # Everything dynamic
# There has to be a userid
if not userid:
raise MissingAttribute("Missing userid specification")
# Find the service that provides information about the OP
issuer = client.wf.discovery_query(userid)
# Gather OP information
_ = client.provider_config(issuer)
# register the client
_ = client.register(client.provider_info["registration_endpoint"],
**kwargs["client_info"])
elif _key_set == set(["client_info", "srv_discovery_url"]):
# Ship the webfinger part
# Gather OP information
_ = client.provider_config(kwargs["srv_discovery_url"])
# register the client
_ = client.register(client.provider_info["registration_endpoint"],
**kwargs["client_info"])
elif _key_set == set(["provider_info", "client_info"]):
client.handle_provider_config(
ProviderConfigurationResponse(**kwargs["provider_info"]),
kwargs["provider_info"]["issuer"])
_ = client.register(client.provider_info["registration_endpoint"],
**kwargs["client_info"])
elif _key_set == set(["provider_info", "client_registration"]):
client.handle_provider_config(
ProviderConfigurationResponse(**kwargs["provider_info"]),
kwargs["provider_info"]["issuer"])
client.store_registration_info(RegistrationResponse(
**kwargs["client_registration"]))
elif _key_set == set(["srv_discovery_url", "client_registration"]):
_ = client.provider_config(kwargs["srv_discovery_url"])
client.store_registration_info(RegistrationResponse(
**kwargs["client_registration"]))
else:
raise Exception("Configuration error ?")
return client
def dynamic_client(self, userid):
client = self.client_cls(client_authn_method=CLIENT_AUTHN_METHOD,
verify_ssl=self.config.VERIFY_SSL)
issuer = client.wf.discovery_query(userid)
if issuer in self.client:
return self.client[issuer]
else:
# Gather OP information
_pcr = client.provider_config(issuer)
# register the client
_ = client.register(_pcr["registration_endpoint"],
**self.config.CLIENTS[""]["client_info"])
try:
client.behaviour.update(**self.config.CLIENTS[""]["behaviour"])
except KeyError:
pass
self.client[issuer] = client
return client
def __getitem__(self, item):
"""
Given a service or user identifier return a suitable client
:param item:
:return:
"""
try:
return self.client[item]
except KeyError:
return self.dynamic_client(item)
def keys(self):
return self.client.keys()

59
ckanext/ozwillo_pyoidc/plugin.py Normal file → Executable file
View File

@ -1,13 +1,70 @@
import logging
import ckan.plugins as plugins
import ckan.plugins.toolkit as toolkit
from ckan.common import session
import ckan.lib.base as base
from pylons import config, request
from oidc import OIDCClients
import conf
from oic.oic import Client, AuthorizationRequest
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
plugin_config_prefix = 'ckanext.ozwillo_pyoidc.'
log = logging.getLogger(__name__)
Client = OIDCClients(conf)['ozwillo']
def openid_callback(context, data):
print context
print data
class OzwilloPyoidcPlugin(plugins.SingletonPlugin):
plugins.implements(plugins.IConfigurer)
plugins.implements(plugins.IRoutes)
plugins.implements(plugins.IAuthenticator, inherit=True)
# IConfigurer
def __init__(self, name=None):
self.client = Client
def before_map(self, map):
map.redirect('/organization/{id:.*}/sso', '/user/login')
map.connect('/openid/callback',
controller='ckanext.ozwillo_pyoidc.plugin:OpenidController',
action='openid_callback')
return map
def after_map(self, map):
return map
def identify(self):
# must set toolkit.c.user
pass
def login(self):
url, ht_args = self.client.create_authn_request(session, conf.ACR_VALUES)
if ht_args:
toolkit.request.headers.update(ht_args)
toolkit.redirect_to(url)
def logout(self):
# revoke all auth tokens
# redirect to logout in ozwillo
revoke_endpoint = 'https://portal.ozwillo-preprod.eu/a/revoke'
toolkit.redirect('/user/_logout')
def update_config(self, config_):
toolkit.add_template_directory(config_, 'templates')
toolkit.add_public_directory(config_, 'public')
toolkit.add_resource('fanstatic', 'ozwillo_pyoidc')
class OpenidController(base.BaseController):
def openid_callback(self):
userinfo = Client.callback(request.GET)
return "userinfo: %s" % userinfo