Merge branch 'release/2.1' into dev
This commit is contained in:
commit
8c797639e5
|
|
@ -225,11 +225,28 @@ namespace OpenIdConnectSample
|
|||
return;
|
||||
}
|
||||
|
||||
if (context.Request.Path.Equals("/login-challenge"))
|
||||
{
|
||||
// Challenge the user authentication, and force a login prompt by overwriting the
|
||||
// "prompt". This could be used for example to require the user to re-enter their
|
||||
// credentials at the authentication provider, to add an extra confirmation layer.
|
||||
await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new OpenIdConnectChallengeProperties()
|
||||
{
|
||||
Prompt = "login",
|
||||
|
||||
// it is also possible to specify different scopes, e.g.
|
||||
// Scope = new string[] { "openid", "profile", "other" }
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await WriteHtmlAsync(response, async res =>
|
||||
{
|
||||
await res.WriteAsync($"<h1>Hello Authenticated User {HtmlEncode(user.Identity.Name)}</h1>");
|
||||
await res.WriteAsync("<a class=\"btn btn-default\" href=\"/refresh\">Refresh tokens</a>");
|
||||
await res.WriteAsync("<a class=\"btn btn-default\" href=\"/restricted\">Restricted</a>");
|
||||
await res.WriteAsync("<a class=\"btn btn-default\" href=\"/login-challenge\">Login challenge</a>");
|
||||
await res.WriteAsync("<a class=\"btn btn-default\" href=\"/signout\">Sign Out</a>");
|
||||
await res.WriteAsync("<a class=\"btn btn-default\" href=\"/signout-remote\">Sign Out Remote</a>");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
|
|
@ -65,12 +66,15 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
}
|
||||
}
|
||||
|
||||
protected override string FormatScope()
|
||||
protected override string FormatScope(IEnumerable<string> scopes)
|
||||
{
|
||||
// Facebook deviates from the OAuth spec here. They require comma separated instead of space separated.
|
||||
// https://developers.facebook.com/docs/reference/dialogs/oauth
|
||||
// http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
return string.Join(",", Options.Scope);
|
||||
return string.Join(",", scopes);
|
||||
}
|
||||
|
||||
protected override string FormatScope()
|
||||
=> base.FormatScope();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication.Google
|
||||
{
|
||||
public class GoogleChallengeProperties : OAuthChallengeProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// The parameter key for the "access_type" argument being used for a challenge request.
|
||||
/// </summary>
|
||||
public static readonly string AccessTypeKey = "access_type";
|
||||
|
||||
/// <summary>
|
||||
/// The parameter key for the "approval_prompt" argument being used for a challenge request.
|
||||
/// </summary>
|
||||
public static readonly string ApprovalPromptKey = "approval_prompt";
|
||||
|
||||
/// <summary>
|
||||
/// The parameter key for the "include_granted_scopes" argument being used for a challenge request.
|
||||
/// </summary>
|
||||
public static readonly string IncludeGrantedScopesKey = "include_granted_scopes";
|
||||
|
||||
/// <summary>
|
||||
/// The parameter key for the "login_hint" argument being used for a challenge request.
|
||||
/// </summary>
|
||||
public static readonly string LoginHintKey = "login_hint";
|
||||
|
||||
/// <summary>
|
||||
/// The parameter key for the "prompt" argument being used for a challenge request.
|
||||
/// </summary>
|
||||
public static readonly string PromptParameterKey = "prompt";
|
||||
|
||||
public GoogleChallengeProperties()
|
||||
{ }
|
||||
|
||||
public GoogleChallengeProperties(IDictionary<string, string> items)
|
||||
: base(items)
|
||||
{ }
|
||||
|
||||
public GoogleChallengeProperties(IDictionary<string, string> items, IDictionary<string, object> parameters)
|
||||
: base(items, parameters)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// The "access_type" parameter value being used for a challenge request.
|
||||
/// </summary>
|
||||
public string AccessType
|
||||
{
|
||||
get => GetParameter<string>(AccessTypeKey);
|
||||
set => SetParameter(AccessTypeKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "approval_prompt" parameter value being used for a challenge request.
|
||||
/// </summary>
|
||||
public string ApprovalPrompt
|
||||
{
|
||||
get => GetParameter<string>(ApprovalPromptKey);
|
||||
set => SetParameter(ApprovalPromptKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "include_granted_scopes" parameter value being used for a challenge request.
|
||||
/// </summary>
|
||||
public bool? IncludeGrantedScopes
|
||||
{
|
||||
get => GetParameter<bool?>(IncludeGrantedScopesKey);
|
||||
set => SetParameter(IncludeGrantedScopesKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "login_hint" parameter value being used for a challenge request.
|
||||
/// </summary>
|
||||
public string LoginHint
|
||||
{
|
||||
get => GetParameter<string>(LoginHintKey);
|
||||
set => SetParameter(LoginHintKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "prompt" parameter value being used for a challenge request.
|
||||
/// </summary>
|
||||
public string Prompt
|
||||
{
|
||||
get => GetParameter<string>(PromptParameterKey);
|
||||
set => SetParameter(PromptParameterKey, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,12 +57,12 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
|||
queryStrings.Add("client_id", Options.ClientId);
|
||||
queryStrings.Add("redirect_uri", redirectUri);
|
||||
|
||||
AddQueryString(queryStrings, properties, "scope", FormatScope());
|
||||
AddQueryString(queryStrings, properties, "access_type", Options.AccessType);
|
||||
AddQueryString(queryStrings, properties, "approval_prompt");
|
||||
AddQueryString(queryStrings, properties, "prompt");
|
||||
AddQueryString(queryStrings, properties, "login_hint");
|
||||
AddQueryString(queryStrings, properties, "include_granted_scopes");
|
||||
AddQueryString(queryStrings, properties, GoogleChallengeProperties.ScopeKey, FormatScope, Options.Scope);
|
||||
AddQueryString(queryStrings, properties, GoogleChallengeProperties.AccessTypeKey, Options.AccessType);
|
||||
AddQueryString(queryStrings, properties, GoogleChallengeProperties.ApprovalPromptKey);
|
||||
AddQueryString(queryStrings, properties, GoogleChallengeProperties.PromptParameterKey);
|
||||
AddQueryString(queryStrings, properties, GoogleChallengeProperties.LoginHintKey);
|
||||
AddQueryString(queryStrings, properties, GoogleChallengeProperties.IncludeGrantedScopesKey, v => v?.ToString().ToLower(), (bool?)null);
|
||||
|
||||
var state = Options.StateDataFormat.Protect(properties);
|
||||
queryStrings.Add("state", state);
|
||||
|
|
@ -71,29 +71,38 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
|||
return authorizationEndpoint;
|
||||
}
|
||||
|
||||
private static void AddQueryString(
|
||||
private void AddQueryString<T>(
|
||||
IDictionary<string, string> queryStrings,
|
||||
AuthenticationProperties properties,
|
||||
string name,
|
||||
Func<T, string> formatter,
|
||||
T defaultValue)
|
||||
{
|
||||
string value = null;
|
||||
var parameterValue = properties.GetParameter<T>(name);
|
||||
if (parameterValue != null)
|
||||
{
|
||||
value = formatter(parameterValue);
|
||||
}
|
||||
else if (!properties.Items.TryGetValue(name, out value))
|
||||
{
|
||||
value = formatter(defaultValue);
|
||||
}
|
||||
|
||||
// Remove the parameter from AuthenticationProperties so it won't be serialized into the state
|
||||
properties.Items.Remove(name);
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
queryStrings[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddQueryString(
|
||||
IDictionary<string, string> queryStrings,
|
||||
AuthenticationProperties properties,
|
||||
string name,
|
||||
string defaultValue = null)
|
||||
{
|
||||
string value;
|
||||
if (!properties.Items.TryGetValue(name, out value))
|
||||
{
|
||||
value = defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove the parameter from AuthenticationProperties so it won't be serialized to state parameter
|
||||
properties.Items.Remove(name);
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
queryStrings[name] = value;
|
||||
}
|
||||
=> AddQueryString(queryStrings, properties, name, x => x, defaultValue);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication.OAuth
|
||||
{
|
||||
public class OAuthChallengeProperties : AuthenticationProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// The parameter key for the "scope" argument being used for a challenge request.
|
||||
/// </summary>
|
||||
public static readonly string ScopeKey = "scope";
|
||||
|
||||
public OAuthChallengeProperties()
|
||||
{ }
|
||||
|
||||
public OAuthChallengeProperties(IDictionary<string, string> items)
|
||||
: base(items)
|
||||
{ }
|
||||
|
||||
public OAuthChallengeProperties(IDictionary<string, string> items, IDictionary<string, object> parameters)
|
||||
: base(items, parameters)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// The "scope" parameter value being used for a challenge request.
|
||||
/// </summary>
|
||||
public ICollection<string> Scope
|
||||
{
|
||||
get => GetParameter<ICollection<string>>(ScopeKey);
|
||||
set => SetParameter(ScopeKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the "scope" parameter value.
|
||||
/// </summary>
|
||||
/// <param name="scopes">List of scopes.</param>
|
||||
public virtual void SetScope(params string[] scopes)
|
||||
{
|
||||
Scope = scopes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -209,7 +209,8 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
|
|||
|
||||
protected virtual string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
|
||||
{
|
||||
var scope = FormatScope();
|
||||
var scopeParameter = properties.GetParameter<ICollection<string>>(OAuthChallengeProperties.ScopeKey);
|
||||
var scope = scopeParameter != null ? FormatScope(scopeParameter) : FormatScope();
|
||||
|
||||
var state = Options.StateDataFormat.Protect(properties);
|
||||
var parameters = new Dictionary<string, string>
|
||||
|
|
@ -223,10 +224,20 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
|
|||
return QueryHelpers.AddQueryString(Options.AuthorizationEndpoint, parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format a list of OAuth scopes.
|
||||
/// </summary>
|
||||
/// <param name="scopes">List of scopes.</param>
|
||||
/// <returns>Formatted scopes.</returns>
|
||||
protected virtual string FormatScope(IEnumerable<string> scopes)
|
||||
=> string.Join(" ", scopes); // OAuth2 3.3 space separated
|
||||
|
||||
/// <summary>
|
||||
/// Format the <see cref="OAuthOptions.Scope"/> property.
|
||||
/// </summary>
|
||||
/// <returns>Formatted scopes.</returns>
|
||||
/// <remarks>Subclasses should rather override <see cref="FormatScope(IEnumerable{string})"/>.</remarks>
|
||||
protected virtual string FormatScope()
|
||||
{
|
||||
// OAuth2 3.3 space separated
|
||||
return string.Join(" ", Options.Scope);
|
||||
}
|
||||
=> FormatScope(Options.Scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||
{
|
||||
public class OpenIdConnectChallengeProperties : OAuthChallengeProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// The parameter key for the "max_age" argument being used for a challenge request.
|
||||
/// </summary>
|
||||
public static readonly string MaxAgeKey = OpenIdConnectParameterNames.MaxAge;
|
||||
|
||||
/// <summary>
|
||||
/// The parameter key for the "prompt" argument being used for a challenge request.
|
||||
/// </summary>
|
||||
public static readonly string PromptKey = OpenIdConnectParameterNames.Prompt;
|
||||
|
||||
public OpenIdConnectChallengeProperties()
|
||||
{ }
|
||||
|
||||
public OpenIdConnectChallengeProperties(IDictionary<string, string> items)
|
||||
: base(items)
|
||||
{ }
|
||||
|
||||
public OpenIdConnectChallengeProperties(IDictionary<string, string> items, IDictionary<string, object> parameters)
|
||||
: base(items, parameters)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// The "max_age" parameter value being used for a challenge request.
|
||||
/// </summary>
|
||||
public TimeSpan? MaxAge
|
||||
{
|
||||
get => GetParameter<TimeSpan?>(MaxAgeKey);
|
||||
set => SetParameter(MaxAgeKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "prompt" parameter value being used for a challenge request.
|
||||
/// </summary>
|
||||
public string Prompt
|
||||
{
|
||||
get => GetParameter<string>(PromptKey);
|
||||
set => SetParameter(PromptKey, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -329,15 +329,16 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
RedirectUri = BuildRedirectUri(Options.CallbackPath),
|
||||
Resource = Options.Resource,
|
||||
ResponseType = Options.ResponseType,
|
||||
Prompt = Options.Prompt,
|
||||
Scope = string.Join(" ", Options.Scope)
|
||||
Prompt = properties.GetParameter<string>(OpenIdConnectParameterNames.Prompt) ?? Options.Prompt,
|
||||
Scope = string.Join(" ", properties.GetParameter<ICollection<string>>(OpenIdConnectParameterNames.Scope) ?? Options.Scope),
|
||||
};
|
||||
|
||||
// Add the 'max_age' parameter to the authentication request if MaxAge is not null.
|
||||
// See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
if (Options.MaxAge.HasValue)
|
||||
var maxAge = properties.GetParameter<TimeSpan?>(OpenIdConnectParameterNames.MaxAge) ?? Options.MaxAge;
|
||||
if (maxAge.HasValue)
|
||||
{
|
||||
message.MaxAge = Convert.ToInt64(Math.Floor((Options.MaxAge.Value).TotalSeconds))
|
||||
message.MaxAge = Convert.ToInt64(Math.Floor((maxAge.Value).TotalSeconds))
|
||||
.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
|
|
@ -783,7 +784,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
/// <param name="properties">The authentication properties.</param>
|
||||
/// <returns><see cref="HandleRequestResult"/> which is used to determine if the remote authentication was successful.</returns>
|
||||
protected virtual async Task<HandleRequestResult> GetUserInformationAsync(
|
||||
OpenIdConnectMessage message, JwtSecurityToken jwt,
|
||||
OpenIdConnectMessage message, JwtSecurityToken jwt,
|
||||
ClaimsPrincipal principal, AuthenticationProperties properties)
|
||||
{
|
||||
var userInfoEndpoint = _configuration?.UserInfoEndpoint;
|
||||
|
|
|
|||
|
|
@ -426,15 +426,15 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
{
|
||||
var server = CreateServer(
|
||||
app => { },
|
||||
services => services.AddAuthentication().AddFacebook(o => {
|
||||
services => services.AddAuthentication().AddFacebook(o =>
|
||||
{
|
||||
o.AppId = "whatever";
|
||||
o.AppSecret = "whatever";
|
||||
o.SignInScheme = FacebookDefaults.AuthenticationScheme;
|
||||
}),
|
||||
context =>
|
||||
async context =>
|
||||
{
|
||||
// Gross
|
||||
context.ChallengeAsync("Facebook").GetAwaiter().GetResult();
|
||||
await context.ChallengeAsync("Facebook");
|
||||
return true;
|
||||
});
|
||||
var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
|
||||
|
|
@ -446,14 +446,14 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
{
|
||||
var server = CreateServer(
|
||||
app => { },
|
||||
services => services.AddAuthentication(o => o.DefaultScheme = FacebookDefaults.AuthenticationScheme).AddFacebook(o => {
|
||||
services => services.AddAuthentication(o => o.DefaultScheme = FacebookDefaults.AuthenticationScheme).AddFacebook(o =>
|
||||
{
|
||||
o.AppId = "whatever";
|
||||
o.AppSecret = "whatever";
|
||||
}),
|
||||
context =>
|
||||
async context =>
|
||||
{
|
||||
// Gross
|
||||
context.ChallengeAsync("Facebook").GetAwaiter().GetResult();
|
||||
await context.ChallengeAsync("Facebook");
|
||||
return true;
|
||||
});
|
||||
var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
|
||||
|
|
@ -465,14 +465,14 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
{
|
||||
var server = CreateServer(
|
||||
app => { },
|
||||
services => services.AddAuthentication(o => o.DefaultSignInScheme = FacebookDefaults.AuthenticationScheme).AddFacebook(o => {
|
||||
services => services.AddAuthentication(o => o.DefaultSignInScheme = FacebookDefaults.AuthenticationScheme).AddFacebook(o =>
|
||||
{
|
||||
o.AppId = "whatever";
|
||||
o.AppSecret = "whatever";
|
||||
}),
|
||||
context =>
|
||||
async context =>
|
||||
{
|
||||
// Gross
|
||||
context.ChallengeAsync("Facebook").GetAwaiter().GetResult();
|
||||
await context.ChallengeAsync("Facebook");
|
||||
return true;
|
||||
});
|
||||
var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
|
||||
|
|
@ -498,10 +498,9 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
var server = CreateServer(
|
||||
app => { },
|
||||
services => services.AddAuthentication().AddFacebook(o => o.SignInScheme = "Whatever"),
|
||||
context =>
|
||||
async context =>
|
||||
{
|
||||
// REVIEW: Gross.
|
||||
Assert.Throws<ArgumentException>("AppId", () => context.ChallengeAsync("Facebook").GetAwaiter().GetResult());
|
||||
await Assert.ThrowsAsync<ArgumentException>("AppId", () => context.ChallengeAsync("Facebook"));
|
||||
return true;
|
||||
});
|
||||
var transaction = await server.SendAsync("http://example.com/challenge");
|
||||
|
|
@ -514,10 +513,9 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
var server = CreateServer(
|
||||
app => { },
|
||||
services => services.AddAuthentication().AddFacebook(o => o.AppId = "Whatever"),
|
||||
context =>
|
||||
async context =>
|
||||
{
|
||||
// REVIEW: Gross.
|
||||
Assert.Throws<ArgumentException>("AppSecret", () => context.ChallengeAsync("Facebook").GetAwaiter().GetResult());
|
||||
await Assert.ThrowsAsync<ArgumentException>("AppSecret", () => context.ChallengeAsync("Facebook"));
|
||||
return true;
|
||||
});
|
||||
var transaction = await server.SendAsync("http://example.com/challenge");
|
||||
|
|
@ -550,10 +548,9 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
};
|
||||
});
|
||||
},
|
||||
context =>
|
||||
async context =>
|
||||
{
|
||||
// REVIEW: Gross.
|
||||
context.ChallengeAsync("Facebook").GetAwaiter().GetResult();
|
||||
await context.ChallengeAsync("Facebook");
|
||||
return true;
|
||||
});
|
||||
var transaction = await server.SendAsync("http://example.com/challenge");
|
||||
|
|
@ -562,6 +559,97 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
Assert.Contains("custom=test", query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillIncludeScopeAsConfigured()
|
||||
{
|
||||
var server = CreateServer(
|
||||
app => app.UseAuthentication(),
|
||||
services =>
|
||||
{
|
||||
services.AddAuthentication().AddFacebook(o =>
|
||||
{
|
||||
o.AppId = "Test App Id";
|
||||
o.AppSecret = "Test App Secret";
|
||||
o.Scope.Clear();
|
||||
o.Scope.Add("foo");
|
||||
o.Scope.Add("bar");
|
||||
});
|
||||
},
|
||||
async context =>
|
||||
{
|
||||
await context.ChallengeAsync(FacebookDefaults.AuthenticationScheme);
|
||||
return true;
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("http://example.com/challenge");
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
Assert.Contains("scope=foo,bar", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillIncludeScopeAsOverwritten()
|
||||
{
|
||||
var server = CreateServer(
|
||||
app => app.UseAuthentication(),
|
||||
services =>
|
||||
{
|
||||
services.AddAuthentication().AddFacebook(o =>
|
||||
{
|
||||
o.AppId = "Test App Id";
|
||||
o.AppSecret = "Test App Secret";
|
||||
o.Scope.Clear();
|
||||
o.Scope.Add("foo");
|
||||
o.Scope.Add("bar");
|
||||
});
|
||||
},
|
||||
async context =>
|
||||
{
|
||||
var properties = new OAuthChallengeProperties();
|
||||
properties.SetScope("baz", "qux");
|
||||
await context.ChallengeAsync(FacebookDefaults.AuthenticationScheme, properties);
|
||||
return true;
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("http://example.com/challenge");
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
Assert.Contains("scope=baz,qux", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillIncludeScopeAsOverwrittenWithBaseAuthenticationProperties()
|
||||
{
|
||||
var server = CreateServer(
|
||||
app => app.UseAuthentication(),
|
||||
services =>
|
||||
{
|
||||
services.AddAuthentication().AddFacebook(o =>
|
||||
{
|
||||
o.AppId = "Test App Id";
|
||||
o.AppSecret = "Test App Secret";
|
||||
o.Scope.Clear();
|
||||
o.Scope.Add("foo");
|
||||
o.Scope.Add("bar");
|
||||
});
|
||||
},
|
||||
async context =>
|
||||
{
|
||||
var properties = new AuthenticationProperties();
|
||||
properties.SetParameter(OAuthChallengeProperties.ScopeKey, new string[] { "baz", "qux" });
|
||||
await context.ChallengeAsync(FacebookDefaults.AuthenticationScheme, properties);
|
||||
return true;
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("http://example.com/challenge");
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
Assert.Contains("scope=baz,qux", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NestedMapWillNotAffectRedirect()
|
||||
{
|
||||
|
|
@ -620,7 +708,7 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
Assert.Contains("https://www.facebook.com/v2.12/dialog/oauth", location);
|
||||
Assert.Contains("response_type=code", location);
|
||||
Assert.Contains("client_id=", location);
|
||||
Assert.Contains("redirect_uri="+ UrlEncoder.Default.Encode("http://example.com/signin-facebook"), location);
|
||||
Assert.Contains("redirect_uri=" + UrlEncoder.Default.Encode("http://example.com/signin-facebook"), location);
|
||||
Assert.Contains("scope=", location);
|
||||
Assert.Contains("state=", location);
|
||||
}
|
||||
|
|
@ -643,10 +731,9 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
o.AppSecret = "Test App Secret";
|
||||
});
|
||||
},
|
||||
context =>
|
||||
async context =>
|
||||
{
|
||||
// REVIEW: gross
|
||||
context.ChallengeAsync("Facebook").GetAwaiter().GetResult();
|
||||
await context.ChallengeAsync("Facebook");
|
||||
return true;
|
||||
});
|
||||
var transaction = await server.SendAsync("http://example.com/challenge");
|
||||
|
|
@ -672,7 +759,7 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
{
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie()
|
||||
.AddFacebook(o =>
|
||||
.AddFacebook(o =>
|
||||
{
|
||||
o.AppId = "Test App Id";
|
||||
o.AppSecret = "Test App Secret";
|
||||
|
|
@ -728,7 +815,7 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
Assert.Contains("&access_token=", finalUserInfoEndpoint);
|
||||
}
|
||||
|
||||
private static TestServer CreateServer(Action<IApplicationBuilder> configure, Action<IServiceCollection> configureServices, Func<HttpContext, bool> handler)
|
||||
private static TestServer CreateServer(Action<IApplicationBuilder> configure, Action<IServiceCollection> configureServices, Func<HttpContext, Task<bool>> handler)
|
||||
{
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
|
|
@ -736,7 +823,7 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
|
|||
configure?.Invoke(app);
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (handler == null || !handler(context))
|
||||
if (handler == null || !await handler(context))
|
||||
{
|
||||
await next();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using Microsoft.AspNetCore.DataProtection;
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -545,43 +546,160 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillUseAuthenticationPropertiesAsParameters()
|
||||
public async Task ChallengeWillUseAuthenticationPropertiesParametersAsQueryArguments()
|
||||
{
|
||||
var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(o =>
|
||||
{
|
||||
o.ClientId = "Test Id";
|
||||
o.ClientSecret = "Test Secret";
|
||||
//AutomaticChallenge = true
|
||||
o.StateDataFormat = stateFormat;
|
||||
},
|
||||
context =>
|
||||
{
|
||||
var req = context.Request;
|
||||
var res = context.Response;
|
||||
if (req.Path == new PathString("/challenge2"))
|
||||
{
|
||||
var req = context.Request;
|
||||
var res = context.Response;
|
||||
if (req.Path == new PathString("/challenge2"))
|
||||
return context.ChallengeAsync("Google", new GoogleChallengeProperties
|
||||
{
|
||||
return context.ChallengeAsync("Google", new AuthenticationProperties(
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{ "scope", "https://www.googleapis.com/auth/plus.login" },
|
||||
{ "access_type", "offline" },
|
||||
{ "approval_prompt", "force" },
|
||||
{ "prompt", "consent" },
|
||||
{ "login_hint", "test@example.com" },
|
||||
{ "include_granted_scopes", "false" }
|
||||
}));
|
||||
}
|
||||
Scope = new string[] { "openid", "https://www.googleapis.com/auth/plus.login" },
|
||||
AccessType = "offline",
|
||||
ApprovalPrompt = "force",
|
||||
Prompt = "consent",
|
||||
LoginHint = "test@example.com",
|
||||
IncludeGrantedScopes = false,
|
||||
});
|
||||
}
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
});
|
||||
return Task.FromResult<object>(null);
|
||||
});
|
||||
var transaction = await server.SendAsync("https://example.com/challenge2");
|
||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||
var query = transaction.Response.Headers.Location.Query;
|
||||
Assert.Contains("scope=" + UrlEncoder.Default.Encode("https://www.googleapis.com/auth/plus.login"), query);
|
||||
Assert.Contains("access_type=offline", query);
|
||||
Assert.Contains("approval_prompt=force", query);
|
||||
Assert.Contains("prompt=consent", query);
|
||||
Assert.Contains("include_granted_scopes=false", query);
|
||||
Assert.Contains("login_hint=" + UrlEncoder.Default.Encode("test@example.com"), query);
|
||||
|
||||
// verify query arguments
|
||||
var query = QueryHelpers.ParseQuery(transaction.Response.Headers.Location.Query);
|
||||
Assert.Equal("openid https://www.googleapis.com/auth/plus.login", query["scope"]);
|
||||
Assert.Equal("offline", query["access_type"]);
|
||||
Assert.Equal("force", query["approval_prompt"]);
|
||||
Assert.Equal("consent", query["prompt"]);
|
||||
Assert.Equal("false", query["include_granted_scopes"]);
|
||||
Assert.Equal("test@example.com", query["login_hint"]);
|
||||
|
||||
// verify that the passed items were not serialized
|
||||
var stateProperties = stateFormat.Unprotect(query["state"]);
|
||||
Assert.DoesNotContain("scope", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("access_type", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("include_granted_scopes", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("approval_prompt", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("prompt", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("login_hint", stateProperties.Items.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillUseAuthenticationPropertiesItemsAsParameters()
|
||||
{
|
||||
var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(o =>
|
||||
{
|
||||
o.ClientId = "Test Id";
|
||||
o.ClientSecret = "Test Secret";
|
||||
o.StateDataFormat = stateFormat;
|
||||
},
|
||||
context =>
|
||||
{
|
||||
var req = context.Request;
|
||||
var res = context.Response;
|
||||
if (req.Path == new PathString("/challenge2"))
|
||||
{
|
||||
return context.ChallengeAsync("Google", new AuthenticationProperties(new Dictionary<string, string>()
|
||||
{
|
||||
{ "scope", "https://www.googleapis.com/auth/plus.login" },
|
||||
{ "access_type", "offline" },
|
||||
{ "approval_prompt", "force" },
|
||||
{ "prompt", "consent" },
|
||||
{ "login_hint", "test@example.com" },
|
||||
{ "include_granted_scopes", "false" }
|
||||
}));
|
||||
}
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
});
|
||||
var transaction = await server.SendAsync("https://example.com/challenge2");
|
||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||
|
||||
// verify query arguments
|
||||
var query = QueryHelpers.ParseQuery(transaction.Response.Headers.Location.Query);
|
||||
Assert.Equal("https://www.googleapis.com/auth/plus.login", query["scope"]);
|
||||
Assert.Equal("offline", query["access_type"]);
|
||||
Assert.Equal("force", query["approval_prompt"]);
|
||||
Assert.Equal("consent", query["prompt"]);
|
||||
Assert.Equal("false", query["include_granted_scopes"]);
|
||||
Assert.Equal("test@example.com", query["login_hint"]);
|
||||
|
||||
// verify that the passed items were not serialized
|
||||
var stateProperties = stateFormat.Unprotect(query["state"]);
|
||||
Assert.DoesNotContain("scope", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("access_type", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("include_granted_scopes", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("approval_prompt", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("prompt", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("login_hint", stateProperties.Items.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillUseAuthenticationPropertiesItemsAsQueryArgumentsButParametersWillOverwrite()
|
||||
{
|
||||
var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(o =>
|
||||
{
|
||||
o.ClientId = "Test Id";
|
||||
o.ClientSecret = "Test Secret";
|
||||
o.StateDataFormat = stateFormat;
|
||||
},
|
||||
context =>
|
||||
{
|
||||
var req = context.Request;
|
||||
var res = context.Response;
|
||||
if (req.Path == new PathString("/challenge2"))
|
||||
{
|
||||
return context.ChallengeAsync("Google", new GoogleChallengeProperties(new Dictionary<string, string>
|
||||
{
|
||||
["scope"] = "https://www.googleapis.com/auth/plus.login",
|
||||
["access_type"] = "offline",
|
||||
["include_granted_scopes"] = "false",
|
||||
["approval_prompt"] = "force",
|
||||
["prompt"] = "login",
|
||||
["login_hint"] = "this-will-be-overwritten@example.com",
|
||||
})
|
||||
{
|
||||
Prompt = "consent",
|
||||
LoginHint = "test@example.com",
|
||||
});
|
||||
}
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
});
|
||||
var transaction = await server.SendAsync("https://example.com/challenge2");
|
||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||
|
||||
// verify query arguments
|
||||
var query = QueryHelpers.ParseQuery(transaction.Response.Headers.Location.Query);
|
||||
Assert.Equal("https://www.googleapis.com/auth/plus.login", query["scope"]);
|
||||
Assert.Equal("offline", query["access_type"]);
|
||||
Assert.Equal("force", query["approval_prompt"]);
|
||||
Assert.Equal("consent", query["prompt"]);
|
||||
Assert.Equal("false", query["include_granted_scopes"]);
|
||||
Assert.Equal("test@example.com", query["login_hint"]);
|
||||
|
||||
// verify that the passed items were not serialized
|
||||
var stateProperties = stateFormat.Unprotect(query["state"]);
|
||||
Assert.DoesNotContain("scope", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("access_type", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("include_granted_scopes", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("approval_prompt", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("prompt", stateProperties.Items.Keys);
|
||||
Assert.DoesNotContain("login_hint", stateProperties.Items.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -525,6 +525,57 @@ namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount
|
|||
Assert.Contains("state=", location);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillIncludeScopeAsConfigured()
|
||||
{
|
||||
var server = CreateServer(o =>
|
||||
{
|
||||
o.ClientId = "Test Id";
|
||||
o.ClientSecret = "Test Secret";
|
||||
o.Scope.Clear();
|
||||
o.Scope.Add("foo");
|
||||
o.Scope.Add("bar");
|
||||
});
|
||||
var transaction = await server.SendAsync("http://example.com/challenge");
|
||||
var res = transaction.Response;
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
Assert.Contains("scope=foo%20bar", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillIncludeScopeAsOverwritten()
|
||||
{
|
||||
var server = CreateServer(o =>
|
||||
{
|
||||
o.ClientId = "Test Id";
|
||||
o.ClientSecret = "Test Secret";
|
||||
o.Scope.Clear();
|
||||
o.Scope.Add("foo");
|
||||
o.Scope.Add("bar");
|
||||
});
|
||||
var transaction = await server.SendAsync("http://example.com/challengeWithOtherScope");
|
||||
var res = transaction.Response;
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
Assert.Contains("scope=baz%20qux", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillIncludeScopeAsOverwrittenWithBaseAuthenticationProperties()
|
||||
{
|
||||
var server = CreateServer(o =>
|
||||
{
|
||||
o.ClientId = "Test Id";
|
||||
o.ClientSecret = "Test Secret";
|
||||
o.Scope.Clear();
|
||||
o.Scope.Add("foo");
|
||||
o.Scope.Add("bar");
|
||||
});
|
||||
var transaction = await server.SendAsync("http://example.com/challengeWithOtherScopeWithBaseAuthenticationProperties");
|
||||
var res = transaction.Response;
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
Assert.Contains("scope=baz%20qux", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AuthenticatedEventCanGetRefreshToken()
|
||||
{
|
||||
|
|
@ -608,6 +659,18 @@ namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount
|
|||
{
|
||||
await context.ChallengeAsync("Microsoft");
|
||||
}
|
||||
else if (req.Path == new PathString("/challengeWithOtherScope"))
|
||||
{
|
||||
var properties = new OAuthChallengeProperties();
|
||||
properties.SetScope("baz", "qux");
|
||||
await context.ChallengeAsync("Microsoft", properties);
|
||||
}
|
||||
else if (req.Path == new PathString("/challengeWithOtherScopeWithBaseAuthenticationProperties"))
|
||||
{
|
||||
var properties = new AuthenticationProperties();
|
||||
properties.SetParameter(OAuthChallengeProperties.ScopeKey, new string[] { "baz", "qux" });
|
||||
await context.ChallengeAsync("Microsoft", properties);
|
||||
}
|
||||
else if (req.Path == new PathString("/me"))
|
||||
{
|
||||
res.Describe(context.User);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Authentication.Google;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication.Test
|
||||
{
|
||||
public class OAuthChallengePropertiesTest
|
||||
{
|
||||
[Fact]
|
||||
public void ScopeProperty()
|
||||
{
|
||||
var properties = new OAuthChallengeProperties
|
||||
{
|
||||
Scope = new string[] { "foo", "bar" }
|
||||
};
|
||||
Assert.Equal(new string[] { "foo", "bar" }, properties.Scope);
|
||||
Assert.Equal(new string[] { "foo", "bar" }, properties.Parameters["scope"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScopeProperty_NullValue()
|
||||
{
|
||||
var properties = new OAuthChallengeProperties();
|
||||
properties.Parameters["scope"] = new string[] { "foo", "bar" };
|
||||
Assert.Equal(new string[] { "foo", "bar" }, properties.Scope);
|
||||
|
||||
properties.Scope = null;
|
||||
Assert.Null(properties.Scope);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetScope()
|
||||
{
|
||||
var properties = new OAuthChallengeProperties();
|
||||
properties.SetScope("foo", "bar");
|
||||
Assert.Equal(new string[] { "foo", "bar" }, properties.Scope);
|
||||
Assert.Equal(new string[] { "foo", "bar" }, properties.Parameters["scope"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OidcMaxAge()
|
||||
{
|
||||
var properties = new OpenIdConnectChallengeProperties()
|
||||
{
|
||||
MaxAge = TimeSpan.FromSeconds(200)
|
||||
};
|
||||
Assert.Equal(TimeSpan.FromSeconds(200), properties.MaxAge);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OidcMaxAge_NullValue()
|
||||
{
|
||||
var properties = new OpenIdConnectChallengeProperties();
|
||||
properties.Parameters["max_age"] = TimeSpan.FromSeconds(500);
|
||||
Assert.Equal(TimeSpan.FromSeconds(500), properties.MaxAge);
|
||||
|
||||
properties.MaxAge = null;
|
||||
Assert.Null(properties.MaxAge);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OidcPrompt()
|
||||
{
|
||||
var properties = new OpenIdConnectChallengeProperties()
|
||||
{
|
||||
Prompt = "login"
|
||||
};
|
||||
Assert.Equal("login", properties.Prompt);
|
||||
Assert.Equal("login", properties.Parameters["prompt"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OidcPrompt_NullValue()
|
||||
{
|
||||
var properties = new OpenIdConnectChallengeProperties();
|
||||
properties.Parameters["prompt"] = "consent";
|
||||
Assert.Equal("consent", properties.Prompt);
|
||||
|
||||
properties.Prompt = null;
|
||||
Assert.Null(properties.Prompt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GoogleProperties()
|
||||
{
|
||||
var properties = new GoogleChallengeProperties()
|
||||
{
|
||||
AccessType = "offline",
|
||||
ApprovalPrompt = "force",
|
||||
LoginHint = "test@example.com",
|
||||
Prompt = "login",
|
||||
};
|
||||
Assert.Equal("offline", properties.AccessType);
|
||||
Assert.Equal("offline", properties.Parameters["access_type"]);
|
||||
Assert.Equal("force", properties.ApprovalPrompt);
|
||||
Assert.Equal("force", properties.Parameters["approval_prompt"]);
|
||||
Assert.Equal("test@example.com", properties.LoginHint);
|
||||
Assert.Equal("test@example.com", properties.Parameters["login_hint"]);
|
||||
Assert.Equal("login", properties.Prompt);
|
||||
Assert.Equal("login", properties.Parameters["prompt"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GoogleProperties_NullValues()
|
||||
{
|
||||
var properties = new GoogleChallengeProperties();
|
||||
properties.Parameters["access_type"] = "offline";
|
||||
properties.Parameters["approval_prompt"] = "force";
|
||||
properties.Parameters["login_hint"] = "test@example.com";
|
||||
properties.Parameters["prompt"] = "login";
|
||||
Assert.Equal("offline", properties.AccessType);
|
||||
Assert.Equal("force", properties.ApprovalPrompt);
|
||||
Assert.Equal("test@example.com", properties.LoginHint);
|
||||
Assert.Equal("login", properties.Prompt);
|
||||
|
||||
properties.AccessType = null;
|
||||
Assert.Null(properties.AccessType);
|
||||
|
||||
properties.ApprovalPrompt = null;
|
||||
Assert.Null(properties.ApprovalPrompt);
|
||||
|
||||
properties.LoginHint = null;
|
||||
Assert.Null(properties.LoginHint);
|
||||
|
||||
properties.Prompt = null;
|
||||
Assert.Null(properties.Prompt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GoogleIncludeGrantedScopes()
|
||||
{
|
||||
var properties = new GoogleChallengeProperties()
|
||||
{
|
||||
IncludeGrantedScopes = true
|
||||
};
|
||||
Assert.True(properties.IncludeGrantedScopes);
|
||||
Assert.Equal(true, properties.Parameters["include_granted_scopes"]);
|
||||
|
||||
properties.IncludeGrantedScopes = false;
|
||||
Assert.False(properties.IncludeGrantedScopes);
|
||||
Assert.Equal(false, properties.Parameters["include_granted_scopes"]);
|
||||
|
||||
properties.IncludeGrantedScopes = null;
|
||||
Assert.Null(properties.IncludeGrantedScopes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -572,6 +572,88 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
|
|||
Assert.Contains("path=/", correlation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RedirectToAuthorizeEndpoint_HasScopeAsConfigured()
|
||||
{
|
||||
var server = CreateServer(
|
||||
s => s.AddAuthentication().AddOAuth(
|
||||
"Weblie",
|
||||
opt =>
|
||||
{
|
||||
ConfigureDefaults(opt);
|
||||
opt.Scope.Clear();
|
||||
opt.Scope.Add("foo");
|
||||
opt.Scope.Add("bar");
|
||||
}),
|
||||
async ctx =>
|
||||
{
|
||||
await ctx.ChallengeAsync("Weblie");
|
||||
return true;
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("https://www.example.com/challenge");
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
Assert.Contains("scope=foo%20bar", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RedirectToAuthorizeEndpoint_HasScopeAsOverwritten()
|
||||
{
|
||||
var server = CreateServer(
|
||||
s => s.AddAuthentication().AddOAuth(
|
||||
"Weblie",
|
||||
opt =>
|
||||
{
|
||||
ConfigureDefaults(opt);
|
||||
opt.Scope.Clear();
|
||||
opt.Scope.Add("foo");
|
||||
opt.Scope.Add("bar");
|
||||
}),
|
||||
async ctx =>
|
||||
{
|
||||
var properties = new OAuthChallengeProperties();
|
||||
properties.SetScope("baz", "qux");
|
||||
await ctx.ChallengeAsync("Weblie", properties);
|
||||
return true;
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("https://www.example.com/challenge");
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
Assert.Contains("scope=baz%20qux", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RedirectToAuthorizeEndpoint_HasScopeAsOverwrittenWithBaseAuthenticationProperties()
|
||||
{
|
||||
var server = CreateServer(
|
||||
s => s.AddAuthentication().AddOAuth(
|
||||
"Weblie",
|
||||
opt =>
|
||||
{
|
||||
ConfigureDefaults(opt);
|
||||
opt.Scope.Clear();
|
||||
opt.Scope.Add("foo");
|
||||
opt.Scope.Add("bar");
|
||||
}),
|
||||
async ctx =>
|
||||
{
|
||||
var properties = new AuthenticationProperties();
|
||||
properties.SetParameter(OAuthChallengeProperties.ScopeKey, new string[] { "baz", "qux" });
|
||||
await ctx.ChallengeAsync("Weblie", properties);
|
||||
return true;
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("https://www.example.com/challenge");
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
Assert.Contains("scope=baz%20qux", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
private void ConfigureDefaults(OAuthOptions o)
|
||||
{
|
||||
o.ClientId = "Test Id";
|
||||
|
|
|
|||
|
|
@ -51,14 +51,14 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
|
|||
[Fact]
|
||||
public async Task AuthorizationRequestDoesNotIncludeTelemetryParametersWhenDisabled()
|
||||
{
|
||||
var setting = new TestSettings(opt =>
|
||||
var settings = new TestSettings(opt =>
|
||||
{
|
||||
opt.ClientId = "Test Id";
|
||||
opt.Authority = TestServerBuilder.DefaultAuthority;
|
||||
opt.DisableTelemetry = true;
|
||||
});
|
||||
|
||||
var server = setting.CreateTestServer();
|
||||
var server = settings.CreateTestServer();
|
||||
var transaction = await server.SendAsync(ChallengeEndpoint);
|
||||
|
||||
var res = transaction.Response;
|
||||
|
|
@ -425,6 +425,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
|
|||
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
settings.ValidateChallengeRedirect(
|
||||
res.Headers.Location,
|
||||
OpenIdConnectParameterNames.MaxAge);
|
||||
|
|
@ -446,9 +447,170 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
|
|||
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
settings.ValidateChallengeRedirect(
|
||||
res.Headers.Location,
|
||||
OpenIdConnectParameterNames.MaxAge);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Challenge_HasExpectedPromptParam()
|
||||
{
|
||||
var settings = new TestSettings(opt =>
|
||||
{
|
||||
opt.ClientId = "Test Id";
|
||||
opt.Authority = TestServerBuilder.DefaultAuthority;
|
||||
opt.Prompt = "consent";
|
||||
});
|
||||
|
||||
var server = settings.CreateTestServer();
|
||||
var transaction = await server.SendAsync(ChallengeEndpoint);
|
||||
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
settings.ValidateChallengeRedirect(res.Headers.Location, OpenIdConnectParameterNames.Prompt);
|
||||
Assert.Contains("prompt=consent", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Challenge_HasOverwrittenPromptParam()
|
||||
{
|
||||
var settings = new TestSettings(opt =>
|
||||
{
|
||||
opt.ClientId = "Test Id";
|
||||
opt.Authority = TestServerBuilder.DefaultAuthority;
|
||||
opt.Prompt = "consent";
|
||||
});
|
||||
var properties = new OpenIdConnectChallengeProperties()
|
||||
{
|
||||
Prompt = "login",
|
||||
};
|
||||
|
||||
var server = settings.CreateTestServer(properties);
|
||||
var transaction = await server.SendAsync(TestServerBuilder.TestHost + TestServerBuilder.ChallengeWithProperties);
|
||||
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
settings.ValidateChallengeRedirect(res.Headers.Location);
|
||||
Assert.Contains("prompt=login", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Challenge_HasOverwrittenPromptParamFromBaseAuthenticationProperties()
|
||||
{
|
||||
var settings = new TestSettings(opt =>
|
||||
{
|
||||
opt.ClientId = "Test Id";
|
||||
opt.Authority = TestServerBuilder.DefaultAuthority;
|
||||
opt.Prompt = "consent";
|
||||
});
|
||||
var properties = new AuthenticationProperties();
|
||||
properties.SetParameter(OpenIdConnectChallengeProperties.PromptKey, "login");
|
||||
|
||||
var server = settings.CreateTestServer(properties);
|
||||
var transaction = await server.SendAsync(TestServerBuilder.TestHost + TestServerBuilder.ChallengeWithProperties);
|
||||
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
settings.ValidateChallengeRedirect(res.Headers.Location);
|
||||
Assert.Contains("prompt=login", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Challenge_HasOverwrittenScopeParam()
|
||||
{
|
||||
var settings = new TestSettings(opt =>
|
||||
{
|
||||
opt.ClientId = "Test Id";
|
||||
opt.Authority = TestServerBuilder.DefaultAuthority;
|
||||
opt.Scope.Clear();
|
||||
opt.Scope.Add("foo");
|
||||
opt.Scope.Add("bar");
|
||||
});
|
||||
var properties = new OpenIdConnectChallengeProperties();
|
||||
properties.SetScope("baz", "qux");
|
||||
|
||||
var server = settings.CreateTestServer(properties);
|
||||
var transaction = await server.SendAsync(TestServerBuilder.TestHost + TestServerBuilder.ChallengeWithProperties);
|
||||
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
settings.ValidateChallengeRedirect(res.Headers.Location);
|
||||
Assert.Contains("scope=baz%20qux", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Challenge_HasOverwrittenScopeParamFromBaseAuthenticationProperties()
|
||||
{
|
||||
var settings = new TestSettings(opt =>
|
||||
{
|
||||
opt.ClientId = "Test Id";
|
||||
opt.Authority = TestServerBuilder.DefaultAuthority;
|
||||
opt.Scope.Clear();
|
||||
opt.Scope.Add("foo");
|
||||
opt.Scope.Add("bar");
|
||||
});
|
||||
var properties = new AuthenticationProperties();
|
||||
properties.SetParameter(OpenIdConnectChallengeProperties.ScopeKey, new string[] { "baz", "qux" });
|
||||
|
||||
var server = settings.CreateTestServer(properties);
|
||||
var transaction = await server.SendAsync(TestServerBuilder.TestHost + TestServerBuilder.ChallengeWithProperties);
|
||||
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
settings.ValidateChallengeRedirect(res.Headers.Location);
|
||||
Assert.Contains("scope=baz%20qux", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Challenge_HasOverwrittenMaxAgeParam()
|
||||
{
|
||||
var settings = new TestSettings(opt =>
|
||||
{
|
||||
opt.ClientId = "Test Id";
|
||||
opt.Authority = TestServerBuilder.DefaultAuthority;
|
||||
opt.MaxAge = TimeSpan.FromSeconds(500);
|
||||
});
|
||||
var properties = new OpenIdConnectChallengeProperties()
|
||||
{
|
||||
MaxAge = TimeSpan.FromSeconds(1234),
|
||||
};
|
||||
|
||||
var server = settings.CreateTestServer(properties);
|
||||
var transaction = await server.SendAsync(TestServerBuilder.TestHost + TestServerBuilder.ChallengeWithProperties);
|
||||
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
settings.ValidateChallengeRedirect(res.Headers.Location);
|
||||
Assert.Contains("max_age=1234", res.Headers.Location.Query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Challenge_HasOverwrittenMaxAgeParaFromBaseAuthenticationPropertiesm()
|
||||
{
|
||||
var settings = new TestSettings(opt =>
|
||||
{
|
||||
opt.ClientId = "Test Id";
|
||||
opt.Authority = TestServerBuilder.DefaultAuthority;
|
||||
opt.MaxAge = TimeSpan.FromSeconds(500);
|
||||
});
|
||||
var properties = new AuthenticationProperties();
|
||||
properties.SetParameter(OpenIdConnectChallengeProperties.MaxAgeKey, TimeSpan.FromSeconds(1234));
|
||||
|
||||
var server = settings.CreateTestServer(properties);
|
||||
var transaction = await server.SendAsync(TestServerBuilder.TestHost + TestServerBuilder.ChallengeWithProperties);
|
||||
|
||||
var res = transaction.Response;
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||
settings.ValidateChallengeRedirect(res.Headers.Location);
|
||||
Assert.Contains("max_age=1234", res.Headers.Location.Query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -206,6 +206,9 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
|
|||
case OpenIdConnectParameterNames.MaxAge:
|
||||
ValidateMaxAge(actualValues, errors, htmlEncoded);
|
||||
break;
|
||||
case OpenIdConnectParameterNames.Prompt:
|
||||
ValidatePrompt(actualValues, errors, htmlEncoded);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown parameter \"{paramToValidate}\".");
|
||||
}
|
||||
|
|
@ -284,6 +287,9 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
|
|||
}
|
||||
}
|
||||
|
||||
private void ValidatePrompt(IDictionary<string, string> actualParams, ICollection<string> errors, bool htmlEncoded) =>
|
||||
ValidateParameter(OpenIdConnectParameterNames.Prompt, _options.Prompt, actualParams, errors, htmlEncoded);
|
||||
|
||||
private void ValidateParameter(
|
||||
string parameterName,
|
||||
string expectedValue,
|
||||
|
|
|
|||
Loading…
Reference in New Issue