parent
2ea746f37d
commit
4fa5a228cf
|
|
@ -71,27 +71,40 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
|
||||||
// Since it's a frequent scenario (that is not caused by incorrect configuration),
|
// Since it's a frequent scenario (that is not caused by incorrect configuration),
|
||||||
// denied errors are handled differently using HandleAccessDeniedErrorAsync().
|
// denied errors are handled differently using HandleAccessDeniedErrorAsync().
|
||||||
// Visit https://tools.ietf.org/html/rfc6749#section-4.1.2.1 for more information.
|
// Visit https://tools.ietf.org/html/rfc6749#section-4.1.2.1 for more information.
|
||||||
|
var errorDescription = query["error_description"];
|
||||||
|
var errorUri = query["error_uri"];
|
||||||
if (StringValues.Equals(error, "access_denied"))
|
if (StringValues.Equals(error, "access_denied"))
|
||||||
{
|
{
|
||||||
var result = await HandleAccessDeniedErrorAsync(properties);
|
var result = await HandleAccessDeniedErrorAsync(properties);
|
||||||
return !result.None ? result
|
if (!result.None)
|
||||||
: HandleRequestResult.Fail("Access was denied by the resource owner or by the remote server.", properties);
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
var deniedEx = new Exception("Access was denied by the resource owner or by the remote server.");
|
||||||
|
deniedEx.Data["error"] = error.ToString();
|
||||||
|
deniedEx.Data["error_description"] = errorDescription.ToString();
|
||||||
|
deniedEx.Data["error_uri"] = errorUri.ToString();
|
||||||
|
|
||||||
|
return HandleRequestResult.Fail(deniedEx, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
var failureMessage = new StringBuilder();
|
var failureMessage = new StringBuilder();
|
||||||
failureMessage.Append(error);
|
failureMessage.Append(error);
|
||||||
var errorDescription = query["error_description"];
|
|
||||||
if (!StringValues.IsNullOrEmpty(errorDescription))
|
if (!StringValues.IsNullOrEmpty(errorDescription))
|
||||||
{
|
{
|
||||||
failureMessage.Append(";Description=").Append(errorDescription);
|
failureMessage.Append(";Description=").Append(errorDescription);
|
||||||
}
|
}
|
||||||
var errorUri = query["error_uri"];
|
|
||||||
if (!StringValues.IsNullOrEmpty(errorUri))
|
if (!StringValues.IsNullOrEmpty(errorUri))
|
||||||
{
|
{
|
||||||
failureMessage.Append(";Uri=").Append(errorUri);
|
failureMessage.Append(";Uri=").Append(errorUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
return HandleRequestResult.Fail(failureMessage.ToString(), properties);
|
var ex = new Exception(failureMessage.ToString());
|
||||||
|
ex.Data["error"] = error.ToString();
|
||||||
|
ex.Data["error_description"] = errorDescription.ToString();
|
||||||
|
ex.Data["error_uri"] = errorUri.ToString();
|
||||||
|
|
||||||
|
return HandleRequestResult.Fail(ex, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
var code = query["code"];
|
var code = query["code"];
|
||||||
|
|
|
||||||
|
|
@ -1309,12 +1309,16 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||||
Logger.ResponseError(message.Error, description, errorUri);
|
Logger.ResponseError(message.Error, description, errorUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OpenIdConnectProtocolException(string.Format(
|
var ex = new OpenIdConnectProtocolException(string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
Resources.MessageContainsError,
|
Resources.MessageContainsError,
|
||||||
message.Error,
|
message.Error,
|
||||||
description,
|
description,
|
||||||
errorUri));
|
errorUri));
|
||||||
|
ex.Data["error"] = message.Error;
|
||||||
|
ex.Data["error_description"] = description;
|
||||||
|
ex.Data["error_uri"] = errorUri;
|
||||||
|
return ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -420,6 +420,50 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
||||||
Assert.Equal("/custom-denied-page?rurl=http%3A%2F%2Fwww.google.com%2F", transaction.Response.Headers.GetValues("Location").First());
|
Assert.Equal("/custom-denied-page?rurl=http%3A%2F%2Fwww.google.com%2F", transaction.Response.Headers.GetValues("Location").First());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReplyPathWithAccessDeniedErrorAndNoAccessDeniedPath_FallsBackToRemoteError()
|
||||||
|
{
|
||||||
|
var accessDeniedCalled = false;
|
||||||
|
var remoteFailureCalled = false;
|
||||||
|
var server = CreateServer(o =>
|
||||||
|
{
|
||||||
|
o.ClientId = "Test Id";
|
||||||
|
o.ClientSecret = "Test Secret";
|
||||||
|
o.StateDataFormat = new TestStateDataFormat();
|
||||||
|
o.Events = new OAuthEvents()
|
||||||
|
{
|
||||||
|
OnAccessDenied = ctx =>
|
||||||
|
{
|
||||||
|
Assert.Null(ctx.AccessDeniedPath.Value);
|
||||||
|
Assert.Equal("http://testhost/redirect", ctx.ReturnUrl);
|
||||||
|
Assert.Equal("ReturnUrl", ctx.ReturnUrlParameter);
|
||||||
|
accessDeniedCalled = true;
|
||||||
|
return Task.FromResult(0);
|
||||||
|
},
|
||||||
|
OnRemoteFailure = ctx =>
|
||||||
|
{
|
||||||
|
var ex = ctx.Failure;
|
||||||
|
Assert.True(ex.Data.Contains("error"), "error");
|
||||||
|
Assert.True(ex.Data.Contains("error_description"), "error_description");
|
||||||
|
Assert.True(ex.Data.Contains("error_uri"), "error_uri");
|
||||||
|
Assert.Equal("access_denied", ex.Data["error"]);
|
||||||
|
Assert.Equal("whyitfailed", ex.Data["error_description"]);
|
||||||
|
Assert.Equal("https://example.com/fail", ex.Data["error_uri"]);
|
||||||
|
remoteFailureCalled = true;
|
||||||
|
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
|
||||||
|
ctx.HandleResponse();
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
var transaction = await server.SendAsync("https://example.com/signin-google?error=access_denied&error_description=whyitfailed&error_uri=https://example.com/fail&state=protected_state",
|
||||||
|
".AspNetCore.Correlation.Google.correlationId=N");
|
||||||
|
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||||
|
Assert.StartsWith("/error?FailureMessage=", transaction.Response.Headers.GetValues("Location").First());
|
||||||
|
Assert.True(accessDeniedCalled);
|
||||||
|
Assert.True(remoteFailureCalled);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
|
|
@ -434,24 +478,31 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
||||||
{
|
{
|
||||||
OnRemoteFailure = ctx =>
|
OnRemoteFailure = ctx =>
|
||||||
{
|
{
|
||||||
|
var ex = ctx.Failure;
|
||||||
|
Assert.True(ex.Data.Contains("error"), "error");
|
||||||
|
Assert.True(ex.Data.Contains("error_description"), "error_description");
|
||||||
|
Assert.True(ex.Data.Contains("error_uri"), "error_uri");
|
||||||
|
Assert.Equal("itfailed", ex.Data["error"]);
|
||||||
|
Assert.Equal("whyitfailed", ex.Data["error_description"]);
|
||||||
|
Assert.Equal("https://example.com/fail", ex.Data["error_uri"]);
|
||||||
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
|
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
|
||||||
ctx.HandleResponse();
|
ctx.HandleResponse();
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
} : new OAuthEvents();
|
} : new OAuthEvents();
|
||||||
});
|
});
|
||||||
var sendTask = server.SendAsync("https://example.com/signin-google?error=OMG&error_description=SoBad&error_uri=foobar&state=protected_state",
|
var sendTask = server.SendAsync("https://example.com/signin-google?error=itfailed&error_description=whyitfailed&error_uri=https://example.com/fail&state=protected_state",
|
||||||
".AspNetCore.Correlation.Google.correlationId=N");
|
".AspNetCore.Correlation.Google.correlationId=N");
|
||||||
if (redirect)
|
if (redirect)
|
||||||
{
|
{
|
||||||
var transaction = await sendTask;
|
var transaction = await sendTask;
|
||||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||||
Assert.Equal("/error?FailureMessage=OMG" + UrlEncoder.Default.Encode(";Description=SoBad;Uri=foobar"), transaction.Response.Headers.GetValues("Location").First());
|
Assert.Equal("/error?FailureMessage=itfailed" + UrlEncoder.Default.Encode(";Description=whyitfailed;Uri=https://example.com/fail"), transaction.Response.Headers.GetValues("Location").First());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var error = await Assert.ThrowsAnyAsync<Exception>(() => sendTask);
|
var error = await Assert.ThrowsAnyAsync<Exception>(() => sendTask);
|
||||||
Assert.Equal("OMG;Description=SoBad;Uri=foobar", error.GetBaseException().Message);
|
Assert.Equal("itfailed;Description=whyitfailed;Uri=https://example.com/fail", error.GetBaseException().Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -63,5 +68,73 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("Hi from the callback path", transaction.ResponseText);
|
Assert.Equal("Hi from the callback path", transaction.ResponseText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ErrorResponseWithDetails()
|
||||||
|
{
|
||||||
|
var settings = new TestSettings(
|
||||||
|
opt =>
|
||||||
|
{
|
||||||
|
opt.StateDataFormat = new TestStateDataFormat();
|
||||||
|
opt.Authority = TestServerBuilder.DefaultAuthority;
|
||||||
|
opt.ClientId = "Test Id";
|
||||||
|
opt.Events = new OpenIdConnectEvents()
|
||||||
|
{
|
||||||
|
OnRemoteFailure = ctx =>
|
||||||
|
{
|
||||||
|
var ex = ctx.Failure;
|
||||||
|
Assert.True(ex.Data.Contains("error"), "error");
|
||||||
|
Assert.True(ex.Data.Contains("error_description"), "error_description");
|
||||||
|
Assert.True(ex.Data.Contains("error_uri"), "error_uri");
|
||||||
|
Assert.Equal("itfailed", ex.Data["error"]);
|
||||||
|
Assert.Equal("whyitfailed", ex.Data["error_description"]);
|
||||||
|
Assert.Equal("https://example.com/fail", ex.Data["error_uri"]);
|
||||||
|
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
|
||||||
|
ctx.HandleResponse();
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
var server = settings.CreateTestServer();
|
||||||
|
|
||||||
|
var transaction = await server.SendAsync(
|
||||||
|
"https://example.com/signin-oidc?error=itfailed&error_description=whyitfailed&error_uri=https://example.com/fail&state=protected_state",
|
||||||
|
".AspNetCore.Correlation.OpenIdConnect.correlationId=N");
|
||||||
|
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||||
|
Assert.StartsWith("/error?FailureMessage=", transaction.Response.Headers.GetValues("Location").First());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestStateDataFormat : ISecureDataFormat<AuthenticationProperties>
|
||||||
|
{
|
||||||
|
private AuthenticationProperties Data { get; set; }
|
||||||
|
|
||||||
|
public string Protect(AuthenticationProperties data)
|
||||||
|
{
|
||||||
|
return "protected_state";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Protect(AuthenticationProperties data, string purpose)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationProperties Unprotect(string protectedText)
|
||||||
|
{
|
||||||
|
Assert.Equal("protected_state", protectedText);
|
||||||
|
var properties = new AuthenticationProperties(new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ ".xsrf", "correlationId" },
|
||||||
|
{ "testkey", "testvalue" }
|
||||||
|
});
|
||||||
|
properties.RedirectUri = "http://testhost/redirect";
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationProperties Unprotect(string protectedText, string purpose)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue