Skip to content

Commit 630ebfa

Browse files
authored
feat: get name of user in Apple backend
Apple sends the name only via POST in the first authentication (ever)
1 parent fbc42e7 commit 630ebfa

File tree

1 file changed

+74
-33
lines changed

1 file changed

+74
-33
lines changed

src/satosa/backends/apple.py

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,7 @@ def start_auth(self, context, request_info):
7676
"""
7777
oidc_nonce = rndstr()
7878
oidc_state = rndstr()
79-
state_data = {
80-
NONCE_KEY: oidc_nonce,
81-
STATE_KEY: oidc_state
82-
}
79+
state_data = {NONCE_KEY: oidc_nonce, STATE_KEY: oidc_state}
8380
context.state[self.name] = state_data
8481

8582
args = {
@@ -88,7 +85,7 @@ def start_auth(self, context, request_info):
8885
"client_id": self.client.client_id,
8986
"redirect_uri": self.client.registration_response["redirect_uris"][0],
9087
"state": oidc_state,
91-
"nonce": oidc_nonce
88+
"nonce": oidc_nonce,
9289
}
9390
args.update(self.config["client"]["auth_req_params"])
9491
auth_req = self.client.construct_AuthorizationRequest(request_args=args)
@@ -104,7 +101,9 @@ def register_endpoints(self):
104101
:return: A list that can be used to map the request to SATOSA to this endpoint.
105102
"""
106103
url_map = []
107-
redirect_path = urlparse(self.config["client"]["client_metadata"]["redirect_uris"][0]).path
104+
redirect_path = urlparse(
105+
self.config["client"]["client_metadata"]["redirect_uris"][0]
106+
).path
108107
if not redirect_path:
109108
raise SATOSAError("Missing path in redirect uri")
110109

@@ -122,10 +121,16 @@ def _verify_nonce(self, nonce, context):
122121
"""
123122
backend_state = context.state[self.name]
124123
if nonce != backend_state[NONCE_KEY]:
125-
msg = "Missing or invalid nonce in authn response for state: {}".format(backend_state)
126-
logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg)
124+
msg = "Missing or invalid nonce in authn response for state: {}".format(
125+
backend_state
126+
)
127+
logline = lu.LOG_FMT.format(
128+
id=lu.get_session_id(context.state), message=msg
129+
)
127130
logger.debug(logline)
128-
raise SATOSAAuthenticationError(context.state, "Missing or invalid nonce in authn response")
131+
raise SATOSAAuthenticationError(
132+
context.state, "Missing or invalid nonce in authn response"
133+
)
129134

130135
def _get_tokens(self, authn_response, context):
131136
"""
@@ -142,22 +147,24 @@ def _get_tokens(self, authn_response, context):
142147
"client_secret": self.client.client_secret,
143148
"code": authn_response["code"],
144149
"grant_type": "authorization_code",
145-
"redirect_uri": self.client.registration_response['redirect_uris'][0],
150+
"redirect_uri": self.client.registration_response["redirect_uris"][0],
146151
}
147152

148153
token_resp = requests.post(
149154
"https://appleid.apple.com/auth/token",
150155
data=args,
151-
headers={"Content-Type": "application/x-www-form-urlencoded"}
152-
).json()
156+
headers={"Content-Type": "application/x-www-form-urlencoded"},
157+
).json()
153158

154159
logger.debug("apple response received")
155160
logger.debug(token_resp)
156161

157162
self._check_error_response(token_resp, context)
158163

159164
keyjar = self.client.keyjar
160-
id_token_claims = dict(Message().from_jwt(token_resp["id_token"], keyjar=keyjar))
165+
id_token_claims = dict(
166+
Message().from_jwt(token_resp["id_token"], keyjar=keyjar)
167+
)
161168

162169
return token_resp["access_token"], id_token_claims
163170

@@ -176,7 +183,9 @@ def _check_error_response(self, response, context):
176183
error=response["error"],
177184
description=response.get("error_description", ""),
178185
)
179-
logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg)
186+
logline = lu.LOG_FMT.format(
187+
id=lu.get_session_id(context.state), message=msg
188+
)
180189
logger.debug(logline)
181190
raise SATOSAAuthenticationError(context.state, "Access denied")
182191

