mirror of
https://github.com/bitwarden/server.git
synced 2024-11-22 12:15:36 +01:00
push registration through relay apis
This commit is contained in:
parent
0ad76a5487
commit
0f37920de2
@ -4,3 +4,5 @@ globalSettings:baseServiceUri:vault=http://localhost
|
||||
globalSettings:baseServiceUri:api=http://localhost/api
|
||||
globalSettings:baseServiceUri:identity=http://localhost/identity
|
||||
globalSettings:baseServiceUri:internalIdentity=http://identity
|
||||
globalSettings:pushRelayBaseUri=https://push.bitwarden.com
|
||||
globalSettings:installation:identityUri=https://identity.bitwarden.com
|
||||
|
@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Api;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace Bit.Api.Controllers
|
||||
{
|
||||
@ -12,25 +15,76 @@ namespace Bit.Api.Controllers
|
||||
public class PushController : Controller
|
||||
{
|
||||
private readonly IPushRegistrationService _pushRegistrationService;
|
||||
private readonly IHostingEnvironment _environment;
|
||||
private readonly CurrentContext _currentContext;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public PushController(
|
||||
IPushRegistrationService pushRegistrationService,
|
||||
CurrentContext currentContext)
|
||||
IHostingEnvironment environment,
|
||||
CurrentContext currentContext,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_environment = environment;
|
||||
_pushRegistrationService = pushRegistrationService;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
[HttpGet("register")]
|
||||
public Object Register()
|
||||
[HttpPost("register")]
|
||||
public async Task PostRegister(PushRegistrationRequestModel model)
|
||||
{
|
||||
if(!_currentContext.InstallationId.HasValue)
|
||||
CheckUsage();
|
||||
await _pushRegistrationService.CreateOrUpdateRegistrationAsync(model.PushToken, Prefix(model.DeviceId),
|
||||
Prefix(model.UserId), Prefix(model.Identifier), model.Type);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task Delete(string id)
|
||||
{
|
||||
CheckUsage();
|
||||
await _pushRegistrationService.DeleteRegistrationAsync(Prefix(id));
|
||||
}
|
||||
|
||||
[HttpPut("add-organization")]
|
||||
public async Task PutAddOrganization(PushUpdateRequestModel model)
|
||||
{
|
||||
CheckUsage();
|
||||
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(
|
||||
model.DeviceIds.Select(d => Prefix(d)), Prefix(model.OrganizationId));
|
||||
}
|
||||
|
||||
[HttpPut("delete-organization")]
|
||||
public async Task PutDeleteOrganization(PushUpdateRequestModel model)
|
||||
{
|
||||
CheckUsage();
|
||||
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(
|
||||
model.DeviceIds.Select(d => Prefix(d)), Prefix(model.OrganizationId));
|
||||
}
|
||||
|
||||
private string Prefix(string value)
|
||||
{
|
||||
return $"{_currentContext.InstallationId.Value}_{value}";
|
||||
}
|
||||
|
||||
private void CheckUsage()
|
||||
{
|
||||
if(CanUse())
|
||||
{
|
||||
throw new BadRequestException("bad request.");
|
||||
return;
|
||||
}
|
||||
|
||||
return new { Foo = "bar" };
|
||||
throw new BadRequestException("Not correctly configured for push relays.");
|
||||
}
|
||||
|
||||
private bool CanUse()
|
||||
{
|
||||
if(_environment.IsDevelopment())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _currentContext.InstallationId.HasValue && _globalSettings.SelfHosted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace Bit.Core
|
||||
using System;
|
||||
|
||||
namespace Bit.Core
|
||||
{
|
||||
public class GlobalSettings
|
||||
{
|
||||
@ -8,6 +10,8 @@
|
||||
public virtual string ProjectName { get; set; }
|
||||
public virtual string LogDirectory { get; set; }
|
||||
public virtual string LicenseDirectory { get; set; }
|
||||
public virtual string PushRelayBaseUri { get; set; }
|
||||
public virtual InstallationSettings Installation { get; set; } = new InstallationSettings();
|
||||
public virtual BaseServiceUriSettings BaseServiceUri { get; set; } = new BaseServiceUriSettings();
|
||||
public virtual SqlServerSettings SqlServer { get; set; } = new SqlServerSettings();
|
||||
public virtual MailSettings Mail { get; set; } = new MailSettings();
|
||||
@ -104,5 +108,12 @@
|
||||
public string PublicKey { get; set; }
|
||||
public string PrivateKey { get; set; }
|
||||
}
|
||||
|
||||
public class InstallationSettings
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string IdentityUri { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
src/Core/Models/Api/Request/PushRegistrationRequestModel.cs
Normal file
19
src/Core/Models/Api/Request/PushRegistrationRequestModel.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Bit.Core.Enums;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class PushRegistrationRequestModel
|
||||
{
|
||||
[Required]
|
||||
public string DeviceId { get; set; }
|
||||
[Required]
|
||||
public string PushToken { get; set; }
|
||||
[Required]
|
||||
public string UserId { get; set; }
|
||||
[Required]
|
||||
public DeviceType Type { get; set; }
|
||||
[Required]
|
||||
public string Identifier { get; set; }
|
||||
}
|
||||
}
|
22
src/Core/Models/Api/Request/PushUpdateRequestModel.cs
Normal file
22
src/Core/Models/Api/Request/PushUpdateRequestModel.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class PushUpdateRequestModel
|
||||
{
|
||||
public PushUpdateRequestModel()
|
||||
{ }
|
||||
|
||||
public PushUpdateRequestModel(IEnumerable<string> deviceIds, string organizationId)
|
||||
{
|
||||
DeviceIds = deviceIds;
|
||||
OrganizationId = organizationId;
|
||||
}
|
||||
|
||||
[Required]
|
||||
public IEnumerable<string> DeviceIds { get; set; }
|
||||
[Required]
|
||||
public string OrganizationId { get; set; }
|
||||
}
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public interface IPushRegistrationService
|
||||
{
|
||||
Task CreateOrUpdateRegistrationAsync(Device device);
|
||||
Task DeleteRegistrationAsync(Guid deviceId);
|
||||
Task AddUserRegistrationOrganizationAsync(Guid userId, Guid organizationId);
|
||||
Task DeleteUserRegistrationOrganizationAsync(Guid userId, Guid organizationId);
|
||||
Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId,
|
||||
string identifier, DeviceType type);
|
||||
Task DeleteRegistrationAsync(string deviceId);
|
||||
Task AddUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId);
|
||||
Task DeleteUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId);
|
||||
}
|
||||
}
|
||||
|
@ -30,19 +30,20 @@ namespace Bit.Core.Services
|
||||
await _deviceRepository.ReplaceAsync(device);
|
||||
}
|
||||
|
||||
await _pushRegistrationService.CreateOrUpdateRegistrationAsync(device);
|
||||
await _pushRegistrationService.CreateOrUpdateRegistrationAsync(device.PushToken, device.Id.ToString(),
|
||||
device.UserId.ToString(), device.Identifier, device.Type);
|
||||
}
|
||||
|
||||
public async Task ClearTokenAsync(Device device)
|
||||
{
|
||||
await _deviceRepository.ClearPushTokenAsync(device.Id);
|
||||
await _pushRegistrationService.DeleteRegistrationAsync(device.Id);
|
||||
await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString());
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(Device device)
|
||||
{
|
||||
await _deviceRepository.DeleteAsync(device);
|
||||
await _pushRegistrationService.DeleteRegistrationAsync(device.Id);
|
||||
await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,63 +1,59 @@
|
||||
#if NET461
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.NotificationHubs;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class NotificationHubPushRegistrationService : IPushRegistrationService
|
||||
{
|
||||
private readonly NotificationHubClient _client;
|
||||
private readonly IDeviceRepository _deviceRepository;
|
||||
|
||||
public NotificationHubPushRegistrationService(
|
||||
GlobalSettings globalSettings,
|
||||
IDeviceRepository deviceRepository)
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
_client = NotificationHubClient.CreateClientFromConnectionString(globalSettings.NotificationHub.ConnectionString,
|
||||
globalSettings.NotificationHub.HubName);
|
||||
|
||||
_deviceRepository = deviceRepository;
|
||||
}
|
||||
|
||||
public async Task CreateOrUpdateRegistrationAsync(Device device)
|
||||
public async Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId,
|
||||
string identifier, DeviceType type)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(device.PushToken))
|
||||
if(string.IsNullOrWhiteSpace(pushToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var installation = new Microsoft.Azure.NotificationHubs.Installation
|
||||
var installation = new Installation
|
||||
{
|
||||
InstallationId = device.Id.ToString(),
|
||||
PushChannel = device.PushToken,
|
||||
InstallationId = deviceId,
|
||||
PushChannel = pushToken,
|
||||
Templates = new Dictionary<string, InstallationTemplate>()
|
||||
};
|
||||
|
||||
installation.Tags = new List<string>
|
||||
{
|
||||
$"userId:{device.UserId}"
|
||||
$"userId:{userId}"
|
||||
};
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(device.Identifier))
|
||||
if(!string.IsNullOrWhiteSpace(identifier))
|
||||
{
|
||||
installation.Tags.Add("deviceIdentifier:" + device.Identifier);
|
||||
installation.Tags.Add("deviceIdentifier:" + identifier);
|
||||
}
|
||||
|
||||
string payloadTemplate = null, messageTemplate = null, badgeMessageTemplate = null;
|
||||
switch(device.Type)
|
||||
switch(type)
|
||||
{
|
||||
case Enums.DeviceType.Android:
|
||||
case DeviceType.Android:
|
||||
payloadTemplate = "{\"data\":{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}}}";
|
||||
messageTemplate = "{\"data\":{\"data\":{\"type\":\"#(type)\"}," +
|
||||
"\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}";
|
||||
|
||||
installation.Platform = NotificationPlatform.Gcm;
|
||||
break;
|
||||
case Enums.DeviceType.iOS:
|
||||
case DeviceType.iOS:
|
||||
payloadTemplate = "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}," +
|
||||
"\"aps\":{\"alert\":null,\"badge\":null,\"content-available\":1}}";
|
||||
messageTemplate = "{\"data\":{\"type\":\"#(type)\"}," +
|
||||
@ -67,7 +63,7 @@ namespace Bit.Core.Services
|
||||
|
||||
installation.Platform = NotificationPlatform.Apns;
|
||||
break;
|
||||
case Enums.DeviceType.AndroidAmazon:
|
||||
case DeviceType.AndroidAmazon:
|
||||
payloadTemplate = "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}}";
|
||||
messageTemplate = "{\"data\":{\"type\":\"#(type)\",\"message\":\"$(message)\"}}";
|
||||
|
||||
@ -77,16 +73,15 @@ namespace Bit.Core.Services
|
||||
break;
|
||||
}
|
||||
|
||||
BuildInstallationTemplate(installation, "payload", payloadTemplate, device.UserId, device.Identifier);
|
||||
BuildInstallationTemplate(installation, "message", messageTemplate, device.UserId, device.Identifier);
|
||||
BuildInstallationTemplate(installation, "badgeMessage", badgeMessageTemplate ?? messageTemplate, device.UserId,
|
||||
device.Identifier);
|
||||
BuildInstallationTemplate(installation, "payload", payloadTemplate, userId, identifier);
|
||||
BuildInstallationTemplate(installation, "message", messageTemplate, userId, identifier);
|
||||
BuildInstallationTemplate(installation, "badgeMessage", badgeMessageTemplate ?? messageTemplate, userId, identifier);
|
||||
|
||||
await _client.CreateOrUpdateInstallationAsync(installation);
|
||||
}
|
||||
|
||||
private void BuildInstallationTemplate(Microsoft.Azure.NotificationHubs.Installation installation,
|
||||
string templateId, string templateBody, Guid userId, string deviceIdentifier)
|
||||
private void BuildInstallationTemplate(Installation installation, string templateId, string templateBody,
|
||||
string userId, string identifier)
|
||||
{
|
||||
if(templateBody == null)
|
||||
{
|
||||
@ -105,32 +100,37 @@ namespace Bit.Core.Services
|
||||
}
|
||||
};
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(deviceIdentifier))
|
||||
if(!string.IsNullOrWhiteSpace(identifier))
|
||||
{
|
||||
template.Tags.Add($"{fullTemplateId}_deviceIdentifier:{deviceIdentifier}");
|
||||
template.Tags.Add($"{fullTemplateId}_deviceIdentifier:{identifier}");
|
||||
}
|
||||
|
||||
installation.Templates.Add(fullTemplateId, template);
|
||||
}
|
||||
|
||||
public async Task DeleteRegistrationAsync(Guid deviceId)
|
||||
public async Task DeleteRegistrationAsync(string deviceId)
|
||||
{
|
||||
await _client.DeleteInstallationAsync(deviceId.ToString());
|
||||
await _client.DeleteInstallationAsync(deviceId);
|
||||
}
|
||||
|
||||
public async Task AddUserRegistrationOrganizationAsync(Guid userId, Guid organizationId)
|
||||
public async Task AddUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId)
|
||||
{
|
||||
await PatchTagsForUserDevicesAsync(userId, UpdateOperationType.Add, $"organizationId:{organizationId}");
|
||||
await PatchTagsForUserDevicesAsync(deviceIds, UpdateOperationType.Add, $"organizationId:{organizationId}");
|
||||
}
|
||||
|
||||
public async Task DeleteUserRegistrationOrganizationAsync(Guid userId, Guid organizationId)
|
||||
public async Task DeleteUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId)
|
||||
{
|
||||
await PatchTagsForUserDevicesAsync(userId, UpdateOperationType.Remove, $"organizationId:{organizationId}");
|
||||
await PatchTagsForUserDevicesAsync(deviceIds, UpdateOperationType.Remove,
|
||||
$"organizationId:{organizationId}");
|
||||
}
|
||||
|
||||
private async Task PatchTagsForUserDevicesAsync(Guid userId, UpdateOperationType op, string tag)
|
||||
private async Task PatchTagsForUserDevicesAsync(IEnumerable<string> deviceIds, UpdateOperationType op, string tag)
|
||||
{
|
||||
var devices = await _deviceRepository.GetManyByUserIdAsync(userId);
|
||||
if(!deviceIds.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var operation = new PartialUpdateOperation
|
||||
{
|
||||
Operation = op,
|
||||
@ -138,9 +138,9 @@ namespace Bit.Core.Services
|
||||
Value = tag
|
||||
};
|
||||
|
||||
foreach(var device in devices)
|
||||
foreach(var id in deviceIds)
|
||||
{
|
||||
await _client.PatchInstallationAsync(device.Id.ToString(), new List<PartialUpdateOperation> { operation });
|
||||
await _client.PatchInstallationAsync(id, new List<PartialUpdateOperation> { operation });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ namespace Bit.Core.Services
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IPushRegistrationService _pushRegistrationService;
|
||||
private readonly IDeviceRepository _deviceRepository;
|
||||
private readonly StripePaymentService _stripePaymentService;
|
||||
|
||||
public OrganizationService(
|
||||
@ -36,7 +37,8 @@ namespace Bit.Core.Services
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
IMailService mailService,
|
||||
IPushNotificationService pushNotificationService,
|
||||
IPushRegistrationService pushRegistrationService)
|
||||
IPushRegistrationService pushRegistrationService,
|
||||
IDeviceRepository deviceRepository)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@ -47,6 +49,7 @@ namespace Bit.Core.Services
|
||||
_mailService = mailService;
|
||||
_pushNotificationService = pushNotificationService;
|
||||
_pushRegistrationService = pushRegistrationService;
|
||||
_deviceRepository = deviceRepository;
|
||||
_stripePaymentService = new StripePaymentService();
|
||||
}
|
||||
|
||||
@ -496,8 +499,8 @@ namespace Bit.Core.Services
|
||||
await _organizationUserRepository.CreateAsync(orgUser);
|
||||
|
||||
// push
|
||||
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(orgUser.UserId.Value,
|
||||
organization.Id);
|
||||
var deviceIds = await GetUserDeviceIdsAsync(orgUser.UserId.Value);
|
||||
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(deviceIds, organization.Id.ToString());
|
||||
await _pushNotificationService.PushSyncOrgKeysAsync(signup.Owner.Id);
|
||||
|
||||
return new Tuple<Organization, OrganizationUser>(organization, orgUser);
|
||||
@ -732,7 +735,8 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
// push
|
||||
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(orgUser.UserId.Value, organizationId);
|
||||
var deviceIds = await GetUserDeviceIdsAsync(orgUser.UserId.Value);
|
||||
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(deviceIds, organizationId.ToString());
|
||||
await _pushNotificationService.PushSyncOrgKeysAsync(orgUser.UserId.Value);
|
||||
|
||||
return orgUser;
|
||||
@ -784,7 +788,8 @@ namespace Bit.Core.Services
|
||||
if(orgUser.UserId.HasValue)
|
||||
{
|
||||
// push
|
||||
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(orgUser.UserId.Value, organizationId);
|
||||
var deviceIds = await GetUserDeviceIdsAsync(orgUser.UserId.Value);
|
||||
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(deviceIds, organizationId.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -807,7 +812,8 @@ namespace Bit.Core.Services
|
||||
if(orgUser.UserId.HasValue)
|
||||
{
|
||||
// push
|
||||
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(orgUser.UserId.Value, organizationId);
|
||||
var deviceIds = await GetUserDeviceIdsAsync(orgUser.UserId.Value);
|
||||
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(deviceIds, organizationId.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -980,5 +986,11 @@ namespace Bit.Core.Services
|
||||
OrganizationUserType.Owner);
|
||||
return owners.Where(o => o.Status == OrganizationUserStatusType.Confirmed);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<string>> GetUserDeviceIdsAsync(Guid userId)
|
||||
{
|
||||
var devices = await _deviceRepository.GetManyByUserIdAsync(userId);
|
||||
return devices.Select(d => d.Id.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,253 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
using System;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Bit.Core.Utilities;
|
||||
using System.Net;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Enums;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class RelayPushRegistrationService : IPushRegistrationService
|
||||
{
|
||||
private readonly HttpClient _pushClient;
|
||||
private readonly HttpClient _identityClient;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private string _accessToken;
|
||||
private dynamic _decodedToken;
|
||||
private DateTime? _nextAuthAttempt = null;
|
||||
|
||||
public RelayPushRegistrationService(
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
|
||||
_pushClient = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(globalSettings.PushRelayBaseUri)
|
||||
};
|
||||
|
||||
_identityClient = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(globalSettings.Installation.IdentityUri)
|
||||
};
|
||||
}
|
||||
|
||||
public async Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId,
|
||||
string identifier, DeviceType type)
|
||||
{
|
||||
var tokenStateResponse = await HandleTokenStateAsync();
|
||||
if(!tokenStateResponse)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var requestModel = new PushRegistrationRequestModel
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
Identifier = identifier,
|
||||
PushToken = pushToken,
|
||||
Type = type,
|
||||
UserId = userId
|
||||
};
|
||||
|
||||
var message = new TokenHttpRequestMessage(requestModel, _accessToken)
|
||||
{
|
||||
Method = HttpMethod.Post,
|
||||
RequestUri = new Uri(_pushClient.BaseAddress, "register")
|
||||
};
|
||||
await _pushClient.SendAsync(message);
|
||||
}
|
||||
|
||||
public async Task DeleteRegistrationAsync(string deviceId)
|
||||
{
|
||||
var tokenStateResponse = await HandleTokenStateAsync();
|
||||
if(!tokenStateResponse)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = new TokenHttpRequestMessage(_accessToken)
|
||||
{
|
||||
Method = HttpMethod.Delete,
|
||||
RequestUri = new Uri(_pushClient.BaseAddress, deviceId)
|
||||
};
|
||||
await _pushClient.SendAsync(message);
|
||||
}
|
||||
|
||||
public async Task AddUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId)
|
||||
{
|
||||
if(!deviceIds.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tokenStateResponse = await HandleTokenStateAsync();
|
||||
if(!tokenStateResponse)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var requestModel = new PushUpdateRequestModel(deviceIds, organizationId);
|
||||
var message = new TokenHttpRequestMessage(requestModel, _accessToken)
|
||||
{
|
||||
Method = HttpMethod.Put,
|
||||
RequestUri = new Uri(_pushClient.BaseAddress, "add-organization")
|
||||
};
|
||||
await _pushClient.SendAsync(message);
|
||||
}
|
||||
|
||||
public async Task DeleteUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId)
|
||||
{
|
||||
if(!deviceIds.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tokenStateResponse = await HandleTokenStateAsync();
|
||||
if(!tokenStateResponse)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var requestModel = new PushUpdateRequestModel(deviceIds, organizationId);
|
||||
var message = new TokenHttpRequestMessage(requestModel, _accessToken)
|
||||
{
|
||||
Method = HttpMethod.Put,
|
||||
RequestUri = new Uri(_pushClient.BaseAddress, "delete-organization")
|
||||
};
|
||||
await _pushClient.SendAsync(message);
|
||||
}
|
||||
|
||||
private async Task<bool> HandleTokenStateAsync()
|
||||
{
|
||||
if(_nextAuthAttempt.HasValue && DateTime.UtcNow > _nextAuthAttempt.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_nextAuthAttempt = null;
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(_accessToken) && !TokenExpired())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var requestMessage = new HttpRequestMessage
|
||||
{
|
||||
Method = HttpMethod.Post,
|
||||
RequestUri = new Uri(_identityClient.BaseAddress, "connect/token"),
|
||||
Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "grant_type", "client_credentials" },
|
||||
{ "scope", "api.push" },
|
||||
{ "client_id", $"installation.{_globalSettings.Installation.Id}" },
|
||||
{ "client_secret", $"{_globalSettings.Installation.Key}" }
|
||||
})
|
||||
};
|
||||
|
||||
var response = await _identityClient.SendAsync(requestMessage);
|
||||
if(!response.IsSuccessStatusCode)
|
||||
{
|
||||
if(response.StatusCode == HttpStatusCode.BadRequest)
|
||||
{
|
||||
_nextAuthAttempt = DateTime.UtcNow.AddDays(1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
dynamic tokenResponse = JsonConvert.DeserializeObject(responseContent);
|
||||
_accessToken = (string)tokenResponse.access_token;
|
||||
return true;
|
||||
}
|
||||
|
||||
public class TokenHttpRequestMessage : HttpRequestMessage
|
||||
{
|
||||
public TokenHttpRequestMessage(string token)
|
||||
{
|
||||
Headers.Add("Authorization", $"Bearer {token}");
|
||||
}
|
||||
|
||||
public TokenHttpRequestMessage(object requestObject, string token)
|
||||
: this(token)
|
||||
{
|
||||
var stringContent = JsonConvert.SerializeObject(requestObject);
|
||||
Content = new StringContent(stringContent, Encoding.UTF8, "application/json");
|
||||
}
|
||||
}
|
||||
|
||||
public bool TokenExpired()
|
||||
{
|
||||
var decoded = DecodeToken();
|
||||
var exp = decoded?["exp"];
|
||||
if(exp == null)
|
||||
{
|
||||
throw new InvalidOperationException("No exp in token.");
|
||||
}
|
||||
|
||||
var expiration = CoreHelpers.FromEpocMilliseconds(1000 * exp.Value<long>());
|
||||
return DateTime.UtcNow < expiration;
|
||||
}
|
||||
|
||||
private JObject DecodeToken()
|
||||
{
|
||||
if(_decodedToken != null)
|
||||
{
|
||||
return _decodedToken;
|
||||
}
|
||||
|
||||
if(_accessToken == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(_accessToken)} not found.");
|
||||
}
|
||||
|
||||
var parts = _accessToken.Split('.');
|
||||
if(parts.Length != 3)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(_accessToken)} must have 3 parts");
|
||||
}
|
||||
|
||||
var decodedBytes = Base64UrlDecode(parts[1]);
|
||||
if(decodedBytes == null || decodedBytes.Length < 1)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(_accessToken)} must have 3 parts");
|
||||
}
|
||||
|
||||
_decodedToken = JObject.Parse(Encoding.UTF8.GetString(decodedBytes, 0, decodedBytes.Length));
|
||||
return _decodedToken;
|
||||
}
|
||||
|
||||
private byte[] Base64UrlDecode(string input)
|
||||
{
|
||||
var output = input;
|
||||
// 62nd char of encoding
|
||||
output = output.Replace('-', '+');
|
||||
// 63rd char of encoding
|
||||
output = output.Replace('_', '/');
|
||||
// Pad with trailing '='s
|
||||
switch(output.Length % 4)
|
||||
{
|
||||
case 0:
|
||||
// No pad chars in this case
|
||||
break;
|
||||
case 2:
|
||||
// Two pad chars
|
||||
output += "=="; break;
|
||||
case 3:
|
||||
// One pad char
|
||||
output += "="; break;
|
||||
default:
|
||||
throw new InvalidOperationException("Illegal base64url string!");
|
||||
}
|
||||
|
||||
// Standard base64 decoder
|
||||
return Convert.FromBase64String(output);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class NoopPushRegistrationService : IPushRegistrationService
|
||||
{
|
||||
public Task AddUserRegistrationOrganizationAsync(Guid userId, Guid organizationId)
|
||||
public Task AddUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task CreateOrUpdateRegistrationAsync(Device device)
|
||||
public Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId,
|
||||
string identifier, DeviceType type)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task DeleteRegistrationAsync(Guid deviceId)
|
||||
public Task DeleteRegistrationAsync(string deviceId)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task DeleteUserRegistrationOrganizationAsync(Guid userId, Guid organizationId)
|
||||
public Task DeleteUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
@ -17,6 +17,9 @@ namespace Setup
|
||||
private static bool _ssl = false;
|
||||
private static bool _selfSignedSsl = false;
|
||||
private static bool _letsEncrypt = false;
|
||||
private static string _installationId = null;
|
||||
private static string _installationKey = null;
|
||||
private static bool _push = false;
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
@ -50,6 +53,14 @@ namespace Setup
|
||||
|
||||
_url = _ssl ? $"https://{_domain}" : $"http://{_domain}";
|
||||
BuildNginxConfig();
|
||||
|
||||
Console.Write("Installation ID: ");
|
||||
_installationId = Console.ReadLine().ToLowerInvariant();
|
||||
Console.Write("Installation key: ");
|
||||
_installationKey = Console.ReadLine().ToLowerInvariant();
|
||||
Console.Write("Do you want to use push notifications? (y/n): ");
|
||||
_push = Console.ReadLine().ToLowerInvariant() == "y";
|
||||
|
||||
BuildEnvironmentFiles();
|
||||
BuildAppSettingsFiles();
|
||||
}
|
||||
@ -262,8 +273,15 @@ globalSettings:dataProtection:directory={_outputDir}/core/aspnet-dataprotection
|
||||
globalSettings:logDirectory={_outputDir}/core/logs
|
||||
globalSettings:licenseDirectory={_outputDir}/core/licenses
|
||||
globalSettings:duo:aKey={Helpers.SecureRandomString(32, alpha: true, numeric: true)}
|
||||
globalSettings:installation:id={_installationId}
|
||||
globalSettings:installation:key={_installationKey}
|
||||
globalSettings:yubico:clientId=REPLACE
|
||||
globalSettings:yubico:REPLACE");
|
||||
globalSettings:yubico:key=REPLACE");
|
||||
|
||||
if(!_push)
|
||||
{
|
||||
sw.Write("globalSettings:pushRelayBaseUri=REPLACE");
|
||||
}
|
||||
}
|
||||
|
||||
using(var sw = File.CreateText("/bitwarden/docker/mssql.override.env"))
|
||||
|
Loading…
Reference in New Issue
Block a user