Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Builder;
using Bitwarden.Server.Sdk.Authentication;

namespace Bitwarden.Server.Sdk.Authentication;
namespace Microsoft.AspNetCore.Builder;

/// <summary>
/// Extension methods to add Bitwarden-style authentication to the HTTP application pipeline.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using Bitwarden.Server.Sdk.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace Bitwarden.Server.Sdk.Authentication;
namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Extension methods for setting up Bitwarden-style authentication in a <see cref="IServiceCollection"/>.
/// </summary>
public static class AuthenticationServiceCollectionExtensions
public static class BitwardenAuthenticationServiceCollectionExtensions
{
/// <summary>
/// Adds Bitwarden compatible authentication, can be configured through the `Authentication:Schemes:Bearer` config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<IncludeBuildOutput>false</IncludeBuildOutput>

<PackageReadmeFile>./README.md</PackageReadmeFile>
<PackageReadmeFile>./PACKAGE.md</PackageReadmeFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>

<VersionPrefix>1.1.0</VersionPrefix>
Expand All @@ -27,10 +27,6 @@
<InternalsVisibleTo Include="Bitwarden.Server.Sdk.UnitTests" />
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<PropertyGroup>
<Authors>Bitwarden Inc.</Authors>
<Description>Bitwarden Server SDK.</Description>
Expand All @@ -43,7 +39,7 @@
</PropertyGroup>

<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="/" />
<None Include="PACKAGE.md" Pack="true" PackagePath="/" />
<None Include="Sdk/**" Pack="true" PackagePath="Sdk/" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public static TBuilder UseBitwardenSdk<TBuilder>(this TBuilder builder)
builder.Services.AddFeatureFlagServices();
#endif

#if BIT_INCLUDE_AUTHENTICATION
builder.Services.AddBitwardenAuthentication();
#endif

return builder;
}

Expand Down Expand Up @@ -73,6 +77,13 @@ public static IHostBuilder UseBitwardenSdk(this IHostBuilder hostBuilder)
});
#endif

#if BIT_INCLUDE_AUTHENTICATION
hostBuilder.ConfigureServices((_, services) =>
{
services.AddBitwardenAuthentication();
});
#endif

return hostBuilder;
}

Expand Down
32 changes: 32 additions & 0 deletions extensions/Bitwarden.Server.Sdk/src/PACKAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Bitwarden.Server.Sdk

The Bitwarden Server SDK is built for quickly getting started building
a Bitwarden-flavored service. The entrypoint for using it is adding `UseBitwardenSdk()`
on your web application and configuring MSBuild properties to configure the features you
want.

## Telemetry

Enabled by default and able to be removed using `<BitIncludeTelemetry>false</BitIncludeTelemetry>`
in your project file.

This feature automatically includes a suite of OpenTelemetry libraries and registers those services
into the `IServiceCollection`.

## Features

Enabled by default and able to be removed using `<BitIncludeFeatures>false</BitIncludeFeatures>` in
your project file.

This feature automatically includes the `Bitwarden.Server.Sdk.Features` library and when using the
`Microsoft.NET.Sdk.Web` SDK it will register the services in `UseBitwardenSdk()`. All considerations
of that package apply to this SDK.

## Authentication

Enabled by default and able to be removed using
`<BitIncludeAuthentication>false</BitIncludeAuthentication>` in your project file.

This feature automatically includes the `Bitwarden.Server.Sdk.Authentication` library and when using
the `Microsoft.NET.Sdk.Web` SDK it will register Bitwarden style authentication in
`UseBitwardenSdk()`. All considerations of that package apply to this SDK.
35 changes: 22 additions & 13 deletions extensions/Bitwarden.Server.Sdk/src/README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
# Bitwarden.Server.Sdk

The Bitwarden Server SDK is built for quickly getting started building
a Bitwarden-flavored service. The entrypoint for using it is adding `UseBitwardenSdk()`
on your web application and configuring MSBuild properties to configure the features you
want.
The Bitwarden Server SDK is used as a [MSBuild SDK][msbuild-sdk]. This allows us to have a lot of
customizability of the applications that consume us. It allows for us to have features turned on or
off using compile time switches. This means consumers can only take the features they want and can
have a smaller output instead of being forced to take on all features.

## Feature Flags
## Structure

Feature flag support can be added by adding `<BitIncludeFeatures>true</BitIncludeFeatures>` to
your `csproj` file. The following APIs become available:
The [./Sdk/Sdk.props](./Sdk/Sdk.props) file is expected to be imported first and done through the
`<Sdk>` element. Therefore no properties set in a consumers csproj will have been read yet. For this
reason the consumer can't make decisions about what features they want in the SDK and have those
decisions be known to us in the props file. This is why all we do is declare a marker property
`UsingBitwardenServerSdk` to `true`.

