Skip to content

Commit d62f550

Browse files
committed
Imrpove code structure
1 parent 6e2d347 commit d62f550

7 files changed

Lines changed: 581 additions & 397 deletions

File tree

auth-service/src/handlers/acs.rs

Lines changed: 117 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
// Helpers return Result<T, Response> so the orchestrator can fan out early-return
2+
// HTTP responses; the Err size is dominated by axum::Response (~128 bytes) which
3+
// is fine for non-hot-path handler control flow.
4+
#![allow(clippy::result_large_err)]
5+
16
use crate::{
27
bindings::soap::{send_soap_request, wrap_soap},
8+
config::AuthConfig,
39
saml::{
4-
messages::create_artifact_resolve,
10+
idp_metadata::IdpMetadata,
11+
messages::{CreatedMessage, create_artifact_resolve},
512
validation::{
6-
MINIMUM_LOA, ValidateAssertionOpts, validate_artifact_response, validate_assertion,
7-
validate_response,
13+
Claims, MINIMUM_LOA, ValidateAssertionOpts, validate_artifact_response,
14+
validate_assertion, validate_response,
815
},
916
},
1017
state::{AuthServiceState, AuthState},
@@ -64,77 +71,141 @@ where
6471
);
6572

6673
// 1. Create signed ArtifactResolve (eID §7.5)
74+
let resolve = match build_artifact_resolve(artifact, cfg, rd, &dv_keys.signing[0]) {
75+
Ok(r) => r,
76+
Err(resp) => return resp,
77+
};
78+
79+
// 2. Wrap in SOAP and send to ARS over mTLS (eID §9.4)
80+
let soap_response = match send_artifact_resolve(&resolve.xml, rd, cfg).await {
81+
Ok(r) => r,
82+
Err(resp) => return resp,
83+
};
84+
85+
// 3. Validate ArtifactResponse (eID §7.6.1) using RD signing certs from metadata
86+
let response_xml = match resolve_artifact_response(&soap_response, rd, &resolve.id) {
87+
Ok(xml) => xml,
88+
Err(resp) => return resp,
89+
};
90+
91+
// 4. Validate Response (eID §7.6.2) — handle cancellation / IdP errors
92+
let assertion_xml = match resolve_response(&response_xml) {
93+
Ok(xml) => xml,
94+
Err(resp) => return resp,
95+
};
96+
97+
// 5. Validate Assertion (eID §7.6.3, §7.6.3.5).
98+
let claims = match resolve_assertion(&assertion_xml, &auth_state, cfg, rd) {
99+
Ok(c) => c,
100+
Err(resp) => return resp,
101+
};
102+
103+
// SECURITY: never log decrypted SubjectID values — they are PII (BSN /
104+
// pseudonym per eID §7.6.3.4). Log only non-PII metadata for tracing.
105+
info!(
106+
"[DV] Authentication successful. acting_subject_present={}, \
107+
legal_subject_present={}, loa={:?}, authenticating_authority={:?}, \
108+
service_uuid_present={}",
109+
claims.acting_subject_id.is_some(),
110+
claims.legal_subject_id.is_some(),
111+
claims.authn_context_class_ref.as_deref(),
112+
claims.authenticating_authority.as_deref(),
113+
claims.service_uuid.is_some(),
114+
);
115+
116+
// eID §9.7: consume the matched pending request ID to prevent replay.
117+
if let Some(ref irt) = claims.in_response_to {
118+
debug!("[DV][ACS] Consuming pending AuthnRequest id={irt}");
119+
auth_state.consume_pending_request(irt.as_str());
120+
}
121+
122+
// 6. Hand off to the embedding application to create its session.
123+
debug!("[DV][ACS] Step 6: handing off to AuthState::on_authenticated");
124+
state.on_authenticated(claims, jar, &headers).await
125+
}
126+
127+
fn build_artifact_resolve(
128+
artifact: &str,
129+
cfg: &AuthConfig,
130+
rd: &IdpMetadata,
131+
signing_key: &crate::keys::KeyPair,
132+
) -> Result<CreatedMessage, Response> {
67133
debug!("[DV][ACS] Step 1: building signed ArtifactResolve");
68-
let resolve = match create_artifact_resolve(
69-
artifact,
70-
&cfg.dv.entity_id,
71-
&rd.ars_url,
72-
&dv_keys.signing[0],
73-
) {
134+
match create_artifact_resolve(artifact, &cfg.dv.entity_id, &rd.ars_url, signing_key) {
74135
Ok(m) => {
75136
debug!(
76137
"[DV][ACS] ArtifactResolve built: id={}, xml_len={}",
77138
m.id,
78139
m.xml.len()
79140
);
80-
m
141+
Ok(m)
81142
}
82143
Err(e) => {
83144
error!("[DV] Failed to create ArtifactResolve: {e}");
84-
return (
145+
Err((
85146
StatusCode::INTERNAL_SERVER_ERROR,
86147
"ArtifactResolve build failed",
87148
)
88-
.into_response();
149+
.into_response())
89150
}
90-
};
151+
}
152+
}
91153

92-
// 2. Wrap in SOAP and send to ARS over mTLS (eID §9.4)
154+
async fn send_artifact_resolve(
155+
resolve_xml: &str,
156+
rd: &IdpMetadata,
157+
cfg: &AuthConfig,
158+
) -> Result<String, Response> {
93159
debug!("[DV][ACS] Step 2: sending ArtifactResolve over mTLS SOAP back-channel");
94-
let soap_xml = wrap_soap(&resolve.xml);
95-
let soap_response = match send_soap_request(&rd.ars_url, &soap_xml, &cfg.tls).await {
160+
let soap_xml = wrap_soap(resolve_xml);
161+
match send_soap_request(&rd.ars_url, &soap_xml, &cfg.tls).await {
96162
Ok(r) => {
97163
debug!(
98164
"[DV][ACS] SOAP back-channel returned response (len={})",
99165
r.len()
100166
);
101-
r
167+
Ok(r)
102168
}
103169
Err(e) => {
104170
error!("[DV] SOAP back-channel failed: {e}");
105-
return (StatusCode::BAD_GATEWAY, "ArtifactResolve failed").into_response();
171+
Err((StatusCode::BAD_GATEWAY, "ArtifactResolve failed").into_response())
106172
}
107-
};
173+
}
174+
}
108175

109-
// 3. Validate ArtifactResponse (eID §7.6.1) using RD signing certs from metadata
176+
fn resolve_artifact_response(
177+
soap_response: &str,
178+
rd: &IdpMetadata,
179+
expected_id: &str,
180+
) -> Result<String, Response> {
110181
debug!(
111182
"[DV][ACS] Step 3: validating ArtifactResponse against {} RD signing key(s), \
112183
expected InResponseTo={}",
113184
rd.signing_keys.len(),
114-
resolve.id
185+
expected_id
115186
);
116-
let rd_signing = &rd.signing_keys;
117-
let art_result = validate_artifact_response(&soap_response, rd_signing, &resolve.id);
187+
let art_result = validate_artifact_response(soap_response, &rd.signing_keys, expected_id);
118188
if !art_result.valid {
119189
error!(
120190
"[DV] ArtifactResponse validation failed: {:?}",
121191
art_result.errors
122192
);
123-
return (
193+
return Err((
124194
StatusCode::BAD_REQUEST,
125195
format!(
126196
"ArtifactResponse validation failed: {}",
127197
art_result.errors.join("; ")
128198
),
129199
)
130-
.into_response();
200+
.into_response());
131201
}
132202
debug!("[DV][ACS] Step 3: ArtifactResponse OK");
203+
Ok(art_result.response_xml.unwrap())
204+
}
133205

134-
// 4. Validate Response (eID §7.6.2) — handle cancellation / IdP errors
206+
fn resolve_response(response_xml: &str) -> Result<String, Response> {
135207
debug!("[DV][ACS] Step 4: validating inner Response status");
136-
let response_xml = art_result.response_xml.unwrap();
137-
let resp_result = validate_response(&response_xml);
208+
let resp_result = validate_response(response_xml);
138209

139210
if !resp_result.valid {
140211
let errors_str = resp_result.errors.join("; ");
@@ -144,21 +215,27 @@ where
144215
// TVS "Checklist Testen" v2.1 T3: user cancelled — take them home.
145216
if is_authn_failed || is_cancelled {
146217
warn!("[DV] Authentication cancelled by user");
147-
return Redirect::to("/").into_response();
218+
return Err(Redirect::to("/").into_response());
148219
}
149220

150221
// TVS "Checklist Testen" v2.1 L10: DigiD error — terminate the flow.
151222
warn!("[DV] Authentication failed: {errors_str}");
152-
return (StatusCode::BAD_REQUEST, "Authentication failed").into_response();
223+
return Err((StatusCode::BAD_REQUEST, "Authentication failed").into_response());
153224
}
154225
debug!("[DV][ACS] Step 4: Response status Success");
155226

156-
let Some(assertion_xml) = resp_result.assertion_xml else {
227+
resp_result.assertion_xml.ok_or_else(|| {
157228
warn!("[DV][ACS] No Assertion element extracted from successful Response");
158-
return (StatusCode::BAD_REQUEST, "No assertion in response").into_response();
159-
};
229+
(StatusCode::BAD_REQUEST, "No assertion in response").into_response()
230+
})
231+
}
160232

161-
// 5. Validate Assertion (eID §7.6.3, §7.6.3.5).
233+
fn resolve_assertion(
234+
assertion_xml: &str,
235+
auth_state: &AuthServiceState,
236+
cfg: &AuthConfig,
237+
rd: &IdpMetadata,
238+
) -> Result<Claims, Response> {
162239
// Rule 4: snapshot of pending request IDs for InResponseTo verification.
163240
let pending_ids = auth_state.pending_request_ids();
164241
let pending_refs: Vec<&str> = pending_ids.iter().map(|s| s.as_str()).collect();
@@ -169,6 +246,7 @@ where
169246
pending_refs.len()
170247
);
171248

249+
let dv_keys = auth_state.dv_keys();
172250
let priv_keys: Vec<(&str, &str, &str)> = dv_keys
173251
.encryption
174252
.iter()
@@ -182,11 +260,11 @@ where
182260
.collect();
183261

184262
let assert_result = validate_assertion(ValidateAssertionOpts {
185-
assertion_xml: &assertion_xml,
263+
assertion_xml,
186264
dv_entity_id: &cfg.dv.entity_id,
187265
expected_recipient: &cfg.dv.acs_url,
188266
pending_request_ids: &pending_refs,
189-
trusted_keys: rd_signing,
267+
trusted_keys: &rd.signing_keys,
190268
private_keys: &priv_keys,
191269
minimum_loa: Some(MINIMUM_LOA),
192270
});
@@ -196,37 +274,15 @@ where
196274
"[DV] Assertion validation failed: {:?}",
197275
assert_result.errors
198276
);
199-
return (
277+
return Err((
200278
StatusCode::BAD_REQUEST,
201279
format!(
202280
"Assertion validation failed: {}",
203281
assert_result.errors.join("; ")
204282
),
205283
)
206-
.into_response();
284+
.into_response());
207285
}
208286

209-
let claims = assert_result.claims.unwrap();
210-
// SECURITY: never log decrypted SubjectID values — they are PII (BSN /
211-
// pseudonym per eID §7.6.3.4). Log only non-PII metadata for tracing.
212-
info!(
213-
"[DV] Authentication successful. acting_subject_present={}, \
214-
legal_subject_present={}, loa={:?}, authenticating_authority={:?}, \
215-
service_uuid_present={}",
216-
claims.acting_subject_id.is_some(),
217-
claims.legal_subject_id.is_some(),
218-
claims.authn_context_class_ref.as_deref(),
219-
claims.authenticating_authority.as_deref(),
220-
claims.service_uuid.is_some(),
221-
);
222-
223-
// eID §9.7: consume the matched pending request ID to prevent replay.
224-
if let Some(ref irt) = claims.in_response_to {
225-
debug!("[DV][ACS] Consuming pending AuthnRequest id={irt}");
226-
auth_state.consume_pending_request(irt.as_str());
227-
}
228-
229-
// 6. Hand off to the embedding application to create its session.
230-
debug!("[DV][ACS] Step 6: handing off to AuthState::on_authenticated");
231-
state.on_authenticated(claims, jar, &headers).await
287+
Ok(assert_result.claims.unwrap())
232288
}

0 commit comments

Comments
 (0)