Project

General

Profile

« Previous | Next » 

Revision c8204b73

Added by Serghei Mihai over 9 years ago

login function redirecting to idp and callback view added

View differences:

ckanext/ozwillo_pyoidc/conf.py
1
PORT = 8666
2
#BASE = "https://lingon.ladok.umu.se:" + str(PORT) + "/"
3
BASE = "http://ckan.dev.entrouvert.org"
4

  
5

  
6
# If BASE is https these has to be specified
7
SERVER_CERT = "certs/server.crt"
8
SERVER_KEY = "certs/server.key"
9
CA_BUNDLE = None
10

  
11
VERIFY_SSL = False
12

  
13
# information used when registering the client, this may be the same for all OPs
14

  
15
ME = {
16
    "application_type": "web",
17
    "application_name": "idpproxy",
18
    "contacts": ["ops@example.com"],
19
    "redirect_uris": ["%sauthz_cb" % BASE],
20
    "post_logout_redirect_uris": ["%slogout" % BASE],
21
    "response_types": ["code"]
22
}
23

  
24
BEHAVIOUR = {
25
    "response_type": "code",
26
    "scope": ["openid", "profile", "email", "address", "phone"],
27
}
28

  
29
ACR_VALUES = ["SAML"]
30

  
31
# The keys in this dictionary are the OPs short userfriendly name
32
# not the issuer (iss) name.
33

  
34
CLIENTS = {
35
    # The ones that support webfinger, OP discovery and client registration
36
    # This is the default, any client that is not listed here is expected to
37
    # support dynamic discovery and registration.
38
    # Supports OP information lookup but not client registration
39
    "ozwillo": {
40
        "srv_discovery_url": "https://accounts.ozwillo-preprod.eu/",
41
        "client_registration": {
42
            "client_id": "64a1002e-3149-4e1d-a374-6ff08b79dae6",
43
            "client_secret": "RCjT6YTN7CY0l8UAbGUOtSOrAKZKW4XXzK1ZWi7u0nE",
44
            "redirect_uris": ["https://ckan.dev.entrouvert.org/openid/callback"],
45
        },
46
        "behaviour": {
47
            "response_type": "code",
48
            "scope": ["openid", "profile"]
49
        },
50
        "allow": {
51
            "issuer_mismatch": True
52
        }
53
    }
54
}
ckanext/ozwillo_pyoidc/oidc.py
1
from oic.utils.http_util import Redirect
2
from oic.exception import MissingAttribute
3
from oic import oic
4
from oic.oauth2 import rndstr, ErrorResponse
5
from oic.oic import ProviderConfigurationResponse, AuthorizationResponse
6
from oic.oic import RegistrationResponse
7
from oic.oic import AuthorizationRequest
8
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
9

  
10
__author__ = 'roland'
11

  
12
import logging
13

  
14
logger = logging.getLogger(__name__)
15

  
16

  
17
class OIDCError(Exception):
18
    pass
19

  
20

  
21
class Client(oic.Client):
22
    def __init__(self, client_id=None, ca_certs=None,
23
                 client_prefs=None, client_authn_method=None, keyjar=None,
24
                 verify_ssl=True, behaviour=None):
25
        oic.Client.__init__(self, client_id, ca_certs, client_prefs,
26
                            client_authn_method, keyjar, verify_ssl)
27
        if behaviour:
28
            self.behaviour = behaviour
29

  
30
    def create_authn_request(self, session, acr_value=None):
31
        session["state"] = rndstr()
32
        session["nonce"] = rndstr()
33
        request_args = {
34
            "response_type": self.behaviour["response_type"],
35
            "scope": self.behaviour["scope"],
36
            "state": session["state"],
37
            # "nonce": session["nonce"],
38
            "redirect_uri": self.registration_response["redirect_uris"][0]
39
        }
40

  
41
        if acr_value is not None:
42
            request_args["acr_values"] = acr_value
43

  
44
        cis = self.construct_AuthorizationRequest(request_args=request_args)
45
        logger.debug("request: %s" % cis)
46

  
47
        url, body, ht_args, cis = self.uri_and_body(AuthorizationRequest, cis,
48
                                                    method="GET",
49
                                                    request_args=request_args)
50

  
51
        logger.debug("body: %s" % body)
52
        logger.info("URL: %s" % url)
53
        logger.debug("ht_args: %s" % ht_args)
54

  
55
        return str(url), ht_args
