1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 16:42:50 -05:00

rename hub to notifications

This commit is contained in:
Kyle Spearrin
2018-08-16 13:45:31 -04:00
parent 42d3a2d8e3
commit 80a49e53ac
12 changed files with 28 additions and 22 deletions

View File

@ -0,0 +1,86 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Models;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue;
using Newtonsoft.Json;
namespace Bit.Notifications
{
public class AzureQueueHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private readonly IHubContext<NotificationsHub> _hubContext;
private readonly GlobalSettings _globalSettings;
private Task _executingTask;
private CancellationTokenSource _cts;
private CloudQueue _queue;
public AzureQueueHostedService(
ILogger<AzureQueueHostedService> logger,
IHubContext<NotificationsHub> hubContext,
GlobalSettings globalSettings)
{
_logger = logger;
_hubContext = hubContext;
_globalSettings = globalSettings;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_executingTask = ExecuteAsync(_cts.Token);
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
if(_executingTask == null)
{
return;
}
_cts.Cancel();
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
cancellationToken.ThrowIfCancellationRequested();
}
public void Dispose()
{
// TODO
}
private async Task ExecuteAsync(CancellationToken cancellationToken)
{
var storageAccount = CloudStorageAccount.Parse(_globalSettings.Notifications.ConnectionString);
var queueClient = storageAccount.CreateCloudQueueClient();
_queue = queueClient.GetQueueReference("notifications");
while(!cancellationToken.IsCancellationRequested)
{
var messages = await _queue.GetMessagesAsync(32, TimeSpan.FromMinutes(1),
null, null, cancellationToken);
if(messages.Any())
{
foreach(var message in messages)
{
var notification = JsonConvert.DeserializeObject<PushNotificationData<object>>(
message.AsString);
await HubHelpers.SendNotificationToHubAsync(notification, _hubContext, cancellationToken);
await _queue.DeleteMessageAsync(message);
}
}
else
{
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
}
}
}
}
}

View File

@ -0,0 +1,27 @@
using System.Threading.Tasks;
using Bit.Core.Models;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
namespace Bit.Notifications
{
[Authorize("Internal")]
[SelfHosted(SelfHostedOnly = true)]
public class NotificationsController : Controller
{
private readonly IHubContext<NotificationsHub> _hubContext;
public NotificationsController(IHubContext<NotificationsHub> hubContext)
{
_hubContext = hubContext;
}
[HttpPost("~/notifications")]
public async Task PostNotification([FromBody]PushNotificationData<object> model)
{
await HubHelpers.SendNotificationToHubAsync(model, _hubContext);
}
}
}

View File