- `IFeatureService` for getting values of features.
- `RequireFeatureAttribute` for requiring a feature is enabled on controllers and controller actions.
- `IEndpointConventionBuilder.RequireFeature()` for requiring a feature is enabled on minimal APIs.
- `IApplicationBuilder.UseFeatureFlagChecks()` for adding the middleware to do the above checks.
- `IServiceCollection.AddKnownFeatureFlags()` for adding flags that will show up in `IFeatureService.GetAll()`
- `IServiceCollection.AddFeatureFlagValues()` for adding values for feature flags.
The [./Sdk/Sdk.targets](./Sdk/Sdk.targets) file is expected to be imported last. This way,
properties defined in a consumer project will have been evaluated and can be used to make decisions
on which packages to automatically reference.

The [`Content`](./Content/) directory are additional files that are packaged into the resulting
NuGet package in their plaintext form. The `Sdk.targets` file can use files from here and include
them in the consuming projects compilation using `<Compile Include="$(SdkContentRoot)/File.cs" />`.
Since the files in Content aren't automatically compiled when building the SDK project it's
important for an integration test to consume the SDK and that file to check that it will be able to
build.

[msbuild-sdk]:[https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2022]
9 changes: 8 additions & 1 deletion extensions/Bitwarden.Server.Sdk/src/Sdk/Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
</PropertyGroup>

<!-- TODO Use the constants in places to avoid any configuration that doesn't compile -->
<PropertyGroup>
<PropertyGroup Label="Define feature defaults and constants">
<!-- Telemetry defaults to on -->
<BitIncludeTelemetry Condition="'$(BitIncludeTelemetry)' == ''">true</BitIncludeTelemetry>
<DefineConstants Condition="'$(BitIncludeTelemetry)' == 'true'">$(DefineConstants);BIT_INCLUDE_TELEMETRY</DefineConstants>
<!-- Features defaults to on -->
<BitIncludeFeatures Condition="'$(BitIncludeFeatures)' == ''">true</BitIncludeFeatures>
<DefineConstants Condition="'$(BitIncludeFeatures)' == 'true'">$(DefineConstants);BIT_INCLUDE_FEATURES</DefineConstants>
<!-- Authentication defaults to on -->
<BitIncludeAuthentication Condition="'$(BitIncludeAuthentication)' == ''">true</BitIncludeAuthentication>
<DefineConstants Condition="'$(BitIncludeAuthentication)' == 'true'">$(DefineConstants);BIT_INCLUDE_AUTHENTICATION</DefineConstants>
</PropertyGroup>

<ItemGroup Condition="'$(BitIncludeTelemetry)' == 'true'">
Expand All @@ -45,4 +48,8 @@
<ItemGroup Condition="'$(BitIncludeFeatures)' == 'true'">
<PackageReference Include="Bitwarden.Server.Sdk.Features" Version="[0.1.0]" />
</ItemGroup>

<ItemGroup Condition="'$(BitIncludeAuthentication)' == 'true'">
<PackageReference Include="Bitwarden.Server.Sdk.Authentication" Version="[0.1.0]" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@

<!-- All local features -->
<ItemGroup>
<ProjectReference Include="..\..\..\Bitwarden.Server.Sdk.Features\src\Bitwarden.Server.Sdk.Features.csproj" />
<SdkPackage Include="Features" />
<SdkPackage Include="Authentication" />

<!-- Create a project reference for each Sdk package -->
<ProjectReference Include="@(SdkPackage->'..\..\..\Bitwarden.Server.Sdk.%(Identity)\src\Bitwarden.Server.Sdk.%(Identity).csproj')" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
๏ปฟusing Microsoft.Build.Utilities.ProjectCreation;
using Bitwarden.Server.Sdk.Features;
using Microsoft.Extensions.DependencyInjection;

namespace Bitwarden.Server.Sdk.IntegrationTests;