56

  
57
    def callback(self, response):
58
        """
59
        This is the method that should be called when an AuthN response has been
60
        received from the OP.
61

  
62
        :param response: The URL returned by the OP
63
        :return:
64
        """
65
        authresp = self.parse_response(AuthorizationResponse, response,
66
                                       sformat="dict", keyjar=self.keyjar)
67

  
68
        if isinstance(authresp, ErrorResponse):
69
            return OIDCError("Access denied")
70

  
71
        try:
72
            self.id_token[authresp["state"]] = authresp["id_token"]
73
        except KeyError:
74
            pass
75

  
76
        if self.behaviour["response_type"] == "code":
77
            # get the access token
78
            try:
79
                args = {
80
                    "grant_type": "authorization_code",
81
                    "code": authresp["code"],
82
                    "redirect_uri": self.registration_response[
83
                        "redirect_uris"][0],
84
                    "client_id": self.client_id,
85
                    "client_secret": self.client_secret
86
                }
87

  
88
                atresp = self.do_access_token_request(
89
                    scope="openid", state=authresp["state"], request_args=args,
90
                    authn_method=self.registration_response["token_endpoint_auth_method"])
91
            except Exception as err:
92
                logger.error("%s" % err)
93
                raise
94

  
95
            if isinstance(atresp, ErrorResponse):
96
                raise OIDCError("Invalid response %s." % atresp["error"])
97

  
98
        inforesp = self.do_user_info_request(state=authresp["state"])
99

  
100
        if isinstance(inforesp, ErrorResponse):
101
            raise OIDCError("Invalid response %s." % inforesp["error"])
102

  
103
        userinfo = inforesp.to_dict()
104

  
105
        logger.debug("UserInfo: %s" % inforesp)
106

  
107
        return userinfo
108

  
109

  
110
class OIDCClients(object):
111
    def __init__(self, config):
112
        """
113

  
114
        :param config: Imported configuration module
115
        :return:
116
        """
117
        self.client = {}
118
        self.client_cls = Client
119
        self.config = config
120

  
121
        for key, val in config.CLIENTS.items():
122
            if key == "":
123
                continue
124
            else:
125
                self.client[key] = self.create_client(**val)
126

  
127
    def create_client(self, userid="", **kwargs):
128
        """
129
        Do an instantiation of a client instance
130

  
131
        :param userid: An identifier of the user
132
        :param: Keyword arguments
133
            Keys are ["srv_discovery_url", "client_info", "client_registration",
134
            "provider_info"]
135
        :return: client instance
136
        """
137

  
138
        _key_set = set(kwargs.keys())
139
        args = {}
140
        for param in ["verify_ssl"]:
141
            try:
142
                args[param] = kwargs[param]
143
            except KeyError:
144
                pass
145
            else:
146
                _key_set.discard(param)
147

  
148
        client = self.client_cls(client_authn_method=CLIENT_AUTHN_METHOD,
149
                                 behaviour=kwargs["behaviour"], verify_ssl=self.config.VERIFY_SSL, **args)
150

  
151
        # The behaviour parameter is not significant for the election process
152
        _key_set.discard("behaviour")
153
        for param in ["allow"]:
154
            try:
155
                setattr(client, param, kwargs[param])
156
            except KeyError:
157
                pass
158
            else:
159
                _key_set.discard(param)
160

  
161
        if _key_set == set(["client_info"]):  # Everything dynamic
162
            # There has to be a userid
163
            if not userid:
164
                raise MissingAttribute("Missing userid specification")
165

  
166
            # Find the service that provides information about the OP
167
            issuer = client.wf.discovery_query(userid)
168
            # Gather OP information
169
            _ = client.provider_config(issuer)
170
            # register the client
171
            _ = client.register(client.provider_info["registration_endpoint"],
172
                                **kwargs["client_info"])
173
        elif _key_set == set(["client_info", "srv_discovery_url"]):
174
            # Ship the webfinger part
175
            # Gather OP information
176
            _ = client.provider_config(kwargs["srv_discovery_url"])
177
            # register the client
178
            _ = client.register(client.provider_info["registration_endpoint"],
179
                                **kwargs["client_info"])
180
        elif _key_set == set(["provider_info", "client_info"]):
181
            client.handle_provider_config(
182
                ProviderConfigurationResponse(**kwargs["provider_info"]),
183
                kwargs["provider_info"]["issuer"])
