Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
/// </summary>
public class OAuthBearerAuthenticationMiddleware : AuthenticationMiddleware<OAuthBearerAuthenticationOptions>
{
private readonly ILogger _logger;

/// <summary>
/// Bearer authentication component which is added to an HTTP pipeline. This constructor is not
/// called by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ protected override async Task ApplyResponseGrantAsync()
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}

var openIdConnectMessage = new OpenIdConnectMessage()
var message = new OpenIdConnectMessage()
{
IssuerAddress = _configuration == null ? string.Empty : (_configuration.EndSessionEndpoint ?? string.Empty),
RequestType = OpenIdConnectRequestType.LogoutRequest,
Expand All @@ -69,21 +69,20 @@ protected override async Task ApplyResponseGrantAsync()
var properties = new AuthenticationProperties(signout.Properties);
if (!string.IsNullOrEmpty(properties.RedirectUri))
{
openIdConnectMessage.PostLogoutRedirectUri = properties.RedirectUri;
message.PostLogoutRedirectUri = properties.RedirectUri;
}
else if (!string.IsNullOrWhiteSpace(Options.PostLogoutRedirectUri))
{
openIdConnectMessage.PostLogoutRedirectUri = Options.PostLogoutRedirectUri;
message.PostLogoutRedirectUri = Options.PostLogoutRedirectUri;
}

var notification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
var notification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options, message, properties);
if (Options.Notifications.RedirectToIdentityProvider != null)
{
ProtocolMessage = openIdConnectMessage
};

await Options.Notifications.RedirectToIdentityProvider(notification);
await Options.Notifications.RedirectToIdentityProvider(notification);
}

if (!notification.HandledResponse)
if (!notification.HandledResponse || !notification.Skipped)
{
var redirectUri = notification.ProtocolMessage.CreateLogoutRequestUrl();
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
Expand Down Expand Up @@ -174,18 +173,36 @@ protected override async Task ApplyResponseChallengeAsync()
ClientId = Options.ClientId,
IssuerAddress = _configuration?.AuthorizationEndpoint ?? string.Empty,
RedirectUri = Options.RedirectUri,
// [brentschmaltz] - this should be a property on RedirectToIdentityProviderNotification not on the OIDCMessage.
// [brentschmaltz] - #215 this should be a property on RedirectToIdentityProviderNotification not on the OIDCMessage.
RequestType = OpenIdConnectRequestType.AuthenticationRequest,
Resource = Options.Resource,
ResponseMode = Options.ResponseMode,
ResponseType = Options.ResponseType,
Scope = Options.Scope,
State = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + UrlEncoder.UrlEncode(Options.StateDataFormat.Protect(properties))
Scope = Options.Scope
};

if (Options.ProtocolValidator.RequireNonce)
{
message.Nonce = Options.ProtocolValidator.GenerateNonce();
}

var redirectToIdentityProviderNotification =
new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options, message, properties);

await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification);
if (redirectToIdentityProviderNotification.HandledResponse)
{
Logger.LogInformation(Resources.OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse);
return;
}
else if (redirectToIdentityProviderNotification.Skipped)
{
Logger.LogInformation(Resources.OIDCH_0035_RedirectToIdentityProviderNotificationSkipped);
return;
}

if (!string.IsNullOrWhiteSpace(message.Nonce))
{
if (Options.NonceCache != null)
{
if (!Options.NonceCache.TryAddNonce(message.Nonce))
Expand All @@ -200,21 +217,15 @@ protected override async Task ApplyResponseChallengeAsync()
}
}

var redirectToIdentityProviderNotification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
ProtocolMessage = message
};

