1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

remove scim project

This commit is contained in:
Kyle Spearrin 2018-07-18 21:58:49 -04:00
parent 61806cd8ac
commit fffe92e634
20 changed files with 1 additions and 796 deletions

View File

@ -45,11 +45,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Icons", "src\Icons\Icons.cs
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Events", "src\Events\Events.csproj", "{994DD611-F266-4BD3-8072-3B1B57267ED5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Scim", "src\Scim\Scim.csproj", "{B8C5FFEB-186A-46FF-B914-BB3D50AA8D61}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventsProcessor", "src\EventsProcessor\EventsProcessor.csproj", "{2235D24F-E607-47F4-81AD-BB4504ADF9C6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Admin", "src\Admin\Admin.csproj", "{B131CEF3-89FB-4C90-ADB0-9E9C4246EB56}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin", "src\Admin\Admin.csproj", "{B131CEF3-89FB-4C90-ADB0-9E9C4246EB56}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -107,10 +105,6 @@ Global
{994DD611-F266-4BD3-8072-3B1B57267ED5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{994DD611-F266-4BD3-8072-3B1B57267ED5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{994DD611-F266-4BD3-8072-3B1B57267ED5}.Release|Any CPU.Build.0 = Release|Any CPU
{B8C5FFEB-186A-46FF-B914-BB3D50AA8D61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8C5FFEB-186A-46FF-B914-BB3D50AA8D61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8C5FFEB-186A-46FF-B914-BB3D50AA8D61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8C5FFEB-186A-46FF-B914-BB3D50AA8D61}.Release|Any CPU.Build.0 = Release|Any CPU
{2235D24F-E607-47F4-81AD-BB4504ADF9C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2235D24F-E607-47F4-81AD-BB4504ADF9C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2235D24F-E607-47F4-81AD-BB4504ADF9C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -136,7 +130,6 @@ Global
{A6C44A84-8E51-4C64-B9C4-7B7C23253345} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
{9CF59342-3912-4B45-A2BA-0F173666586D} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
{994DD611-F266-4BD3-8072-3B1B57267ED5} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
{B8C5FFEB-186A-46FF-B914-BB3D50AA8D61} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
{2235D24F-E607-47F4-81AD-BB4504ADF9C6} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
{B131CEF3-89FB-4C90-ADB0-9E9C4246EB56} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
EndGlobalSection

View File

@ -1,20 +0,0 @@
namespace Bit.Scim
{
public class Constants
{
public static class Schemas
{
public const string User = @"urn:ietf:params:scim:schemas:core:2.0:User";
public const string UserEnterprise = @"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User";
public const string Group = @"urn:ietf:params:scim:schemas:core:2.0:Group";
}
public static class Messages
{
public const string Error = @"urn:ietf:params:scim:api:messages:2.0:Error";
public const string PatchOp = @"urn:ietf:params:scim:api:messages:2.0:PatchOp";
public const string ListResponse = @"urn:ietf:params:scim:api:messages:2.0:ListResponse";
public const string SearchRequest = @"urn:ietf:params:scim:api:messages:2.0:SearchRequest";
}
}
}

View File

@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Bit.Core.Models;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Scim.Controllers
{
public class BaseController : Controller
{
protected ICollection<T> FilterResources<T>(ICollection<T> resources, string filter) where T : IExternal
{
if(!string.IsNullOrWhiteSpace(filter))
{
var filterMatch = Regex.Match(filter, "(\\w+) eq \"([^\"]*)\"");
if(filterMatch.Success && filterMatch.Groups.Count > 2)
{
var searchKey = filterMatch.Groups[1].Value;
var searchValue = filterMatch.Groups[2].Value;
if(!string.IsNullOrWhiteSpace(searchKey) && !string.IsNullOrWhiteSpace(searchValue))
{
var searchKeyLower = searchKey.ToLowerInvariant();
if(searchKeyLower == "externalid")
{
resources = resources.Where(u => u.ExternalId == searchValue).ToList();
}
}
}
}
return resources;
}
}
}

View File

@ -1,117 +0,0 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Repositories;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using Bit.Scim.Models;
using System.Diagnostics;
using System.IO;
using Bit.Core.Services;
using Bit.Core.Exceptions;
namespace Bit.Scim.Controllers
{
[Route("groups")]
[Route("scim/groups")]
public class GroupsController : BaseController
{
private readonly IGroupRepository _groupRepository;
private readonly IGroupService _groupService;
private Guid _orgId = new Guid("2933f760-9c0b-4efb-a437-a82a00ed3fc1"); // TODO: come from context
public GroupsController(
IGroupRepository groupRepository,
IGroupService groupService)
{
_groupRepository = groupRepository;
_groupService = groupService;
}
[HttpGet]
public async Task<IActionResult> GetAll([FromQuery]string filter, [FromQuery]string excludedAttributes,
[FromQuery]string attributes)
{
var groups = await _groupRepository.GetManyByOrganizationIdAsync(_orgId);
groups = FilterResources(groups, filter);
var groupsResult = groups.Select(g => new ScimGroup(g));
var result = new ScimListResponse(groupsResult);
return new OkObjectResult(result);
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(string id)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if(group == null || group.OrganizationId != _orgId)
{
throw new NotFoundException();
}
var result = new ScimGroup(group);
return new OkObjectResult(result);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]ScimGroup model)
{
var group = model.ToGroup(_orgId);
await _groupService.SaveAsync(group);
var result = new ScimGroup(group);
var getUrl = Url.Action("Get", "Groups", new { id = group.Id.ToString() }, Request.Protocol, Request.Host.Value);
return new CreatedResult(getUrl, result);
}
[HttpPut("{id}")]
public async Task<IActionResult> Put(string id, [FromBody]ScimGroup model)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if(group == null || group.OrganizationId != _orgId)
{
throw new NotFoundException();
}
group = model.ToGroup(group);
await _groupService.SaveAsync(group);
var result = new ScimGroup(group);
return new OkObjectResult(result);
}
[HttpPatch("{id}")]
public async Task<IActionResult> Patch(string id)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if(group == null || group.OrganizationId != _orgId)
{
throw new NotFoundException();
}
var memstream = new MemoryStream();
Request.Body.CopyTo(memstream);
memstream.Position = 0;
using(var reader = new StreamReader(memstream))
{
var text = reader.ReadToEnd();
Debug.WriteLine(text);
}
// TODO: Do patch
var result = new ScimGroup(group);
return new OkObjectResult(result);
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(string id)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if(group == null || group.OrganizationId != _orgId)
{
throw new NotFoundException();
}
await _groupService.DeleteAsync(group);
return new OkResult();
}
}
}

View File

@ -1,122 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Scim.Models;
using Microsoft.AspNetCore.Mvc;
using Bit.Core.Models.Data;
using Bit.Core.Exceptions;
namespace Bit.Scim.Controllers
{
[Route("users")]
[Route("scim/users")]
public class UsersController : BaseController
{
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationService _organizationService;
private Guid _orgId = new Guid("2933f760-9c0b-4efb-a437-a82a00ed3fc1"); // TODO: come from context
public UsersController(
IOrganizationUserRepository organizationUserRepository,
IOrganizationService organizationService)
{
_organizationUserRepository = organizationUserRepository;
_organizationService = organizationService;
}
[HttpGet]
public async Task<IActionResult> GetAll([FromQuery]string filter, [FromQuery]string excludedAttributes,
[FromQuery]string attributes)
{
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(_orgId);
users = FilterResources(users, filter);
var usersResult = users.Select(u => new ScimUser(u));
var result = new ScimListResponse(usersResult);
return new OkObjectResult(result);
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(string id)
{
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(_orgId);
var user = users.SingleOrDefault(u => u.Id == new Guid(id));
if(user == null)
{
throw new NotFoundException();
}
var result = new ScimUser(user);
return new OkObjectResult(result);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]ScimUser model)
{
var email = model.Emails?.FirstOrDefault();
if(email == null)
{
throw new BadRequestException("No email address available.");
}
var orgUser = await _organizationService.InviteUserAsync(_orgId, null, email.Value,
OrganizationUserType.User, false, model.ExternalId, new List<SelectionReadOnly>());
var result = new ScimUser(orgUser);
var getUrl = Url.Action("Get", "Users", new { id = orgUser.Id.ToString() }, Request.Protocol, Request.Host.Value);
return new CreatedResult(getUrl, result);
}
[HttpPut("{id}")]
public async Task<IActionResult> Put(string id, [FromBody]ScimUser model)
{
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(_orgId);
var user = users.SingleOrDefault(u => u.Id == new Guid(id));
if(user == null)
{
throw new NotFoundException();
}
// TODO: update
var result = new ScimUser(user);
return new OkObjectResult(result);
}
[HttpPatch("{id}")]
public async Task<IActionResult> Patch(string id)
{
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(_orgId);
var user = users.SingleOrDefault(u => u.Id == new Guid(id));
if(user == null)
{
throw new NotFoundException();
}
var memstream = new MemoryStream();
Request.Body.CopyTo(memstream);
memstream.Position = 0;
using(var reader = new StreamReader(memstream))
{
var text = reader.ReadToEnd();
Debug.WriteLine(text);
}
// TODO: patch
var result = new ScimUser(user);
return new OkObjectResult(result);
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(string id)
{
await _organizationService.DeleteUserAsync(_orgId, new Guid(id), null);
return new OkResult();
}
}
}

View File

@ -1,34 +0,0 @@
using System.Collections.Generic;
using System.Net;
using Newtonsoft.Json;
namespace Bit.Scim.Models
{
public class ScimError
{
private IEnumerable<string> _schemas;
public ScimError()
{
_schemas = new[] { Constants.Messages.Error };
}
public ScimError(HttpStatusCode status, string detail = null)
: this()
{
Status = (int)status;
Detail = detail;
}
[JsonProperty("schemas")]
public IEnumerable<string> Schemas
{
get => _schemas;
set { _schemas = value; }
}
[JsonProperty("status")]
public int Status { get; set; }
[JsonProperty("detail")]
public string Detail { get; set; }
}
}

View File

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using Bit.Core.Models.Table;
using Newtonsoft.Json;
namespace Bit.Scim.Models
{
public class ScimGroup : ScimResource
{
public ScimGroup() { }
public ScimGroup(Group group)
{
Id = group.Id.ToString();
ExternalId = group.ExternalId;
DisplayName = group.Name;
Meta = new ScimResourceMetadata("Group");
}
public override string SchemaIdentifier => Constants.Schemas.Group;
[JsonProperty("displayName")]
public string DisplayName { get; set; }
[JsonProperty("members")]
public IEnumerable<ScimMultiValuedAttribute> Members { get; set; }
public Group ToGroup(Guid orgId)
{
return new Group
{
ExternalId = ExternalId,
Name = DisplayName,
OrganizationId = orgId
};
}
public Group ToGroup(Group group)
{
group.Name = DisplayName;
return group;
}
}
}

View File

@ -1,24 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace Bit.Scim.Models
{
public class ScimListResponse : ScimSchemaBase
{
public ScimListResponse(IEnumerable<ScimResource> resources)
{
Resources = resources;
}
public override string SchemaIdentifier => Constants.Messages.ListResponse;
[JsonProperty("totalResults", Order = 0)]
public int TotalResults => Resources == null ? 0 : Resources.Count();
[JsonProperty("Resources", Order = 1)]
public IEnumerable<ScimResource> Resources { get; private set; }
[JsonProperty("startIndex", Order = 2)]
public int StartIndex { get; set; } = 0;
[JsonProperty("itemsPerPage", Order = 3)]
public int ItemsPerPage => Resources == null ? 0 : Resources.Count();
}
}

View File

@ -1,19 +0,0 @@
using System;
using Newtonsoft.Json;
namespace Bit.Scim.Models
{
public class ScimMultiValuedAttribute
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("primary")]
public bool Primary { get; set; }
[JsonProperty("display")]
public string Display { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
[JsonProperty("$ref")]
public Uri Ref { get; set; }
}
}

View File

@ -1,14 +0,0 @@
using Newtonsoft.Json;
namespace Bit.Scim.Models
{
public abstract class ScimResource : ScimSchemaBase
{
[JsonProperty(Order = -5, PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "externalId")]
public string ExternalId { get; set; }
[JsonProperty(Order = 9999, PropertyName = "meta")]
public ScimResourceMetadata Meta { get; set; }
}
}

View File

@ -1,17 +0,0 @@
using Newtonsoft.Json;
namespace Bit.Scim.Models
{
public class ScimResourceMetadata
{
private ScimResourceMetadata() { }
public ScimResourceMetadata(string resourceType)
{
ResourceType = resourceType;
}
[JsonProperty("resourceType")]
public string ResourceType { get; set; }
}
}

View File

@ -1,13 +0,0 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Bit.Scim.Models
{
public abstract class ScimSchemaBase
{
[JsonProperty("schemas", Order = -10)]
public virtual ISet<string> Schemas => new HashSet<string>(new[] { SchemaIdentifier });
[JsonIgnore]
public abstract string SchemaIdentifier { get; }
}
}

View File

@ -1,63 +0,0 @@
using System.Collections.Generic;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table;
using Newtonsoft.Json;
namespace Bit.Scim.Models
{
public class ScimUser : ScimResource
{
public ScimUser() { }
public ScimUser(OrganizationUserUserDetails userDetails)
{
Id = userDetails.Id.ToString();
ExternalId = userDetails.ExternalId;
UserName = userDetails.Email;
Name = new ScimName
{
Formatted = userDetails.Name
};
DisplayName = userDetails.Name;
Active = true;
Emails = new List<ScimMultiValuedAttribute> {
new ScimMultiValuedAttribute { Type = "work", Value = userDetails.Email } };
Meta = new ScimResourceMetadata("User");
}
public ScimUser(OrganizationUser orgUser)
{
Id = orgUser.Id.ToString();
ExternalId = orgUser.ExternalId;
UserName = orgUser.Email;
Active = true;
Emails = new List<ScimMultiValuedAttribute> {
new ScimMultiValuedAttribute { Type = "work", Value = orgUser.Email } };
Meta = new ScimResourceMetadata("User");
}
public override string SchemaIdentifier => Constants.Schemas.User;
[JsonProperty("userName")]
public string UserName { get; set; }
[JsonProperty("name")]
public ScimName Name { get; set; }
[JsonProperty("displayName")]
public string DisplayName { get; set; }
[JsonProperty("active")]
public bool Active { get; set; }
[JsonProperty("emails")]
public IEnumerable<ScimMultiValuedAttribute> Emails { get; set; }
[JsonProperty("groups")]
public IEnumerable<ScimMultiValuedAttribute> Groups { get; set; }
public class ScimName
{
[JsonProperty("formatted")]
public string Formatted { get; set; }
[JsonProperty("familyName")]
public string FamilyName { get; set; }
[JsonProperty("givenName")]
public string GivenName { get; set; }
}
}
}

View File

@ -1,17 +0,0 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace Bit.Scim
{
public class Program
{
public static void Main(string[] args)
{
WebHost
.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build()
.Run();
}
}
}

View File

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

View File

@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<Version>1.20.0</Version>
<TargetFramework>netcoreapp2.0</TargetFramework>
<RootNamespace>Bit.Scim</RootNamespace>
<UserSecretsId>bitwarden-Scim</UserSecretsId>
<MvcRazorCompileOnPublish>false</MvcRazorCompileOnPublish>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
</ItemGroup>
</Project>

View File

@ -1,79 +0,0 @@
using Bit.Core;
using Bit.Core.Utilities;
using Bit.Scim.Utilities;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Serilog.Events;
namespace Bit.Scim
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// Options
services.AddOptions();
// Settings
var globalSettings = services.AddGlobalSettingsServices(Configuration);
// Repositories
services.AddSqlServerRepositories(globalSettings);
// Context
services.AddScoped<CurrentContext>();
// Identity
services.AddCustomIdentityServices(globalSettings);
// Services
services.AddBaseServices();
services.AddDefaultServices(globalSettings);
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Mvc
services.AddMvc(config =>
{
config.Filters.Add(new ExceptionHandlerFilterAttribute());
});
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
IApplicationLifetime appLifetime,
GlobalSettings globalSettings,
ILoggerFactory loggerFactory)
{
// Disable app insights
var telConfig = app.ApplicationServices.GetService<TelemetryConfiguration>();
telConfig.DisableTelemetry = true;
loggerFactory.AddSerilog(app, env, appLifetime, globalSettings, (e) => e.Level >= LogEventLevel.Error);
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Default Middleware
app.UseDefaultMiddleware(env);
app.UseMvc();
}
}
}