184
            _ = client.register(client.provider_info["registration_endpoint"],
185
                                **kwargs["client_info"])
186
        elif _key_set == set(["provider_info", "client_registration"]):
187
            client.handle_provider_config(
188
                ProviderConfigurationResponse(**kwargs["provider_info"]),
189
                kwargs["provider_info"]["issuer"])
190
            client.store_registration_info(RegistrationResponse(
191
                **kwargs["client_registration"]))
192
        elif _key_set == set(["srv_discovery_url", "client_registration"]):
193
            _ = client.provider_config(kwargs["srv_discovery_url"])
194
            client.store_registration_info(RegistrationResponse(
195
                **kwargs["client_registration"]))
196
        else:
197
            raise Exception("Configuration error ?")
198

  
199
        return client
200

  
201
    def dynamic_client(self, userid):
202
        client = self.client_cls(client_authn_method=CLIENT_AUTHN_METHOD,
203
                                 verify_ssl=self.config.VERIFY_SSL)
204

  
205
        issuer = client.wf.discovery_query(userid)
206
        if issuer in self.client:
207
            return self.client[issuer]
208
        else:
209
            # Gather OP information
210
            _pcr = client.provider_config(issuer)
211
            # register the client
212
            _ = client.register(_pcr["registration_endpoint"],
213
                                **self.config.CLIENTS[""]["client_info"])
214
            try:
215
                client.behaviour.update(**self.config.CLIENTS[""]["behaviour"])
216
            except KeyError:
217
                pass
218

  
219
            self.client[issuer] = client
220
            return client
221

  
222
    def __getitem__(self, item):
223
        """
224
        Given a service or user identifier return a suitable client
225
        :param item:
226
        :return:
227
        """
228
        try:
229
            return self.client[item]
230
        except KeyError:
231
            return self.dynamic_client(item)
232

  
233
    def keys(self):
234
        return self.client.keys()
ckanext/ozwillo_pyoidc/plugin.py
1
import logging
2

  
1 3
import ckan.plugins as plugins
2 4
import ckan.plugins.toolkit as toolkit
5
from ckan.common import session
6
import ckan.lib.base as base
7

  
8
from pylons import config, request
9

  
10
from oidc import OIDCClients
11

  
12
import conf
13

  
14
from oic.oic import Client, AuthorizationRequest
15
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
16

  
17
plugin_config_prefix = 'ckanext.ozwillo_pyoidc.'
18

  
19
log = logging.getLogger(__name__)
20

  
21
Client = OIDCClients(conf)['ozwillo']
3 22

  
23
def openid_callback(context, data):
24
    print context
25
    print data
4 26

  
5 27
class OzwilloPyoidcPlugin(plugins.SingletonPlugin):
6 28
    plugins.implements(plugins.IConfigurer)
29
    plugins.implements(plugins.IRoutes)
30
    plugins.implements(plugins.IAuthenticator, inherit=True)
7 31

  
8
    # IConfigurer
32
    def __init__(self, name=None):
33
        self.client = Client
34

  
35
    def before_map(self, map):
36
        map.redirect('/organization/{id:.*}/sso', '/user/login')
37
        map.connect('/openid/callback',
38
                    controller='ckanext.ozwillo_pyoidc.plugin:OpenidController',
39
                    action='openid_callback')
40
        return map
41

  
42
    def after_map(self, map):
43
        return map
44

  
45
    def identify(self):
46
        # must set toolkit.c.user
47
        pass
48

  
49
    def login(self):
50
        url, ht_args = self.client.create_authn_request(session, conf.ACR_VALUES)
51
        if ht_args:
52
            toolkit.request.headers.update(ht_args)
53
        toolkit.redirect_to(url)
54

  
55
    def logout(self):
56
        # revoke all auth tokens
57
        # redirect to logout in ozwillo
58
        revoke_endpoint = 'https://portal.ozwillo-preprod.eu/a/revoke'
59
        toolkit.redirect('/user/_logout')
9 60

  
10 61
    def update_config(self, config_):
11 62
        toolkit.add_template_directory(config_, 'templates')
12 63
        toolkit.add_public_directory(config_, 'public')
13 64
        toolkit.add_resource('fanstatic', 'ozwillo_pyoidc')
65

  
66
class OpenidController(base.BaseController):
67

  
68
    def openid_callback(self):
69
        userinfo = Client.callback(request.GET)
70
        return "userinfo: %s" % userinfo

Also available in: Unified diff