Skip to content

Commit

Permalink
Add OpenSamlAssertingPartyMetadataRepository
Browse files Browse the repository at this point in the history
Closes gh-12116
Closes gh-15395
  • Loading branch information
jzheaux committed Jul 20, 2024
1 parent 7ad9ee9 commit e6dfb63
Show file tree
Hide file tree
Showing 5 changed files with 940 additions and 4 deletions.
123 changes: 121 additions & 2 deletions docs/modules/ROOT/pages/servlet/saml2/metadata.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,130 @@ Kotlin::
[source,kotlin,role="secondary"]
----
val details: OpenSamlAssertingPartyDetails =
registration.getAssertingPartyDetails() as OpenSamlAssertingPartyDetails;
val openSamlEntityDescriptor: EntityDescriptor = details.getEntityDescriptor();
registration.getAssertingPartyDetails() as OpenSamlAssertingPartyDetails
val openSamlEntityDescriptor: EntityDescriptor = details.getEntityDescriptor()
----
======

=== Using `AssertingPartyMetadataRepository`

You can also be more targeted than `RelyingPartyRegistrations` by using `AssertingPartyMetadataRepository`, an interface that allows for only retrieving the asserting party metadata.

This allows three valuable features:

* Implementations can refresh asserting party metadata in an expiry-aware fashion
* Implementations of `RelyingPartyRegistrationRepository` can more easily articulate a relationship between a relying party and its one or many corresponding asserting parties
* Implementations can verify metadata signatures

For example, `OpenSamlAssertingPartyMetadataRepository` uses OpenSAML's `MetadataResolver`, and API whose implementations regularly refresh the underlying metadata in an expiry-aware fashion.

This means that you can now create a refreshable `RelyingPartyRegistrationRepository` in just a few lines of code:

[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Component
public class RefreshableRelyingPartyRegistrationRepository
implements IterableRelyingPartyRegistrationRepository {
private final AssertingPartyMetadataRepository metadata =
OpenSamlAssertingPartyMetadataRepository
.fromTrustedMetadataLocation("https://idp.example.org/metadata").build();
@Override
public RelyingPartyRegistration findByRegistrationId(String registrationId) {
AssertingPartyMetadata metadata = this.metadata.findByEntityId(registrationId);
if (metadata == null) {
return null;
}
return applyRelyingParty(metadata);
}
@Override
public Iterator<RelyingPartyRegistration> iterator() {
return StreamSupport.stream(this.metadata.spliterator(), false)
.map(this::applyRelyingParty).iterator();
}
private RelyingPartyRegistration applyRelyingParty(AssertingPartyMetadata metadata) {
AssertingPartyDetails details = (AssertingPartyDetails) metadata;
return RelyingPartyRegistration.withAssertingPartyDetails(details)
// apply any relying party configuration
.build();
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Component
class RefreshableRelyingPartyRegistrationRepository : IterableRelyingPartyRegistrationRepository {
private val metadata: AssertingPartyMetadataRepository =
OpenSamlAssertingPartyMetadataRepository.fromTrustedMetadataLocation(
"https://idp.example.org/metadata").build()
fun findByRegistrationId(registrationId:String?): RelyingPartyRegistration {
val metadata = this.metadata.findByEntityId(registrationId)
if (metadata == null) {
return null
}
return applyRelyingParty(metadata)
}
fun iterator(): Iterator<RelyingPartyRegistration> {
return StreamSupport.stream(this.metadata.spliterator(), false)
.map(this::applyRelyingParty).iterator()
}
private fun applyRelyingParty(metadata: AssertingPartyMetadata): RelyingPartyRegistration {
val details: AssertingPartyDetails = metadata as AssertingPartyDetails
return RelyingPartyRegistration.withAssertingPartyDetails(details)
// apply any relying party configuration
.build()
}
}
----
======

[TIP]
`OpenSamlAssertingPartyMetadataRepository` also ships with a constructor so you can provide a custom `MetadataResolver`. Since the underlying `MetadataResolver` is doing the expirying and refreshing, if you use the constructor directly, you will only get these features by providing an implementation that does so.

=== Verifying Metadata Signatures

You can also verify metadata signatures using `OpenSamlAssertingPartyMetadataRepository` by providing the appropriate set of ``Saml2X509Credential``s as follows:

[tabs]
======
Java::
+
[source,java,role="primary"]
----
OpenSamlAssertingPartyMetadataRepository.withMetadataLocation("https://idp.example.org/metadata")
.verificationCredentials((c) -> c.add(myVerificationCredential))
.build();
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
OpenSamlAssertingPartyMetadataRepository.withMetadataLocation("https://idp.example.org/metadata")
.verificationCredentials({ c : Collection<Saml2X509Credential> ->
c.add(myVerificationCredential) })
.build()
----
======

[NOTE]
If no credentials are provided, the component will not perform signature validation.

[[publishing-relying-party-metadata]]
== Producing `<saml2:SPSSODescriptor>` Metadata

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,13 @@ static String serialize(XMLObject object) {
}

static <O extends SignableXMLObject> O sign(O object, RelyingPartyRegistration relyingPartyRegistration) {
SignatureSigningParameters parameters = resolveSigningParameters(relyingPartyRegistration);
List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
return sign(object, algorithms, credentials);
}

static <O extends SignableXMLObject> O sign(O object, List<String> algorithms, List<Credential> credentials) {
SignatureSigningParameters parameters = resolveSigningParameters(algorithms, credentials);
try {
SignatureSupport.signObject(object, parameters);
return object;
Expand All @@ -98,6 +104,11 @@ private static SignatureSigningParameters resolveSigningParameters(
RelyingPartyRegistration relyingPartyRegistration) {
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
return resolveSigningParameters(algorithms, credentials);
}

private static SignatureSigningParameters resolveSigningParameters(List<String> algorithms,
List<Credential> credentials) {
List<String> digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256);
String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS;
SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver();
Expand Down
Loading

0 comments on commit e6dfb63

Please sign in to comment.