11using System . Diagnostics ;
22using EventStore . Plugins . Diagnostics ;
3+ using EventStore . Plugins . Licensing ;
34using Microsoft . AspNetCore . Builder ;
45using Microsoft . Extensions . Configuration ;
56using Microsoft . Extensions . DependencyInjection ;
67using Microsoft . Extensions . Logging ;
78using static System . StringComparison ;
89using static EventStore . Plugins . Diagnostics . PluginDiagnosticsDataCollectionMode ;
9- using License = EventStore . Plugins . Licensing . License ;
1010
1111namespace EventStore . Plugins ;
1212
1313public record PluginOptions {
1414 public string ? Name { get ; init ; }
1515 public string ? Version { get ; init ; }
1616 public string ? LicensePublicKey { get ; init ; }
17+ public string [ ] ? RequiredEntitlements { get ; init ; }
1718 public string ? DiagnosticsName { get ; init ; }
1819 public KeyValuePair < string , object ? > [ ] DiagnosticsTags { get ; init ; } = [ ] ;
1920}
@@ -24,8 +25,10 @@ protected Plugin(
2425 string ? name = null ,
2526 string ? version = null ,
2627 string ? licensePublicKey = null ,
28+ string [ ] ? requiredEntitlements = null ,
2729 string ? diagnosticsName = null ,
2830 params KeyValuePair < string , object ? > [ ] diagnosticsTags ) {
31+
2932 var pluginType = GetType ( ) ;
3033
3134 Name = name ?? pluginType . Name
@@ -38,6 +41,7 @@ protected Plugin(
3841 Version = GetPluginVersion ( version , pluginType ) ;
3942
4043 LicensePublicKey = licensePublicKey ;
44+ RequiredEntitlements = requiredEntitlements ;
4145
4246 DiagnosticsName = diagnosticsName ?? Name ;
4347 DiagnosticsTags = diagnosticsTags ;
@@ -65,11 +69,14 @@ protected Plugin(PluginOptions options) : this(
6569 options . Name ,
6670 options . Version ,
6771 options . LicensePublicKey ,
72+ options . RequiredEntitlements ,
6873 options . DiagnosticsName ,
6974 options . DiagnosticsTags ) { }
7075
7176 public string ? LicensePublicKey { get ; }
7277
78+ public string [ ] ? RequiredEntitlements { get ; }
79+
7380 DiagnosticListener DiagnosticListener { get ; }
7481
7582 ( bool Enabled , string EnableInstructions ) IsEnabledResult { get ; set ; }
@@ -127,21 +134,45 @@ void IPlugableComponent.ConfigureApplication(IApplicationBuilder app, IConfigura
127134 return ;
128135 }
129136
130- // if the plugin is enabled, but the license is invalid, throw an exception and effectivly disable the plugin
131- var license = app . ApplicationServices . GetService < License > ( ) ;
132- if ( Enabled && LicensePublicKey is not null && ( license is null || ! license . IsValid ( LicensePublicKey ) ) ) {
133- var ex = new PluginLicenseException ( Name ) ;
134-
135- IsEnabledResult = ( false , ex . Message ) ;
136-
137- PublishDiagnosticsData ( new ( ) { [ "enabled" ] = Enabled } , Partial ) ;
138-
139- logger . LogInformation (
140- "{PluginName} {Version} plugin disabled. {EnableInstructions}" ,
141- Name , Version , IsEnabledResult . EnableInstructions
142- ) ;
143-
144- throw ex ;
137+ if ( Enabled && LicensePublicKey is not null ) {
138+ // the plugin is enabled and requires a license
139+ // the EULA prevents tampering with the license mechanism. we make the license mechanism
140+ // robust enough that circumventing it requires intentional tampering.
141+ var licenseService = app . ApplicationServices . GetRequiredService < ILicenseService > ( ) ;
142+
143+ // authenticate the license service itself so that we can trust it to
144+ // 1. send us any licences at all
145+ // 2. respect our decision to reject licences
146+ Task . Run ( async ( ) => {
147+ var authentic = await licenseService . SelfLicense . ValidateAsync ( LicensePublicKey ) ;
148+ if ( ! authentic ) {
149+ // this should never happen, but could if we end up with some unknown LicenseService.
150+ logger . LogCritical ( "LicenseService could not be authenticated" ) ;
151+ Environment . Exit ( 11 ) ;
152+ }
153+ } ) ;
154+
155+ // authenticate the licenses that the license service sends us
156+ licenseService . Licenses . Subscribe (
157+ onNext : async license => {
158+ if ( await license . ValidateAsync ( LicensePublicKey ) ) {
159+ // got an authentic license. check required entitlements
160+ if ( license . HasEntitlement ( "ALL" ) )
161+ return ;
162+
163+ if ( ! license . HasEntitlements ( RequiredEntitlements ?? [ ] , out var missing ) ) {
164+ licenseService . RejectLicense ( new PluginLicenseEntitlementException ( Name , missing ) ) ;
165+ }
166+ } else {
167+ // this should never happen
168+ logger . LogCritical ( "ESDB License was not valid" ) ;
169+ licenseService . RejectLicense ( new PluginLicenseException ( Name , new Exception ( "ESDB License was not valid" ) ) ) ;
170+ Environment . Exit ( 12 ) ;
171+ }
172+ } ,
173+ onError : ex => {
174+ licenseService . RejectLicense ( new PluginLicenseException ( Name , ex ) ) ;
175+ } ) ;
145176 }
146177
147178 // there is still a chance to disable the plugin when configuring the application
@@ -213,4 +244,4 @@ protected internal void PublishDiagnosticsEvent<T>(T pluginEvent) =>
213244
214245 /// <inheritdoc />
215246 public void Dispose ( ) => DiagnosticListener . Dispose ( ) ;
216- }
247+ }
0 commit comments