mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
[SG-419] Fix problems with push notifications on self-host (#2338)
* Added "internal" to non-user-based request types to avoid failing validation. * Added handling of unsuccessful response so that JSON parsing eror doesn't occur. * Added logging for token errors. (cherry picked from commit dad143b3e42247bc6b397b60803e25d243bd83a5) * Fixed bug in next auth attempt handling. * Fixed linting. * Added deserialization options to handle case insensitivity. * Added a new method for SendAsync that does not expect a result from the client. * hasJsonResult param to make Send more reusable * some cleanup * fix lint problems * Added launch config for Notifications. * Added Notifications to Full Server config. Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com>
This commit is contained in:
parent
14074e1e33
commit
e277b9e84e
50
.vscode/launch.json
vendored
50
.vscode/launch.json
vendored
@ -40,7 +40,8 @@
|
|||||||
"Identity",
|
"Identity",
|
||||||
"Sso",
|
"Sso",
|
||||||
"Icons",
|
"Icons",
|
||||||
"Billing"
|
"Billing",
|
||||||
|
"Notifications"
|
||||||
],
|
],
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
@ -57,6 +58,7 @@
|
|||||||
"EventsProcessor-SelfHost",
|
"EventsProcessor-SelfHost",
|
||||||
"Identity-SelfHost",
|
"Identity-SelfHost",
|
||||||
"Sso-SelfHost",
|
"Sso-SelfHost",
|
||||||
|
"Notifications-SelfHost"
|
||||||
],
|
],
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
@ -238,6 +240,28 @@
|
|||||||
"/Views": "${workspaceFolder}/Views"
|
"/Views": "${workspaceFolder}/Views"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Notifications",
|
||||||
|
"presentation": {
|
||||||
|
"hidden": true,
|
||||||
|
"group": "cloud",
|
||||||
|
"order": 100
|
||||||
|
},
|
||||||
|
"requireExactSource": true,
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "buildNotifications",
|
||||||
|
"program": "${workspaceFolder}/src/Notifications/bin/Debug/net6.0/Notifications.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}/src/Notifications",
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"env": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"sourceFileMap": {
|
||||||
|
"/Views": "${workspaceFolder}/Views"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Identity-SelfHost",
|
"name": "Identity-SelfHost",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
@ -336,6 +360,30 @@
|
|||||||
"/Views": "${workspaceFolder}/Views"
|
"/Views": "${workspaceFolder}/Views"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Notifications-SelfHost",
|
||||||
|
"presentation": {
|
||||||
|
"hidden": true,
|
||||||
|
"group": "self-host",
|
||||||
|
"order": 999
|
||||||
|
},
|
||||||
|
"requireExactSource": true,
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "buildNotifications",
|
||||||
|
"program": "${workspaceFolder}/src/Notifications/bin/Debug/net6.0/Notifications.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}/src/Notifications",
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"env": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"ASPNETCORE_URLS": "http://localhost:61841",
|
||||||
|
"developSelfHosted": "true"
|
||||||
|
},
|
||||||
|
"sourceFileMap": {
|
||||||
|
"/Views": "${workspaceFolder}/Views"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "EventsProcessor-SelfHost",
|
"name": "EventsProcessor-SelfHost",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
|
16
.vscode/tasks.json
vendored
16
.vscode/tasks.json
vendored
@ -89,6 +89,22 @@
|
|||||||
"isDefault": true
|
"isDefault": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "buildNotifications",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/src/Notifications/Notifications.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile",
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "buildBilling",
|
"label": "buildBilling",
|
||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
|
@ -72,12 +72,13 @@ public class SelfHostedSyncSponsorshipsCommand : BaseIdentityClientService, ISel
|
|||||||
|
|
||||||
foreach (var orgSponsorshipsBatch in organizationSponsorshipsDict.Values.Chunk(1000))
|
foreach (var orgSponsorshipsBatch in organizationSponsorshipsDict.Values.Chunk(1000))
|
||||||
{
|
{
|
||||||
var response = await SendAsync<OrganizationSponsorshipSyncRequestModel, OrganizationSponsorshipSyncResponseModel>(HttpMethod.Post, "organization/sponsorship/sync", new OrganizationSponsorshipSyncRequestModel
|
var response = await SendAsync<OrganizationSponsorshipSyncRequestModel, OrganizationSponsorshipSyncResponseModel>(
|
||||||
|
HttpMethod.Post, "organization/sponsorship/sync", new OrganizationSponsorshipSyncRequestModel
|
||||||
{
|
{
|
||||||
BillingSyncKey = billingSyncConfig.BillingSyncKey,
|
BillingSyncKey = billingSyncConfig.BillingSyncKey,
|
||||||
SponsoringOrganizationCloudId = cloudOrganizationId,
|
SponsoringOrganizationCloudId = cloudOrganizationId,
|
||||||
SponsorshipsBatch = orgSponsorshipsBatch.Select(s => new OrganizationSponsorshipRequestModel(s))
|
SponsorshipsBatch = orgSponsorshipsBatch.Select(s => new OrganizationSponsorshipRequestModel(s))
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
if (response == null)
|
if (response == null)
|
||||||
{
|
{
|
||||||
|
@ -47,19 +47,21 @@ public abstract class BaseIdentityClientService : IDisposable
|
|||||||
protected string AccessToken { get; private set; }
|
protected string AccessToken { get; private set; }
|
||||||
|
|
||||||
protected Task SendAsync(HttpMethod method, string path) =>
|
protected Task SendAsync(HttpMethod method, string path) =>
|
||||||
SendAsync<object, object>(method, path, null);
|
SendAsync<object>(method, path, null);
|
||||||
|
|
||||||
protected Task SendAsync<TRequest>(HttpMethod method, string path, TRequest body) =>
|
protected Task SendAsync<TRequest>(HttpMethod method, string path, TRequest requestModel) =>
|
||||||
SendAsync<TRequest, object>(method, path, body);
|
SendAsync<TRequest, object>(method, path, requestModel, false);
|
||||||
|
|
||||||
protected async Task<TResult> SendAsync<TRequest, TResult>(HttpMethod method, string path, TRequest requestModel)
|
protected async Task<TResult> SendAsync<TRequest, TResult>(HttpMethod method, string path,
|
||||||
|
TRequest requestModel, bool hasJsonResult)
|
||||||
{
|
{
|
||||||
var fullRequestPath = string.Concat(Client.BaseAddress, path);
|
var fullRequestPath = string.Concat(Client.BaseAddress, path);
|
||||||
|
|
||||||
var tokenStateResponse = await HandleTokenStateAsync();
|
var tokenStateResponse = await HandleTokenStateAsync();
|
||||||
if (!tokenStateResponse)
|
if (!tokenStateResponse)
|
||||||
{
|
{
|
||||||
_logger.LogError("Unable to send {method} request to {requestUri} because an access token was unable to be obtained", method.Method, fullRequestPath);
|
_logger.LogError("Unable to send {method} request to {requestUri} because an access token was unable to be obtained",
|
||||||
|
method.Method, fullRequestPath);
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +73,20 @@ public abstract class BaseIdentityClientService : IDisposable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await Client.SendAsync(message);
|
var response = await Client.SendAsync(message);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
if (hasJsonResult)
|
||||||
|
{
|
||||||
return await response.Content.ReadFromJsonAsync<TResult>();
|
return await response.Content.ReadFromJsonAsync<TResult>();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError("Request to {url} is unsuccessful with status of {code}-{reason}",
|
||||||
|
message.RequestUri.ToString(), response.StatusCode, response.ReasonPhrase);
|
||||||
|
}
|
||||||
|
return default;
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(12334, e, "Failed to send to {0}.", message.RequestUri.ToString());
|
_logger.LogError(12334, e, "Failed to send to {0}.", message.RequestUri.ToString());
|
||||||
@ -82,8 +96,9 @@ public abstract class BaseIdentityClientService : IDisposable
|
|||||||
|
|
||||||
protected async Task<bool> HandleTokenStateAsync()
|
protected async Task<bool> HandleTokenStateAsync()
|
||||||
{
|
{
|
||||||
if (_nextAuthAttempt.HasValue && DateTime.UtcNow > _nextAuthAttempt.Value)
|
if (_nextAuthAttempt.HasValue && DateTime.UtcNow < _nextAuthAttempt.Value)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Not requesting a token at {now} because the next request time is {nextAttempt}", DateTime.UtcNow, _nextAuthAttempt.Value);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_nextAuthAttempt = null;
|
_nextAuthAttempt = null;
|
||||||
@ -118,12 +133,13 @@ public abstract class BaseIdentityClientService : IDisposable
|
|||||||
|
|
||||||
if (response == null)
|
if (response == null)
|
||||||
{
|
{
|
||||||
|
_logger.LogError("Empty token response from {identity} for client {clientId} with status {code}-{reason}", IdentityClient.BaseAddress, _identityClientId, response.StatusCode, response.ReasonPhrase);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Unsuccessful token response from {identity} for client {clientId} with status code {StatusCode}", IdentityClient.BaseAddress, _identityClientId, response.StatusCode);
|
_logger.LogError("Unsuccessful token response from {identity} for client {clientId} with status {code}-{reason}", IdentityClient.BaseAddress, _identityClientId, response.StatusCode, response.ReasonPhrase);
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.BadRequest)
|
if (response.StatusCode == HttpStatusCode.BadRequest)
|
||||||
{
|
{
|
||||||
@ -139,7 +155,8 @@ public abstract class BaseIdentityClientService : IDisposable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var jsonDocument = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
|
var content = await response.Content.ReadAsStreamAsync();
|
||||||
|
using var jsonDocument = await JsonDocument.ParseAsync(content);
|
||||||
|
|
||||||
AccessToken = jsonDocument.RootElement.GetProperty("access_token").GetString();
|
AccessToken = jsonDocument.RootElement.GetProperty("access_token").GetString();
|
||||||
return true;
|
return true;
|
||||||
|
@ -52,7 +52,8 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
|||||||
string[] allowedGrantTypes = { "authorization_code", "client_credentials" };
|
string[] allowedGrantTypes = { "authorization_code", "client_credentials" };
|
||||||
if (!allowedGrantTypes.Contains(context.Result.ValidatedRequest.GrantType)
|
if (!allowedGrantTypes.Contains(context.Result.ValidatedRequest.GrantType)
|
||||||
|| context.Result.ValidatedRequest.ClientId.StartsWith("organization")
|
|| context.Result.ValidatedRequest.ClientId.StartsWith("organization")
|
||||||
|| context.Result.ValidatedRequest.ClientId.StartsWith("installation"))
|
|| context.Result.ValidatedRequest.ClientId.StartsWith("installation")
|
||||||
|
|| context.Result.ValidatedRequest.ClientId.StartsWith("internal"))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,9 @@ namespace Bit.Notifications;
|
|||||||
|
|
||||||
public static class HubHelpers
|
public static class HubHelpers
|
||||||
{
|
{
|
||||||
|
private static JsonSerializerOptions _deserializerOptions =
|
||||||
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||||
|
|
||||||
public static async Task SendNotificationToHubAsync(
|
public static async Task SendNotificationToHubAsync(
|
||||||
string notificationJson,
|
string notificationJson,
|
||||||
IHubContext<NotificationsHub> hubContext,
|
IHubContext<NotificationsHub> hubContext,
|
||||||
@ -23,7 +26,7 @@ public static class HubHelpers
|
|||||||
case PushType.SyncLoginDelete:
|
case PushType.SyncLoginDelete:
|
||||||
var cipherNotification =
|
var cipherNotification =
|
||||||
JsonSerializer.Deserialize<PushNotificationData<SyncCipherPushNotification>>(
|
JsonSerializer.Deserialize<PushNotificationData<SyncCipherPushNotification>>(
|
||||||
notificationJson);
|
notificationJson, _deserializerOptions);
|
||||||
if (cipherNotification.Payload.UserId.HasValue)
|
if (cipherNotification.Payload.UserId.HasValue)
|
||||||
{
|
{
|
||||||
await hubContext.Clients.User(cipherNotification.Payload.UserId.ToString())
|
await hubContext.Clients.User(cipherNotification.Payload.UserId.ToString())
|
||||||
@ -41,7 +44,7 @@ public static class HubHelpers
|
|||||||
case PushType.SyncFolderDelete:
|
case PushType.SyncFolderDelete:
|
||||||
var folderNotification =
|
var folderNotification =
|
||||||
JsonSerializer.Deserialize<PushNotificationData<SyncFolderPushNotification>>(
|
JsonSerializer.Deserialize<PushNotificationData<SyncFolderPushNotification>>(
|
||||||
notificationJson);
|
notificationJson, _deserializerOptions);
|
||||||
await hubContext.Clients.User(folderNotification.Payload.UserId.ToString())
|
await hubContext.Clients.User(folderNotification.Payload.UserId.ToString())
|
||||||
.SendAsync("ReceiveMessage", folderNotification, cancellationToken);
|
.SendAsync("ReceiveMessage", folderNotification, cancellationToken);
|
||||||
break;
|
break;
|
||||||
@ -52,7 +55,7 @@ public static class HubHelpers
|
|||||||
case PushType.LogOut:
|
case PushType.LogOut:
|
||||||
var userNotification =
|
var userNotification =
|
||||||
JsonSerializer.Deserialize<PushNotificationData<UserPushNotification>>(
|
JsonSerializer.Deserialize<PushNotificationData<UserPushNotification>>(
|
||||||
notificationJson);
|
notificationJson, _deserializerOptions);
|
||||||
await hubContext.Clients.User(userNotification.Payload.UserId.ToString())
|
await hubContext.Clients.User(userNotification.Payload.UserId.ToString())
|
||||||
.SendAsync("ReceiveMessage", userNotification, cancellationToken);
|
.SendAsync("ReceiveMessage", userNotification, cancellationToken);
|
||||||
break;
|
break;
|
||||||
@ -61,21 +64,21 @@ public static class HubHelpers
|
|||||||
case PushType.SyncSendDelete:
|
case PushType.SyncSendDelete:
|
||||||
var sendNotification =
|
var sendNotification =
|
||||||
JsonSerializer.Deserialize<PushNotificationData<SyncSendPushNotification>>(
|
JsonSerializer.Deserialize<PushNotificationData<SyncSendPushNotification>>(
|
||||||
notificationJson);
|
notificationJson, _deserializerOptions);
|
||||||
await hubContext.Clients.User(sendNotification.Payload.UserId.ToString())
|
await hubContext.Clients.User(sendNotification.Payload.UserId.ToString())
|
||||||
.SendAsync("ReceiveMessage", sendNotification, cancellationToken);
|
.SendAsync("ReceiveMessage", sendNotification, cancellationToken);
|
||||||
break;
|
break;
|
||||||
case PushType.AuthRequestResponse:
|
case PushType.AuthRequestResponse:
|
||||||
var authRequestResponseNotification =
|
var authRequestResponseNotification =
|
||||||
JsonSerializer.Deserialize<PushNotificationData<AuthRequestPushNotification>>(
|
JsonSerializer.Deserialize<PushNotificationData<AuthRequestPushNotification>>(
|
||||||
notificationJson);
|
notificationJson, _deserializerOptions);
|
||||||
await anonymousHubContext.Clients.Group(authRequestResponseNotification.Payload.Id.ToString())
|
await anonymousHubContext.Clients.Group(authRequestResponseNotification.Payload.Id.ToString())
|
||||||
.SendAsync("AuthRequestResponseRecieved", authRequestResponseNotification, cancellationToken);
|
.SendAsync("AuthRequestResponseRecieved", authRequestResponseNotification, cancellationToken);
|
||||||
break;
|
break;
|
||||||
case PushType.AuthRequest:
|
case PushType.AuthRequest:
|
||||||
var authRequestNotification =
|
var authRequestNotification =
|
||||||
JsonSerializer.Deserialize<PushNotificationData<AuthRequestPushNotification>>(
|
JsonSerializer.Deserialize<PushNotificationData<AuthRequestPushNotification>>(
|
||||||
notificationJson);
|
notificationJson, _deserializerOptions);
|
||||||
await hubContext.Clients.User(authRequestNotification.Payload.UserId.ToString())
|
await hubContext.Clients.User(authRequestNotification.Payload.UserId.ToString())
|
||||||
.SendAsync("ReceiveMessage", authRequestNotification, cancellationToken);
|
.SendAsync("ReceiveMessage", authRequestNotification, cancellationToken);
|
||||||
break;
|
break;
|
||||||
|
Loading…
Reference in New Issue
Block a user