はじめに
プリザンターには OSS 版と商用ライセンス版があり、一部の機能は上位のライセンスを当てないと動きません。設定ファイルで有効にしたつもりなのに機能が効かない、というときは、たいていライセンス判定に引っかかっています。
この判定がどこでどう行われているのかは、実はソースコードから追いかけられます。しかも 1.5.7.0 でオプションが 3 つ増え、判定の書き方に例外的なパターンも登場しました。
この記事では、プリザンターのライセンス判定の実装を読み解き、1.5.7.0 で何が変わったのかを整理します。
バージョン 1.5.7.0 を対象にしています
全体の構造
ライセンス判定は Implem.ParameterAccessor の Parameters クラスに集約されています。
登場人物は 3 つです。
| 要素 | 実体 | 役割 |
|---|---|---|
License |
外部アセンブリのクラス | 商用ライセンスの内容(期限・ユーザー数・オプション) |
TrialLicense |
OSS 側のクラス | トライアルライセンス |
LicenseOptions |
OSS 側の enum | 機能ごとのフラグ |
License はリフレクション越しに読まれている
Parameters.License の型はソースツリーに含まれていません。
public static License.License License = new License.License();
Implem.ParameterAccessor のプロジェクトを見ても License.License の定義は見当たりません。外部アセンブリとして供給される想定になっているためです。
その結果、プロパティの読み取りがすべてリフレクション経由になっています。
private static LicenseOptions GetLicenseOptions()
{
if (TrialLicense?.Check() == true)
{
return LicenseOptions.Trial;
}
var prop = License.GetType().GetProperty("Options");
return (LicenseOptions)(int?)(prop?.GetValue(License) ?? 0);
}
GetProperty("Options") が null を返した場合は 0、つまり LicenseOptions.None として扱われます。OSS 版のダミー実装で Options プロパティを持っていなくても、例外にならず「オプションなし」に落ちるという作りです。
同じパターンは他の項目でも使われています。
public static string Copyright()
{
var prop = License.GetType().GetProperty("Copyright");
return (string)(prop?.GetValue(License))
?? $"Copyright © Implem Inc. 2014 - {DateTime.Now.Year}";
}
public static bool DisableAds()
{
var prop = License.GetType().GetProperty("DisableAds");
return (bool?)(prop?.GetValue(License)) ?? false;
}
Copyright / CopyrightUrl / DisableAds / Environment / GracePeriodDays / Options の 6 つがリフレクションで読まれています。いずれも「プロパティがなければ既定値」という書き方です。
LicenseOptions:機能ごとのフラグ
オプションはビットフラグの enum です。
[Flags]
public enum LicenseOptions
{
None = 0,
AdvancedCache = 1 << 0,
MultiTenants = 1 << 1,
Queue = 1 << 2,
RateLimit = 1 << 3,
SamlExtendedAttributes = 1 << 4,
Scim = 1 << 5,
BlockSiteTaskWhileRunning = 1 << 6,
Trial = AdvancedCache
| Queue
| RateLimit
| SamlExtendedAttributes
| Scim
| BlockSiteTaskWhileRunning
}
各フラグが対応する機能は次のとおりです。
| フラグ | 値 | 対応する機能 |
|---|---|---|
AdvancedCache |
1 | サイト設定キャッシュ(Sites.EnableSsCache) |
MultiTenants |
2 | マルチテナント(テナント管理 API・保護テナント以外の認証) |
Queue |
4 | バックグラウンドジョブ(エクスポート・インポート) |
RateLimit |
8 | レートリミット |
SamlExtendedAttributes |
16 | SAML の拡張属性 |
Scim |
32 | SCIM |
BlockSiteTaskWhileRunning |
64 | テーブル単位の排他制御(TableExclusive) |
1.5.7.0 での変更点
1.5.6.0 と比べると、フラグが 3 つ増えています。
AdvancedCache = 1 << 0,
MultiTenants = 1 << 1,
Queue = 1 << 2,
RateLimit = 1 << 3,
+ SamlExtendedAttributes = 1 << 4,
+ Scim = 1 << 5,
+ BlockSiteTaskWhileRunning = 1 << 6,
- Trial = AdvancedCache | Queue | RateLimit
+ Trial = AdvancedCache
+ | Queue
+ | RateLimit
+ | SamlExtendedAttributes
+ | Scim
+ | BlockSiteTaskWhileRunning
追加された 3 つはすべて Trial にも含められています。トライアルライセンスで試せる範囲が広がったということです。
一方で MultiTenants は 1.5.6.0 のときと同様、Trial に含まれていません。マルチテナントだけはトライアルの対象外という扱いが続いています。
Scim については、1.5.7.0 時点でフラグと HasScim() は用意されているものの、参照している箇所がまだありません。将来の機能追加に向けた準備と見るのが自然です。
Allow* と Has* の関係
判定メソッドは 2 層に分かれています。
-
Has*()… オプションフラグが立っているかだけを見る -
Allow*()… ライセンスの有効性とフラグを組み合わせて、実際に使えるかを返す
利用側が呼ぶのは基本的に Allow*() のほうです。
public static bool AllowQueue()
{
return TrialLicense?.Check() ?? License.Check() && HasQueue();
}
public static bool AllowSsCache()
{
return TrialLicense?.Check() ?? License.Check() && HasAdvancedCache();
}
public static bool AllowRateLimit()
{
return TrialLicense?.Check() ?? License.Check() && HasRateLimit();
}
public static bool AllowBlockSiteTaskWhileRunning()
{
return TrialLicense?.Check() ?? License.Check() && HasBlockSiteTaskWhileRunning();
}
public static bool AllowMultiTenants()
{
return License.Check() && HasMultiTenants();
}
演算子の優先順位に注意
?? と && が並んでいるので、どちらが先に評価されるか迷うところです。C# では && のほうが ?? より優先順位が高いため、次のように解釈されます。
TrialLicense?.Check() ?? (License.Check() && HasQueue())
つまり動きはこうなります。
TrialLicense |
評価 | 結果 |
|---|---|---|
null |
右辺を評価 | 商用ライセンスが有効かつフラグが立っていれば true
|
| 期限内 |
true を返す |
フラグを見ずに true |
| 期限切れ |
false を返す |
商用ライセンスを見ずに false |
注目したいのは 3 行目です。TrialLicense が設定されていて期限が切れている場合、?? の左辺は false(null ではない)なので右辺は評価されません。
期限切れのトライアルライセンスが残っていると、有効な商用ライセンスがあってもオプション機能が無効になります。 トライアルから商用へ切り替える際は、トライアルライセンスのファイルを確実に取り除いてください。
GetLicenseOptions() の側も同じ考え方です。トライアルが有効なら LicenseOptions.Trial を返して商用ライセンスの Options を一切見ません。
AllowMultiTenants だけ形が違う
前掲のコードを見ると、AllowMultiTenants() だけ TrialLicense?.Check() ?? ... の形をとっていません。
public static bool AllowMultiTenants()
{
return License.Check() && HasMultiTenants();
}
LicenseOptions.Trial に MultiTenants が含まれていないので、仮に TrialLicense?.Check() ?? ... の形にしても HasMultiTenants() は false になります。とはいえ、トライアル期間中は TrialLicense.Check() が true を返してそのまま通ってしまうため、意図的に形を変えているのだと読めます。
結果として、マルチテナント機能は商用ライセンスに MultiTenants オプションが含まれている場合にのみ利用できます。
1.5.7.0 で追加された判定メソッド
Parameters クラスに次のメソッドが増えました。
+ public static bool AllowBlockSiteTaskWhileRunning()
+ {
+ return TrialLicense?.Check() ?? License.Check() && HasBlockSiteTaskWhileRunning();
+ }
+
+ public static bool AllowMultiTenants()
+ {
+ return License.Check() && HasMultiTenants();
+ }
+ public static bool HasBlockSiteTaskWhileRunning()
+ public static bool HasSamlExtendedAttributes()
+ public static bool HasScim()
このうち運用に影響が出やすいのは AllowBlockSiteTaskWhileRunning() です。
TableExclusive にライセンス判定が入った
テーブル単位の排他制御(TableExclusive)は、General.json の BlockSiteTaskWhileRunning で有効化する機能です。BulkUpsert などの一括処理で、同じサイトへの同時実行を防ぐために使われます。
1.5.7.0 で、この有効条件にライセンス判定が追加されました。
public TableExclusive(Context context, long? siteId = null, [CallerMemberName] string callerMethodName = "")
: base(
context: context,
- enabled: Parameters.General.BlockSiteTaskWhileRunning == true,
+ enabled: Parameters.General.BlockSiteTaskWhileRunning == true
+ && Parameters.AllowBlockSiteTaskWhileRunning(),
key: $"TableExclusive_SiteId={siteId ?? context.SiteId}",
siteId: siteId,
comment: callerMethodName)
General.json で BlockSiteTaskWhileRunning を true にしていても、対象ライセンスがない環境では排他制御が効きません。しかも enabled が false になるだけでエラーにはならないため、設定したつもりで効いていないという状態に気づきにくい点に注意が必要です。
マルチテナントが認証にも効くようになった
AllowMultiTenants() は API のガードだけでなく、Context の認証処理からも呼ばれます。
if (!Parameters.MultiTenant.IsProtectedTenant(TenantId) && !Parameters.AllowMultiTenants())
{
Authenticated = false;
}
保護テナント(MultiTenant.json の DefaultTenantId、既定は 1)以外は、マルチテナントライセンスがないと認証が通りません。
ライセンスなしの環境で 2 つ目以降のテナントを運用していた場合、1.5.7.0 へバージョンアップした時点でそのテナントのユーザーがログインできなくなります。該当する環境では、バージョンアップ前に Tenants テーブルの状況を確認しておいてください。
ライセンス種別の判定
管理画面などに表示するライセンス種別は GetLicenseType() が返します。
public static int GetLicenseType()
{
if (License.Deadline == DateTime.MinValue
&& (License.Licensee == null || License.Licensee == "")
&& License.Users == 0)
{
if (TrialLicense == null) return 0x02;
return TrialLicense.Check() ? 0x08 : 0x09;
}
return License.Check() ? 0x04 : 0x05;
}
商用ライセンスの中身が空(期限が DateTime.MinValue、ライセンシー未設定、ユーザー数 0)かどうかで大きく分岐しています。
| 戻り値 | 状態 |
|---|---|
0x02 |
商用ライセンスなし・トライアルもなし(OSS 版) |
0x08 |
トライアルライセンスが有効 |
0x09 |
トライアルライセンスが期限切れ |
0x04 |
商用ライセンスが有効 |
0x05 |
商用ライセンスが期限切れ |
猶予期間
商用ライセンスの期限切れには猶予期間があります。
public static bool GracePeriod()
{
if (TrialLicense != null && TrialLicense.Check()) return false;
var prop = License.GetType().GetProperty("GracePeriodDays");
return License.Deadline < DateTime.Now
&& License.Deadline.AddDays((int)(prop?.GetValue(License) ?? 0)) >= DateTime.Now;
}
GracePeriodDays もリフレクションで読まれ、プロパティがなければ 0 日、つまり猶予なしとして扱われます。期限切れ後この日数以内であれば GracePeriod() が true を返し、警告表示をしつつ利用を継続できる作りです。
トライアルライセンスの中身
トライアルライセンスは OSS 側にクラスがあります。
[Serializable()]
public class TrialLicense
{
public bool Check() => Deadline >= DateTime.Now.Date;
public int? Users { get; set; }
public DateTime Deadline { get; set; }
public string Licensee { get; set; }
public DateTime CreatedTime { get; set; }
public string CheckSum { get; set; }
}
Check() は期限の比較だけで、オプションフラグは持ちません。持っていない代わりに GetLicenseOptions() が LicenseOptions.Trial を返す、という役割分担になっています。
CheckSum を持っているとおり改ざん検知の仕組みはありますが、検証は Implem.Pleasanter/Libraries/TrialLicenses/TrialLicenseUtilities.cs 側で行われます。
機能とライセンスの対応まとめ
現時点でライセンス判定が入っている機能を整理します。
| 機能 | 判定メソッド | 必要なオプション | トライアル |
|---|---|---|---|
| サイト設定キャッシュ | AllowSsCache() |
AdvancedCache |
可 |
| バックグラウンドジョブ | AllowQueue() |
Queue |
可 |
| レートリミット | AllowRateLimit() |
RateLimit |
可 |
| テーブル排他制御 | AllowBlockSiteTaskWhileRunning() |
BlockSiteTaskWhileRunning |
可 |
| SAML 拡張属性 | HasSamlExtendedAttributes() |
SamlExtendedAttributes |
可 |
| マルチテナント | AllowMultiTenants() |
MultiTenants |
不可 |
| SCIM | HasScim() |
Scim |
可(未使用) |
SAML 拡張属性だけ Allow*() ではなく Has*() が直接呼ばれています。
if (!Parameters.HasSamlExtendedAttributes())
Has*() は GetLicenseOptions() を経由するため、トライアルが有効なら LicenseOptions.Trial 側で判定されます。ただし License.Check() を通らないので、期限切れの商用ライセンスでもフラグが立っていれば通ってしまうという違いがあります。意図的なのか実装の揺れなのかは、コードだけでは判断がつきません。
まとめ
プリザンターのライセンス判定の実装を見てきました。
- ライセンス判定は
Implem.ParameterAccessorのParametersクラスに集約されている - 商用ライセンスの実体は外部アセンブリで、プロパティはすべてリフレクション経由で読まれる。プロパティがなければ既定値に落ちるため、OSS 版でも例外にならない
-
LicenseOptionsはビットフラグで、1.5.7.0 でSamlExtendedAttributes・Scim・BlockSiteTaskWhileRunningの 3 つが追加された - 追加された 3 つは
Trialにも含まれ、トライアルで試せる範囲が広がった -
Allow*()はTrialLicense?.Check() ?? License.Check() && Has*()の形。期限切れのトライアルが残っていると商用ライセンスが評価されない -
AllowMultiTenants()だけトライアルのフォールバックがなく、商用ライセンス必須 - 1.5.7.0 では
TableExclusiveにライセンス判定が入り、マルチテナントは認証そのものにも効くようになった
設定したはずの機能が動かないときは、まず Allow*() に相当する判定を疑ってみてください。エラーにならず静かに無効化される作りのものが多いため、設定値だけを見ていると原因にたどり着きにくくなっています。