Expand All @@ -9,16 +10,20 @@ static CustomProjectCreatorTemplates()
{
// Use this as a list of marker types for assemblies that should be added as available in a
// pseudo nuget feed.
List<Type> packages = [typeof(IFeatureService)];
var packageMap = new Dictionary<Type, string>
{
{ typeof(IFeatureService), "0.1.0" },
{ typeof(BitwardenAuthenticationServiceCollectionExtensions), "0.1.0" },
};

var feeds = new List<Uri>(packages.Count);
var feeds = new List<Uri>(packageMap.Count);

foreach (var package in packages)
foreach (var (package, version) in packageMap)
{
var assembly = package.Assembly;
var assemblyName = assembly.GetName()!;
var pr = PackageFeed.Create(new FileInfo(assembly.Location).Directory!)
.Package(assemblyName.Name!, assemblyName.Version!.ToString(3))
.Package(assemblyName.Name!, version)
.FileCustom(Path.Combine("lib", TargetFramework, assemblyName.Name + ".dll"), new FileInfo(assembly.Location))
.Save();

Expand Down Expand Up @@ -59,14 +64,14 @@ public static ProjectCreator SdkProject(this ProjectCreatorTemplates templates,
}
}

public static ProjectCreator SdkProject(this ProjectCreatorTemplates templates, Action<ProjectCreator>? customAction = null)
public static ProjectCreator SdkProject(this ProjectCreatorTemplates templates, Action<ProjectCreator>? customAction = null, string sdk = "Microsoft.NET.Sdk.Web")
{
var dir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(dir);

return ProjectCreator.Templates.SdkCsproj(
path: Path.Combine(dir, "Test.csproj"),
sdk: "Microsoft.NET.Sdk.Web",
sdk: sdk,
targetFramework: TargetFramework)
.Import(Path.Combine(ThisAssemblyDirectory, "Sdk", "Sdk.props"))
.CustomAction(customAction)
Expand All @@ -81,7 +86,7 @@ public static void AdditionalFile(this ProjectCreator project, string fileName,

public static PackageRepository CreateDefaultPackageRepository(this ProjectCreator project)
{
return PackageRepository.Create(project.GetProjectDirectory(), [new Uri("https://api.nuget.org/v3/index.json"), .. Feeds]);
return PackageRepository.Create(project.GetProjectDirectory(), [.. Feeds, new Uri("https://api.nuget.org/v3/index.json")]);
}

public static string GetProjectDirectory(this ProjectCreator project)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ public void NoOverridingProperties_CanCompile()
{
ProjectCreator.Templates.SdkProject(out var result, out var buildOutput)
.TryGetConstant("BIT_INCLUDE_TELEMETRY", out var hasTelementryConstant)
.TryGetConstant("BIT_INCLUDE_FEATURES", out var hasFeaturesConstant);
.TryGetConstant("BIT_INCLUDE_FEATURES", out var hasFeaturesConstant)
.TryGetConstant("BIT_INCLUDE_AUTHENTICATION", out var hasAuthenticationConstant);

Assert.True(result, buildOutput.GetConsoleLog());

Assert.True(hasTelementryConstant);
Assert.True(hasFeaturesConstant);
Assert.True(hasAuthenticationConstant);
}

[Fact]
Expand Down Expand Up @@ -105,22 +107,38 @@ public void FeaturesTurnedOn_CanUseFeatureService()
Assert.True(result, buildOutput.GetConsoleLog());
}

public static TheoryData<bool, bool> MatrixData
=> new MatrixTheoryData<bool, bool>([true, false], [true, false]);
[Fact]
public void AuthenticationTurnedOff_CanCompile()
{
ProjectCreator.Templates.SdkProject(
out var result,
out var buildOutput,
customAction: (project) =>
{
project.Property("BitIncludeAuthentication", bool.FalseString);
}
);

Assert.True(result, buildOutput.GetConsoleLog());
}

public static TheoryData<bool, bool, bool> MatrixData
=> new MatrixTheoryData<bool, bool, bool>([true, false], [true, false], [true, false]);

// There will be some variants that disallow the use of feature Y if feature X is not also enabled.
// Use this set to exclude those known variants from being tested.
public static HashSet<(bool, bool)> ExcludedVariants => [];
public static HashSet<(bool, bool, bool)> ExcludedVariants => [];

[Theory, MemberData(nameof(MatrixData))]
public void AllVariants_Work(bool includeTelemetry, bool includeFeatures)
public void AllVariants_Work(bool includeTelemetry, bool includeFeatures, bool includeAuthentication)
{
if (ExcludedVariants.Contains((includeTelemetry, includeFeatures)))
if (ExcludedVariants.Contains((includeTelemetry, includeFeatures, includeAuthentication)))
{
Assert.Skip($"""
Excluded Variant Skipped:
IncludeTelemetry = {includeTelemetry}
IncludeFeatures = {includeFeatures}
IncludeAuthentication = {includeAuthentication}
""");
}

Expand All @@ -131,6 +149,7 @@ public void AllVariants_Work(bool includeTelemetry, bool includeFeatures)
{
project.Property("BitIncludeTelemetry", includeTelemetry.ToString());
project.Property("BitIncludeFeatures", includeFeatures.ToString());
project.Property("BitIncludeAuthentication", includeAuthentication.ToString());
}
);

Expand Down
Loading