1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! # gamlastan
//!
//! gamlastan is a layered SAML 2.0 library. It provides the protocol data
//! model, hardened XML parsing, XML Digital Signature and XML Encryption
//! wrappers, metadata helpers, protocol bindings, Web SSO profile operations,
//! and IdP-side policy/identifier infrastructure.
//!
//! The crate is intentionally split by responsibility. A web application or
//! proxy normally uses the high-level profile and binding modules first, while
//! lower-level modules remain available when an integration needs direct access
//! to metadata, XML, crypto, or validation primitives.
//!
//! ## Common Workflows
//!
//! ### Create an SP AuthnRequest
//!
//! Use [`profiles::sso::sp::create_authn_request`] to build the typed request,
//! [`xml::SamlSerialize`] to serialize it, then one of the binding helpers to
//! send it to the IdP.
//!
//! ```
//! use gamlastan::core::constants::{BINDING_HTTP_POST, NAMEID_PERSISTENT};
//! use gamlastan::profiles::sso::sp::create_authn_request;
//! use gamlastan::profiles::sso::web_browser::AuthnRequestOptions;
//! use gamlastan::xml::SamlSerialize;
//!
//! let request = create_authn_request(&AuthnRequestOptions {
//! sp_entity_id: "https://sp.example.org/metadata".to_string(),
//! destination: Some("https://idp.example.org/sso".to_string()),
//! acs_url: Some("https://sp.example.org/acs".to_string()),
//! protocol_binding: Some(BINDING_HTTP_POST.to_string()),
//! name_id_format: Some(NAMEID_PERSISTENT.to_string()),
//! allow_create: true,
//! ..Default::default()
//! })?;
//!
//! let xml = request.to_xml_string()?;
//! assert!(xml.contains("AuthnRequest"));
//!
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! ### Parse Typed SAML XML
//!
//! Parse untrusted XML through [`xml::parse_secure`], then deserialize the
//! document root into a borrowed SAML type. Borrowed `*Ref<'a>` values point into
//! the parsed XML buffer; call `to_owned()` when data must outlive the document.
//!
//! ```
//! use gamlastan::core::protocol::AuthnRequestRef;
//! use gamlastan::profiles::sso::sp::create_authn_request;
//! use gamlastan::profiles::sso::web_browser::AuthnRequestOptions;
//! use gamlastan::xml::{parse_saml, parse_secure, SamlSerialize};
//!
//! let request = create_authn_request(&AuthnRequestOptions {
//! sp_entity_id: "https://sp.example.org/metadata".to_string(),
//! ..Default::default()
//! })?;
//! let xml = request.to_xml_string()?;
//!
//! let doc = parse_secure(&xml)?;
//! let parsed = parse_saml::<AuthnRequestRef<'_>>(&doc)?;
//! assert_eq!(
//! parsed.base.issuer.unwrap().value,
//! "https://sp.example.org/metadata"
//! );
//!
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! ### Convert Attributes Between Wire and Local Names
//!
//! Use [`attribute_map::AttributeConverterSet`] when code wants local names
//! such as `mail` and `givenName`, while SAML XML carries OIDs and NameFormats.
//!
//! ```
//! use gamlastan::attribute_map::{AttributeConverterSet, LocalAttribute};
//! use gamlastan::core::constants::ATTRNAME_FORMAT_URI;
//!
//! let converters = AttributeConverterSet::with_default_maps();
//! let wire = converters.from_local(
//! &[LocalAttribute::from_strings("mail", &["alice@example.org"])],
//! ATTRNAME_FORMAT_URI,
//! );
//!
//! assert_eq!(wire[0].name, "urn:oid:0.9.2342.19200300.100.1.3");
//! assert_eq!(wire[0].friendly_name.as_deref(), Some("mail"));
//! ```
//!
//! ## Modules
//!
//! - [`core`] - SAML data structures, constants, identifiers, and time helpers.
//! Use this when constructing or inspecting protocol/assertion values.
//! - [`xml`] - Secure parsing plus serialization/deserialization traits. Use
//! this at every XML trust boundary.
//! - [`bindings`] - HTTP Redirect, HTTP POST, SOAP, PAOS, Artifact, and URI
//! binding helpers. Use this at the HTTP edge of an SP, IdP, or proxy.
//! - [`profiles`] - Web Browser SSO, ECP, logout, artifact resolution,
//! NameID management, Sweden Connect, and related profile logic. Start here
//! for high-level protocol operations.
//! - [`metadata`] - Metadata types, endpoint selection, validation, cache, and
//! signing profile helpers. Use this when loading or publishing federation
//! metadata.
//! - [`security`] - Validation configuration, assertion/response validation,
//! replay cache traits, and errata checks. Use this before consuming claims.
//! - [`crypto`] - SAML-focused wrappers around `bergshamra` for XML-DSig,
//! XML-Enc, canonicalization, key handling, and digests.
//! - [`attribute_map`] - PySAML2-compatible attribute conversion between wire
//! names/OIDs and local names.
//! - [`idp`] - IdP-side policy, NameID storage, EPTID generation,
//! authentication-context matching, and issued-assertion stores.
//!
//! ## Layering Model
//!
//! The lower layers do not know about your web framework or storage backend:
//!
//! ```text
//! HTTP framework adapter
//! -> bindings
//! -> profiles
//! -> security / metadata / idp
//! -> core / xml / crypto / attribute_map
//! ```
//!
//! Framework-specific crates can implement [`bindings::HttpRequest`],
//! [`bindings::HttpResponseBuilder`], [`bindings::SoapTransport`], and the
//! storage traits in [`idp`] or [`security`] without changing the protocol code.
//!
//! ## PySAML2 Compatibility and Legacy Identifiers
//!
//! gamlastan deliberately uses SHA-256 for newly generated
//! `eduPersonTargetedID` values. Stock PySAML2 used MD5 in
//! `saml2.eptid.Eptid`, so the same IdP entityID, SP entityID, user identifier,
//! and secret produce a different EPTID when moving to gamlastan.
//!
//! For migrations where an SP already stores the stock PySAML2 EPTID as its
//! account key, [`idp::eptid`] exposes a guarded legacy mode. The compatibility
//! mode is not used by default; callers must select
//! [`idp::eptid::EptidDigest::Pysaml2Md5Legacy`] through
//! [`idp::eptid::EptidOptions`] and set `allow_legacy_md5 = true`.
//!
//! See [`idp::eptid`] for the exact formula, when to import old mappings
//! instead of recomputing, and examples for enabling the compatibility path.