Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Conversation

@Tratcher
Copy link
Member

This PR is for review purposes only, we still need to work out the final branching structure.

This is a port of Microsoft.Owin.Security.WsFederation. It targets Core 2.0 dependencies so that we can release it sooner than 2.1. As such some of the project files are unusual. We're also using the IdentityModel 5.2.0-preview1 dependencies from nuget.org. The version for this package will start as 2.0.0-alpha1.

The behavior is approximately the same as the Katana version, with minor updates to mirror those we made to OIDC when it was ported over. Changes:

  • User state in the wctx is nested rather than appended to.
  • CallbackPath is required and defaults to "/signin-wsfed". However it can be set to "/" along with options.SkipUnrecognizedRequests = true; to get some of the stranger legacy behavior apps have asked for in the past. OIDC uses the same mechanic.
  • Better resolving of relative to absolute redirect urls.

@Tratcher Tratcher added this to the 2.0.0-wsfed milestone Sep 19, 2017
@Tratcher Tratcher self-assigned this Sep 19, 2017
@Tratcher Tratcher requested a review from HaoK September 19, 2017 19:14
@dnfclas
Copy link

dnfclas commented Sep 19, 2017

@Tratcher,
Thanks for having already signed the Contribution License Agreement. Your agreement was validated by .NET Foundation. We will now review your pull request.
Thanks,
.NET Foundation Pull Request Bot

@Tratcher
Copy link
Member Author

@Tratcher
Copy link
Member Author

#43

@Tratcher Tratcher requested a review from JunTaoLuo September 19, 2017 21:31
@Tratcher
Copy link
Member Author

We have a build plan, @JunTaoLuo will be in charge of that. We'll start as soon as we get code sign-off from @HaoK.

services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: doesn't DefaultSignInScheme fallback to DefaultScheme?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not correctly in the 2.0.0 branch we're targeting. That was just fixed in 2.1 and 2.0.x.

Copy link
Member

Choose a reason for hiding this comment

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

Ugh, yeah that's too bad


if (context.Request.Path.Equals("/signout"))
{
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
Copy link
Member

Choose a reason for hiding this comment

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

Should this just use the empty overload since you are setting the defaults?

Copy link
Member Author

Choose a reason for hiding this comment

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

We could. This just mirrors the template code.


if (context.Request.Path.Equals("/Account/AccessDenied"))
{
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
Copy link
Member

Choose a reason for hiding this comment

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

Why does AccessDenied sign out?

/// <summary>
/// The context object used in for <see cref="WsFederationEvents.AuthenticationFailed"/>.
/// </summary>
public class AuthenticationFailedContext : RemoteAuthenticationContext<WsFederationOptions>
Copy link
Member

Choose a reason for hiding this comment

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

Nit: should this have a more specific name, i.e. WsFedFailedContext?

Copy link
Member Author

Choose a reason for hiding this comment

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

shrug I don't see why it needs one. This type will rarely be referenced by name, and if you really need to and have a conflict you can do WsFederation.AuthenticationFailedContext.

try
{
// Retrieve our cached redirect uri
string state = wsFederationMessage.Wctx;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: var?

else
{
// Extract the user state from properties and reset.
properties.Items.TryGetValue(WsFederationDefaults.UserstatePropertiesKey, out var userState);
Copy link
Member

Choose a reason for hiding this comment

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

Should this do something if TryGetvalue fails? Or is that ok to just ignore?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it's optional.

if (wsFederationMessage.Wresult == null)
{
Logger.LogWarning("Received a sign-in message without a WResult.");
return (HandleRequestResult)HandleRequestResult.NoResult();
Copy link
Member

Choose a reason for hiding this comment

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

Do we need the cast?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, NoResult returns the base type AuthenticateResult.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why using NoResult here? A WS-Fed sign-in response without a wresult parameter is illegal so this should return an errored result.


// Copy and augment to avoid cross request race conditions for updated configurations.
var tvp = Options.TokenValidationParameters.Clone();
IEnumerable<string> issuers = new[] { _configuration.Issuer };
Copy link
Member

Choose a reason for hiding this comment

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

nit: var?

break;
}
}

Copy link
Member

Choose a reason for hiding this comment

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

Is the validator contract that if principal != null, parsedToken will be non null? Should this check parsedToken as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, ValidateToken always returns both a ClaimsPrincipal and a parsedToken.

Copy link
Member

Choose a reason for hiding this comment

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

Still might be safer to just check principal != null && parsedToken != null

Copy link
Member Author

Choose a reason for hiding this comment

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

We only reference the token for the expiration values, so I'll check there.

/// <summary>
/// Gets or sets the collection of <see cref="ISecurityTokenValidator"/> used to read and validate the <see cref="SecurityToken"/>s.
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "By design")]
Copy link
Member

Choose a reason for hiding this comment

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

Can we remove this supression and just throw the argument exception with parameter name correctly?

/// <summary>
/// Gets or sets the AuthenticationType used when creating the <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary>
public string SignInAsAuthenticationType
Copy link
Member

Choose a reason for hiding this comment

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

Nit: SignInAuthenticationType? Does the As add anything? This is what we use along with SignInScheme right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Obsolete, removed.

/// Gets or sets the <see cref="TokenValidationParameters"/>
/// </summary>
/// <exception cref="ArgumentNullException"> if 'TokenValidationParameters' is null.</exception>
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Name of the property.")]
Copy link
Member

Choose a reason for hiding this comment

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

Remove? use nameof for exceptions everywhere as well instead of constants.

public string Protect(AuthenticationProperties data)
{
lastSavedAuthenticationProperties = Serialize(data);
return "ValidStateData";
Copy link
Member

Choose a reason for hiding this comment

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

Nit: use a const?

var server = new TestServer(builder);

var httpClient = server.CreateClient();

Copy link
Member

Choose a reason for hiding this comment

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

Can we move all the builder/server/client creation into a CreateClient(), the tests all seem to only use httpClient right?

Copy link
Member

@HaoK HaoK left a comment

Choose a reason for hiding this comment

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

Looks fine in general, just some minor stuff

services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
Copy link
Member Author

Choose a reason for hiding this comment

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

We do today due to the 2.0.x patch bug.

Copy link
Contributor

@JunTaoLuo JunTaoLuo left a comment

Choose a reason for hiding this comment

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

Regarding build-related items (*.csproj, etc)

if (string.IsNullOrEmpty(token))
{
Logger.LogWarning("Received a sign-in message without a token.");
return (HandleRequestResult)HandleRequestResult.NoResult();
Copy link
Contributor

Choose a reason for hiding this comment

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

Same remark. Plus it's inconsistent with the OIDC handler.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't see a direct equivalent in OIDC, it detects correct messages by looking for State and CorrelationId, both of which are optional for WsFed. That said, we should probably be using the SkipUnrecognizedRequests pattern here.

Copy link
Contributor

Choose a reason for hiding this comment

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

That said, we should probably be using the SkipUnrecognizedRequests pattern here.

It's something you're already doing at line 129 by making an IsSignInMessage check, which ensures that a wa parameter is present and corresponds to a sign-in operation. Once this check is made, you can consider the request as a valid WS-Fed message. Every missing parameter that is marked as required by the spec should result in an error.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fair.

@kevinchalet
Copy link
Contributor

@Tratcher it's 2017 and CSRF/session fixation attacks are no longer cool so please consider adding built-in antiforgery support like what we have in the OIDC handler. To support unsolicited assertions, an opt-in option AcceptUnsolicitedAssertions could be be added.

@Tratcher
Copy link
Member Author

The anti-xsrf with an opt-out is an interesting suggestion. I'll open a new issue for that.

</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.0.0" />
Copy link
Contributor

Choose a reason for hiding this comment

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

This is going to cause issues. You should always use a ProjectReference to anything that is built from the same repo.

cc @JunTaoLuo

Copy link
Contributor

@JunTaoLuo JunTaoLuo Sep 20, 2017

Choose a reason for hiding this comment

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

Ah I see why this fails universe. We will build this for this release without using Universe as I'll build locally instead. The reason we wanted to take this approach is because we wanted to use the release 2.0.0 packages as dependencies.

@Tratcher Tratcher closed this Sep 22, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants