commit c8204b7344e6c3fa5926a5b96010b21ef827efca
Author: Serghei Mihai <smihai@ckan.dev.entrouvert.org>
Date:   Wed Feb 4 15:24:41 2015 +0100

    login function redirecting to idp and callback view added

diff --git a/ckanext/ozwillo_pyoidc/conf.py b/ckanext/ozwillo_pyoidc/conf.py
new file mode 100644
index 0000000..80a8a84
--- /dev/null
+++ b/ckanext/ozwillo_pyoidc/conf.py
@@ -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
+        }
+    }
+}
diff --git a/ckanext/ozwillo_pyoidc/oidc.py b/ckanext/ozwillo_pyoidc/oidc.py
new file mode 100755
index 0000000..412bdf0
--- /dev/null
+++ b/ckanext/ozwillo_pyoidc/oidc.py
@@ -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()
diff --git a/ckanext/ozwillo_pyoidc/plugin.py b/ckanext/ozwillo_pyoidc/plugin.py
old mode 100644
new mode 100755
index 46b7ad4..2c0cf4d
--- a/ckanext/ozwillo_pyoidc/plugin.py
+++ b/ckanext/ozwillo_pyoidc/plugin.py
@@ -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