View File

@ -1,43 +0,0 @@
using Bit.Core.Exceptions;
using Bit.Scim.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Bit.Scim.Utilities
{
public class ExceptionHandlerFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var exception = context.Exception;
if(exception == null)
{
// Should never happen.
return;
}
var error = new ScimError();
if(exception is BadRequestException)
{
context.HttpContext.Response.StatusCode = error.Status = 400;
error.Detail = exception.Message;
}
else if(exception is NotFoundException)
{
context.HttpContext.Response.StatusCode = error.Status = 404;
error.Detail = "Resource not found.";
}
else
{
context.HttpContext.Response.StatusCode = error.Status = 500;
error.Detail = "An unhandled server error has occurred.";
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ExceptionHandlerFilterAttribute>>();
logger.LogError(0, exception, exception.Message);
}
context.Result = new ObjectResult(error);
}
}
}

View File

@ -1,17 +0,0 @@
{
"globalSettings": {
"baseServiceUri": {
"vault": "https://vault.bitwarden.com",
"api": "https://api.bitwarden.com",
"identity": "https://identity.bitwarden.com",
"admin": "https://admin.bitwarden.com",
"internalAdmin": "https://admin.bitwarden.com",
"internalIdentity": "https://identity.bitwarden.com",
"internalApi": "https://api.bitwarden.com",
"internalVault": "https://vault.bitwarden.com"
},
"braintree": {
"production": true
}
}
}

