1
1
"""
2
- OIDC backend module.
2
+ OIDC/OAuth2 backend module.
3
3
"""
4
4
import logging
5
5
from datetime import datetime
6
6
7
+ from idpyoidc .client .oauth2 .stand_alone_client import StandAloneClient
7
8
from idpyoidc .server .user_authn .authn_context import UNSPECIFIED
8
9
9
10
import satosa .logging_util as lu
10
11
from satosa .backends .base import BackendModule
11
- from satosa .exception import SATOSAAuthenticationError
12
12
from satosa .internal import AuthenticationInformation
13
13
from satosa .internal import InternalData
14
+ from ..exception import SATOSAAuthenticationError
15
+ from ..response import Redirect
14
16
15
17
logger = logging .getLogger (__name__ )
16
18
17
- """
18
- OIDC/OAuth2 backend module.
19
- """
20
- from idpyoidc .client .oauth2 .stand_alone_client import StandAloneClient
21
-
22
19
23
20
class IdpyOIDCBackend (BackendModule ):
24
21
"""
25
22
Backend module for OIDC and OAuth 2.0, can be directly used.
26
23
"""
27
24
28
- def __init__ (self , outgoing , internal_attributes , config , base_url , name ):
25
+ def __init__ (self , auth_callback_func , internal_attributes , config , base_url , name ):
29
26
"""
30
- :type outgoing:
27
+ OIDC backend module.
28
+ :param auth_callback_func: Callback should be called by the module after the authorization
29
+ in the backend is done.
30
+ :param internal_attributes: Mapping dictionary between SATOSA internal attribute names and
31
+ the names returned by underlying IdP's/OP's as well as what attributes the calling SP's and
32
+ RP's expects namevice.
33
+ :param config: Configuration parameters for the module.
34
+ :param base_url: base url of the service
35
+ :param name: name of the plugin
36
+
37
+ :type auth_callback_func:
31
38
(satosa.context.Context, satosa.internal.InternalData) -> satosa.response.Response
32
- :type internal_attributes: dict[str , dict[str, list[ str] | str]]
33
- :type config: dict[str, Any ]
39
+ :type internal_attributes: dict[string , dict[str, str | list[ str] ]]
40
+ :type config: dict[str, dict[str, str] | list[str] ]
34
41
:type base_url: str
35
42
:type name: str
36
-
37
- :param outgoing: Callback should be called by the module after
38
- the authorization in the backend is done.
39
- :param internal_attributes: Internal attribute map
40
- :param config: The module config
41
- :param base_url: base url of the service
42
- :param name: name of the plugin
43
43
"""
44
- super ().__init__ (outgoing , internal_attributes , base_url , name )
45
-
46
- self .client = StandAloneClient (config = config ["client_config" ],
47
- client_type = config ["client_config" ]['client_type' ])
48
- # Deal with provider discovery and client registration
44
+ super ().__init__ (auth_callback_func , internal_attributes , base_url , name )
45
+ # self.auth_callback_func = auth_callback_func
46
+ # self.config = config
47
+ self .client = StandAloneClient (config = config ["client" ], client_type = "oidc" )
49
48
self .client .do_provider_info ()
50
49
self .client .do_client_registration ()
51
50
@@ -57,7 +56,8 @@ def start_auth(self, context, internal_request):
57
56
:type internal_request: satosa.internal.InternalData
58
57
:rtype satosa.response.Redirect
59
58
"""
60
- return self .client .init_authorization ()
59
+ login_url = self .client .init_authorization ()
60
+ return Redirect (login_url )
61
61
62
62
def register_endpoints (self ):
63
63
"""
@@ -67,8 +67,56 @@ def register_endpoints(self):
67
67
:rtype: Sequence[(str, Callable[[satosa.context.Context], satosa.response.Response]]
68
68
:return: A list that can be used to map the request to SATOSA to this endpoint.
69
69
"""
70
+ return self .client .context .claims .get_usage ('redirect_uris' )
71
+
72
+ def response_endpoint (self , context , * args ):
73
+ """
74
+ Handles the authentication response from the OP.
75
+ :type context: satosa.context.Context
76
+ :type args: Any
77
+ :rtype: satosa.response.Response
70
78
71
- return self .client .context .claims .get_usage ('authorization_endpoint' )
79
+ :param context: SATOSA context
80
+ :param args: None
81
+ :return:
82
+ """
83
+
84
+ _info = self .client .finalize (context .request )
85
+ self ._check_error_response (_info , context )
86
+ userinfo = _info .get ('userinfo' )
87
+ id_token = _info .get ('id_token' )
88
+
89
+ if not id_token and not userinfo :
90
+ msg = "No id_token or userinfo, nothing to do.."
91
+ logline = lu .LOG_FMT .format (id = lu .get_session_id (context .state ), message = msg )
92
+ logger .error (logline )
93
+ raise SATOSAAuthenticationError (context .state , "No user info available." )
94
+
95
+ all_user_claims = dict (list (userinfo .items ()) + list (id_token .items ()))
96
+ msg = "UserInfo: {}" .format (all_user_claims )
97
+ logline = lu .LOG_FMT .format (id = lu .get_session_id (context .state ), message = msg )
98
+ logger .debug (logline )
99
+ internal_resp = self ._translate_response (all_user_claims , _info ["issuer" ])
100
+ return self .auth_callback_func (context , internal_resp )
101
+
102
+ def _translate_response (self , response , issuer ):
103
+ """
104
+ Translates oidc response to SATOSA internal response.
105
+ :type response: dict[str, str]
106
+ :type issuer: str
107
+ :type subject_type: str
108
+ :rtype: InternalData
109
+
110
+ :param response: Dictioary with attribute name as key.
111
+ :param issuer: The oidc op that gave the repsonse.
112
+ :param subject_type: public or pairwise according to oidc standard.
113
+ :return: A SATOSA internal response.
114
+ """
115
+ auth_info = AuthenticationInformation (UNSPECIFIED , str (datetime .now ()), issuer )
116
+ internal_resp = InternalData (auth_info = auth_info )
117
+ internal_resp .attributes = self .converter .to_internal ("openid" , response )
118
+ internal_resp .subject_id = response ["sub" ]
119
+ return internal_resp
72
120
73
121
def _check_error_response (self , response , context ):
74
122
"""
@@ -86,46 +134,3 @@ def _check_error_response(self, response, context):
86
134
logline = lu .LOG_FMT .format (id = lu .get_session_id (context .state ), message = msg )
87
135
logger .debug (logline )
88
136
raise SATOSAAuthenticationError (context .state , "Access denied" )
89
-
90
- def _authn_response (self , context ):
91
- """
92
- Handles the authentication response from the AS.
93
-
94
- :type context: satosa.context.Context
95
- :rtype: satosa.response.Response
96
- :param context: The context in SATOSA
97
- :return: A SATOSA response. This method is only responsible to call the callback function
98
- which generates the Response object.
99
- """
100
-
101
- _info = self .client .finalize (context .request )
102
- self ._check_error_response (_info , context )
103
-
104
- try :
105
- auth_info = self .auth_info (context .request )
106
- except NotImplementedError :
107
- auth_info = AuthenticationInformation (auth_class_ref = UNSPECIFIED ,
108
- timestamp = str (datetime .now ()),
109
- issuer = _info ["issuer" ])
110
-
111
- attributes = self .converter .to_internal (
112
- self .client .client_type , _info ['userinfo' ],
113
- )
114
-
115
- internal_response = InternalData (
116
- auth_info = auth_info ,
117
- attributes = attributes ,
118
- subject_id = _info ['userinfo' ]['sub' ]
119
- )
120
- return internal_response
121
-
122
- def auth_info (self , request ):
123
- """
124
- Creates the SATOSA authentication information object.
125
- :type request: dict[str, str]
126
- :rtype: AuthenticationInformation
127
-
128
- :param request: The request parameters in the authentication response sent by the AS.
129
- :return: How, who and when the authentication took place.
130
- """
131
- raise NotImplementedError ("Method 'auth_info' must be implemented in the subclass!" )
0 commit comments