mirror of
https://github.com/bitwarden/server.git
synced 2025-05-28 14:54:50 -05:00
update libs and cleanup
This commit is contained in:
parent
5786be651e
commit
5a4bfe4e61
@ -28,7 +28,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="1.1.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="1.1.2" />
|
||||||
<PackageReference Include="AspNetCoreRateLimit" Version="1.0.5" />
|
<PackageReference Include="AspNetCoreRateLimit" Version="1.0.5" />
|
||||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" />
|
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.1" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.2" />
|
<PackageReference Include="System.Net.Http" Version="4.3.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ namespace Bit.Api.Controllers
|
|||||||
if(userId.HasValue)
|
if(userId.HasValue)
|
||||||
{
|
{
|
||||||
var date = await _userService.GetAccountRevisionDateByIdAsync(userId.Value);
|
var date = await _userService.GetAccountRevisionDateByIdAsync(userId.Value);
|
||||||
revisionDate = Core.Utilities.CoreHelpers.ToEpocMilliseconds(date);
|
revisionDate = CoreHelpers.ToEpocMilliseconds(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
return revisionDate;
|
return revisionDate;
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
"globalSettings": {
|
"globalSettings": {
|
||||||
"siteName": "bitwarden",
|
"siteName": "bitwarden",
|
||||||
"baseVaultUri": "http://localhost:4001/#",
|
"baseVaultUri": "http://localhost:4001/#",
|
||||||
"jwtSigningKey": "THIS IS A SECRET. IT KEEPS YOUR TOKEN SAFE. :)",
|
|
||||||
"stripeApiKey": "SECRET",
|
"stripeApiKey": "SECRET",
|
||||||
"sqlServer": {
|
"sqlServer": {
|
||||||
"connectionString": "SECRET"
|
"connectionString": "SECRET"
|
||||||
@ -11,13 +10,6 @@
|
|||||||
"apiKey": "SECRET",
|
"apiKey": "SECRET",
|
||||||
"replyToEmail": "hello@bitwarden.com"
|
"replyToEmail": "hello@bitwarden.com"
|
||||||
},
|
},
|
||||||
"push": {
|
|
||||||
"apnsCertificateThumbprint": "SECRET",
|
|
||||||
"apnsCertificatePassword": "SECRET",
|
|
||||||
"gcmSenderId": "SECRET",
|
|
||||||
"gcmApiKey": "SECRET",
|
|
||||||
"gcmAppPackageName": "com.x8bit.bitwarden"
|
|
||||||
},
|
|
||||||
"identityServer": {
|
"identityServer": {
|
||||||
"certificateThumbprint": "SECRET"
|
"certificateThumbprint": "SECRET"
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
"globalSettings": {
|
"globalSettings": {
|
||||||
"siteName": "bitwarden",
|
"siteName": "bitwarden",
|
||||||
"baseVaultUri": "http://localhost:4001/#",
|
"baseVaultUri": "http://localhost:4001/#",
|
||||||
"jwtSigningKey": "THIS IS A SECRET. IT KEEPS YOUR TOKEN SAFE. :)",
|
|
||||||
"stripeApiKey": "SECRET",
|
"stripeApiKey": "SECRET",
|
||||||
"sqlServer": {
|
"sqlServer": {
|
||||||
"connectionString": "SECRET"
|
"connectionString": "SECRET"
|
||||||
@ -11,13 +10,6 @@
|
|||||||
"apiKey": "SECRET",
|
"apiKey": "SECRET",
|
||||||
"replyToEmail": "hello@bitwarden.com"
|
"replyToEmail": "hello@bitwarden.com"
|
||||||
},
|
},
|
||||||
"push": {
|
|
||||||
"apnsCertificateThumbprint": "SECRET",
|
|
||||||
"apnsCertificatePassword": "SECRET",
|
|
||||||
"gcmSenderId": "SECRET",
|
|
||||||
"gcmApiKey": "SECRET",
|
|
||||||
"gcmAppPackageName": "com.x8bit.bitwarden"
|
|
||||||
},
|
|
||||||
"identityServer": {
|
"identityServer": {
|
||||||
"certificateThumbprint": "SECRET"
|
"certificateThumbprint": "SECRET"
|
||||||
},
|
},
|
||||||
|
@ -52,16 +52,15 @@
|
|||||||
<PackageReference Include="Microsoft.Azure.NotificationHubs" Version="1.0.8" />
|
<PackageReference Include="Microsoft.Azure.NotificationHubs" Version="1.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||||
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.2" />
|
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.2" />
|
||||||
<PackageReference Include="RazorLight" Version="1.1.0" />
|
<PackageReference Include="RazorLight" Version="1.1.0" />
|
||||||
<PackageReference Include="Sendgrid" Version="9.2.0" />
|
<PackageReference Include="Sendgrid" Version="9.5.0" />
|
||||||
<PackageReference Include="PushSharp" Version="4.0.10" />
|
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="1.4.0" />
|
<PackageReference Include="Serilog.Extensions.Logging" Version="1.4.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.AzureDocumentDB" Version="3.6.1" />
|
<PackageReference Include="Serilog.Sinks.AzureDocumentDB" Version="3.6.1" />
|
||||||
<PackageReference Include="Stripe.net" Version="7.8.0" />
|
<PackageReference Include="Stripe.net" Version="7.8.0" />
|
||||||
<PackageReference Include="U2F.Core" Version="1.0.3" />
|
<PackageReference Include="U2F.Core" Version="1.0.3" />
|
||||||
<PackageReference Include="WindowsAzure.Storage" Version="8.1.1" />
|
<PackageReference Include="WindowsAzure.Storage" Version="8.1.4" />
|
||||||
<PackageReference Include="Otp.NET" Version="1.0.1" />
|
<PackageReference Include="Otp.NET" Version="1.0.1" />
|
||||||
<PackageReference Include="YubicoDotNetClient" Version="1.0.0" />
|
<PackageReference Include="YubicoDotNetClient" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -4,11 +4,9 @@
|
|||||||
{
|
{
|
||||||
public virtual string SiteName { get; set; }
|
public virtual string SiteName { get; set; }
|
||||||
public virtual string BaseVaultUri { get; set; }
|
public virtual string BaseVaultUri { get; set; }
|
||||||
public virtual string JwtSigningKey { get; set; }
|
|
||||||
public virtual string StripeApiKey { get; set; }
|
public virtual string StripeApiKey { get; set; }
|
||||||
public virtual SqlServerSettings SqlServer { get; set; } = new SqlServerSettings();
|
public virtual SqlServerSettings SqlServer { get; set; } = new SqlServerSettings();
|
||||||
public virtual MailSettings Mail { get; set; } = new MailSettings();
|
public virtual MailSettings Mail { get; set; } = new MailSettings();
|
||||||
public virtual PushSettings Push { get; set; } = new PushSettings();
|
|
||||||
public virtual StorageSettings Storage { get; set; } = new StorageSettings();
|
public virtual StorageSettings Storage { get; set; } = new StorageSettings();
|
||||||
public virtual AttachmentSettings Attachment { get; set; } = new AttachmentSettings();
|
public virtual AttachmentSettings Attachment { get; set; } = new AttachmentSettings();
|
||||||
public virtual IdentityServerSettings IdentityServer { get; set; } = new IdentityServerSettings();
|
public virtual IdentityServerSettings IdentityServer { get; set; } = new IdentityServerSettings();
|
||||||
@ -51,15 +49,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PushSettings
|
|
||||||
{
|
|
||||||
public string ApnsCertificateThumbprint { get; set; }
|
|
||||||
public string ApnsCertificatePassword { get; set; }
|
|
||||||
public string GcmSenderId { get; set; }
|
|
||||||
public string GcmApiKey { get; set; }
|
|
||||||
public string GcmAppPackageName { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class IdentityServerSettings
|
public class IdentityServerSettings
|
||||||
{
|
{
|
||||||
public string CertificateThumbprint { get; set; }
|
public string CertificateThumbprint { get; set; }
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Bit.Core
|
|
||||||
{
|
|
||||||
public interface IDataObject<T> where T : IEquatable<T>
|
|
||||||
{
|
|
||||||
T Id { get; set; }
|
|
||||||
void SetNewId();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Bit.Core.Models.Table;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Utilities.Duo;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
|
||||||
|
|
||||||
namespace Bit.Core.Identity
|
|
||||||
{
|
|
||||||
public class DuoTokenProvider : IUserTwoFactorTokenProvider<User>
|
|
||||||
{
|
|
||||||
public Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
|
||||||
{
|
|
||||||
if(!user.Premium)
|
|
||||||
{
|
|
||||||
return Task.FromResult(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
|
|
||||||
var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.Duo)
|
|
||||||
&& !string.IsNullOrWhiteSpace((string)provider?.MetaData["UserId"]);
|
|
||||||
|
|
||||||
return Task.FromResult(canGenerate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <param name="purpose">Ex: "auto", "push", "passcode:123456", "sms", "phone"</param>
|
|
||||||
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
|
||||||
{
|
|
||||||
if(!user.Premium)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
|
|
||||||
var duoClient = new DuoApi((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"],
|
|
||||||
(string)provider.MetaData["Host"]);
|
|
||||||
var parts = purpose.Split(':');
|
|
||||||
|
|
||||||
var parameters = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
["async"] = "1",
|
|
||||||
["user_id"] = (string)provider.MetaData["UserId"],
|
|
||||||
["factor"] = parts[0]
|
|
||||||
};
|
|
||||||
|
|
||||||
if(parameters["factor"] == "passcode" && parts.Length > 1)
|
|
||||||
{
|
|
||||||
parameters["passcode"] = parts[1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
parameters["device"] = "auto";
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await duoClient.JSONApiCallAsync<Dictionary<string, object>>(HttpMethod.Post,
|
|
||||||
"/auth/v2/auth", parameters);
|
|
||||||
|
|
||||||
if(response.ContainsKey("txid"))
|
|
||||||
{
|
|
||||||
var txId = response["txid"] as string;
|
|
||||||
return txId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(DuoException) { }
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
|
|
||||||
{
|
|
||||||
if(!user.Premium)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
|
|
||||||
var duoClient = new DuoApi((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"],
|
|
||||||
(string)provider.MetaData["Host"]);
|
|
||||||
|
|
||||||
var parameters = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
["txid"] = token
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await duoClient.JSONApiCallAsync<Dictionary<string, object>>(HttpMethod.Get,
|
|
||||||
"/auth/v2/auth_status", parameters);
|
|
||||||
|
|
||||||
var result = response["result"] as string;
|
|
||||||
return string.Equals(result, "allow");
|
|
||||||
}
|
|
||||||
catch(DuoException)
|
|
||||||
{
|
|
||||||
// TODO: We might want to return true in some cases? What if Duo is down?
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public class Cipher : IDataObject<Guid>
|
public class Cipher : ITableObject<Guid>
|
||||||
{
|
{
|
||||||
private Dictionary<string, CipherAttachment.MetaData> _attachmentData;
|
private Dictionary<string, CipherAttachment.MetaData> _attachmentData;
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ using Bit.Core.Utilities;
|
|||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public class Collection : IDataObject<Guid>
|
public class Collection : ITableObject<Guid>
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid OrganizationId { get; set; }
|
public Guid OrganizationId { get; set; }
|
||||||
|
@ -3,7 +3,7 @@ using Bit.Core.Utilities;
|
|||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public class Device : IDataObject<Guid>
|
public class Device : ITableObject<Guid>
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
@ -3,7 +3,7 @@ using Bit.Core.Utilities;
|
|||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public class Folder : IDataObject<Guid>
|
public class Folder : ITableObject<Guid>
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
@ -3,7 +3,7 @@ using Bit.Core.Utilities;
|
|||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public class Group : IDataObject<Guid>
|
public class Group : ITableObject<Guid>
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid OrganizationId { get; set; }
|
public Guid OrganizationId { get; set; }
|
||||||
|
10
src/Core/Models/Table/ITableObject.cs
Normal file
10
src/Core/Models/Table/ITableObject.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Bit.Core.Models.Table
|
||||||
|
{
|
||||||
|
public interface ITableObject<T> where T : IEquatable<T>
|
||||||
|
{
|
||||||
|
T Id { get; set; }
|
||||||
|
void SetNewId();
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ using Bit.Core.Enums;
|
|||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public class Organization : IDataObject<Guid>, ISubscriber, IStorable, IStorableSubscriber, IRevisable
|
public class Organization : ITableObject<Guid>, ISubscriber, IStorable, IStorableSubscriber, IRevisable
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
@ -4,7 +4,7 @@ using Bit.Core.Enums;
|
|||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public class OrganizationUser : IDataObject<Guid>
|
public class OrganizationUser : ITableObject<Guid>
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid OrganizationId { get; set; }
|
public Guid OrganizationId { get; set; }
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public class U2f : IDataObject<int>
|
public class U2f : ITableObject<int>
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
@ -7,7 +7,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public class User : IDataObject<Guid>, ISubscriber, IStorable, IStorableSubscriber, IRevisable
|
public class User : ITableObject<Guid>, ISubscriber, IStorable, IStorableSubscriber, IRevisable
|
||||||
{
|
{
|
||||||
private Dictionary<TwoFactorProviderType, TwoFactorProvider> _twoFactorProviders;
|
private Dictionary<TwoFactorProviderType, TwoFactorProvider> _twoFactorProviders;
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
using System;
|
using Bit.Core.Models.Table;
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Bit.Core.Repositories
|
namespace Bit.Core.Repositories
|
||||||
{
|
{
|
||||||
public interface IRepository<T, TId> where TId : IEquatable<TId> where T : class, IDataObject<TId>
|
public interface IRepository<T, TId> where TId : IEquatable<TId> where T : class, ITableObject<TId>
|
||||||
{
|
{
|
||||||
Task<T> GetByIdAsync(TId id);
|
Task<T> GetByIdAsync(TId id);
|
||||||
Task CreateAsync(T obj);
|
Task CreateAsync(T obj);
|
||||||
|
@ -4,10 +4,13 @@ using System.Data.SqlClient;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
|
using Bit.Core.Models.Table;
|
||||||
|
|
||||||
namespace Bit.Core.Repositories.SqlServer
|
namespace Bit.Core.Repositories.SqlServer
|
||||||
{
|
{
|
||||||
public abstract class Repository<T, TId> : BaseRepository, IRepository<T, TId> where TId : IEquatable<TId> where T : class, IDataObject<TId>
|
public abstract class Repository<T, TId> : BaseRepository, IRepository<T, TId>
|
||||||
|
where TId : IEquatable<TId>
|
||||||
|
where T : class, ITableObject<TId>
|
||||||
{
|
{
|
||||||
public Repository(string connectionString, string schema = null, string table = null)
|
public Repository(string connectionString, string schema = null, string table = null)
|
||||||
: base(connectionString)
|
: base(connectionString)
|
||||||
|
@ -1,352 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using PushSharp.Google;
|
|
||||||
using PushSharp.Apple;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using PushSharp.Core;
|
|
||||||
using Bit.Core.Models.Table;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Bit.Core.Models;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
[Obsolete]
|
|
||||||
public class PushSharpPushNotificationService : IPushNotificationService
|
|
||||||
{
|
|
||||||
private readonly IDeviceRepository _deviceRepository;
|
|
||||||
private readonly ILogger<IPushNotificationService> _logger;
|
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
||||||
private GcmServiceBroker _gcmBroker;
|
|
||||||
private ApnsServiceBroker _apnsBroker;
|
|
||||||
|
|
||||||
public PushSharpPushNotificationService(
|
|
||||||
IDeviceRepository deviceRepository,
|
|
||||||
IHttpContextAccessor httpContextAccessor,
|
|
||||||
ILogger<IPushNotificationService> logger,
|
|
||||||
IHostingEnvironment hostingEnvironment,
|
|
||||||
GlobalSettings globalSettings)
|
|
||||||
{
|
|
||||||
_deviceRepository = deviceRepository;
|
|
||||||
_httpContextAccessor = httpContextAccessor;
|
|
||||||
_logger = logger;
|
|
||||||
|
|
||||||
InitGcmBroker(globalSettings);
|
|
||||||
InitApnsBroker(globalSettings, hostingEnvironment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PushSyncCipherCreateAsync(Cipher cipher)
|
|
||||||
{
|
|
||||||
await PushCipherAsync(cipher, PushType.SyncCipherCreate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PushSyncCipherUpdateAsync(Cipher cipher)
|
|
||||||
{
|
|
||||||
await PushCipherAsync(cipher, PushType.SyncCipherUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PushSyncCipherDeleteAsync(Cipher cipher)
|
|
||||||
{
|
|
||||||
switch(cipher.Type)
|
|
||||||
{
|
|
||||||
case CipherType.Login:
|
|
||||||
await PushCipherAsync(cipher, PushType.SyncLoginDelete);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PushSyncFolderCreateAsync(Folder folder)
|
|
||||||
{
|
|
||||||
await PushFolderAsync(folder, PushType.SyncFolderCreate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PushSyncFolderUpdateAsync(Folder folder)
|
|
||||||
{
|
|
||||||
await PushFolderAsync(folder, PushType.SyncFolderUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PushSyncFolderDeleteAsync(Folder folder)
|
|
||||||
{
|
|
||||||
await PushFolderAsync(folder, PushType.SyncFolderDelete);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task PushCipherAsync(Cipher cipher, PushType type)
|
|
||||||
{
|
|
||||||
if(!cipher.UserId.HasValue)
|
|
||||||
{
|
|
||||||
// No push for org ciphers at the moment.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = new SyncCipherPushNotification
|
|
||||||
{
|
|
||||||
Id = cipher.Id,
|
|
||||||
UserId = cipher.UserId,
|
|
||||||
OrganizationId = cipher.OrganizationId,
|
|
||||||
RevisionDate = cipher.RevisionDate
|
|
||||||
};
|
|
||||||
|
|
||||||
var excludedTokens = new List<string>();
|
|
||||||
var currentContext = _httpContextAccessor?.HttpContext?.
|
|
||||||
RequestServices.GetService(typeof(CurrentContext)) as CurrentContext;
|
|
||||||
if(!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier))
|
|
||||||
{
|
|
||||||
excludedTokens.Add(currentContext.DeviceIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
await PushToAllUserDevicesAsync(cipher.UserId.Value, type, message, excludedTokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task PushFolderAsync(Folder folder, PushType type)
|
|
||||||
{
|
|
||||||
var message = new SyncFolderPushNotification
|
|
||||||
{
|
|
||||||
Id = folder.Id,
|
|
||||||
UserId = folder.UserId,
|
|
||||||
RevisionDate = folder.RevisionDate
|
|
||||||
};
|
|
||||||
|
|
||||||
var excludedTokens = new List<string>();
|
|
||||||
var currentContext = _httpContextAccessor?.HttpContext?.
|
|
||||||
RequestServices.GetService(typeof(CurrentContext)) as CurrentContext;
|
|
||||||
if(!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier))
|
|
||||||
{
|
|
||||||
excludedTokens.Add(currentContext.DeviceIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
await PushToAllUserDevicesAsync(folder.UserId, type, message, excludedTokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PushSyncCiphersAsync(Guid userId)
|
|
||||||
{
|
|
||||||
await PushSyncUserAsync(userId, PushType.SyncCiphers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PushSyncVaultAsync(Guid userId)
|
|
||||||
{
|
|
||||||
await PushSyncUserAsync(userId, PushType.SyncVault);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PushSyncOrgKeysAsync(Guid userId)
|
|
||||||
{
|
|
||||||
await PushSyncUserAsync(userId, PushType.SyncOrgKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PushSyncSettingsAsync(Guid userId)
|
|
||||||
{
|
|
||||||
await PushSyncUserAsync(userId, PushType.SyncSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task PushSyncUserAsync(Guid userId, PushType type)
|
|
||||||
{
|
|
||||||
var message = new SyncUserPushNotification
|
|
||||||
{
|
|
||||||
UserId = userId,
|
|
||||||
Date = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
await PushToAllUserDevicesAsync(userId, type, message, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitGcmBroker(GlobalSettings globalSettings)
|
|
||||||
{
|
|
||||||
if(string.IsNullOrWhiteSpace(globalSettings.Push.GcmSenderId) || string.IsNullOrWhiteSpace(globalSettings.Push.GcmApiKey)
|
|
||||||
|| string.IsNullOrWhiteSpace(globalSettings.Push.GcmAppPackageName))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var gcmConfig = new GcmConfiguration(globalSettings.Push.GcmSenderId, globalSettings.Push.GcmApiKey,
|
|
||||||
globalSettings.Push.GcmAppPackageName);
|
|
||||||
|
|
||||||
_gcmBroker = new GcmServiceBroker(gcmConfig);
|
|
||||||
_gcmBroker.OnNotificationFailed += GcmBroker_OnNotificationFailed;
|
|
||||||
_gcmBroker.OnNotificationSucceeded += (notification) =>
|
|
||||||
{
|
|
||||||
Debug.WriteLine("GCM Notification Sent!");
|
|
||||||
};
|
|
||||||
_gcmBroker.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GcmBroker_OnNotificationFailed(GcmNotification notification, AggregateException exception)
|
|
||||||
{
|
|
||||||
exception.Handle(ex =>
|
|
||||||
{
|
|
||||||
// See what kind of exception it was to further diagnose
|
|
||||||
if(ex is GcmNotificationException)
|
|
||||||
{
|
|
||||||
var notificationException = ex as GcmNotificationException;
|
|
||||||
|
|
||||||
// Deal with the failed notification
|
|
||||||
var gcmNotification = notificationException.Notification;
|
|
||||||
var description = notificationException.Description;
|
|
||||||
|
|
||||||
Debug.WriteLine($"GCM Notification Failed: ID={gcmNotification.MessageId}, Desc={description}");
|
|
||||||
}
|
|
||||||
else if(ex is GcmMulticastResultException)
|
|
||||||
{
|
|
||||||
var multicastException = ex as GcmMulticastResultException;
|
|
||||||
|
|
||||||
foreach(var succeededNotification in multicastException.Succeeded)
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"GCM Notification Failed: ID={succeededNotification.MessageId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(var failedKvp in multicastException.Failed)
|
|
||||||
{
|
|
||||||
var n = failedKvp.Key;
|
|
||||||
var e = failedKvp.Value;
|
|
||||||
|
|
||||||
Debug.WriteLine($"GCM Notification Failed: ID={n.MessageId}, Desc={e.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(ex is DeviceSubscriptionExpiredException)
|
|
||||||
{
|
|
||||||
var expiredException = ex as DeviceSubscriptionExpiredException;
|
|
||||||
|
|
||||||
var oldId = expiredException.OldSubscriptionId;
|
|
||||||
var newId = expiredException.NewSubscriptionId;
|
|
||||||
|
|
||||||
Debug.WriteLine($"Device RegistrationId Expired: {oldId}");
|
|
||||||
|
|
||||||
if(!string.IsNullOrWhiteSpace(newId))
|
|
||||||
{
|
|
||||||
// If this value isn't null, our subscription changed and we should update our database
|
|
||||||
Debug.WriteLine($"Device RegistrationId Changed To: {newId}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(ex is RetryAfterException)
|
|
||||||
{
|
|
||||||
var retryException = (RetryAfterException)ex;
|
|
||||||
// If you get rate limited, you should stop sending messages until after the RetryAfterUtc date
|
|
||||||
Debug.WriteLine($"GCM Rate Limited, don't send more until after {retryException.RetryAfterUtc}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.WriteLine("GCM Notification Failed for some unknown reason");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark it as handled
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitApnsBroker(GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
|
|
||||||
{
|
|
||||||
if(string.IsNullOrWhiteSpace(globalSettings.Push.ApnsCertificatePassword)
|
|
||||||
|| string.IsNullOrWhiteSpace(globalSettings.Push.ApnsCertificateThumbprint))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var apnsCertificate = CoreHelpers.GetCertificate(globalSettings.Push.ApnsCertificateThumbprint);
|
|
||||||
if(apnsCertificate == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var apnsConfig = new ApnsConfiguration(hostingEnvironment.IsProduction() ?
|
|
||||||
ApnsConfiguration.ApnsServerEnvironment.Production : ApnsConfiguration.ApnsServerEnvironment.Sandbox,
|
|
||||||
apnsCertificate.RawData, globalSettings.Push.ApnsCertificatePassword);
|
|
||||||
|
|
||||||
_apnsBroker = new ApnsServiceBroker(apnsConfig);
|
|
||||||
_apnsBroker.OnNotificationFailed += ApnsBroker_OnNotificationFailed;
|
|
||||||
_apnsBroker.OnNotificationSucceeded += (notification) =>
|
|
||||||
{
|
|
||||||
Debug.WriteLine("Apple Notification Sent!");
|
|
||||||
};
|
|
||||||
_apnsBroker.Start();
|
|
||||||
|
|
||||||
var feedbackService = new FeedbackService(apnsConfig);
|
|
||||||
feedbackService.FeedbackReceived += FeedbackService_FeedbackReceived;
|
|
||||||
feedbackService.Check();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApnsBroker_OnNotificationFailed(ApnsNotification notification, AggregateException exception)
|
|
||||||
{
|
|
||||||
exception.Handle(ex =>
|
|
||||||
{
|
|
||||||
// See what kind of exception it was to further diagnose
|
|
||||||
if(ex is ApnsNotificationException)
|
|
||||||
{
|
|
||||||
var notificationException = ex as ApnsNotificationException;
|
|
||||||
|
|
||||||
// Deal with the failed notification
|
|
||||||
var apnsNotification = notificationException.Notification;
|
|
||||||
var statusCode = notificationException.ErrorStatusCode;
|
|
||||||
|
|
||||||
Debug.WriteLine($"Apple Notification Failed: ID={apnsNotification.Identifier}, Code={statusCode}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Inner exception might hold more useful information like an ApnsConnectionException
|
|
||||||
Debug.WriteLine($"Apple Notification Failed for some unknown reason : {ex.InnerException}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark it as handled
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FeedbackService_FeedbackReceived(string deviceToken, DateTime timestamp)
|
|
||||||
{
|
|
||||||
// Remove the deviceToken from your database
|
|
||||||
// timestamp is the time the token was reported as expired
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task PushToAllUserDevicesAsync(Guid userId, PushType type, object message, IEnumerable<string> tokensToSkip)
|
|
||||||
{
|
|
||||||
var devices = (await _deviceRepository.GetManyByUserIdAsync(userId))
|
|
||||||
.Where(d => !string.IsNullOrWhiteSpace(d.PushToken) && (!tokensToSkip?.Contains(d.PushToken) ?? true));
|
|
||||||
if(devices.Count() == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_apnsBroker != null)
|
|
||||||
{
|
|
||||||
var appleNotification = new ApplePayloadPushNotification
|
|
||||||
{
|
|
||||||
Data = new PayloadPushNotification.DataObj(type, JsonConvert.SerializeObject(message))
|
|
||||||
};
|
|
||||||
|
|
||||||
var obj = JObject.FromObject(appleNotification);
|
|
||||||
|
|
||||||
// Send to each iOS device
|
|
||||||
foreach(var device in devices.Where(d => d.Type == DeviceType.iOS))
|
|
||||||
{
|
|
||||||
_apnsBroker.QueueNotification(new ApnsNotification
|
|
||||||
{
|
|
||||||
DeviceToken = device.PushToken,
|
|
||||||
Payload = obj
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Android can send to many devices at once
|
|
||||||
var androidDevices = devices.Where(d => d.Type == DeviceType.Android);
|
|
||||||
if(_gcmBroker != null && androidDevices.Count() > 0)
|
|
||||||
{
|
|
||||||
var gcmData = new PayloadPushNotification.DataObj(type, JsonConvert.SerializeObject(message));
|
|
||||||
var obj = JObject.FromObject(gcmData);
|
|
||||||
|
|
||||||
_gcmBroker.QueueNotification(new GcmNotification
|
|
||||||
{
|
|
||||||
RegistrationIds = androidDevices.Select(d => d.PushToken).ToList(),
|
|
||||||
Data = obj
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ using Newtonsoft.Json;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -16,7 +15,7 @@ namespace Bit.Core.Utilities
|
|||||||
public static class CoreHelpers
|
public static class CoreHelpers
|
||||||
{
|
{
|
||||||
private static readonly long _baseDateTicks = new DateTime(1900, 1, 1).Ticks;
|
private static readonly long _baseDateTicks = new DateTime(1900, 1, 1).Ticks;
|
||||||
private static readonly DateTime _epoc = new DateTime(1970, 1, 1);
|
private static readonly DateTime _epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate sequential Guid for Sql Server.
|
/// Generate sequential Guid for Sql Server.
|
||||||
|
@ -1,285 +0,0 @@
|
|||||||
/*
|
|
||||||
Original source modified from https://github.com/duosecurity/duo_api_csharp
|
|
||||||
|
|
||||||
=============================================================================
|
|
||||||
=============================================================================
|
|
||||||
|
|
||||||
ref: https://github.com/duosecurity/duo_api_csharp/blob/master/LICENSE
|
|
||||||
|
|
||||||
Copyright (c) 2013, Duo Security, Inc.
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions
|
|
||||||
are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
3. The name of the author may not be used to endorse or promote products
|
|
||||||
derived from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
||||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
||||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
||||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
||||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
||||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Text;
|
|
||||||
using System.Globalization;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Utilities.Duo
|
|
||||||
{
|
|
||||||
public class DuoApi
|
|
||||||
{
|
|
||||||
public const string DefaultAgent = "Duo.NET, bitwarden";
|
|
||||||
|
|
||||||
private readonly string _ikey;
|
|
||||||
private readonly string _skey;
|
|
||||||
private readonly string _host;
|
|
||||||
private readonly string _userAgent;
|
|
||||||
|
|
||||||
public DuoApi(string ikey, string skey, string host)
|
|
||||||
: this(ikey, skey, host, null)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected DuoApi(string ikey, string skey, string host, string userAgent)
|
|
||||||
{
|
|
||||||
_ikey = ikey;
|
|
||||||
_skey = skey;
|
|
||||||
_host = host;
|
|
||||||
_userAgent = string.IsNullOrWhiteSpace(userAgent) ? DefaultAgent : userAgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Tuple<string, HttpStatusCode>> ApiCallAsync(HttpMethod method, string path,
|
|
||||||
Dictionary<string, string> parameters, int? timeout = null, DateTime? date = null)
|
|
||||||
{
|
|
||||||
var canonParams = CanonicalizeParams(parameters);
|
|
||||||
var query = string.Empty;
|
|
||||||
if(method != HttpMethod.Post && method != HttpMethod.Put && parameters.Count > 0)
|
|
||||||
{
|
|
||||||
query = "?" + canonParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = $"https://{_host}{path}{query}";
|
|
||||||
|
|
||||||
var dateString = DateToRFC822(date.GetValueOrDefault(DateTime.UtcNow));
|
|
||||||
var auth = Sign(method.ToString(), path, canonParams, dateString);
|
|
||||||
|
|
||||||
var client = new HttpClient();
|
|
||||||
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
|
||||||
client.DefaultRequestHeaders.Add("Authorization", auth);
|
|
||||||
client.DefaultRequestHeaders.Add("X-Duo-Date", dateString);
|
|
||||||
client.DefaultRequestHeaders.Add("User-Agent", _userAgent);
|
|
||||||
|
|
||||||
if(timeout.GetValueOrDefault(0) > 0)
|
|
||||||
{
|
|
||||||
client.Timeout = new TimeSpan(0, 0, 0, 0, timeout.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = new HttpRequestMessage
|
|
||||||
{
|
|
||||||
RequestUri = new Uri(url),
|
|
||||||
Method = method
|
|
||||||
};
|
|
||||||
|
|
||||||
if(method == HttpMethod.Post || method == HttpMethod.Put)
|
|
||||||
{
|
|
||||||
request.Content = new FormUrlEncodedContent(parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponseMessage response = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
response = await client.SendAsync(request);
|
|
||||||
}
|
|
||||||
catch(WebException)
|
|
||||||
{
|
|
||||||
if(response?.Content == null)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await response.Content.ReadAsStringAsync();
|
|
||||||
return new Tuple<string, HttpStatusCode>(result, response.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<T> JSONApiCallAsync<T>(HttpMethod method, string path, Dictionary<string, string> parameters,
|
|
||||||
int? timeout = null, DateTime? date = null) where T : class
|
|
||||||
{
|
|
||||||
var resTuple = await ApiCallAsync(method, path, parameters, timeout, date);
|
|
||||||
var res = resTuple.Item1;
|
|
||||||
HttpStatusCode statusCode = resTuple.Item2;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var resDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(res);
|
|
||||||
var stat = resDict["stat"] as string;
|
|
||||||
if(stat == "OK")
|
|
||||||
{
|
|
||||||
return JsonConvert.DeserializeObject<T>(resDict["response"].ToString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var code = resDict["code"] as int?;
|
|
||||||
var message = resDict["message"] as string;
|
|
||||||
|
|
||||||
var messageDetail = string.Empty;
|
|
||||||
if(resDict.ContainsKey("message_detail"))
|
|
||||||
{
|
|
||||||
messageDetail = resDict["message_detail"] as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new DuoApiException(code.GetValueOrDefault(0), statusCode, message, messageDetail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
throw new DuoBadResponseException(statusCode, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string CanonicalizeParams(Dictionary<string, string> parameters)
|
|
||||||
{
|
|
||||||
var ret = new List<string>();
|
|
||||||
foreach(var pair in parameters)
|
|
||||||
{
|
|
||||||
var p = $"{WebUtility.UrlEncode(pair.Key)}={WebUtility.UrlEncode(pair.Value)}";
|
|
||||||
// Signatures require upper-case hex digits.
|
|
||||||
p = Regex.Replace(p, "(%[0-9A-Fa-f][0-9A-Fa-f])", c => c.Value.ToUpperInvariant());
|
|
||||||
// Escape only the expected characters.
|
|
||||||
p = Regex.Replace(p, "([!'()*])", c => "%" + Convert.ToByte(c.Value[0]).ToString("X"));
|
|
||||||
p = p.Replace("%7E", "~");
|
|
||||||
// UrlEncode converts space (" ") to "+". The
|
|
||||||
// signature algorithm requires "%20" instead. Actual
|
|
||||||
// + has already been replaced with %2B.
|
|
||||||
p = p.Replace("+", "%20");
|
|
||||||
|
|
||||||
ret.Add(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Sort(StringComparer.Ordinal);
|
|
||||||
return string.Join("&", ret.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
private string CanonicalizeRequest(string method, string path, string canon_params, string date)
|
|
||||||
{
|
|
||||||
string[] lines = { date, method.ToUpperInvariant(), _host.ToLower(), path, canon_params };
|
|
||||||
return string.Join("\n", lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string Sign(string method, string path, string canon_params, string date)
|
|
||||||
{
|
|
||||||
var canon = CanonicalizeRequest(method, path, canon_params, date);
|
|
||||||
var sig = HmacSign(canon);
|
|
||||||
var auth = $"{_ikey }:{sig}";
|
|
||||||
var authBytes = Encoding.ASCII.GetBytes(auth);
|
|
||||||
return $"Basic {Convert.ToBase64String(authBytes)}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private string HmacSign(string data)
|
|
||||||
{
|
|
||||||
var keyBytes = Encoding.ASCII.GetBytes(_skey);
|
|
||||||
var dataBytes = Encoding.ASCII.GetBytes(data);
|
|
||||||
|
|
||||||
using(var hmac = new HMACSHA1(keyBytes))
|
|
||||||
{
|
|
||||||
var hash = hmac.ComputeHash(dataBytes);
|
|
||||||
var hex = BitConverter.ToString(hash);
|
|
||||||
return hex.Replace("-", "").ToLower();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string DateToRFC822(DateTime date)
|
|
||||||
{
|
|
||||||
// Can't use the "zzzz" format because it adds a ":"
|
|
||||||
// between the offset's hours and minutes.
|
|
||||||
var dateString = date.ToString("ddd, dd MMM yyyy HH:mm:ss", CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
// TODO: Get proper timezone offset. hardcoded to UTC for now.
|
|
||||||
var offset = 0;
|
|
||||||
|
|
||||||
string zone;
|
|
||||||
// + or -, then 0-pad, then offset, then more 0-padding.
|
|
||||||
if(offset < 0)
|
|
||||||
{
|
|
||||||
offset *= -1;
|
|
||||||
zone = "-";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
zone = "+";
|
|
||||||
}
|
|
||||||
|
|
||||||
zone += offset.ToString(CultureInfo.InvariantCulture).PadLeft(2, '0');
|
|
||||||
dateString += (" " + zone.PadRight(5, '0'));
|
|
||||||
return dateString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DuoException : Exception
|
|
||||||
{
|
|
||||||
public HttpStatusCode Status { get; private set; }
|
|
||||||
|
|
||||||
public DuoException(HttpStatusCode status, string message, Exception inner)
|
|
||||||
: base(message, inner)
|
|
||||||
{
|
|
||||||
Status = status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DuoApiException : DuoException
|
|
||||||
{
|
|
||||||
public int Code { get; private set; }
|
|
||||||
public string ApiMessage { get; private set; }
|
|
||||||
public string ApiMessageDetail { get; private set; }
|
|
||||||
|
|
||||||
public DuoApiException(int code, HttpStatusCode status, string message, string messageDetail)
|
|
||||||
: base(status, FormatMessage(code, message, messageDetail), null)
|
|
||||||
{
|
|
||||||
Code = code;
|
|
||||||
ApiMessage = message;
|
|
||||||
ApiMessageDetail = messageDetail;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FormatMessage(int code, string message, string messageDetail)
|
|
||||||
{
|
|
||||||
return $"Duo API Error {code}: '{message}' ('{messageDetail}').";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DuoBadResponseException : DuoException
|
|
||||||
{
|
|
||||||
public DuoBadResponseException(HttpStatusCode status, Exception inner)
|
|
||||||
: base(status, FormatMessage(status, inner), inner)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
private static string FormatMessage(HttpStatusCode status, Exception inner)
|
|
||||||
{
|
|
||||||
var innerMessage = "(null)";
|
|
||||||
if(inner != null)
|
|
||||||
{
|
|
||||||
innerMessage = string.Format("'{0}'", inner.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"Got error '{innerMessage}' with HTTP status {(int)status}.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,6 @@
|
|||||||
"globalSettings": {
|
"globalSettings": {
|
||||||
"siteName": "bitwarden",
|
"siteName": "bitwarden",
|
||||||
"baseVaultUri": "http://localhost:4001/#",
|
"baseVaultUri": "http://localhost:4001/#",
|
||||||
"jwtSigningKey": "THIS IS A SECRET. IT KEEPS YOUR TOKEN SAFE. :)",
|
|
||||||
"stripeApiKey": "SECRET",
|
"stripeApiKey": "SECRET",
|
||||||
"sqlServer": {
|
"sqlServer": {
|
||||||
"connectionString": "SECRET"
|
"connectionString": "SECRET"
|
||||||
@ -11,13 +10,6 @@
|
|||||||
"apiKey": "SECRET",
|
"apiKey": "SECRET",
|
||||||
"replyToEmail": "hello@bitwarden.com"
|
"replyToEmail": "hello@bitwarden.com"
|
||||||
},
|
},
|
||||||
"push": {
|
|
||||||
"apnsCertificateThumbprint": "SECRET",
|
|
||||||
"apnsCertificatePassword": "SECRET",
|
|
||||||
"gcmSenderId": "SECRET",
|
|
||||||
"gcmApiKey": "SECRET",
|
|
||||||
"gcmAppPackageName": "com.x8bit.bitwarden"
|
|
||||||
},
|
|
||||||
"identityServer": {
|
"identityServer": {
|
||||||
"certificateThumbprint": "SECRET"
|
"certificateThumbprint": "SECRET"
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user