mirror of
https://github.com/jakeswenson/BitBetter.git
synced 2025-12-15 10:46:19 +00:00
Unified work (#269)
* Some work on line endings * Enable buildkit * Update documentation * Settle the newline and tab vs spaces for now Not perfect, but it's a standard * Change wording * Update version memo * Add correct definition for markdown * Make things clearer
This commit is contained in:
@@ -1,21 +1,21 @@
|
||||
version: 2.1
|
||||
jobs:
|
||||
build:
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Print the Current Time
|
||||
command: date
|
||||
- run:
|
||||
name: Generate Keys
|
||||
command: ./generateKeys.sh
|
||||
- run:
|
||||
name: Build script
|
||||
command: ./build.sh update
|
||||
- run:
|
||||
name: Test generating user license
|
||||
command: ./licenseGen.sh user TestName TestEmail@example.com 4a619d4a-522d-4c70-8596-affb5b607c23
|
||||
- run:
|
||||
name: Test generating organization license
|
||||
version: 2.1
|
||||
jobs:
|
||||
build:
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Print the Current Time
|
||||
command: date
|
||||
- run:
|
||||
name: Generate Keys
|
||||
command: ./generateKeys.sh
|
||||
- run:
|
||||
name: Build script
|
||||
command: ./build.sh update
|
||||
- run:
|
||||
name: Test generating user license
|
||||
command: ./licenseGen.sh user TestName TestEmail@example.com 4a619d4a-522d-4c70-8596-affb5b607c23
|
||||
- run:
|
||||
name: Test generating organization license
|
||||
command: ./licenseGen.sh org TestName TestEmail@example.com 4a619d4a-522d-4c70-8596-affb5b607c23
|
||||
19
.editorconfig
Normal file
19
.editorconfig
Normal file
@@ -0,0 +1,19 @@
|
||||
root=true
|
||||
|
||||
###############################
|
||||
# Core EditorConfig Options #
|
||||
###############################
|
||||
# All files
|
||||
[*]
|
||||
indent_style=tab
|
||||
indent_size=4
|
||||
trim_trailing_whitespace=true
|
||||
end_of_line=lf
|
||||
charset=utf-8
|
||||
|
||||
[*.{cs}]
|
||||
insert_final_newline=false
|
||||
|
||||
[*.{md,mkdn}]
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1 +1 @@
|
||||
*.ps1 text eol=crlf
|
||||
* text eol=lf
|
||||
@@ -1,4 +1,4 @@
|
||||
# Uncomment a line below and fill in the missing values or add your own. Every line in this file will be called by build.[sh|ps1] once the patched image is built.
|
||||
# docker run -d --name bitwarden --restart=always -v <full-local-path>\logs:/var/log/bitwarden -v <full-local-path>\bwdata:/etc/bitwarden -p 80:8080 --env-file <full-local-path>\settings.env bitwarden-patched
|
||||
# <OR>
|
||||
# docker-compose -f <full-local-path>/docker-compose.yml up -d
|
||||
# Uncomment a line below and fill in the missing values or add your own. Every line in this file will be called by build.[sh|ps1] once the patched image is built.
|
||||
# docker run -d --name bitwarden --restart=always -v <full-local-path>\logs:/var/log/bitwarden -v <full-local-path>\bwdata:/etc/bitwarden -p 80:8080 --env-file <full-local-path>\settings.env bitwarden-patched
|
||||
# <OR>
|
||||
# docker-compose -f <full-local-path>/docker-compose.yml up -d
|
||||
|
||||
19
README.md
19
README.md
@@ -1,12 +1,14 @@
|
||||
# BitBetter
|
||||
# BitBetter lite
|
||||
|
||||
BitBetter is is a tool to modify Bitwarden's core dll to allow you to generate your own individual and organisation licenses.
|
||||
|
||||
Please see the FAQ below for details on why this software was created.
|
||||
|
||||
_Beware! BitBetter does some semi janky stuff to rewrite the bitwarden core dll and allow the installation of a self signed certificate. Use at your own risk!_
|
||||
Be aware that this branch is **only** for the lite (formerly unified) version of bitwarden. It has been rewritten and works in different ways than the master branch.
|
||||
|
||||
Credit to https://github.com/h44z/BitBetter and https://github.com/jakeswenson/BitBetter
|
||||
_Beware! BitBetter is a solution that generates a personal certificate and uses that to generate custom licences. This requires (automated) modifying of libraries. Use at your own risk!_
|
||||
|
||||
Credit to https://github.com/h44z/BitBetter and https://github.com/jakeswenson/BitBetter and https://github.com/GieltjE/BitBetter
|
||||
|
||||
# Table of Contents
|
||||
- [BitBetter](#bitbetter)
|
||||
@@ -30,7 +32,7 @@ The following instructions are for unix-based systems (Linux, BSD, macOS) and Wi
|
||||
## Dependencies
|
||||
Aside from docker, which you also need for Bitwarden, BitBetter requires the following:
|
||||
|
||||
* Bitwarden (tested with 1.47.1, might work on lower versions)
|
||||
* Bitwarden (tested with 2025.11.1 might work on lower versions), for safety always stay up to date
|
||||
* openssl (probably already installed on most Linux or WSL systems, any version should work, on Windows it will be auto installed using winget)
|
||||
|
||||
## Setting up BitBetter
|
||||
@@ -156,6 +158,15 @@ docker exec bitwarden ln -s /usr/share/zoneinfo/Europe/Amsterdam /etc/localtime
|
||||
|
||||
Require a recreation of the docker container, build.sh will suffice too.
|
||||
|
||||
## Migrating from the old unified branch
|
||||
|
||||
```
|
||||
git branch -m unified lite
|
||||
git fetch origin
|
||||
git branch -u origin/lite lite
|
||||
git remote set-head origin -a
|
||||
```
|
||||
|
||||
# Footnotes
|
||||
|
||||
<a name="#f1"><sup>1</sup></a>This tool builds on top of the `bitbetter/api` container image so make sure you've built that above using the root `./build.sh` script.
|
||||
|
||||
@@ -4,6 +4,10 @@ $PSNativeCommandUseErrorActionPreference = $true
|
||||
# detect buildx, ErrorActionPreference will ensure the script stops execution if not found
|
||||
docker buildx version
|
||||
|
||||
# Enable BuildKit for better build experience and to ensure platform args are populated
|
||||
$env:DOCKER_BUILDKIT=1
|
||||
$env:COMPOSE_DOCKER_CLI_BUILD=1
|
||||
|
||||
# define temporary directory
|
||||
$tempdirectory = "$pwd\temp"
|
||||
# define services to patch
|
||||
|
||||
12
build.sh
12
build.sh
@@ -4,6 +4,10 @@ set -e
|
||||
# detect buildx, set -e will ensure the script stops execution if not found
|
||||
docker buildx version
|
||||
|
||||
# Enable BuildKit for better build experience and to ensure platform args are populated
|
||||
export DOCKER_BUILDKIT=1
|
||||
export COMPOSE_DOCKER_CLI_BUILD=1
|
||||
|
||||
# define temporary directory
|
||||
TEMPDIRECTORY="$PWD/temp"
|
||||
|
||||
@@ -16,19 +20,19 @@ if [ -d "$TEMPDIRECTORY" ]; then
|
||||
fi
|
||||
|
||||
if [ -f "$PWD/src/licenseGen/Core.dll" ]; then
|
||||
rm -f "$PWD/src/licenseGen/Core.dll"
|
||||
rm -f "$PWD/src/licenseGen/Core.dll"
|
||||
fi
|
||||
|
||||
if [ -f "$PWD/src/licenseGen/cert.pfx" ]; then
|
||||
rm -f "$PWD/src/licenseGen/cert.pfx"
|
||||
rm -f "$PWD/src/licenseGen/cert.pfx"
|
||||
fi
|
||||
|
||||
if [ -f "$PWD/src/bitBetter/cert.cer" ]; then
|
||||
rm -f "$PWD/src/bitBetter/cert.cer"
|
||||
rm -f "$PWD/src/bitBetter/cert.cer"
|
||||
fi
|
||||
|
||||
if [ -f "$PWD/.keys/cert.cert" ]; then
|
||||
mv "$PWD/.keys/cert.cert" "$PWD/.keys/cert.cer"
|
||||
mv "$PWD/.keys/cert.cert" "$PWD/.keys/cert.cer"
|
||||
fi
|
||||
|
||||
# generate keys if none are available
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$PSNativeCommandUseErrorActionPreference = $true
|
||||
|
||||
# get the basic openssl binary path
|
||||
$opensslbinary = "$Env:Programfiles\OpenSSL-Win64\bin\openssl.exe"
|
||||
|
||||
# if openssl is not installed attempt to install it
|
||||
if (!(Get-Command $opensslbinary -errorAction SilentlyContinue))
|
||||
{
|
||||
winget install openssl
|
||||
}
|
||||
|
||||
# if previous keys exist, remove them
|
||||
if (Test-Path "$pwd\.keys")
|
||||
{
|
||||
Remove-Item "$pwd\.keys" -Recurse -Force
|
||||
}
|
||||
|
||||
# create new directory
|
||||
New-item -ItemType Directory -Path "$pwd\.keys"
|
||||
|
||||
# generate actual keys
|
||||
Invoke-Expression "& '$opensslbinary' req -x509 -newkey rsa:4096 -keyout `"$pwd\.keys\key.pem`" -out `"$pwd\.keys\cert.cer`" -days 36500 -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US' -outform DER -passout pass:test"
|
||||
Invoke-Expression "& '$opensslbinary' x509 -inform DER -in `"$pwd\.keys\cert.cer`" -out `"$pwd\.keys\cert.pem`""
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$PSNativeCommandUseErrorActionPreference = $true
|
||||
|
||||
# get the basic openssl binary path
|
||||
$opensslbinary = "$Env:Programfiles\OpenSSL-Win64\bin\openssl.exe"
|
||||
|
||||
# if openssl is not installed attempt to install it
|
||||
if (!(Get-Command $opensslbinary -errorAction SilentlyContinue))
|
||||
{
|
||||
winget install openssl
|
||||
}
|
||||
|
||||
# if previous keys exist, remove them
|
||||
if (Test-Path "$pwd\.keys")
|
||||
{
|
||||
Remove-Item "$pwd\.keys" -Recurse -Force
|
||||
}
|
||||
|
||||
# create new directory
|
||||
New-item -ItemType Directory -Path "$pwd\.keys"
|
||||
|
||||
# generate actual keys
|
||||
Invoke-Expression "& '$opensslbinary' req -x509 -newkey rsa:4096 -keyout `"$pwd\.keys\key.pem`" -out `"$pwd\.keys\cert.cer`" -days 36500 -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US' -outform DER -passout pass:test"
|
||||
Invoke-Expression "& '$opensslbinary' x509 -inform DER -in `"$pwd\.keys\cert.cer`" -out `"$pwd\.keys\cert.pem`""
|
||||
Invoke-Expression "& '$opensslbinary' pkcs12 -export -out `"$pwd\.keys\cert.pfx`" -inkey `"$pwd\.keys\key.pem`" -in `"$pwd\.keys\cert.pem`" -passin pass:test -passout pass:test"
|
||||
@@ -1,17 +1,17 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$PSNativeCommandUseErrorActionPreference = $true
|
||||
|
||||
if ($($args.Count) -lt 1) {
|
||||
echo "USAGE: <License Gen action> [License Gen args...]"
|
||||
echo "ACTIONS:"
|
||||
echo " interactive"
|
||||
echo " user"
|
||||
echo " org"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
if ($args[0] -eq "interactive") {
|
||||
docker run -it --rm bitbetter/licensegen interactive
|
||||
} else {
|
||||
docker run bitbetter/licensegen $args
|
||||
}
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$PSNativeCommandUseErrorActionPreference = $true
|
||||
|
||||
if ($($args.Count) -lt 1) {
|
||||
echo "USAGE: <License Gen action> [License Gen args...]"
|
||||
echo "ACTIONS:"
|
||||
echo " interactive"
|
||||
echo " user"
|
||||
echo " org"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
if ($args[0] -eq "interactive") {
|
||||
docker run -it --rm bitbetter/licensegen interactive
|
||||
} else {
|
||||
docker run bitbetter/licensegen $args
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
set -e
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "USAGE: <License Gen action> [License Gen args...]"
|
||||
echo "ACTIONS:"
|
||||
echo " interactive"
|
||||
echo " user"
|
||||
echo " org"
|
||||
exit 1
|
||||
echo "USAGE: <License Gen action> [License Gen args...]"
|
||||
echo "ACTIONS:"
|
||||
echo " interactive"
|
||||
echo " user"
|
||||
echo " org"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$1" = "interactive" ]; then
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
FROM ghcr.io/bitwarden/lite:latest
|
||||
|
||||
COPY ./temp/ /app/
|
||||
FROM ghcr.io/bitwarden/lite:latest
|
||||
|
||||
COPY ./temp/ /app/
|
||||
@@ -1,67 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using dnlib.DotNet;
|
||||
using dnlib.DotNet.Emit;
|
||||
using dnlib.DotNet.Writer;
|
||||
using dnlib.IO;
|
||||
|
||||
namespace bitBetter;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static Int32 Main()
|
||||
{
|
||||
const String certFile = "/app/cert.cer";
|
||||
String[] files = Directory.GetFiles("/app/mount", "Core.dll", SearchOption.AllDirectories);
|
||||
|
||||
foreach (String file in files)
|
||||
{
|
||||
Console.WriteLine(file);
|
||||
ModuleDefMD moduleDefMd = ModuleDefMD.Load(file);
|
||||
Byte[] cert = File.ReadAllBytes(certFile);
|
||||
|
||||
EmbeddedResource embeddedResourceToRemove = moduleDefMd.Resources.OfType<EmbeddedResource>().First(r => r.Name.Equals("Bit.Core.licensing.cer"));
|
||||
EmbeddedResource embeddedResourceToAdd = new("Bit.Core.licensing.cer", cert) { Attributes = embeddedResourceToRemove.Attributes };
|
||||
moduleDefMd.Resources.Add(embeddedResourceToAdd);
|
||||
moduleDefMd.Resources.Remove(embeddedResourceToRemove);
|
||||
|
||||
DataReader reader = embeddedResourceToRemove.CreateReader();
|
||||
X509Certificate2 existingCert = new(reader.ReadRemainingBytes());
|
||||
|
||||
Console.WriteLine($"Existing Cert Thumbprint: {existingCert.Thumbprint}");
|
||||
X509Certificate2 certificate = new(cert);
|
||||
|
||||
Console.WriteLine($"New Cert Thumbprint: {certificate.Thumbprint}");
|
||||
|
||||
IEnumerable<TypeDef> services = moduleDefMd.Types.Where(t => t.Namespace == "Bit.Core.Billing.Services");
|
||||
TypeDef type = services.First(t => t.Name == "LicensingService");
|
||||
MethodDef constructor = type.FindConstructors().First();
|
||||
|
||||
Instruction instructionToPatch = constructor.Body.Instructions.FirstOrDefault(i => i.OpCode == OpCodes.Ldstr && String.Equals((String)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (instructionToPatch != null)
|
||||
{
|
||||
instructionToPatch.Operand = certificate.Thumbprint;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Can't find constructor to patch");
|
||||
}
|
||||
|
||||
ModuleWriterOptions moduleWriterOptions = new(moduleDefMd);
|
||||
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.KeepOldMaxStack;
|
||||
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveAll;
|
||||
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveRids;
|
||||
|
||||
moduleDefMd.Write(file + ".new");
|
||||
moduleDefMd.Dispose();
|
||||
File.Delete(file);
|
||||
File.Move(file + ".new", file);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using dnlib.DotNet;
|
||||
using dnlib.DotNet.Emit;
|
||||
using dnlib.DotNet.Writer;
|
||||
using dnlib.IO;
|
||||
|
||||
namespace bitBetter;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static Int32 Main()
|
||||
{
|
||||
const String certFile = "/app/cert.cer";
|
||||
String[] files = Directory.GetFiles("/app/mount", "Core.dll", SearchOption.AllDirectories);
|
||||
|
||||
foreach (String file in files)
|
||||
{
|
||||
Console.WriteLine(file);
|
||||
ModuleDefMD moduleDefMd = ModuleDefMD.Load(file);
|
||||
Byte[] cert = File.ReadAllBytes(certFile);
|
||||
|
||||
EmbeddedResource embeddedResourceToRemove = moduleDefMd.Resources.OfType<EmbeddedResource>().First(r => r.Name.Equals("Bit.Core.licensing.cer"));
|
||||
EmbeddedResource embeddedResourceToAdd = new("Bit.Core.licensing.cer", cert) { Attributes = embeddedResourceToRemove.Attributes };
|
||||
moduleDefMd.Resources.Add(embeddedResourceToAdd);
|
||||
moduleDefMd.Resources.Remove(embeddedResourceToRemove);
|
||||
|
||||
DataReader reader = embeddedResourceToRemove.CreateReader();
|
||||
X509Certificate2 existingCert = new(reader.ReadRemainingBytes());
|
||||
|
||||
Console.WriteLine($"Existing Cert Thumbprint: {existingCert.Thumbprint}");
|
||||
X509Certificate2 certificate = new(cert);
|
||||
|
||||
Console.WriteLine($"New Cert Thumbprint: {certificate.Thumbprint}");
|
||||
|
||||
IEnumerable<TypeDef> services = moduleDefMd.Types.Where(t => t.Namespace == "Bit.Core.Billing.Services");
|
||||
TypeDef type = services.First(t => t.Name == "LicensingService");
|
||||
MethodDef constructor = type.FindConstructors().First();
|
||||
|
||||
Instruction instructionToPatch = constructor.Body.Instructions.FirstOrDefault(i => i.OpCode == OpCodes.Ldstr && String.Equals((String)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (instructionToPatch != null)
|
||||
{
|
||||
instructionToPatch.Operand = certificate.Thumbprint;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Can't find constructor to patch");
|
||||
}
|
||||
|
||||
ModuleWriterOptions moduleWriterOptions = new(moduleDefMd);
|
||||
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.KeepOldMaxStack;
|
||||
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveAll;
|
||||
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveRids;
|
||||
|
||||
moduleDefMd.Write(file + ".new");
|
||||
moduleDefMd.Dispose();
|
||||
File.Delete(file);
|
||||
File.Move(file + ".new", file);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dnlib" Version="4.5.0" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dnlib" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -12,4 +12,4 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
|
||||
ENTRYPOINT ["dotnet", "/app/licenseGen.dll", "--cert=/app/cert.pfx", "--core=/app/Core.dll"]
|
||||
ENTRYPOINT ["dotnet", "/app/licenseGen.dll", "--cert=/app/cert.pfx", "--core=/app/Core.dll"]
|
||||
@@ -1,485 +1,485 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
|
||||
namespace licenseGen;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static readonly CommandLineApplication App = new();
|
||||
private static readonly CommandOption Cert = App.Option("--cert", "Certificate file", CommandOptionType.SingleValue);
|
||||
private static readonly CommandOption CoreDll = App.Option("--core", "Path to Core.dll", CommandOptionType.SingleValue);
|
||||
|
||||
private static Int32 Main(String[] args)
|
||||
{
|
||||
App.Command("interactive", config =>
|
||||
{
|
||||
String buff, licenseType = "", name = "", email = "", businessName="";
|
||||
Int16 storage = 0;
|
||||
Boolean validGuid = false, validInstallid = false;
|
||||
Guid guid = Guid.Empty, installid = Guid.Empty;
|
||||
|
||||
config.OnExecute(() =>
|
||||
{
|
||||
Check();
|
||||
Console.WriteLine("Interactive license mode...");
|
||||
|
||||
while (licenseType == "")
|
||||
{
|
||||
Console.WriteLine("What would you like to generate, a [u]ser license or an [o]rg license: ");
|
||||
buff = Console.ReadLine();
|
||||
|
||||
switch (buff)
|
||||
{
|
||||
case "u":
|
||||
{
|
||||
licenseType = "user";
|
||||
Console.WriteLine("Okay, we will generate a user license.");
|
||||
|
||||
while (!validGuid)
|
||||
{
|
||||
Console.WriteLine("Please provide the user's guid — refer to the Readme for details on how to retrieve this. [GUID]: ");
|
||||
buff = Console.ReadLine();
|
||||
|
||||
if (Guid.TryParse(buff, out guid))validGuid = true;
|
||||
else Console.WriteLine("The user-guid provided does not appear to be valid!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "o":
|
||||
{
|
||||
licenseType = "org";
|
||||
Console.WriteLine("Okay, we will generate an organization license.");
|
||||
|
||||
while (!validInstallid)
|
||||
{
|
||||
Console.WriteLine("Please provide your Bitwarden Install-ID — refer to the Readme for details on how to retrieve this. [Install-ID]: ");
|
||||
buff = Console.ReadLine();
|
||||
|
||||
if (Guid.TryParse(buff, out installid)) validInstallid = true;
|
||||
else Console.WriteLine("The install-id provided does not appear to be valid.");
|
||||
}
|
||||
|
||||
while (businessName == "")
|
||||
{
|
||||
Console.WriteLine("Please enter a business name, default is BitBetter. [Business Name]: ");
|
||||
buff = Console.ReadLine();
|
||||
if (buff == "")
|
||||
{
|
||||
businessName = "BitBetter";
|
||||
}
|
||||
else if (CheckBusinessName(buff))
|
||||
{
|
||||
businessName = buff;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Console.WriteLine("Unrecognized option \'" + buff + "\'.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (name == "")
|
||||
{
|
||||
Console.WriteLine("Please provide the username this license will be registered to. [username]: ");
|
||||
buff = Console.ReadLine();
|
||||
if (CheckUsername(buff)) name = buff;
|
||||
}
|
||||
|
||||
while (email == "")
|
||||
{
|
||||
Console.WriteLine("Please provide the email address for the user " + name + ". [email]: ");
|
||||
buff = Console.ReadLine();
|
||||
if (CheckEmail(buff))
|
||||
{
|
||||
email = buff;
|
||||
}
|
||||
}
|
||||
|
||||
while (storage == 0)
|
||||
{
|
||||
Console.WriteLine("Extra storage space for the user " + name + ". (max.: " + Int16.MaxValue + "). Defaults to maximum value. [storage]");
|
||||
buff = Console.ReadLine();
|
||||
if (String.IsNullOrWhiteSpace(buff))
|
||||
{
|
||||
storage = Int16.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CheckStorage(buff))
|
||||
{
|
||||
storage = Int16.Parse(buff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (licenseType)
|
||||
{
|
||||
case "user":
|
||||
{
|
||||
Console.WriteLine("Confirm creation of \"user\" license for username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", User-GUID: \"" + guid + "\"? Y/n");
|
||||
buff = Console.ReadLine();
|
||||
if (buff is "" or "y" or "Y")
|
||||
{
|
||||
GenerateUserLicense(new X509Certificate2(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, guid, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Exiting...");
|
||||
return 0;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "org":
|
||||
{
|
||||
Console.WriteLine("Confirm creation of \"organization\" license for business name: \"" + businessName + "\", username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", Install-ID: \"" + installid + "\"? Y/n");
|
||||
buff = Console.ReadLine();
|
||||
if (buff is "" or "y" or "Y")
|
||||
{
|
||||
GenerateOrgLicense(new X509Certificate2(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, installid, businessName, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Exiting...");
|
||||
return 0;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
App.Command("user", config =>
|
||||
{
|
||||
CommandArgument name = config.Argument("Name", "your name");
|
||||
CommandArgument email = config.Argument("Email", "your email");
|
||||
CommandArgument userIdArg = config.Argument("User ID", "your user id");
|
||||
CommandArgument storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + Int16.MaxValue + " (optional, default = max)");
|
||||
CommandArgument key = config.Argument("Key", "your key id (optional)");
|
||||
|
||||
config.OnExecute(() =>
|
||||
{
|
||||
Check();
|
||||
|
||||
if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value))
|
||||
{
|
||||
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}'");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(userIdArg.Value) || !Guid.TryParse(userIdArg.Value, out Guid userId))
|
||||
{
|
||||
config.Error.WriteLine("User ID not provided");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Int16 storageShort = 0;
|
||||
if (!String.IsNullOrWhiteSpace(storage.Value))
|
||||
{
|
||||
Double parsedStorage = Double.Parse(storage.Value);
|
||||
if (parsedStorage is > Int16.MaxValue or < 0)
|
||||
{
|
||||
config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
storageShort = (Int16) parsedStorage;
|
||||
}
|
||||
|
||||
GenerateUserLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, userId, key.Value);
|
||||
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
App.Command("org", config =>
|
||||
{
|
||||
CommandArgument name = config.Argument("Name", "your name");
|
||||
CommandArgument email = config.Argument("Email", "your email");
|
||||
CommandArgument installId = config.Argument("InstallId", "your installation id (GUID)");
|
||||
CommandArgument storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + Int16.MaxValue + " (optional, default = max)");
|
||||
CommandArgument businessName = config.Argument("BusinessName", "name for the organization (optional)");
|
||||
CommandArgument key = config.Argument("Key", "your key id (optional)");
|
||||
|
||||
config.OnExecute(() =>
|
||||
{
|
||||
Check();
|
||||
|
||||
if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value) || String.IsNullOrWhiteSpace(installId.Value))
|
||||
{
|
||||
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}' InstallId='{installId.Value}'");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!Guid.TryParse(installId.Value, out Guid installationId))
|
||||
{
|
||||
config.Error.WriteLine("Unable to parse your installation id as a GUID");
|
||||
config.Error.WriteLine($"Here's a new guid: {Guid.NewGuid()}");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Int16 storageShort = 0;
|
||||
if (!String.IsNullOrWhiteSpace(storage.Value))
|
||||
{
|
||||
Double parsedStorage = Double.Parse(storage.Value);
|
||||
if (parsedStorage is > Int16.MaxValue or < 0)
|
||||
{
|
||||
config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
storageShort = (Int16)parsedStorage;
|
||||
}
|
||||
|
||||
GenerateOrgLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value);
|
||||
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
|
||||
App.OnExecute(() =>
|
||||
{
|
||||
App.ShowHelp();
|
||||
return 10;
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
App.HelpOption("-? | -h | --help");
|
||||
return App.Execute(args);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.Error.WriteLine("Oops: {0}", exception);
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Check()
|
||||
{
|
||||
if (Cert == null || String.IsNullOrWhiteSpace(Cert.Value()))
|
||||
{
|
||||
App.Error.WriteLine("No certificate specified");
|
||||
App.ShowHelp();
|
||||
Environment.Exit(1);
|
||||
}
|
||||
else if (CoreDll == null || String.IsNullOrWhiteSpace(CoreDll.Value()))
|
||||
{
|
||||
App.Error.WriteLine("No core dll specified");
|
||||
App.ShowHelp();
|
||||
Environment.Exit(1);
|
||||
}
|
||||
else if (!File.Exists(Cert.Value()))
|
||||
{
|
||||
App.Error.WriteLine($"Can't find certificate at: {Cert.Value()}");
|
||||
App.ShowHelp();
|
||||
Environment.Exit(1);
|
||||
}
|
||||
else if (!File.Exists(CoreDll.Value()))
|
||||
{
|
||||
App.Error.WriteLine($"Can't find core dll at: {CoreDll.Value()}");
|
||||
App.ShowHelp();
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// checkUsername Checks that the username is a valid username
|
||||
private static Boolean CheckUsername(String s)
|
||||
{
|
||||
// TODO: Actually validate
|
||||
if (!String.IsNullOrWhiteSpace(s)) return true;
|
||||
|
||||
Console.WriteLine("The username provided doesn't appear to be valid!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// checkBusinessName Checks that the Business Name is a valid username
|
||||
private static Boolean CheckBusinessName(String s)
|
||||
{
|
||||
// TODO: Actually validate
|
||||
if (!String.IsNullOrWhiteSpace(s)) return true;
|
||||
|
||||
Console.WriteLine("The Business Name provided doesn't appear to be valid!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// checkEmail Checks that the email address is a valid email address
|
||||
private static Boolean CheckEmail(String s)
|
||||
{
|
||||
// TODO: Actually validate
|
||||
if (!String.IsNullOrWhiteSpace(s)) return true;
|
||||
|
||||
Console.WriteLine("The email provided doesn't appear to be valid!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// checkStorage Checks that the storage is in a valid range
|
||||
private static Boolean CheckStorage(String s)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(s))
|
||||
{
|
||||
Console.WriteLine("The storage provided doesn't appear to be valid!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(Double.Parse(s) > Int16.MaxValue) && !(Double.Parse(s) < 0)) return true;
|
||||
|
||||
Console.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]!");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
||||
private static void GenerateUserLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid userId, String key)
|
||||
{
|
||||
Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(corePath));
|
||||
|
||||
Type type = core.GetType("Bit.Core.Billing.Models.Business.UserLicense");
|
||||
Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType");
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
Console.WriteLine("Could not find type!");
|
||||
return;
|
||||
}
|
||||
if (licenseTypeEnum == null)
|
||||
{
|
||||
Console.WriteLine("Could not find license licenseTypeEnum!");
|
||||
return;
|
||||
}
|
||||
|
||||
Object license = Activator.CreateInstance(type);
|
||||
|
||||
MethodInfo computeHash = type.GetMethod("ComputeHash");
|
||||
if (computeHash == null)
|
||||
{
|
||||
Console.WriteLine("Could not find ComputeHash!");
|
||||
return;
|
||||
}
|
||||
|
||||
MethodInfo sign = type.GetMethod("Sign");
|
||||
if (sign == null)
|
||||
{
|
||||
Console.WriteLine("Could not find sign!");
|
||||
return;
|
||||
}
|
||||
|
||||
Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
|
||||
Set(type, license, "Id", userId);
|
||||
Set(type, license, "Name", userName);
|
||||
Set(type, license, "Email", email);
|
||||
Set(type, license, "Premium", true);
|
||||
Set(type, license, "MaxStorageGb", storage == 0 ? Int16.MaxValue : storage);
|
||||
Set(type, license, "Version", 1);
|
||||
Set(type, license, "Issued", DateTime.UtcNow);
|
||||
Set(type, license, "Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
|
||||
Set(type, license, "Expires", DateTime.UtcNow.AddYears(100));
|
||||
Set(type, license, "Trial", false);
|
||||
Set(type, license, "LicenseType", Enum.Parse(licenseTypeEnum, "User"));
|
||||
Set(type, license, "Hash", Convert.ToBase64String(((Byte[])computeHash.Invoke(license, []))!));
|
||||
Set(type, license, "Signature", Convert.ToBase64String((Byte[])sign.Invoke(license, [cert])!));
|
||||
|
||||
Console.WriteLine(JsonSerializer.Serialize(license, JsonOptions));
|
||||
}
|
||||
private static void GenerateOrgLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid instalId, String businessName, String key)
|
||||
{
|
||||
Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(corePath));
|
||||
Type type = core.GetType("Bit.Core.Billing.Organizations.Models.OrganizationLicense");
|
||||
Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType");
|
||||
Type planTypeEnum = core.GetType("Bit.Core.Billing.Enums.PlanType");
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
Console.WriteLine("Could not find type!");
|
||||
return;
|
||||
}
|
||||
if (licenseTypeEnum == null)
|
||||
{
|
||||
Console.WriteLine("Could not find licenseTypeEnum!");
|
||||
return;
|
||||
}
|
||||
if (planTypeEnum == null)
|
||||
{
|
||||
Console.WriteLine("Could not find planTypeEnum!");
|
||||
return;
|
||||
}
|
||||
|
||||
Object license = Activator.CreateInstance(type);
|
||||
|
||||
MethodInfo computeHash = type.GetMethod("ComputeHash");
|
||||
if (computeHash == null)
|
||||
{
|
||||
Console.WriteLine("Could not find ComputeHash!");
|
||||
return;
|
||||
}
|
||||
|
||||
MethodInfo sign = type.GetMethod("Sign");
|
||||
if (sign == null)
|
||||
{
|
||||
Console.WriteLine("Could not find sign!");
|
||||
return;
|
||||
}
|
||||
|
||||
Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
|
||||
Set(type, license, "InstallationId", instalId);
|
||||
Set(type, license, "Id", Guid.NewGuid());
|
||||
Set(type, license, "Name", userName);
|
||||
Set(type, license, "BillingEmail", email);
|
||||
Set(type, license, "BusinessName", String.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName);
|
||||
Set(type, license, "Enabled", true);
|
||||
Set(type, license, "Plan", "Enterprise (Annually)");
|
||||
Set(type, license, "PlanType", Enum.Parse(planTypeEnum, "EnterpriseAnnually"));
|
||||
Set(type, license, "Seats", Int32.MaxValue);
|
||||
Set(type, license, "MaxCollections", Int16.MaxValue);
|
||||
Set(type, license, "UsePolicies", true);
|
||||
Set(type, license, "UseSso", true);
|
||||
Set(type, license, "UseKeyConnector", true);
|
||||
Set(type, license, "UseScim", true);
|
||||
Set(type, license, "UseGroups", true);
|
||||
Set(type, license, "UseEvents", true);
|
||||
Set(type, license, "UseDirectory", true);
|
||||
Set(type, license, "UseTotp", true);
|
||||
Set(type, license, "Use2fa", true);
|
||||
Set(type, license, "UseApi", true);
|
||||
Set(type, license, "UseResetPassword", true);
|
||||
Set(type, license, "MaxStorageGb", storage == 0 ? Int16.MaxValue : storage);
|
||||
Set(type, license, "SelfHost", true);
|
||||
Set(type, license, "UsersGetPremium", true);
|
||||
Set(type, license, "UseCustomPermissions", true);
|
||||
Set(type, license, "Version", 16);
|
||||
Set(type, license, "Issued", DateTime.UtcNow);
|
||||
Set(type, license, "Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
|
||||
Set(type, license, "Expires", DateTime.UtcNow.AddYears(100));
|
||||
Set(type, license, "ExpirationWithoutGracePeriod", DateTime.UtcNow.AddYears(100));
|
||||
Set(type, license, "UsePasswordManager", true);
|
||||
Set(type, license, "UseSecretsManager", true);
|
||||
Set(type, license, "SmSeats", Int32.MaxValue);
|
||||
Set(type, license, "SmServiceAccounts", Int32.MaxValue);
|
||||
Set(type, license, "UseRiskInsights", true);
|
||||
Set(type, license, "LimitCollectionCreationDeletion", true);
|
||||
Set(type, license, "AllowAdminAccessToAllCollectionItems", true);
|
||||
Set(type, license, "Trial", false);
|
||||
Set(type, license, "LicenseType", Enum.Parse(licenseTypeEnum, "Organization"));
|
||||
Set(type, license, "UseOrganizationDomains", true);
|
||||
Set(type, license, "UseAdminSponsoredFamilies", true);
|
||||
Set(type, license, "Hash", Convert.ToBase64String((Byte[])computeHash.Invoke(license, [])!));
|
||||
Set(type, license, "Signature", Convert.ToBase64String((Byte[])sign.Invoke(license, [cert])!));
|
||||
|
||||
Console.WriteLine(JsonSerializer.Serialize(license, JsonOptions));
|
||||
}
|
||||
private static void Set(Type type, Object license, String name, Object value)
|
||||
{
|
||||
type.GetProperty(name)?.SetValue(license, value);
|
||||
}
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
|
||||
namespace licenseGen;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static readonly CommandLineApplication App = new();
|
||||
private static readonly CommandOption Cert = App.Option("--cert", "Certificate file", CommandOptionType.SingleValue);
|
||||
private static readonly CommandOption CoreDll = App.Option("--core", "Path to Core.dll", CommandOptionType.SingleValue);
|
||||
|
||||
private static Int32 Main(String[] args)
|
||||
{
|
||||
App.Command("interactive", config =>
|
||||
{
|
||||
String buff, licenseType = "", name = "", email = "", businessName="";
|
||||
Int16 storage = 0;
|
||||
Boolean validGuid = false, validInstallid = false;
|
||||
Guid guid = Guid.Empty, installid = Guid.Empty;
|
||||
|
||||
config.OnExecute(() =>
|
||||
{
|
||||
Check();
|
||||
Console.WriteLine("Interactive license mode...");
|
||||
|
||||
while (licenseType == "")
|
||||
{
|
||||
Console.WriteLine("What would you like to generate, a [u]ser license or an [o]rg license: ");
|
||||
buff = Console.ReadLine();
|
||||
|
||||
switch (buff)
|
||||
{
|
||||
case "u":
|
||||
{
|
||||
licenseType = "user";
|
||||
Console.WriteLine("Okay, we will generate a user license.");
|
||||
|
||||
while (!validGuid)
|
||||
{
|
||||
Console.WriteLine("Please provide the user's guid — refer to the Readme for details on how to retrieve this. [GUID]: ");
|
||||
buff = Console.ReadLine();
|
||||
|
||||
if (Guid.TryParse(buff, out guid))validGuid = true;
|
||||
else Console.WriteLine("The user-guid provided does not appear to be valid!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "o":
|
||||
{
|
||||
licenseType = "org";
|
||||
Console.WriteLine("Okay, we will generate an organization license.");
|
||||
|
||||
while (!validInstallid)
|
||||
{
|
||||
Console.WriteLine("Please provide your Bitwarden Install-ID — refer to the Readme for details on how to retrieve this. [Install-ID]: ");
|
||||
buff = Console.ReadLine();
|
||||
|
||||
if (Guid.TryParse(buff, out installid)) validInstallid = true;
|
||||
else Console.WriteLine("The install-id provided does not appear to be valid.");
|
||||
}
|
||||
|
||||
while (businessName == "")
|
||||
{
|
||||
Console.WriteLine("Please enter a business name, default is BitBetter. [Business Name]: ");
|
||||
buff = Console.ReadLine();
|
||||
if (buff == "")
|
||||
{
|
||||
businessName = "BitBetter";
|
||||
}
|
||||
else if (CheckBusinessName(buff))
|
||||
{
|
||||
businessName = buff;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Console.WriteLine("Unrecognized option \'" + buff + "\'.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (name == "")
|
||||
{
|
||||
Console.WriteLine("Please provide the username this license will be registered to. [username]: ");
|
||||
buff = Console.ReadLine();
|
||||
if (CheckUsername(buff)) name = buff;
|
||||
}
|
||||
|
||||
while (email == "")
|
||||
{
|
||||
Console.WriteLine("Please provide the email address for the user " + name + ". [email]: ");
|
||||
buff = Console.ReadLine();
|
||||
if (CheckEmail(buff))
|
||||
{
|
||||
email = buff;
|
||||
}
|
||||
}
|
||||
|
||||
while (storage == 0)
|
||||
{
|
||||
Console.WriteLine("Extra storage space for the user " + name + ". (max.: " + Int16.MaxValue + "). Defaults to maximum value. [storage]");
|
||||
buff = Console.ReadLine();
|
||||
if (String.IsNullOrWhiteSpace(buff))
|
||||
{
|
||||
storage = Int16.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CheckStorage(buff))
|
||||
{
|
||||
storage = Int16.Parse(buff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (licenseType)
|
||||
{
|
||||
case "user":
|
||||
{
|
||||
Console.WriteLine("Confirm creation of \"user\" license for username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", User-GUID: \"" + guid + "\"? Y/n");
|
||||
buff = Console.ReadLine();
|
||||
if (buff is "" or "y" or "Y")
|
||||
{
|
||||
GenerateUserLicense(new X509Certificate2(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, guid, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Exiting...");
|
||||
return 0;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "org":
|
||||
{
|
||||
Console.WriteLine("Confirm creation of \"organization\" license for business name: \"" + businessName + "\", username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", Install-ID: \"" + installid + "\"? Y/n");
|
||||
buff = Console.ReadLine();
|
||||
if (buff is "" or "y" or "Y")
|
||||
{
|
||||
GenerateOrgLicense(new X509Certificate2(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, installid, businessName, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Exiting...");
|
||||
return 0;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
App.Command("user", config =>
|
||||
{
|
||||
CommandArgument name = config.Argument("Name", "your name");
|
||||
CommandArgument email = config.Argument("Email", "your email");
|
||||
CommandArgument userIdArg = config.Argument("User ID", "your user id");
|
||||
CommandArgument storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + Int16.MaxValue + " (optional, default = max)");
|
||||
CommandArgument key = config.Argument("Key", "your key id (optional)");
|
||||
|
||||
config.OnExecute(() =>
|
||||
{
|
||||
Check();
|
||||
|
||||
if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value))
|
||||
{
|
||||
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}'");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(userIdArg.Value) || !Guid.TryParse(userIdArg.Value, out Guid userId))
|
||||
{
|
||||
config.Error.WriteLine("User ID not provided");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Int16 storageShort = 0;
|
||||
if (!String.IsNullOrWhiteSpace(storage.Value))
|
||||
{
|
||||
Double parsedStorage = Double.Parse(storage.Value);
|
||||
if (parsedStorage is > Int16.MaxValue or < 0)
|
||||
{
|
||||
config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
storageShort = (Int16) parsedStorage;
|
||||
}
|
||||
|
||||
GenerateUserLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, userId, key.Value);
|
||||
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
App.Command("org", config =>
|
||||
{
|
||||
CommandArgument name = config.Argument("Name", "your name");
|
||||
CommandArgument email = config.Argument("Email", "your email");
|
||||
CommandArgument installId = config.Argument("InstallId", "your installation id (GUID)");
|
||||
CommandArgument storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + Int16.MaxValue + " (optional, default = max)");
|
||||
CommandArgument businessName = config.Argument("BusinessName", "name for the organization (optional)");
|
||||
CommandArgument key = config.Argument("Key", "your key id (optional)");
|
||||
|
||||
config.OnExecute(() =>
|
||||
{
|
||||
Check();
|
||||
|
||||
if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value) || String.IsNullOrWhiteSpace(installId.Value))
|
||||
{
|
||||
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}' InstallId='{installId.Value}'");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!Guid.TryParse(installId.Value, out Guid installationId))
|
||||
{
|
||||
config.Error.WriteLine("Unable to parse your installation id as a GUID");
|
||||
config.Error.WriteLine($"Here's a new guid: {Guid.NewGuid()}");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Int16 storageShort = 0;
|
||||
if (!String.IsNullOrWhiteSpace(storage.Value))
|
||||
{
|
||||
Double parsedStorage = Double.Parse(storage.Value);
|
||||
if (parsedStorage is > Int16.MaxValue or < 0)
|
||||
{
|
||||
config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]");
|
||||
config.ShowHelp(true);
|
||||
return 1;
|
||||
}
|
||||
storageShort = (Int16)parsedStorage;
|
||||
}
|
||||
|
||||
GenerateOrgLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value);
|
||||
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
|
||||
App.OnExecute(() =>
|
||||
{
|
||||
App.ShowHelp();
|
||||
return 10;
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
App.HelpOption("-? | -h | --help");
|
||||
return App.Execute(args);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.Error.WriteLine("Oops: {0}", exception);
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Check()
|
||||
{
|
||||
if (Cert == null || String.IsNullOrWhiteSpace(Cert.Value()))
|
||||
{
|
||||
App.Error.WriteLine("No certificate specified");
|
||||
App.ShowHelp();
|
||||
Environment.Exit(1);
|
||||
}
|
||||
else if (CoreDll == null || String.IsNullOrWhiteSpace(CoreDll.Value()))
|
||||
{
|
||||
App.Error.WriteLine("No core dll specified");
|
||||
App.ShowHelp();
|
||||
Environment.Exit(1);
|
||||
}
|
||||
else if (!File.Exists(Cert.Value()))
|
||||
{
|
||||
App.Error.WriteLine($"Can't find certificate at: {Cert.Value()}");
|
||||
App.ShowHelp();
|
||||
Environment.Exit(1);
|
||||
}
|
||||
else if (!File.Exists(CoreDll.Value()))
|
||||
{
|
||||
App.Error.WriteLine($"Can't find core dll at: {CoreDll.Value()}");
|
||||
App.ShowHelp();
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// checkUsername Checks that the username is a valid username
|
||||
private static Boolean CheckUsername(String s)
|
||||
{
|
||||
// TODO: Actually validate
|
||||
if (!String.IsNullOrWhiteSpace(s)) return true;
|
||||
|
||||
Console.WriteLine("The username provided doesn't appear to be valid!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// checkBusinessName Checks that the Business Name is a valid username
|
||||
private static Boolean CheckBusinessName(String s)
|
||||
{
|
||||
// TODO: Actually validate
|
||||
if (!String.IsNullOrWhiteSpace(s)) return true;
|
||||
|
||||
Console.WriteLine("The Business Name provided doesn't appear to be valid!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// checkEmail Checks that the email address is a valid email address
|
||||
private static Boolean CheckEmail(String s)
|
||||
{
|
||||
// TODO: Actually validate
|
||||
if (!String.IsNullOrWhiteSpace(s)) return true;
|
||||
|
||||
Console.WriteLine("The email provided doesn't appear to be valid!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// checkStorage Checks that the storage is in a valid range
|
||||
private static Boolean CheckStorage(String s)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(s))
|
||||
{
|
||||
Console.WriteLine("The storage provided doesn't appear to be valid!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(Double.Parse(s) > Int16.MaxValue) && !(Double.Parse(s) < 0)) return true;
|
||||
|
||||
Console.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]!");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
||||
private static void GenerateUserLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid userId, String key)
|
||||
{
|
||||
Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(corePath));
|
||||
|
||||
Type type = core.GetType("Bit.Core.Billing.Models.Business.UserLicense");
|
||||
Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType");
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
Console.WriteLine("Could not find type!");
|
||||
return;
|
||||
}
|
||||
if (licenseTypeEnum == null)
|
||||
{
|
||||
Console.WriteLine("Could not find license licenseTypeEnum!");
|
||||
return;
|
||||
}
|
||||
|
||||
Object license = Activator.CreateInstance(type);
|
||||
|
||||
MethodInfo computeHash = type.GetMethod("ComputeHash");
|
||||
if (computeHash == null)
|
||||
{
|
||||
Console.WriteLine("Could not find ComputeHash!");
|
||||
return;
|
||||
}
|
||||
|
||||
MethodInfo sign = type.GetMethod("Sign");
|
||||
if (sign == null)
|
||||
{
|
||||
Console.WriteLine("Could not find sign!");
|
||||
return;
|
||||
}
|
||||
|
||||
Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
|
||||
Set(type, license, "Id", userId);
|
||||
Set(type, license, "Name", userName);
|
||||
Set(type, license, "Email", email);
|
||||
Set(type, license, "Premium", true);
|
||||
Set(type, license, "MaxStorageGb", storage == 0 ? Int16.MaxValue : storage);
|
||||
Set(type, license, "Version", 1);
|
||||
Set(type, license, "Issued", DateTime.UtcNow);
|
||||
Set(type, license, "Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
|
||||
Set(type, license, "Expires", DateTime.UtcNow.AddYears(100));
|
||||
Set(type, license, "Trial", false);
|
||||
Set(type, license, "LicenseType", Enum.Parse(licenseTypeEnum, "User"));
|
||||
Set(type, license, "Hash", Convert.ToBase64String(((Byte[])computeHash.Invoke(license, []))!));
|
||||
Set(type, license, "Signature", Convert.ToBase64String((Byte[])sign.Invoke(license, [cert])!));
|
||||
|
||||
Console.WriteLine(JsonSerializer.Serialize(license, JsonOptions));
|
||||
}
|
||||
private static void GenerateOrgLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid instalId, String businessName, String key)
|
||||
{
|
||||
Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(corePath));
|
||||
Type type = core.GetType("Bit.Core.Billing.Organizations.Models.OrganizationLicense");
|
||||
Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType");
|
||||
Type planTypeEnum = core.GetType("Bit.Core.Billing.Enums.PlanType");
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
Console.WriteLine("Could not find type!");
|
||||
return;
|
||||
}
|
||||
if (licenseTypeEnum == null)
|
||||
{
|
||||
Console.WriteLine("Could not find licenseTypeEnum!");
|
||||
return;
|
||||
}
|
||||
if (planTypeEnum == null)
|
||||
{
|
||||
Console.WriteLine("Could not find planTypeEnum!");
|
||||
return;
|
||||
}
|
||||
|
||||
Object license = Activator.CreateInstance(type);
|
||||
|
||||
MethodInfo computeHash = type.GetMethod("ComputeHash");
|
||||
if (computeHash == null)
|
||||
{
|
||||
Console.WriteLine("Could not find ComputeHash!");
|
||||
return;
|
||||
}
|
||||
|
||||
MethodInfo sign = type.GetMethod("Sign");
|
||||
if (sign == null)
|
||||
{
|
||||
Console.WriteLine("Could not find sign!");
|
||||
return;
|
||||
}
|
||||
|
||||
Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
|
||||
Set(type, license, "InstallationId", instalId);
|
||||
Set(type, license, "Id", Guid.NewGuid());
|
||||
Set(type, license, "Name", userName);
|
||||
Set(type, license, "BillingEmail", email);
|
||||
Set(type, license, "BusinessName", String.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName);
|
||||
Set(type, license, "Enabled", true);
|
||||
Set(type, license, "Plan", "Enterprise (Annually)");
|
||||
Set(type, license, "PlanType", Enum.Parse(planTypeEnum, "EnterpriseAnnually"));
|
||||
Set(type, license, "Seats", Int32.MaxValue);
|
||||
Set(type, license, "MaxCollections", Int16.MaxValue);
|
||||
Set(type, license, "UsePolicies", true);
|
||||
Set(type, license, "UseSso", true);
|
||||
Set(type, license, "UseKeyConnector", true);
|
||||
Set(type, license, "UseScim", true);
|
||||
Set(type, license, "UseGroups", true);
|
||||
Set(type, license, "UseEvents", true);
|
||||
Set(type, license, "UseDirectory", true);
|
||||
Set(type, license, "UseTotp", true);
|
||||
Set(type, license, "Use2fa", true);
|
||||
Set(type, license, "UseApi", true);
|
||||
Set(type, license, "UseResetPassword", true);
|
||||
Set(type, license, "MaxStorageGb", storage == 0 ? Int16.MaxValue : storage);
|
||||
Set(type, license, "SelfHost", true);
|
||||
Set(type, license, "UsersGetPremium", true);
|
||||
Set(type, license, "UseCustomPermissions", true);
|
||||
Set(type, license, "Version", 16);
|
||||
Set(type, license, "Issued", DateTime.UtcNow);
|
||||
Set(type, license, "Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
|
||||
Set(type, license, "Expires", DateTime.UtcNow.AddYears(100));
|
||||
Set(type, license, "ExpirationWithoutGracePeriod", DateTime.UtcNow.AddYears(100));
|
||||
Set(type, license, "UsePasswordManager", true);
|
||||
Set(type, license, "UseSecretsManager", true);
|
||||
Set(type, license, "SmSeats", Int32.MaxValue);
|
||||
Set(type, license, "SmServiceAccounts", Int32.MaxValue);
|
||||
Set(type, license, "UseRiskInsights", true);
|
||||
Set(type, license, "LimitCollectionCreationDeletion", true);
|
||||
Set(type, license, "AllowAdminAccessToAllCollectionItems", true);
|
||||
Set(type, license, "Trial", false);
|
||||
Set(type, license, "LicenseType", Enum.Parse(licenseTypeEnum, "Organization"));
|
||||
Set(type, license, "UseOrganizationDomains", true);
|
||||
Set(type, license, "UseAdminSponsoredFamilies", true);
|
||||
Set(type, license, "Hash", Convert.ToBase64String((Byte[])computeHash.Invoke(license, [])!));
|
||||
Set(type, license, "Signature", Convert.ToBase64String((Byte[])sign.Invoke(license, [cert])!));
|
||||
|
||||
Console.WriteLine(JsonSerializer.Serialize(license, JsonOptions));
|
||||
}
|
||||
private static void Set(Type type, Object license, String name, Object value)
|
||||
{
|
||||
type.GetProperty(name)?.SetValue(license, value);
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,4 @@
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
|
||||
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user