Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feature): Add ActiveTokenEndpoint to WsFederationConfiguration #2100

Merged
merged 5 commits into from
Jun 21, 2023

Conversation

victorm-hernandez
Copy link
Contributor

Added support to the WsFederationMetadataSerializer to read the SecurityTokenSerivceEndpoint element from WS-Federation metadata and exposed it via the WsFederationConfiguration object as ActiveTokenEndpoint.

Added support to the WsFederationMetadataSerializer to read the
SecurityTokenSerivceEndpoint element from WS-Federation metadata and
exposed it via the WsFederationConfiguration object as ActiveTokenEndpoint.
@victorm-hernandez victorm-hernandez marked this pull request as ready for review June 2, 2023 19:09
Instead of attempting with each of the signing keys, we will match the
key id of the signature with the key id of the signing key.
reader.MoveToContent();
reader.ReadEndElement();

// </PassiveRequestorEndpoint>
Copy link
Member

@brentschmaltz brentschmaltz Jun 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this comment be: // SecurityTokenServiceEndpoint #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, updating

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this was marked resolved but also not updated in the branch so far as I can tell. @victorm-hernandez do you have local changes which still will be persisted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, to avoid any confusion I will mark elements as resolved once I sent another iteration.

/// Or the token_endpoint in the OIDC metadata.
/// </summary>
public virtual string TokenEndpoint { get; set; }

/// <summary>
/// Gets or sets the token endpoint specified via the metadata endpoint.
/// This can is the fed:SecurityTokenServiceType in WS-Federation, http://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html#:~:text=fed%3ASecurityTokenSerivceEndpoint
Copy link
Member

@brentschmaltz brentschmaltz Jun 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'This can is' => 'This is' #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it's not yet fixed?


string tokenEndpoint = null;

while (reader.IsStartElement())
Copy link
Member

@brentschmaltz brentschmaltz Jun 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the reader is not positioned on a StartElement what will happen? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we know that the XML is not malformed, the only scenario where the reader is not on a start element is if the endpointreference has no inner tags and ends right there.

<wsa:EndpointReference xmlns:wsa=""http://www.w3.org/2005/08/addressing""></wsa:EndpointReference>

In that case the MoveToContent below will have no effect and finish reading the SecurityTokenServiceEndpoint tag.

The return value will be null for tokenEndpoint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configuration validator class has a check for null on this URI and would catch that problem.

/// This base class property is not used in OpenIdConnect.
/// </summary>
[JsonIgnore]
public override string ActiveTokenEndpoint { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will modify this in our 7.x release when we will use the utf8Reader and set the ActiveTokenEndpoint to the "token_endpoint" and PassiveTokenEndpoint to "authorization_endpoint".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we keeping track of the changes we're intending to make/are in the process of making somewhere? We'll definitely want to make sure we have really clear release notes when we go to 7.x so it's clear who's impacted.

This comment is more an FYI for the community then? There's no action for this PR correct?

/// Validates a WsFederationConfiguration.
/// </summary>
/// <param name="configuration">WsFederationConfiguration to validate</param>
/// <returns>A <see cref="ConfigurationValidationResult"/> that contains validation result.</returns>
Copy link
Contributor

@FuPingFranco FuPingFranco Jun 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "that contains validation result" -> "containing the validation result" #Resolved

Copy link
Contributor

@FuPingFranco FuPingFranco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

ErrorMessage = LogMessages.IDX22700,
Succeeded = false
};
}
Copy link
Contributor

@TimHannMSFT TimHannMSFT Jun 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider doing all these checks up front and use StringBuilder to build out an error message. In the current model a developer may face one issue, update, face another issue, update...etc. Getting all the errors up front allows for more actionable errors/fewer iterations of debugging. Also less code maintenance costs IMO since if we decide to add another check it doesn't require another log message. #WontFix

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current code assume some of the checks before it. While is not a bad idea it requires a refactor of the whole method. If you dont mind I would like to postpone this improvement.


try
{
configuration.Signature.Verify(key, key.CryptoProviderFactory);
Copy link
Contributor

@TimHannMSFT TimHannMSFT Jun 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this can throw ArgumentNullException too. That would be a bit odd since in other cases where there's unexpected input it's returned as a failed ConfigurationValidationResult. Is this intentional? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good catch.

On the our scenario this is not a real problem. If we deserialize the configuration using the metadataSerializer class the security key always use the default crypto provider and this value is never null.

Having said that, it is possible to modify this property before calling the validator. We need to handle that scenario.

I will update the logic to check if there is a crypto provider before attempting to verify.

internal const string IDX22804 = "IDX22804: Security token type role descriptor is expected.";
internal const string IDX22806 = "IDX22806: Key descriptor for signing is missing in security token service type RoleDescriptor.";
internal const string IDX22807 = "IDX22807: Token endpoint is missing in security token service type RoleDescriptor.";
internal const string IDX22808 = "IDX22808: 'Use' attribute is missing in KeyDescriptor.";
internal const string IDX22810 = "IDX22810: 'Issuer' value is missing in wsfederationconfiguration.";
internal const string IDX22811 = "IDX22811: 'TokenEndpoint' value is missing in wsfederationconfiguration.";
internal const string IDX22812 = "IDX22812: Element: '{0}' was an empty element. 'TokenEndpoint' value is missing in wsfederationconfiguration.";
internal const string IDX22813 = "IDX22813: 'ActiveTokenEndpoint' is missing in 'SecurityTokenServiceTypeRoleDescriptor'.";
Copy link
Contributor

@TimHannMSFT TimHannMSFT Jun 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: [consistency] this is in single quotes here and SecurityTokenServiceEndpoint is not in the next log. Also IDX22806, IDX22807 talk about "token service type RoleDescriptor". I think the way you have it is more clear probably but good to have consistency too for ease of understanding. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, will update.

Copy link
Contributor

@TimHannMSFT TimHannMSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not complete looking through all the testing but the core changes LGTM, left a few comments where I believe some improvements could be considered.

@@ -191,6 +195,9 @@ protected virtual SecurityTokenServiceTypeRoleDescriptor ReadSecurityTokenServic
if (string.IsNullOrEmpty(roleDescriptor.TokenEndpoint))
LogHelper.LogWarning(LogMessages.IDX22807);

if (string.IsNullOrEmpty(roleDescriptor.ActiveTokenEndpoint))
Copy link
Contributor

@dannybtsai dannybtsai Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that roleDescriptor.ActiveTokenEndpoint can be null or empty (not an error), but ReadSecurityTokenServiceEndpoint method throws LogMessages.IDX22812 exception if the element is empty. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct. I understand how this can be confusing.

This inconsistency is there to avoid a breaking change ActiveTokenEndpoint is a new property that may not be used today by consumers of our library, if they chose to populate this class in any way but our metadata serializer this value may be null. So we cannot really enforce this new property without introducing a breaking change.

The story is different for the configuration validator. This is a new class that no one is using so it can properly validate that the field exists and has the correct value.

reader.ReadStartElement(WsAddressing.Elements.Address, WsAddressing.Namespace);
reader.MoveToContent();

tokenEndpoint = Trim(reader.ReadContentAsString());
Copy link
Contributor

@dannybtsai dannybtsai Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to catch exception from ReadContentAsString? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need, the entry point or this flow has a try/catch block that handles all exceptions

};
}

if (string.IsNullOrWhiteSpace(configuration.Signature.SignedInfo.SignatureMethod))
Copy link
Contributor

@dannybtsai dannybtsai Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null check SignedInfo #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

};
}

if (configuration.Signature.SignedInfo.References == null || configuration.Signature.SignedInfo.References.Count == 0)
Copy link
Contributor

@dannybtsai dannybtsai Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null check SignedInfo #Resolved

Succeeded = true
};
}
catch (XmlValidationException)
Copy link
Contributor

@dannybtsai dannybtsai Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Should catch other types of exceptions too?
  2. Should break or continue? #Pending

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although the only exception expected is this, the cryptoprovider may potentially throw any other exception.

We could eat the exception and try to continue however given that we checked for the key thumbprint we do not expect any other key to be valid. We do not expect multiple entries with the same thumbprint and different crypto provider factory.

In my opinion the best thing to do is to let that exception bubble up.

The other alternative I see would be to update the ConfigurationValidationResult object to include exception information, given that that object is shared across ConfigurationValidators I would prefer to not change the pattern.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do want to try rest of the keys in the list if one fails correct? In that case, like Danny suggested, it should be continue.

…config

New scenarios handled on WsFedConfigValidaot, handle null crypto provider
factory on signing keys, null signing key, null signedinfo.
New error string for WsFedConfigValidator for the scenario where we cannot
find the key used to sign the metadata.
Updated strings for consistency.
Minor XML comment updates.
Copy link
Contributor

@sruke sruke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@victorm-hernandez
Copy link
Contributor Author

/azp run

@victorm-hernandez victorm-hernandez merged commit 23808d5 into dev Jun 21, 2023
2 checks passed
Comment on lines +24 to +25
/// Passive Requestor Token endpoint
/// fed:PassiveRequestorEndpoint, https://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html#:~:text=fed%3ASecurityTokenServiceType/fed%3APassiveRequestorEndpoint
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a line or link on what passive requestor is.

/// </summary>
public string TokenEndpoint
{
get;
set;
}

/// <summary>
/// Active Requestor Token Endpoint
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, can we add some description on what active requestor is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants