Skip to content

Commit 47638a7

Browse files
rohec00kiemon5ter
authored andcommitted
New idpyoidc based OAuth2/OIDC backend
1 parent 014e121 commit 47638a7

File tree

1 file changed

+124
-0
lines changed

1 file changed

+124
-0
lines changed

src/satosa/backends/idpy_oidc.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""
2+
OIDC backend module.
3+
"""
4+
import logging
5+
from datetime import datetime
6+
7+
from idpyoidc.server.user_authn.authn_context import UNSPECIFIED
8+
9+
from satosa.backends.base import BackendModule
10+
from satosa.internal import AuthenticationInformation
11+
from satosa.internal import InternalData
12+
13+
logger = logging.getLogger(__name__)
14+
15+
"""
16+
OIDC/OAuth2 backend module.
17+
"""
18+
from idpyoidc.client.oauth2.stand_alone_client import StandAloneClient
19+
20+
21+
class IdpyOIDCBackend(BackendModule):
22+
"""
23+
Backend module for OIDC and OAuth 2.0, can be directly used.
24+
"""
25+
26+
def __init__(self,
27+
outgoing,
28+
internal_attributes,
29+
config,
30+
base_url,
31+
name,
32+
external_type,
33+
user_id_attr
34+
):
35+
"""
36+
:param outgoing: Callback should be called by the module after the authorization in the
37+
backend is done.
38+
:param internal_attributes: Mapping dictionary between SATOSA internal attribute names and
39+
the names returned by underlying IdP's/OP's as well as what attributes the calling SP's and
40+
RP's expects namevice.
41+
:param config: Configuration parameters for the module.
42+
:param base_url: base url of the service
43+
:param name: name of the plugin
44+
:param external_type: The name for this module in the internal attributes.
45+
46+
:type outgoing:
47+
(satosa.context.Context, satosa.internal.InternalData) -> satosa.response.Response
48+
:type internal_attributes: dict[string, dict[str, str | list[str]]]
49+
:type config: dict[str, dict[str, str] | list[str]]
50+
:type base_url: str
51+
:type name: str
52+
:type external_type: str
53+
"""
54+
super().__init__(outgoing, internal_attributes, base_url, name)
55+
self.name = name
56+
self.external_type = external_type
57+
self.user_id_attr = user_id_attr
58+
59+
self.client = StandAloneClient(config=config["client_config"],
60+
client_type=config["client_config"]['client_type'])
61+
# Deal with provider discovery and client registration
62+
self.client.do_provider_info()
63+
self.client.do_client_registration()
64+
65+
def start_auth(self, context, internal_request):
66+
"""
67+
See super class method satosa.backends.base#start_auth
68+
69+
:type context: satosa.context.Context
70+
:type internal_request: satosa.internal.InternalData
71+
:rtype satosa.response.Redirect
72+
"""
73+
return self.client.init_authorization()
74+
75+
def register_endpoints(self):
76+
"""
77+
Creates a list of all the endpoints this backend module needs to listen to. In this case
78+
it's the authentication response from the underlying OP that is redirected from the OP to
79+
the proxy.
80+
:rtype: Sequence[(str, Callable[[satosa.context.Context], satosa.response.Response]]
81+
:return: A list that can be used to map the request to SATOSA to this endpoint.
82+
"""
83+
84+
return self.client.context.claims.get_usage('authorization_endpoint')
85+
86+
def _authn_response(self, context):
87+
"""
88+
Handles the authentication response from the AS.
89+
90+
:type context: satosa.context.Context
91+
:rtype: satosa.response.Response
92+
:param context: The context in SATOSA
93+
:return: A SATOSA response. This method is only responsible to call the callback function
94+
which generates the Response object.
95+
"""
96+
97+
_info = self.client.finalize(context.request)
98+
99+
try:
100+
auth_info = self.auth_info(context.request)
101+
except NotImplementedError:
102+
auth_info = AuthenticationInformation(UNSPECIFIED, str(datetime.now()), _info["issuer"])
103+
104+
internal_response = InternalData(auth_info=auth_info)
105+
internal_response.attributes = self.converter.to_internal(self.external_type,
106+
_info['userinfo'])
107+
internal_response.subject_id = _info['userinfo'][self.user_id_attr]
108+
del context.state[self.name]
109+
# return self.auth_callback_func(context, internal_response)
110+
if 'error' in _info:
111+
return _info
112+
else:
113+
return _info['userinfo']
114+
115+
def auth_info(self, request):
116+
"""
117+
Creates the SATOSA authentication information object.
118+
:type request: dict[str, str]
119+
:rtype: AuthenticationInformation
120+
121+
:param request: The request parameters in the authentication response sent by the AS.
122+
:return: How, who and when the authentication took place.
123+
"""
124+
raise NotImplementedError("Method 'auth_info' must be implemented in the subclass!")

0 commit comments

Comments
 (0)