View File

@ -1,59 +0,0 @@
{
"globalSettings": {
"selfHosted": false,
"siteName": "Bitwarden",
"projectName": "Billing",
"stripeApiKey": "SECRET",
"baseServiceUri": {
"vault": "https://localhost:8080",
"api": "http://localhost:4000",
"identity": "http://localhost:33656",
"admin": "http://localhost:62911",
"internalAdmin": "http://localhost:62911",
"internalIdentity": "http://localhost:33656",
"internalApi": "http://localhost:4000",
"internalVault": "http://localhost:4001"
},
"sqlServer": {
"connectionString": "SECRET"
},
"mail": {
"sendGridApiKey": "SECRET",
"replyToEmail": "hello@bitwarden.com"
},
"identityServer": {
"certificateThumbprint": "SECRET"
},
"dataProtection": {
"certificateThumbprint": "SECRET"
},
"storage": {
"connectionString": "SECRET"
},
"events": {
"connectionString": "SECRET"
},
"documentDb": {
"uri": "SECRET",
"key": "SECRET"
},
"sentry": {
"dsn": "SECRET"
},
"notificationHub": {
"connectionString": "SECRET",
"hubName": "SECRET"
}
},
"billingSettings": {
"stripeWebhookKey": "SECRET",
"stripeWebhookSecret": "SECRET",
"braintreeWebhookKey": "SECRET"
},
"braintree": {
"production": false,
"merchantId": "SECRET",
"publicKey": "SECRET",
"privateKey": "SECRET"
}
}