@@ -192,24 +201,49 @@ def response_endpoint(self, context, *args):
192201
:return:
193202
"""
194203
backend_state = context.state[self.name]
195-
authn_resp = self.client.parse_response(AuthorizationResponse, info=context.request, sformat="dict")
204+
205+
# Apple sends some user information only via POST in the first request
206+
if "user" in context.request:
207+
userinfo = json.load(context.request["user"])
208+
userinfo["name"] = " ".join(
209+
filter(
210+
None,
211+
[
212+
userinfo.get("firstName", ""),
213+
userinfo.get("middleName", ""),
214+
userinfo.get("lastName", ""),
215+
],
216+
)
217+
)
218+
else:
219+
# Apple has no userinfo endpoint
220+
userinfo = {}
221+
222+
authn_resp = self.client.parse_response(
223+
AuthorizationResponse, info=context.request, sformat="dict"
224+
)
196225
if backend_state[STATE_KEY] != authn_resp["state"]:
197-
msg = "Missing or invalid state in authn response for state: {}".format(backend_state)
198-
logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg)
226+
msg = "Missing or invalid state in authn response for state: {}".format(
227+
backend_state
228+
)
229+
logline = lu.LOG_FMT.format(
230+
id=lu.get_session_id(context.state), message=msg
231+
)
199232
logger.debug(logline)
200-
raise SATOSAAuthenticationError(context.state, "Missing or invalid state in authn response")
233+
raise SATOSAAuthenticationError(
234+
context.state, "Missing or invalid state in authn response"
235+
)
201236

202237
self._check_error_response(authn_resp, context)
203238
access_token, id_token_claims = self._get_tokens(authn_resp, context)
204239
if not id_token_claims:
205240
id_token_claims = {}
206241

207-
# Apple has no userinfo endpoint
208-
userinfo = {}
209-
210242
if not id_token_claims and not userinfo:
211243
msg = "No id_token or userinfo, nothing to do.."
212-
logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg)
244+
logline = lu.LOG_FMT.format(
245+
id=lu.get_session_id(context.state), message=msg
246+
)
213247
logger.error(logline)
214248
raise SATOSAAuthenticationError(context.state, "No user info available.")
215249

@@ -218,7 +252,9 @@ def response_endpoint(self, context, *args):
218252
logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg)
219253
logger.debug(logline)
220254
del context.state[self.name]
221-
internal_resp = self._translate_response(all_user_claims, self.client.authorization_endpoint)
255+
internal_resp = self._translate_response(
256+
all_user_claims, self.client.authorization_endpoint
257+
)
222258
return self.auth_callback_func(context, internal_resp)
223259

224260
def _translate_response(self, response, issuer):
@@ -245,7 +281,9 @@ def get_metadata_desc(self):
245281
See satosa.backends.oauth.get_metadata_desc
246282
:rtype: satosa.metadata_creation.description.MetadataDescription
247283
"""
248-
return get_metadata_desc_for_oauth_backend(self.config["provider_metadata"]["issuer"], self.config)
284+
return get_metadata_desc_for_oauth_backend(
285+
self.config["provider_metadata"]["issuer"], self.config
286+
)
249287

250288

251289
def _create_client(provider_metadata, client_metadata, verify_ssl=True):
@@ -258,15 +296,15 @@ def _create_client(provider_metadata, client_metadata, verify_ssl=True):
258296
:return: client instance to use for communicating with the configured provider
259297
:rtype: oic.oic.Client
260298
"""
261-
client = oic.Client(
262-
client_authn_method=CLIENT_AUTHN_METHOD, verify_ssl=verify_ssl
263-
)
299+
client = oic.Client(client_authn_method=CLIENT_AUTHN_METHOD, verify_ssl=verify_ssl)
264300

265301
# Provider configuration information
266302
if "authorization_endpoint" in provider_metadata:
267303
# no dynamic discovery necessary
268-
client.handle_provider_config(ProviderConfigurationResponse(**provider_metadata),
269-
provider_metadata["issuer"])
304+
client.handle_provider_config(
305+
ProviderConfigurationResponse(**provider_metadata),
306+
provider_metadata["issuer"],
307+
)
270308
else:
271309
# do dynamic discovery
272310
client.provider_config(provider_metadata["issuer"])
@@ -277,9 +315,12 @@ def _create_client(provider_metadata, client_metadata, verify_ssl=True):
277315
client.store_registration_info(RegistrationRequest(**client_metadata))
278316
else:
279317
# do dynamic registration
280-
client.register(client.provider_info['registration_endpoint'],
281-
**client_metadata)
318+
client.register(
319+
client.provider_info["registration_endpoint"], **client_metadata
320+
)
282321

283-
client.subject_type = (client.registration_response.get("subject_type") or
284-
client.provider_info["subject_types_supported"][0])
322+
client.subject_type = (
323+
client.registration_response.get("subject_type")
324+
or client.provider_info["subject_types_supported"][0]
325+
)
285326
return client

0 commit comments

Comments
 (0)