@ -0,0 +1,56 @@
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Models;
using Microsoft.AspNetCore.SignalR;
using Newtonsoft.Json;
namespace Bit.Notifications
{
public static class HubHelpers
{
public static async Task SendNotificationToHubAsync(PushNotificationData<object> notification,
IHubContext<NotificationsHub> hubContext, CancellationToken cancellationToken = default(CancellationToken))
{
switch(notification.Type)
{
case Core.Enums.PushType.SyncCipherUpdate:
case Core.Enums.PushType.SyncCipherCreate:
case Core.Enums.PushType.SyncCipherDelete:
case Core.Enums.PushType.SyncLoginDelete:
var cipherPayload = JsonConvert.DeserializeObject<SyncCipherPushNotification>(
JsonConvert.SerializeObject(notification.Payload));
if(cipherPayload.UserId.HasValue)
{
await hubContext.Clients.User(cipherPayload.UserId.ToString())
.SendAsync("ReceiveMessage", notification, cancellationToken);
}
else if(cipherPayload.OrganizationId.HasValue)
{
await hubContext.Clients.Group(
$"Organization_{cipherPayload.OrganizationId}")
.SendAsync("ReceiveMessage", notification, cancellationToken);
}
break;
case Core.Enums.PushType.SyncFolderUpdate:
case Core.Enums.PushType.SyncFolderCreate:
case Core.Enums.PushType.SyncFolderDelete:
var folderPayload = JsonConvert.DeserializeObject<SyncFolderPushNotification>(
JsonConvert.SerializeObject(notification.Payload));
await hubContext.Clients.User(folderPayload.UserId.ToString())
.SendAsync("ReceiveMessage", notification, cancellationToken);
break;
case Core.Enums.PushType.SyncCiphers:
case Core.Enums.PushType.SyncVault:
case Core.Enums.PushType.SyncOrgKeys:
case Core.Enums.PushType.SyncSettings:
var userPayload = JsonConvert.DeserializeObject<SyncUserPushNotification>(
JsonConvert.SerializeObject(notification.Payload));
await hubContext.Clients.User(userPayload.UserId.ToString())
.SendAsync("ReceiveMessage", notification, cancellationToken);
break;
default:
break;
}
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<Version>1.23.0</Version>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>Bit.Notifications</RootNamespace>
<UserSecretsId>bitwarden-Notifications</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" Version="2.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,33 @@
using System;
using System.Threading.Tasks;
using Bit.Core;
using Microsoft.AspNetCore.Authorization;
namespace Bit.Notifications
{
[Authorize("Application")]
public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub
{
public override async Task OnConnectedAsync()
{
var currentContext = new CurrentContext();
currentContext.Build(Context.User);
foreach(var org in currentContext.Organizations)
{
await Groups.AddToGroupAsync(Context.ConnectionId, $"Organization_{org.Id}");
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
var currentContext = new CurrentContext();
currentContext.Build(Context.User);
foreach(var org in currentContext.Organizations)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"Organization_{org.Id}");
}
await base.OnDisconnectedAsync(exception);
}
}
}

View File

@ -0,0 +1,19 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace Bit.Notifications
{
public class Program
{
public static void Main(string[] args)
{
WebHost
.CreateDefaultBuilder(args)
.UseStartup<Startup>()
// ref: https://github.com/aspnet/KestrelHttpServer/issues/2694
.UseLibuv()
.Build()
.Run();
}
}
}

View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:61840",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Notifications": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,115 @@
using Bit.Core;
using Bit.Core.Utilities;
using IdentityModel;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Logging;
using Serilog.Events;
namespace Bit.Notifications
{
public class Startup
{
public Startup(IHostingEnvironment env, IConfiguration configuration)
{
Configuration = configuration;
Environment = env;
}
public IConfiguration Configuration { get; }
public IHostingEnvironment Environment { get; set; }
public void ConfigureServices(IServiceCollection services)
{
// Options
services.AddOptions();
// Settings
var globalSettings = services.AddGlobalSettingsServices(Configuration);
// Repositories
services.AddSqlServerRepositories(globalSettings);
// Context
services.AddScoped<CurrentContext>();
// Identity
services.AddIdentityAuthenticationServices(globalSettings, Environment, config =>
{
config.AddPolicy("Application", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim(JwtClaimTypes.AuthenticationMethod, "Application");
policy.RequireClaim(JwtClaimTypes.Scope, "api");
});
config.AddPolicy("Internal", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim(JwtClaimTypes.Scope, "internal");
});
});
// SignalR
services.AddSignalR();
services.AddSingleton<IUserIdProvider, SubjectUserIdProvider>();
// Mvc
services.AddMvc();
// Hosted Services
if(!globalSettings.SelfHosted &&
CoreHelpers.SettingHasValue(globalSettings.Notifications?.ConnectionString))
{
services.AddHostedService<AzureQueueHostedService>();
}
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
IApplicationLifetime appLifetime,
GlobalSettings globalSettings)
{
IdentityModelEventSource.ShowPII = true;
loggerFactory.AddSerilog(app, env, appLifetime, globalSettings, (e) =>
{
var context = e.Properties["SourceContext"].ToString();
if(context.Contains("IdentityServer4.Validation.TokenValidator") ||
context.Contains("IdentityServer4.Validation.TokenRequestValidator"))
{
return e.Level > LogEventLevel.Error;
}
return e.Level >= LogEventLevel.Error;
});
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Default Middleware
app.UseDefaultMiddleware(env);
// Add Cors
app.UseCors(policy => policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials());
// Add authentication to the request pipeline.
app.UseAuthentication();
// Add SignlarR
app.UseSignalR(routes =>
{
routes.MapHub<NotificationsHub>("/notifications-hub");
});
// Add MVC to the request pipeline.
app.UseMvc();
}
}
}

View File

@ -0,0 +1,13 @@
using IdentityModel;
using Microsoft.AspNetCore.SignalR;
namespace Bit.Notifications
{
public class SubjectUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.FindFirst(JwtClaimTypes.Subject)?.Value;
}
}
}

View File

@ -0,0 +1,16 @@
{
"globalSettings": {
"baseServiceUri": {
"vault": "https://vault.bitwarden.com",
"api": "https://api.bitwarden.com",
"identity": "https://identity.bitwarden.com",
"admin": "https://admin.bitwarden.com",
"hub": "https://hub.bitwarden.com",
"internalHub": "https://hub.bitwarden.com",
"internalAdmin": "https://admin.bitwarden.com",
"internalIdentity": "https://identity.bitwarden.com",
"internalApi": "https://api.bitwarden.com",
"internalVault": "https://vault.bitwarden.com"
}
}
}

View File

@ -0,0 +1,37 @@
{
"globalSettings": {
"selfHosted": false,
"projectName": "Hub",
"baseServiceUri": {
"vault": "https://localhost:8080",
"api": "http://localhost:4000",
"identity": "http://localhost:33656",
"admin": "http://localhost:62911",
"hub": "http://localhost:61840",
"internalHub": "http://localhost:61840",
"internalAdmin": "http://localhost:62911",
"internalIdentity": "http://localhost:33656",
"internalApi": "http://localhost:4000",
"internalVault": "http://localhost:4001"
},
"sqlServer": {
"connectionString": "SECRET"
},
"identityServer": {
"certificateThumbprint": "SECRET"
},
"storage": {
"connectionString": "SECRET"
},
"events": {
"connectionString": "SECRET"
},
"documentDb": {
"uri": "SECRET",
"key": "SECRET"
},
"sentry": {
"dsn": "SECRET"
}
}
}