await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification);
if (redirectToIdentityProviderNotification.HandledResponse)
// If 'OpenIdConnectMessage.State' is null, then the user never set it in the notification. Just set the state
if (string.IsNullOrWhiteSpace(redirectToIdentityProviderNotification.ProtocolMessage.State))
{
Logger.LogInformation(Resources.OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse);
return;
redirectToIdentityProviderNotification.ProtocolMessage.State = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + Options.StateDataFormat.Protect(properties);
}
else if (redirectToIdentityProviderNotification.Skipped)
// the user did set 'OpenIdConnectMessage.State, add a parameter to the state
else
{
Logger.LogInformation(Resources.OIDCH_0035_RedirectToIdentityProviderNotificationSkipped);
return;
redirectToIdentityProviderNotification.ProtocolMessage.State = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + Options.StateDataFormat.Protect(properties) + "&userstate=" + redirectToIdentityProviderNotification.ProtocolMessage.State;
Copy link
Contributor

Choose a reason for hiding this comment

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

This might have been discussed by why is UrlEncoder.UrlEncode not used to encode state data in this change ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@suhasj When the message is created by OpenIdConnectMessage.CreateAuthenticationRequestUrl formatting will take place. If we encode here, it will be duplicated.

Copy link
Member

Choose a reason for hiding this comment

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

We need to parse the state back out for them on the other end.

}

var redirectUri = redirectToIdentityProviderNotification.ProtocolMessage.CreateAuthenticationRequestUrl();
Expand All @@ -223,6 +234,8 @@ protected override async Task ApplyResponseChallengeAsync()
Logger.LogWarning(Resources.OIDCH_0036_UriIsNotWellFormed, redirectUri);
}

Logger.LogDebug(Resources.OIDCH_0037_RedirectUri, redirectUri);

Response.Redirect(redirectUri);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,20 @@ public class OpenIdConnectAuthenticationMiddleware : AuthenticationMiddleware<Op
/// <param name="next">The next middleware in the ASP.NET pipeline to invoke.</param>
/// <param name="dataProtectionProvider"> provider for creating a data protector.</param>
/// <param name="loggerFactory">factory for creating a <see cref="ILogger"/>.</param>
/// <param name="encoder"><see cref="IUrlEncoder"/> for encoding query strings.</param>
/// <param name="externalOptions"></param>
/// <param name="options">a <see cref="IOptions{OpenIdConnectAuthenticationOptions}"/> instance that will supply <see cref="OpenIdConnectAuthenticationOptions"/>
/// if configureOptions is null.</param>
/// <param name="configureOptions">a <see cref="ConfigureOptions{OpenIdConnectAuthenticationOptions}"/> instance that will be passed to an instance of <see cref="OpenIdConnectAuthenticationOptions"/>
/// that is retrieved by calling <see cref="IOptions{OpenIdConnectAuthenticationOptions}.GetNamedOptions(string)"/> where string == <see cref="ConfigureOptions{OpenIdConnectAuthenticationOptions}.Name"/> provides runtime configuration.</param>
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
public OpenIdConnectAuthenticationMiddleware(
[NotNull] RequestDelegate next,
[NotNull] IOptions<OpenIdConnectAuthenticationOptions> options,
[NotNull] IDataProtectionProvider dataProtectionProvider,
[NotNull] ILoggerFactory loggerFactory,
[NotNull] IUrlEncoder encoder,
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
[NotNull] IOptions<OpenIdConnectAuthenticationOptions> options,
ConfigureOptions<OpenIdConnectAuthenticationOptions> configureOptions = null)
: base(next, options, loggerFactory, encoder, configureOptions)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// </summary>
public class OpenIdConnectAuthenticationNotifications
{
Func<RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> _redirectToIdentityProvider;

/// <summary>
/// Creates a new set of notifications. Each notification has a default no-op behavior unless otherwise documented.
/// </summary>
Expand Down Expand Up @@ -44,7 +46,19 @@ public OpenIdConnectAuthenticationNotifications()
/// <summary>
/// Invoked to manipulate redirects to the identity provider for SignIn, SignOut, or Challenge.
/// </summary>
public Func<RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> RedirectToIdentityProvider { get; set; }
public Func<RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> RedirectToIdentityProvider
Copy link
Member

Choose a reason for hiding this comment

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

So why is this Func special compared to all of the other Func setters that allow setting to null? I'm not arguing necessarily that they should be nullable, I'm just pointing out that they should all behave the same

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nothing special with this Func. I stopped here to see if folks would like to see this pattern for all the Func's.
My testing showed it is very easy to generate code that will cause a null-ref.

Copy link
Member

Choose a reason for hiding this comment

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

In theory we could use [NotNull] for these instead. It would be less work for us, and it would generate the same output as a compile step.

Copy link
Member

Choose a reason for hiding this comment

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

👍 for [NotNull]

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 can work in this case, but for the notifications themselves, since the Ticket and Message can be set, we need some stronger validation.

I noticed in FB and Goodle MW and Handlers we don't check for null in public methods.

Copy link
Contributor

Choose a reason for hiding this comment

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

All public methods need to check for null. If we have some that don't, then that's a bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then we need to take action as you will find a lot of such code.

{
get { return _redirectToIdentityProvider; }
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}

_redirectToIdentityProvider = value;
}
}

