mirror of
https://github.com/bitwarden/server.git
synced 2025-06-28 06:36:15 -05:00

* More tests * More tests * Add non-guid tests * Introduce slimmer services * Implement IPushEngine on services * Implement IPushEngine * Fix tests * Format * Switch to `Guid` on `PushSendRequestModel` * Remove TODOs
156 lines
4.7 KiB
C#
156 lines
4.7 KiB
C#
using System.Diagnostics;
|
|
using System.Text.Json;
|
|
using Bit.Core.Context;
|
|
using Bit.Core.Exceptions;
|
|
using Bit.Core.Models.Api;
|
|
using Bit.Core.NotificationHub;
|
|
using Bit.Core.Platform.Push;
|
|
using Bit.Core.Platform.Push.Internal;
|
|
using Bit.Core.Settings;
|
|
using Bit.Core.Utilities;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Bit.Api.Platform.Push;
|
|
|
|
/// <summary>
|
|
/// Routes for push relay: functionality that facilitates communication
|
|
/// between self hosted organizations and Bitwarden cloud.
|
|
/// </summary>
|
|
[Route("push")]
|
|
[Authorize("Push")]
|
|
[SelfHosted(NotSelfHostedOnly = true)]
|
|
public class PushController : Controller
|
|
{
|
|
private readonly IPushRegistrationService _pushRegistrationService;
|
|
private readonly IPushRelayer _pushRelayer;
|
|
private readonly IWebHostEnvironment _environment;
|
|
private readonly ICurrentContext _currentContext;
|
|
private readonly IGlobalSettings _globalSettings;
|
|
|
|
public PushController(
|
|
IPushRegistrationService pushRegistrationService,
|
|
IPushRelayer pushRelayer,
|
|
IWebHostEnvironment environment,
|
|
ICurrentContext currentContext,
|
|
IGlobalSettings globalSettings)
|
|
{
|
|
_currentContext = currentContext;
|
|
_environment = environment;
|
|
_pushRegistrationService = pushRegistrationService;
|
|
_pushRelayer = pushRelayer;
|
|
_globalSettings = globalSettings;
|
|
}
|
|
|
|
[HttpPost("register")]
|
|
public async Task RegisterAsync([FromBody] PushRegistrationRequestModel model)
|
|
{
|
|
CheckUsage();
|
|
await _pushRegistrationService.CreateOrUpdateRegistrationAsync(new PushRegistrationData(model.PushToken),
|
|
Prefix(model.DeviceId), Prefix(model.UserId), Prefix(model.Identifier), model.Type,
|
|
model.OrganizationIds?.Select(Prefix) ?? [], model.InstallationId);
|
|
}
|
|
|
|
[HttpPost("delete")]
|
|
public async Task DeleteAsync([FromBody] PushDeviceRequestModel model)
|
|
{
|
|
CheckUsage();
|
|
await _pushRegistrationService.DeleteRegistrationAsync(Prefix(model.Id));
|
|
}
|
|
|
|
[HttpPut("add-organization")]
|
|
public async Task AddOrganizationAsync([FromBody] PushUpdateRequestModel model)
|
|
{
|
|
CheckUsage();
|
|
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(
|
|
model.Devices.Select(d => Prefix(d.Id)),
|
|
Prefix(model.OrganizationId));
|
|
}
|
|
|
|
[HttpPut("delete-organization")]
|
|
public async Task DeleteOrganizationAsync([FromBody] PushUpdateRequestModel model)
|
|
{
|
|
CheckUsage();
|
|
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(
|
|
model.Devices.Select(d => Prefix(d.Id)),
|
|
Prefix(model.OrganizationId));
|
|
}
|
|
|
|
[HttpPost("send")]
|
|
public async Task SendAsync([FromBody] PushSendRequestModel<JsonElement> model)
|
|
{
|
|
CheckUsage();
|
|
|
|
NotificationTarget target;
|
|
Guid targetId;
|
|
|
|
if (model.InstallationId.HasValue)
|
|
{
|
|
if (_currentContext.InstallationId!.Value != model.InstallationId.Value)
|
|
{
|
|
throw new BadRequestException("InstallationId does not match current context.");
|
|
}
|
|
|
|
target = NotificationTarget.Installation;
|
|
targetId = _currentContext.InstallationId.Value;
|
|
}
|
|
else if (model.UserId.HasValue)
|
|
{
|
|
target = NotificationTarget.User;
|
|
targetId = model.UserId.Value;
|
|
}
|
|
else if (model.OrganizationId.HasValue)
|
|
{
|
|
target = NotificationTarget.Organization;
|
|
targetId = model.OrganizationId.Value;
|
|
}
|
|
else
|
|
{
|
|
throw new UnreachableException("Model validation should have prevented getting here.");
|
|
}
|
|
|
|
var notification = new RelayedNotification
|
|
{
|
|
Type = model.Type,
|
|
Target = target,
|
|
TargetId = targetId,
|
|
Payload = model.Payload,
|
|
Identifier = model.Identifier,
|
|
DeviceId = model.DeviceId,
|
|
ClientType = model.ClientType,
|
|
};
|
|
|
|
await _pushRelayer.RelayAsync(_currentContext.InstallationId.Value, notification);
|
|
}
|
|
|
|
private string Prefix(string value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return $"{_currentContext.InstallationId!.Value}_{value}";
|
|
}
|
|
|
|
private void CheckUsage()
|
|
{
|
|
if (CanUse())
|
|
{
|
|
return;
|
|
}
|
|
|
|
throw new BadRequestException("Not correctly configured for push relays.");
|
|
}
|
|
|
|
private bool CanUse()
|
|
{
|
|
if (_environment.IsDevelopment())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return _currentContext.InstallationId.HasValue && !_globalSettings.SelfHosted;
|
|
}
|
|
}
|