1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 08:02:49 -05:00

chore/SRE-583 Deprecate usage of Auth-Email Header (#5709)

* chore/SRE-583 Deprecate usage of Auth-Email Header

* SRE-583 cleanup function and references

* SRE-583 cleanup tests

---------

Co-authored-by: sneakernuts <671942+sneakernuts@users.noreply.github.com>
This commit is contained in:
bitwarden-charlie
2025-05-16 08:45:04 -05:00
committed by GitHub
parent d72d721684
commit 67f745ebc4
7 changed files with 22 additions and 156 deletions

View File

@ -40,8 +40,6 @@ export function authenticate(
payload["deviceName"] = "chrome";
payload["username"] = username;
payload["password"] = password;
params.headers["Auth-Email"] = encoding.b64encode(username);
} else {
payload["scope"] = "api.organization";
payload["grant_type"] = "client_credentials";

View File

@ -64,12 +64,6 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
if (!AuthEmailHeaderIsValid(context))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant,
"Auth-Email header invalid.");
return;
}
var user = await _userManager.FindByEmailAsync(context.UserName.ToLowerInvariant());
// We want to keep this device around incase the device is new for the user
@ -168,29 +162,4 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
return context.Result.Subject;
}
private bool AuthEmailHeaderIsValid(ResourceOwnerPasswordValidationContext context)
{
if (_currentContext.HttpContext.Request.Headers.TryGetValue("Auth-Email", out var authEmailHeader))
{
try
{
var authEmailDecoded = CoreHelpers.Base64UrlDecodeString(authEmailHeader);
if (authEmailDecoded != context.UserName)
{
return false;
}
}
catch (Exception e) when (e is InvalidOperationException || e is FormatException)
{
// Invalid B64 encoding
return false;
}
}
else
{
return false;
}
return true;
}
}

View File

@ -57,8 +57,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
var localFactory = new IdentityApplicationFactory();
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
var context = await PostLoginAsync(localFactory.Server, user, requestModel.MasterPasswordHash,
context => context.SetAuthEmail(user.Email));
var context = await PostLoginAsync(localFactory.Server, user, requestModel.MasterPasswordHash);
using var body = await AssertDefaultTokenBodyAsync(context);
var root = body.RootElement;
@ -72,71 +71,6 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
AssertUserDecryptionOptions(root);
}
[Theory, BitAutoData, RegisterFinishRequestModelCustomize]
public async Task TokenEndpoint_GrantTypePassword_NoAuthEmailHeader_Fails(
RegisterFinishRequestModel requestModel)
{
requestModel.Email = "test+noauthemailheader@email.com";
var localFactory = new IdentityApplicationFactory();
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
var context = await PostLoginAsync(localFactory.Server, user, requestModel.MasterPasswordHash, null);
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
var root = body.RootElement;
var error = AssertHelper.AssertJsonProperty(root, "error", JsonValueKind.String).GetString();
Assert.Equal("invalid_grant", error);
AssertHelper.AssertJsonProperty(root, "error_description", JsonValueKind.String);
}
[Theory, BitAutoData, RegisterFinishRequestModelCustomize]
public async Task TokenEndpoint_GrantTypePassword_InvalidBase64AuthEmailHeader_Fails(
RegisterFinishRequestModel requestModel)
{
requestModel.Email = "test+badauthheader@email.com";
var localFactory = new IdentityApplicationFactory();
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
var context = await PostLoginAsync(localFactory.Server, user, requestModel.MasterPasswordHash,
context => context.Request.Headers.Append("Auth-Email", "bad_value"));
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
var root = body.RootElement;
var error = AssertHelper.AssertJsonProperty(root, "error", JsonValueKind.String).GetString();
Assert.Equal("invalid_grant", error);
AssertHelper.AssertJsonProperty(root, "error_description", JsonValueKind.String);
}
[Theory, BitAutoData, RegisterFinishRequestModelCustomize]
public async Task TokenEndpoint_GrantTypePassword_WrongAuthEmailHeader_Fails(
RegisterFinishRequestModel requestModel)
{
requestModel.Email = "test+badauthheader@email.com";
var localFactory = new IdentityApplicationFactory();
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
var context = await PostLoginAsync(localFactory.Server, user, requestModel.MasterPasswordHash,
context => context.SetAuthEmail("bad_value"));
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
var root = body.RootElement;
var error = AssertHelper.AssertJsonProperty(root, "error", JsonValueKind.String).GetString();
Assert.Equal("invalid_grant", error);
AssertHelper.AssertJsonProperty(root, "error_description", JsonValueKind.String);
}
[Theory, RegisterFinishRequestModelCustomize]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]
@ -157,8 +91,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
await CreateOrganizationWithSsoPolicyAsync(localFactory,
organizationId, user.Email, organizationUserType, ssoPolicyEnabled: false);
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash,
context => context.SetAuthEmail(user.Email));
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash);
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
}
@ -184,8 +117,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
await CreateOrganizationWithSsoPolicyAsync(
localFactory, organizationId, user.Email, organizationUserType, ssoPolicyEnabled: false);
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash,
context => context.SetAuthEmail(user.Email));
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash);
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
}
@ -209,8 +141,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
await CreateOrganizationWithSsoPolicyAsync(localFactory, organizationId, user.Email, organizationUserType, ssoPolicyEnabled: true);
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash,
context => context.SetAuthEmail(user.Email));
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash);
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
await AssertRequiredSsoAuthenticationResponseAsync(context);
@ -234,8 +165,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
await CreateOrganizationWithSsoPolicyAsync(localFactory, organizationId, user.Email, organizationUserType, ssoPolicyEnabled: true);
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash,
context => context.SetAuthEmail(user.Email));
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash);
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
}
@ -258,8 +188,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
await CreateOrganizationWithSsoPolicyAsync(localFactory, organizationId, user.Email, organizationUserType, ssoPolicyEnabled: true);
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash,
context => context.SetAuthEmail(user.Email));
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash);
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
await AssertRequiredSsoAuthenticationResponseAsync(context);
@ -342,7 +271,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
{ "grant_type", "password" },
{ "username", model.Email },
{ "password", model.MasterPasswordHash },
}), context => context.SetAuthEmail(model.Email));
}));
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
@ -554,12 +483,12 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
{ "grant_type", "password" },
{ "username", user.Email},
{ "password", "master_password_hash" },
}), context => context.SetAuthEmail(user.Email).SetIp("1.1.1.2"));
}), context => context.SetIp("1.1.1.2"));
}
}
private async Task<HttpContext> PostLoginAsync(
TestServer server, User user, string MasterPasswordHash, Action<HttpContext> extraConfiguration)
TestServer server, User user, string MasterPasswordHash)
{
return await server.PostAsync("/connect/token", new FormUrlEncodedContent(new Dictionary<string, string>
{
@ -571,7 +500,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
{ "grant_type", "password" },
{ "username", user.Email },
{ "password", MasterPasswordHash },
}), extraConfiguration);
}));
}
private async Task CreateOrganizationWithSsoPolicyAsync(

View File

@ -143,7 +143,7 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
{ "grant_type", "password" },
{ "username", _testEmail },
{ "password", _testPassword },
}), context => context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(_testEmail)));
}));
// Assert
using var responseBody = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
@ -263,7 +263,7 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
{ "code", "test_code" },
{ "code_verifier", challenge },
{ "redirect_uri", "https://localhost:8080/sso-connector.html" }
}), context => context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(_testEmail)));
}));
// Assert
using var responseBody = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
@ -307,7 +307,7 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
{ "code", "test_code" },
{ "code_verifier", challenge },
{ "redirect_uri", "https://localhost:8080/sso-connector.html" }
}), context => context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(_testEmail)));
}));
Assert.Equal(StatusCodes.Status400BadRequest, failedTokenContext.Response.StatusCode);
Assert.NotNull(emailToken);
@ -326,7 +326,7 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
{ "code", "test_code" },
{ "code_verifier", challenge },
{ "redirect_uri", "https://localhost:8080/sso-connector.html" }
}), context => context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(_testEmail)));
}));
// Assert
@ -363,7 +363,7 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
{ "code", "test_code" },
{ "code_verifier", challenge },
{ "redirect_uri", "https://localhost:8080/sso-connector.html" }
}), context => context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(_testEmail)));
}));
// Assert
using var responseBody = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);

