diff --git a/src/Core/GlobalSettings.cs b/src/Core/GlobalSettings.cs index 3e6844ed4..f7250793a 100644 --- a/src/Core/GlobalSettings.cs +++ b/src/Core/GlobalSettings.cs @@ -11,6 +11,7 @@ namespace Bit.Core public virtual string LogDirectory { get; set; } public virtual string LicenseDirectory { get; set; } public virtual string PushRelayBaseUri { get; set; } + public virtual string InternalIdentityKey { get; set; } public virtual bool DisableUserRegistration { get; set; } public virtual InstallationSettings Installation { get; set; } = new InstallationSettings(); public virtual BaseServiceUriSettings BaseServiceUri { get; set; } = new BaseServiceUriSettings(); diff --git a/src/Core/IdentityServer/ClientStore.cs b/src/Core/IdentityServer/ClientStore.cs index 2a46e9cb5..49eec9a6f 100644 --- a/src/Core/IdentityServer/ClientStore.cs +++ b/src/Core/IdentityServer/ClientStore.cs @@ -6,6 +6,7 @@ using Bit.Core.Repositories; using System; using System.Security.Claims; using IdentityModel; +using Bit.Core.Utilities; namespace Bit.Core.IdentityServer { @@ -14,15 +15,19 @@ namespace Bit.Core.IdentityServer private static IDictionary _apiClients = StaticClients.GetApiClients(); private readonly IInstallationRepository _installationRepository; + private readonly GlobalSettings _globalSettings; + public ClientStore( - IInstallationRepository installationRepository) + IInstallationRepository installationRepository, + GlobalSettings globalSettings) { _installationRepository = installationRepository; + _globalSettings = globalSettings; } public async Task FindClientByIdAsync(string clientId) { - if(clientId.StartsWith("installation.")) + if(!_globalSettings.SelfHosted && clientId.StartsWith("installation.")) { var idParts = clientId.Split('.'); if(idParts.Length > 1 && Guid.TryParse(idParts[1], out Guid id)) @@ -44,6 +49,29 @@ namespace Bit.Core.IdentityServer } } } + else if(_globalSettings.SelfHosted && clientId.StartsWith("internal.") && + CoreHelpers.SettingHasValue(_globalSettings.InternalIdentityKey)) + { + var idParts = clientId.Split('.'); + if(idParts.Length > 1) + { + var id = idParts[1]; + if(!string.IsNullOrWhiteSpace(id)) + { + return new Client + { + ClientId = $"internal.{id}", + RequireClientSecret = true, + ClientSecrets = { new Secret(_globalSettings.InternalIdentityKey.Sha256()) }, + AllowedScopes = new string[] { "internal" }, + AllowedGrantTypes = GrantTypes.ClientCredentials, + AccessTokenLifetime = 3600 * 24, + Enabled = true, + Claims = new List { new Claim(JwtClaimTypes.Subject, id) } + }; + } + } + } return _apiClients.ContainsKey(clientId) ? _apiClients[clientId] : null; } diff --git a/src/Core/Services/Implementations/BaseRelayPushNotificationService.cs b/src/Core/Services/Implementations/BaseIdentityClientService.cs similarity index 76% rename from src/Core/Services/Implementations/BaseRelayPushNotificationService.cs rename to src/Core/Services/Implementations/BaseIdentityClientService.cs index 0be19c33d..f4ecb71aa 100644 --- a/src/Core/Services/Implementations/BaseRelayPushNotificationService.cs +++ b/src/Core/Services/Implementations/BaseIdentityClientService.cs @@ -12,35 +12,44 @@ using Microsoft.Extensions.Logging; namespace Bit.Core.Services { - public abstract class BaseRelayPushNotificationService + public abstract class BaseIdentityClientService { + private readonly string _identityScope; + private readonly string _identityClientId; + private readonly string _identityClientSecret; + private readonly ILogger _logger; + private dynamic _decodedToken; private DateTime? _nextAuthAttempt = null; - private readonly ILogger _logger; - public BaseRelayPushNotificationService( - GlobalSettings globalSettings, - ILogger logger) + public BaseIdentityClientService( + string baseClientServerUri, + string baseIdentityServerUri, + string identityScope, + string identityClientId, + string identityClientSecret, + ILogger logger) { + _identityScope = identityScope; + _identityClientId = identityClientId; + _identityClientSecret = identityClientSecret; _logger = logger; - GlobalSettings = globalSettings; - PushClient = new HttpClient + Client = new HttpClient { - BaseAddress = new Uri(globalSettings.PushRelayBaseUri) + BaseAddress = new Uri(baseClientServerUri) }; - PushClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); IdentityClient = new HttpClient { - BaseAddress = new Uri(globalSettings.Installation.IdentityUri) + BaseAddress = new Uri(baseIdentityServerUri) }; IdentityClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); } - protected HttpClient PushClient { get; private set; } + protected HttpClient Client { get; private set; } protected HttpClient IdentityClient { get; private set; } - protected GlobalSettings GlobalSettings { get; private set; } protected string AccessToken { get; private set; } protected async Task HandleTokenStateAsync() @@ -63,9 +72,9 @@ namespace Bit.Core.Services Content = new FormUrlEncodedContent(new Dictionary { { "grant_type", "client_credentials" }, - { "scope", "api.push" }, - { "client_id", $"installation.{GlobalSettings.Installation.Id}" }, - { "client_secret", $"{GlobalSettings.Installation.Key}" } + { "scope", _identityScope }, + { "client_id", _identityClientId }, + { "client_secret", _identityClientSecret } }) }; @@ -76,7 +85,7 @@ namespace Bit.Core.Services } catch(Exception e) { - _logger.LogError(12339, e, "Unable to auth for push."); + _logger.LogError(12339, e, "Unable to authenticate with identity server."); } if(response == null) diff --git a/src/Core/Services/Implementations/RelayPushNotificationService.cs b/src/Core/Services/Implementations/RelayPushNotificationService.cs index a770f7b58..cb9dcc1d1 100644 --- a/src/Core/Services/Implementations/RelayPushNotificationService.cs +++ b/src/Core/Services/Implementations/RelayPushNotificationService.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; namespace Bit.Core.Services { - public class RelayPushNotificationService : BaseRelayPushNotificationService, IPushNotificationService + public class RelayPushNotificationService : BaseIdentityClientService, IPushNotificationService { private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger _logger; @@ -19,7 +19,13 @@ namespace Bit.Core.Services GlobalSettings globalSettings, IHttpContextAccessor httpContextAccessor, ILogger logger) - : base(globalSettings, logger) + : base( + globalSettings.PushRelayBaseUri, + globalSettings.Installation.IdentityUri, + "api.push", + $"installation.{globalSettings.Installation.Id}", + globalSettings.Installation.Key, + logger) { _httpContextAccessor = httpContextAccessor; _logger = logger; @@ -168,12 +174,12 @@ namespace Bit.Core.Services var message = new TokenHttpRequestMessage(requestModel, AccessToken) { Method = HttpMethod.Post, - RequestUri = new Uri(string.Concat(PushClient.BaseAddress, "/push/send")) + RequestUri = new Uri(string.Concat(Client.BaseAddress, "/push/send")) }; try { - await PushClient.SendAsync(message); + await Client.SendAsync(message); } catch(Exception e) { diff --git a/src/Core/Services/Implementations/RelayPushRegistrationService.cs b/src/Core/Services/Implementations/RelayPushRegistrationService.cs index 89839c2d5..8c17d8f96 100644 --- a/src/Core/Services/Implementations/RelayPushRegistrationService.cs +++ b/src/Core/Services/Implementations/RelayPushRegistrationService.cs @@ -9,14 +9,20 @@ using Microsoft.Extensions.Logging; namespace Bit.Core.Services { - public class RelayPushRegistrationService : BaseRelayPushNotificationService, IPushRegistrationService + public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegistrationService { private readonly ILogger _logger; public RelayPushRegistrationService( GlobalSettings globalSettings, ILogger logger) - : base(globalSettings, logger) + : base( + globalSettings.PushRelayBaseUri, + globalSettings.Installation.IdentityUri, + "api.push", + $"installation.{globalSettings.Installation.Id}", + globalSettings.Installation.Key, + logger) { _logger = logger; } @@ -42,12 +48,12 @@ namespace Bit.Core.Services var message = new TokenHttpRequestMessage(requestModel, AccessToken) { Method = HttpMethod.Post, - RequestUri = new Uri(string.Concat(PushClient.BaseAddress, "/push/register")) + RequestUri = new Uri(string.Concat(Client.BaseAddress, "/push/register")) }; try { - await PushClient.SendAsync(message); + await Client.SendAsync(message); } catch(Exception e) { @@ -66,12 +72,12 @@ namespace Bit.Core.Services var message = new TokenHttpRequestMessage(AccessToken) { Method = HttpMethod.Delete, - RequestUri = new Uri(string.Concat(PushClient.BaseAddress, "/push/", deviceId)) + RequestUri = new Uri(string.Concat(Client.BaseAddress, "/push/", deviceId)) }; try { - await PushClient.SendAsync(message); + await Client.SendAsync(message); } catch(Exception e) { @@ -96,12 +102,12 @@ namespace Bit.Core.Services var message = new TokenHttpRequestMessage(requestModel, AccessToken) { Method = HttpMethod.Put, - RequestUri = new Uri(string.Concat(PushClient.BaseAddress, "/push/add-organization")) + RequestUri = new Uri(string.Concat(Client.BaseAddress, "/push/add-organization")) }; try { - await PushClient.SendAsync(message); + await Client.SendAsync(message); } catch(Exception e) { @@ -126,12 +132,12 @@ namespace Bit.Core.Services var message = new TokenHttpRequestMessage(requestModel, AccessToken) { Method = HttpMethod.Put, - RequestUri = new Uri(string.Concat(PushClient.BaseAddress, "/push/delete-organization")) + RequestUri = new Uri(string.Concat(Client.BaseAddress, "/push/delete-organization")) }; try { - await PushClient.SendAsync(message); + await Client.SendAsync(message); } catch(Exception e) { diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index b9abb0be5..7c5d0b128 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -311,12 +311,9 @@ namespace Bit.Core.Utilities public static bool SettingHasValue(string setting) { - if(string.IsNullOrWhiteSpace(setting) || setting.Equals("SECRET") || setting.Equals("REPLACE")) - { - return false; - } - - return true; + var normalizedSetting = setting?.ToLowerInvariant(); + return !string.IsNullOrWhiteSpace(normalizedSetting) && !normalizedSetting.Equals("secret") && + !normalizedSetting.Equals("replace"); } public static string Base64UrlEncode(byte[] input) diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs index fced9d85b..0bf33fb15 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -234,7 +234,7 @@ namespace Bit.Core.Utilities public static void AddIdentityAuthenticationServices( this IServiceCollection services, GlobalSettings globalSettings, IHostingEnvironment environment, - Action addAuthorization = null) + Action addAuthorization) { services .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) @@ -248,21 +248,13 @@ namespace Bit.Core.Utilities options.SupportedTokens = SupportedTokens.Jwt; }); - services.AddAuthorization(config => + if(addAuthorization != null) { - if(addAuthorization != null) + services.AddAuthorization(config => { - addAuthorization?.Invoke(config); - } - else - { - config.AddPolicy("Application", policy => - { - policy.RequireAuthenticatedUser(); - policy.RequireClaim(JwtClaimTypes.AuthenticationMethod, "Application"); - }); - } - }); + addAuthorization.Invoke(config); + }); + } } public static IIdentityServerBuilder AddCustomIdentityServerServices( diff --git a/src/Events/Startup.cs b/src/Events/Startup.cs index 669554e75..5c15b5d1b 100644 --- a/src/Events/Startup.cs +++ b/src/Events/Startup.cs @@ -1,6 +1,7 @@ using Bit.Core; using Bit.Core.Services; using Bit.Core.Utilities; +using IdentityModel; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -36,7 +37,15 @@ namespace Bit.Events services.AddScoped(); // Identity - services.AddIdentityAuthenticationServices(globalSettings, Environment); + services.AddIdentityAuthenticationServices(globalSettings, Environment, config => + { + config.AddPolicy("Application", policy => + { + policy.RequireAuthenticatedUser(); + policy.RequireClaim(JwtClaimTypes.AuthenticationMethod, "Application"); + policy.RequireClaim(JwtClaimTypes.Scope, "api"); + }); + }); // Services services.AddScoped(); diff --git a/src/Hub/Controllers/EventsController.cs b/src/Hub/Controllers/EventsController.cs index a7f70019e..213e3b690 100644 --- a/src/Hub/Controllers/EventsController.cs +++ b/src/Hub/Controllers/EventsController.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.SignalR; namespace Bit.Hub { - [Authorize("Application")] + [Authorize("Internal")] public class EventsController : Controller { private readonly IHubContext _syncHubContext; diff --git a/src/Hub/Startup.cs b/src/Hub/Startup.cs index 9b7305808..0ac22e93b 100644 --- a/src/Hub/Startup.cs +++ b/src/Hub/Startup.cs @@ -1,5 +1,6 @@ using Bit.Core; using Bit.Core.Utilities; +using IdentityModel; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.SignalR; @@ -37,7 +38,20 @@ namespace Bit.Hub services.AddScoped(); // Identity - services.AddIdentityAuthenticationServices(globalSettings, Environment); + 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(); diff --git a/util/Setup/EnvironmentFileBuilder.cs b/util/Setup/EnvironmentFileBuilder.cs index ea8aa9e09..7381eaf64 100644 --- a/util/Setup/EnvironmentFileBuilder.cs +++ b/util/Setup/EnvironmentFileBuilder.cs @@ -49,7 +49,8 @@ namespace Bit.Setup ["globalSettings__dataProtection__directory"] = $"{OutputDirectory}/core/aspnet-dataprotection", ["globalSettings__logDirectory"] = $"{OutputDirectory}/logs", ["globalSettings__licenseDirectory"] = $"{OutputDirectory}/core/licenses", - ["globalSettings__duo__aKey"] = $"{Helpers.SecureRandomString(64, alpha: true, numeric: true)}", + ["globalSettings__internalIdentityKey"] = Helpers.SecureRandomString(64, alpha: true, numeric: true), + ["globalSettings__duo__aKey"] = Helpers.SecureRandomString(64, alpha: true, numeric: true), ["globalSettings__installation__id"] = InstallationId?.ToString(), ["globalSettings__installation__key"] = InstallationKey, ["globalSettings__yubico__clientId"] = "REPLACE",