/// <summary>
/// Invoked with the security token that has been extracted from the protocol message.
Expand All @@ -55,6 +69,5 @@ public OpenIdConnectAuthenticationNotifications()
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
/// </summary>
public Func<SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> SecurityTokenValidated { get; set; }

}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,10 @@
<value>OIDCH_0035: redirectToIdentityProviderNotification.Skipped</value>
</data>
<data name="OIDCH_0036_UriIsNotWellFormed" xml:space="preserve">
<value>OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: {0}", (redirectUri ?? "null"))</value>
<value>OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: '{0}'.</value>
</data>
<data name="OIDCH_0037_RedirectUri" xml:space="preserve">
<value>OIDCH_0037: RedirectUri is: '{0}'.</value>
</data>
<data name="OIDCH_0000_AuthenticateCoreAsync" xml:space="preserve">
<value>OIDCH_0000: Entering: '{0}'.</value>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,64 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.Framework.Internal;

namespace Microsoft.AspNet.Authentication.Notifications
{
/// <summary>
/// When a user configures the <see cref="AuthenticationMiddleware{TOptions}"/> to be notified prior to redirecting to an IdentityProvider
/// and instance of <see cref="RedirectFromIdentityProviderNotification{TMessage, TOptions, TMessage, AuthenticationProperties}"/> is passed to 'RedirectToIdentityProvider".
/// </summary>
/// <typeparam name="TMessage">protocol specific message.</typeparam>
/// <typeparam name="TOptions">protocol specific options.</typeparam>
public class RedirectToIdentityProviderNotification<TMessage, TOptions> : BaseNotification<TOptions>
{
public RedirectToIdentityProviderNotification(HttpContext context, TOptions options) : base(context, options)
TMessage _message;
AuthenticationProperties _properties;

public RedirectToIdentityProviderNotification([NotNull] HttpContext context, [NotNull] TOptions options, [NotNull] TMessage protocolMessage, [NotNull] AuthenticationProperties properties ) : base(context, options)
{
ProtocolMessage = protocolMessage;
AuthenticationProperties = properties;
}

/// <summary>
/// Gets or sets the <see cref="{TMessage}"/>.
/// </summary>
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
public TMessage ProtocolMessage
{
get { return _message; }
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 public set really necessary on Message/AuthProperties? can these not be private and only settable via the ctor? If the sets are only needed for unit tests, internal setter seems better than than adding null checks.

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 allows users to read a message and modify it. If you think about it, this allows some very powerful extensibility.

set
{
if (value == null)
{
throw new ArgumentNullException("value");
}

_message = value;
}
}

public TMessage ProtocolMessage { get; set; }
/// <summary>
/// Gets or sets the <see cref="AuthenticationProperties"/>.
/// </summary>
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
public AuthenticationProperties AuthenticationProperties
{
get { return _properties; }
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}

_properties = value;
}
}
}
}
}
Loading