View File

@ -29,8 +29,7 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
// Act
var context = await localFactory.Server.PostAsync("/connect/token",
GetFormUrlEncodedContent(),
context => context.SetAuthEmail(DefaultUsername));
GetFormUrlEncodedContent());
// Assert
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
@ -40,27 +39,6 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
Assert.NotNull(token);
}
[Fact]
public async Task ValidateAsync_AuthEmailHeaderInvalid_InvalidGrantResponse()
{
// Arrange
var localFactory = new IdentityApplicationFactory();
await EnsureUserCreatedAsync(localFactory);
// Act
var context = await localFactory.Server.PostAsync(
"/connect/token",
GetFormUrlEncodedContent()
);
// Assert
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
var root = body.RootElement;
var error = AssertHelper.AssertJsonProperty(root, "error_description", JsonValueKind.String).GetString();
Assert.Equal("Auth-Email header invalid.", error);
}
[Theory, BitAutoData]
public async Task ValidateAsync_UserNull_Failure(string username)
{
@ -68,8 +46,7 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
var localFactory = new IdentityApplicationFactory();
// Act
var context = await localFactory.Server.PostAsync("/connect/token",
GetFormUrlEncodedContent(username: username),
context => context.SetAuthEmail(username));
GetFormUrlEncodedContent(username: username));
// Assert
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
@ -106,8 +83,7 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
// Act
var context = await localFactory.Server.PostAsync("/connect/token",
GetFormUrlEncodedContent(password: badPassword),
context => context.SetAuthEmail(DefaultUsername));
GetFormUrlEncodedContent(password: badPassword));
// Assert
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
@ -155,7 +131,7 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
{ "username", DefaultUsername },
{ "password", DefaultPassword },
{ "AuthRequest", authRequest.Id.ToString().ToLowerInvariant() }
}), context => context.SetAuthEmail(DefaultUsername));
}));
// Assert
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
@ -197,7 +173,7 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
{ "username", DefaultUsername },
{ "password", DefaultPassword },
{ "AuthRequest", authRequest.Id.ToString().ToLowerInvariant() }
}), context => context.SetAuthEmail(DefaultUsername));
}));
// Assert

View File

@ -98,7 +98,7 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase<Startup>
{ "grant_type", "password" },
{ "username", username },
{ "password", password },
}), context => context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(username)));
}));
return context;
}
@ -126,7 +126,7 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase<Startup>
{ "TwoFactorToken", twoFactorToken },
{ "TwoFactorProvider", twoFactorProviderType },
{ "TwoFactorRemember", "1" },
}), context => context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(username)));
}));
return context;
}

View File

@ -62,12 +62,6 @@ public static class WebApplicationFactoryExtensions
Action<HttpContext> extraConfiguration = null)
=> SendAsync(server, HttpMethod.Delete, requestUri, content: content, extraConfiguration);
public static HttpContext SetAuthEmail(this HttpContext context, string username)
{
context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(username));
return context;
}
public static HttpContext SetIp(this HttpContext context, string ip)
{
context.Connection.RemoteIpAddress = IPAddress.Parse(ip);