-
Notifications
You must be signed in to change notification settings - Fork 413
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
Conversation
Added support to the WsFederationMetadataSerializer to read the SecurityTokenSerivceEndpoint element from WS-Federation metadata and exposed it via the WsFederationConfiguration object as ActiveTokenEndpoint.
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> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, updating
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; } |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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?
src/Microsoft.IdentityModel.Protocols.WsFederation/WsFederationConstants.cs
Outdated
Show resolved
Hide resolved
/// Validates a WsFederationConfiguration. | ||
/// </summary> | ||
/// <param name="configuration">WsFederationConfiguration to validate</param> | ||
/// <returns>A <see cref="ConfigurationValidationResult"/> that contains validation result.</returns> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
...oft.IdentityModel.Protocols.WsFederation/Configuration/WsFederationConfigurationValidator.cs
Outdated
Show resolved
Hide resolved
...oft.IdentityModel.Protocols.WsFederation/Configuration/WsFederationConfigurationValidator.cs
Show resolved
Hide resolved
ErrorMessage = LogMessages.IDX22700, | ||
Succeeded = false | ||
}; | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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'."; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, will update.
There was a problem hiding this 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)) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
null check SignedInfo #Resolved
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Should catch other types of exceptions too?
- Should break or continue? #Pending
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
/azp run |
/// 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
Added support to the WsFederationMetadataSerializer to read the SecurityTokenSerivceEndpoint element from WS-Federation metadata and exposed it via the WsFederationConfiguration object as ActiveTokenEndpoint.