From 929ae02fd0b3d55c7066445e043aa0031c29708c Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 09:09:08 +0200 Subject: [PATCH 01/44] Add .NET SDK build infrastructure and untrack IDE files Introduce global.json, nuget.config, Directory.Build.props and Directory.Packages.props in preparation for the migration to .NET 10, and stop tracking IDE state (.idea, .vs, *.user) and the checked-in nuget.exe, which `dotnet restore` replaces. Co-Authored-By: Claude Opus 5 --- .gitignore | 3 + .../.idea/.gitignore | 13 - .../.idea/encodings.xml | 4 - .../.idea/indexLayout.xml | 8 - .../.idea/vcs.xml | 6 - .idea/config/applicationhost.config | 972 --------------- .nuget/nuget.exe | Bin 6661528 -> 0 bytes Directory.Build.props | 15 + Directory.Packages.props | 32 + PostSharp.LicenseServer.sln.DotSettings.user | 3 - global.json | 6 + nuget.config | 7 + .../.vs/config/applicationhost.config | 1038 ----------------- .../ParsedLicenseManager.cs | 76 +- .../PostSharp.LicenseServer.csproj.user | 47 - 15 files changed, 101 insertions(+), 2129 deletions(-) delete mode 100644 .idea/.idea.PostSharp.LicenseServer/.idea/.gitignore delete mode 100644 .idea/.idea.PostSharp.LicenseServer/.idea/encodings.xml delete mode 100644 .idea/.idea.PostSharp.LicenseServer/.idea/indexLayout.xml delete mode 100644 .idea/.idea.PostSharp.LicenseServer/.idea/vcs.xml delete mode 100644 .idea/config/applicationhost.config delete mode 100644 .nuget/nuget.exe create mode 100644 Directory.Build.props create mode 100644 Directory.Packages.props delete mode 100644 PostSharp.LicenseServer.sln.DotSettings.user create mode 100644 global.json create mode 100644 nuget.config delete mode 100644 src/PostSharp.LicenseServer/.vs/config/applicationhost.config delete mode 100644 src/PostSharp.LicenseServer/PostSharp.LicenseServer.csproj.user diff --git a/.gitignore b/.gitignore index 465c9f1..4e7ff8f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,8 @@ bin obj /.build **/.vs +.idea/ *.slnLaunch.user *.csproj.user +*.DotSettings.user +/artifacts diff --git a/.idea/.idea.PostSharp.LicenseServer/.idea/.gitignore b/.idea/.idea.PostSharp.LicenseServer/.idea/.gitignore deleted file mode 100644 index b1620ce..0000000 --- a/.idea/.idea.PostSharp.LicenseServer/.idea/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Rider ignored files -/contentModel.xml -/projectSettingsUpdater.xml -/.idea.PostSharp.LicenseServer.iml -/modules.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.idea.PostSharp.LicenseServer/.idea/encodings.xml b/.idea/.idea.PostSharp.LicenseServer/.idea/encodings.xml deleted file mode 100644 index df87cf9..0000000 --- a/.idea/.idea.PostSharp.LicenseServer/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/.idea.PostSharp.LicenseServer/.idea/indexLayout.xml b/.idea/.idea.PostSharp.LicenseServer/.idea/indexLayout.xml deleted file mode 100644 index 7b08163..0000000 --- a/.idea/.idea.PostSharp.LicenseServer/.idea/indexLayout.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/.idea.PostSharp.LicenseServer/.idea/vcs.xml b/.idea/.idea.PostSharp.LicenseServer/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/.idea.PostSharp.LicenseServer/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/config/applicationhost.config b/.idea/config/applicationhost.config deleted file mode 100644 index 1a9997f..0000000 --- a/.idea/config/applicationhost.config +++ /dev/null @@ -1,972 +0,0 @@ - - - - - - -
-
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- -
-
-
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- - -
-
-
-
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.nuget/nuget.exe b/.nuget/nuget.exe deleted file mode 100644 index 8f393dd3cfb858514d4403e39cb1bf29f2fdc41a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6661528 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 z%0A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

>HJWA-MMu_ew7_mrddg_AN|hl&RRiU{5( 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�`nSSKKk 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≦h9HiT@Ug)DqD4eFl&ztwNS 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
sFs@&EJ!(=h@L;+WlBWT|tk_&7G_# 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 zXx834≫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
-2s`!wjt$Nu?N6l|(2uH&}*~ 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

0%7i_E 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∾̳)+M~4;)UiQv@V2KTxS;% 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

a*;czy4z!D&I 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∨tEnxl?g#JD)^>-&FmSoS{$foXm*nb+wTX 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

=#KBsLLCphizA3)CIkfS_lyo;49A>)LX7*3Yn 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О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⋁1Sl5}TOOUz(@xVF!o(rhY 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%0DHtw?KeH==7K&iv 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 z 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%0D*Vw`*k8Tq^^ZI>e%~|RKIVoq_IP{J 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%0DH?DV&OFB;~5qtr$brXU|jp1n4UL#5t<6M*G zoGtYO*X_2G`ig{^Wi}SSF`jM;Ej+|7hQRihT}h`j3h;Qi8ieT-+IjMTA#C?t$}&Ge zNC_33AFNtm}GQB%0T%M`Jq!R5`0Q1M9vi! zY)@EXk=?5*6Fin?fu+BPMRr2KNGgK*){)fVIoT643MS`AV|Fv_&vk{au!ACI8_d2_ z>>+}uZ<18FNi!*$Uyu`xMQ}j@&rymySNEJa!|0%iWCg7PdA(POm6Yg?5?bpa=5 zRrOsz*r%6pm5MA^X#`vTf0wOox2C9_c@)y(1~p-w)RZ*TWaL3-T;yGe!*1$sZ;JEe z6LGq?XayY{TT9N_L)<;#@b6oyGV&CL|g1usF-xXf}Qd-9Zy*{SR;eMs9UL} zs^B`W5<=4+l_{} zz0P!DE-z_fJ-u}q>9x_+Q%wQ^93_gE5l~`q;zD`2UAT$Ghp3bCcAQ*GoqFZT5m#Eg z>Z5a>{2|BaD)wvfv4^E24(dcbAMeUVl^ui;^k3-=wK_bh=BZS&fEvU%?wT zQQNYt`S_DNd`b*ApVIIM1@W+enETVPXa*h{&^j_9mYJ8In=__$#Cclh?d|zgmbA95 zBRyfz;D2tP7@f=~6Ss~Goje8)9lawuWklZi=)~K{+;U4shs+Kg+ueF|`)J!vx84w3 zOg`L0tv)MQf zwEg*;Gd{DvH!FJNkF!3Uytwq}JH>N`_0M=ZXYfDg-f^PqydS)F*SwV@kIld4yx%Tp zJu7w5YfToF?f>Y)B?qe_OJ4YE^YZb7SC{8^d}nF9SL>G+9DQQhl~;eTERucR@{iB! zxI8|gf5qkjKV0*|U6Hl#O(W&-}d_lw;bR6;+A2n`foj}?&+<&UtPTQ z`Pb67&Fmi8{>i3&JAXU-(>!K}0Yc<`Z_3!i)P zlF^60nfmPEQQu5In)$)IKUwhevwnKjjqg_{~lKs{T#J%m;pZ^ZHh=ztQ29*LPK2@y1z?-u6bHDWAV_ z%cS(*mA$$Ach&b?_4~=)p7_H*?tkeI6OP>X*7C8Bywhszrg!om`OUk{=MQ-A^S^)o zUbp|b^8MQv-TtR;rxt$jQTCz__Vu)n&lxeg=1*M?)jWIU*xKel+Fg6>NZf!BOdOz5?Yu&LwbzRW=>~6=_FYNZxmW#UgU*5LIlW(-? zwg1ZUKL7grOZ}$SUf%x?H?IrQG=Ti?0)=L_zfvHN#--ZyyZu$uet|3BP)37A|} znfARPvdAJL`*wjqLegCcSs*qsq?67{l5VmP3_`4~y6LWTbyZWhDxGe2Ms##UR*{IK zAS!|~iijfMMiL!SaoiOb98_?Df!_r|2l?OkeD~aQ&aI_85&y^McLqr6p7WjWeEV|l zJ*yX0`rk15?FXLwr8kUhxaRKLH*CM{t_{Cfzw%9^`}`#Pzzbgp56>Og_sm@@`d(U| z>;KJJm-a9D;g$VYUUF;y={pt|Zn`l)5PoBB@ww+04ZiuHca{BDzqfqJW7iI?+j`~& zKii(Y;H7E5yWorGey{SwM+%!Kp6(6bS2$$L4Qp>2x$d|_E)3tf@xs%8GGUq24`M*#NdRL^@9Fcu%=c}L-9#LHj?x8g?M&zy+6>oWLW|Dm3@|6ZQA`*EK4 z)npE-t$_|+kqQgp9iel zv9BkA(-&Uld8gyIuK?bQr+MD*CVSq8j|JQlJnus6_YmOwDZo|#JKppDgv*t$!Pu#Q zaU{k(2mJTOxOLd$M}gNQ>^lIQ3~=}%{=PrfybsT39EdyEF#ZfYKLPOW#-4xB=6PR# zHJ)Q%i$K2}SZnFNp7+~%c#fNfr(>r2A=oAk)GFy{XYVD`+)B62fPWm`0)MH zJZ}%o`6GBX06KmUygd(lJsj(7#k?P2ACn*pw}aMeFsAz?&->~tJTH&EZH6p-!v}7l z%Q5poH;n%|#=QWTXJNg<0iO5ZM9;eiw;7)U8gIw%`+z?m0F1wZ2h;F;!9vgbE^zr3 z_WMiVH3ehe1=@TcuxuU-##aNMHqh-x?D;g%<0;VmXpEVK z{eBqxx)Xf42)vyF`b|5|^N#NVeSzP-z~{S*Ad7oq8Q?krvV9-;b_VG1Q^4CF@QRR` zPXqU#Vf^`fdEO@vz;nQQ9H>159J(RrFJK>!g10j;b{M}sh_w#_O<%x1_XGX>-97JM z(BUf3{xHycB53y^tXBa|KM8sJE#O@PdHNdmx&b&$1I)7l=Lyi`FZlC2K%XChzrEPc zcI@@N7*mE!{Ya(d%RqUe&c>V_Tz7YF*6tw&k@ckfY_gl#5V_5qs%$YU; z^nm`|fWP00-!BCYF9P1gvmnEA7oom~SB+rb-l?6qU87IkG$OZO^49JW{*N~y z&&S;xy!MDUTXkdHLXY>oR&fCh(D&w>tGM+wI`(ZO_9fi%y#9K&0x?Lh$QzXGdy2Z% zZ6mw|#9TBQd3RxI4{rjK+r6?t(cPVUsR%E$<|SQyn8E97Icq8YEtj71{;Lo-qj(Ux zuCcG_c5gD$H^JH0xRn8D$*u!(Z(*Zh_&b2PH`HZz8`l>P zP$YSE&_?`|!-fbbf>gBv$UDQhagoGd1L(~00F{gH%q2tri;45cGjTPrA^I*KK#f_nuW9=dhGpzww%Ma@ktZu48NGl@&C7fH z!U@l7HpQT8-}xukGTR8x0gy{vAmg=}k*6sn60{M!<&I0>5Dhe~l>3{$+N=}!%Vmfh z_g0{A{;s3o8uG?VY}x}cpu5Cano1nG8{R-T0@tQ*kv~oOJ&j3_P2FBW$rjWTec^0AxM?`tU8r^yx?wyWaB84|%*aUay?y%($%m~A; z#lR)w88{vb^<4m))Cw5IOkYXL+-2E#2amn_LV#e-41t8w0d2F;4sN*CEt&$Z7CsJk1X9csDf_h?jfPK#X{0fa|ca zAi6m`WY{P-AIt5a^wv3VAohY<_V35^Q`6Ijl!?}ei@53wea!`Pdm#P~(o;7BK9wa8 z`Cfx~W*G*pZ$79o<++88YJZ}X;+7c7a$EZP)DVAXPUL7kry3mOR>NN4!#Gr5IoJ*0 z=@2~fPD6@ON60iN;zOrF+2Eb8Tu!L90n^9Cr1^AWsxK=25J1guMKe;NIjCi@7X)?; za-vnu!PH-bI+D7&QgEX2sbJgOa224h1=yL*!AdYOkc6^^LchdB$E@;^A+UQ9tShaW zk7e(zNJd)#q3z&hx4c2o1tDHqNrI(KF=`Lqf+mQ*J9V}RXN`NdVFPRX`1i>l+z@H(_D}40y3|BU9I$Dq8`_2|I{E z6`M)lZ`=H4~3V+8dc?Kt#UR9>7ISPznnM5J3QG;z?Tm$?@SM|0DgBHuy)J;&z5 z&H}Y{B}xH*4#0bx1Lvi-y!4v!CS$n`C{^%p64TMoR8zJjQWiJFzwIdXUX*d#nlGc- zQGnpl+TtQ!)OZ{t?QZ&PU?bkgSbLih=VQ7fa$&l;;F(?zB}9G#b@{vJl#C^EO(27c7aZrTS_Bb4=fOfC&jrSo#Ejob6KFjxTg- zPnFLp(bLgJy}OWmvnjl?iy5dDI&MqwlgngTz_PQi=4GxTz&JG1v6rzZ>10ulsJBA* z0+_=$4y8wv?26me4vXTo0>AUd=i^b%a}0xyInr0QuU3hyg!SCg5`eBEB8a@RPziNJ z&2-gf}f@j8Z-+_jy$ zQeN!jb{Pu9b6jjHK$IlpR89G2iCyGv0RYGIxNBL+ba>0Lp8$Ag}Pg;R+ z<<*W%CZoLWgm=3JW`n9Yv;0G@BC`NkM`O}pHJM#uyA#u#Qj1Juct;DW32ZCDS21Op zEn1v1fPcid7~*v*eK2B-__hmyHa$+I$?^Lz8uoB`#9RF|=u%=nD5r87_`4Ul-ZXHrRrF3x>}(E;k*2 zriW`gPViML8F&<6PU2vbBe~Xq&%~rWkD;^2H&m~tcOQ9^1N2-SMoP=-Ey`n@hTQaIc9asin8BXX^O#lvY z%@*^5A!VPs6d{3uJB3{?qGft^f-JK$mJTEM*H73}7GQ<5VRJU%vAEqn5R6$z=o#BP z`NY@+5U~YHtyoDEr4w^7q!413N_S8DKjJ-%X-ZTYO>-5hIgmdA$WfXo+Vl`U( zNzPzhC>zGZ3ZzTqbqJ_r!M3>=oajbM?ETO#F-ejM^^}@>vW47OgUOW|09L(~n3f2; zJ52<_mET?rU!(WJ{}OAxQMYa>B_@)GS|Vj9JI-^TkJ@;o0oU7{gC<*Or-yfAZ&j$- zM(|Jart2~MB#kz^_dG5m`*TYskvC}yQd={WCXT5j4TJEGI;jtz2?hgk2g#d}Eu1x;s$YIcYYS&{VT-8>aT^UALY}Ln%I7i@h5d zx2gjE7|Wb9o@HD~bP*C8YSLdmWejjQj)^5ybEml5cSFO~n5H5EYIXfIDhV~cic$>i zbU)l1b&!=7oBfiOQZFwAz&VXKDS|B648_x>!fc}GE#BV%VAbCPV1r6%xJ{FAMc&j* z90=BLbbNSiP(if~vIvkWTB!%g`YgSYYpQkCwh;;b6~Lx7-Zl^9G;TsZgh>{y?9!uK z_0f4E+l?a#d1#h+j;spRO?!&Sdl6tQ9@=2XN2`TXo8mP-0F8MV0jvtZ`mo92ayR?6<6>~mh{GTPI4)Rc4#?nJ(MC^ zg=A9> zrW3E(n2<0aJ;9{YT9ar>GN&wQYAR%W*u8T_-%%+Ld0f5vA|O z#3Yc=6Ll4`IfD*6%|TMM*_~F;rn50gRkdi|(zVw#k2Hsbo*Naz=iN_N}Xf*vs8g@Po!h19G#xt;$9F_rOHf6sLh)DZ}b}2tD zxqK3nr!|_~O4RnnD>QAKM9*1?TSPW3`5ug%o$7?@7QNY+Fh?e6V_8vg0o7Hy`WsYO z#`YPzN-P4%o+(1$9RPH##si)vab>IVIYiA0VLd@HP(gj&;WyqudW*8TX-UaJ=(Glg7N z3(eb%PXkk^{)W{ zMu%}-u3>??3+9*3#JuF#sO%XmRd_T9{J9HJOkz z@-oX55o=&2qYuO7qUz^rIpD#f3fL;PgnD2SuOWbdKs5qkzv1ix&_04#eF^{&+U^_x zZY%v#(=PxBgl!Cn6O1Ce*x1`SV;&4B3FyZRMrsgIybTM^-n9iI?-Q8ZHrC{B ztS>$A$a`cwtE~oOn3<(dJ^vPyCy%w7m3?6NTp@#W&sd~mWL5=q7MA%kq*PG(?hc&+ z^PMW5iSlHrLa72)s94Fp8RP4N?yJfQuYK?1zNB50fS| zp5$7tUt?gKsi(Q`3=m>^2cs*Xp=N@c=!*X^2UiH7-q=d{n z(`vYRrah!2d^)a|^$FDF{LcY+j+x98SS-)z64M4aV~V^GR(yxMxpm#p(sXgL-dm^3 zF=qnoh_?xor==(BA_MDS^d5q`NnEyJQb)5%l4A1M9}^V3AA?mWI3|HRss}qxPp4Zi z#e0qLXXeVy)(_4aCjcT|iaWo=h=4Z(mwyLORF6Gr6`K3$p)LblhEdk2-*WCM zf-NEQOaWl`)hGb$8bHQ(+=i|t><>`$b`6vd7g5;-A)8>8lOm>WZLL2!p}$jtBAYLb&~{ z!Em-xE$_HFR|rbAVll{;3PJA(ejlnu)sc)p|4enAdU+TRLbd;zj2n;XdXh4F_X zS752WkOXN92CCccsbqsqVN}KH!(k-?kkRZ=d9;)r1{#|({u>wIW5gpH3j^6|FiB!;bqa+wRHlxFVad!R&Xm996SBuE6dFM|Rv?JesX-%$CBwa*(Z6%av>fSISWTl$I%h8Y<_93K0cls4^Ok>T&*33mBi^YRKVi zezX9f14`q7Sb=j!0*ZGIst^{Hz-r|qX`$K~KVyL^=S$gQxZ{3+i3J&e`zqPN8a|fV zTPqav8Gq?|g*8+Pl1j}7%Zi~wIof_@Q0@h%YJCN2JoF)6HJj_N->v_1U-mnq^pLZO z@h}va%B)~+$__);Gr@XLbR;(lqlLeM;!uPt_T8%plT0aOg92AFmZn(i8DF(WWrtul)NVlG_@f zf6+>#LZgG_JWZ<5!RUUiG452dF>Ne_YPg)Qk=mnu&;@a9)sZ1egnuGehfyzvqqx1P z?AjZ>YQ~>oA?fP*rl1^^DX~haYf26_DdPK`)}&z=czB*_k=k6u=0{@H8G&e*qiy%} z+7R%gfSBDHgrN{Nqp_8lv#gmk``fR?!m({?fpc4*67h_q?GHgluOF^ zo5jGRN(jd}0%KUH4a&sn_C(8PU|Q3~)~&L@s30I84n*N#P!r>n3M59epf`N@jt8^( z5~(bk=by|HN(N^3Fl5q>x;zZl#Ex0KF>MVZA6PP4M!K@?SHfi4TSzP{qJeC&+*dQk ziUKO8%B(=aO~L?xiai80mKL0r5b~J>4De}jL^2`~Nhua(Ac}#5dM$hx+h5Q}@#dhMrz=nvx-slYbbYX>l~II7 z;mN^h2(nkoc^FFA?F8S9zYq)2!H9{XeMjUjGO zi&XSNXrsfGY>wVkk_{*=dF656;iBjW zl+3C@e-yEpHIWXn-e-c<)e&f}q%pOit6VKX$;#zo;`ZY-Ew*jI6cdotSxTjZ&CK*` zJ@DluXAwgmNtsk^TC#Ntv1?a(bhunBQ`B`*sKhTS)rJQ8z_B)pT%(hPaY(APIM<(?;F= zww^49R4a}R$4(MEUKXQRW@gm`f*|}(5aBqrbrl`Zq>9HFghiCS!@`#HIar?poXt?S zn9Euo1UmgDYp*JlP_ee&D1{lHZD*eZ(I$9?(bW!)dz4)%%V`HO zhCVRyy@jHR+bvSL8(Eg%r)Z|wWC;rxlP&g@D!U;;9mEDP>A$pQxGA4XoIkz1R@EZK~~2eG}x#c(47s}Qt1bh{&Eg7 zRz(JcS76tnt$#oywT%4TAE;uvVeLDT{7~%Fks@T6&OFl zs*a8fW%I}w6R#8dfv)m!saVb;3qGQ!S*D)xH?QMmuPQ8vMG9(VOqFIA_rRR4tV%u< z5!cfxYY;D^#93v3r2k!4N1_ zfr5y<3NVaW6vHuMNwZPC#KR=ChwEV|1@USNa;PsP4h6f>?nt5Q1YBw;*ENH*x6Jr! zt)+=6oRHLo=mZ!H1r@nXMFP^qTroB#yI9L}4ZSjjcE!co(Jle>PwSD*DGxkYi&*1R zSp#s~6e3r&31KR!8GrG*xK0vB76~Dhof>Z~HJkAVEGoH)Wh!rKRYqdqhsq;Ln$bvK z34S&yEx8u_W4rZ`S@kiDVl6~BLQ=d5lSe@Zl#CvfWLXtaVku~ovI%7`mQmO$%+w~c zR;lIs%Y)2vhXdHDUOXDjaz%v}(tvd%Qp#--oG~B^^DD}EC3#Y&TCsXQ%T$uIK$Hfu zP!}m81?8dKCMcP@tvVOBhnus-TDFSdEz4Su!Nx+E<^aWM6q2pXQ`uZ4MKO6$=~!A~ zhM6P{HZhH&soke^gN`YWxD{S$?W*O$k~M33R%iTaDWO@dt}ucKZidsZxDhA|?-{=a z=`b3nqfS(@QRmu<%AkoH#iWc_kAx}A#*9qsl9#eK6mTk=iWqe+hU=>{ew$PSN2D^D zuoq00_JCC*MBRO!j7G{c?)*GeQi|70L2z}-7?v5bURD)eSTkferwT7wiz}%mKDj$c zsfE<4h&u6$F*=R2lT5ITxphG$%EQ@6l_W((&jt$FA_DkYzEEyc3xz?oESYemLR~5V zej6E#gNE4j=vo63MmjywUTQQMsN@Rq!B%FOU?tRP$Ac_2(mEhK#c#^`Yqg989z;Rz zf@TSo$Wl}W-KcY+@=GfhjgopY5TX7$)mCBX7Q0G@abn7_5q3V{b+*4X#-4^m=*7+w zA{^pt%c|ajj)h{k%akeZ4Y3q6Jif{I>DXfa*d3r;G4utilhl^xM-6Dva^$KwW>KYb1A zTEipJXl+0>6Ksv;hAd@}>IovC(;Bs93^^5L;(1w9c7gazmyV5&W}B?#&UsxoR17^t zEut`8Tq9zqO)J?--`nAMZpsEEI2GuKN zrC$X4AlKEqlxnCPWXhGksj#RjypQ}T7Jr-! zFv$oKDrQh7poL3*FjB@;g2^MpfaE;UgL>6gMUBO2Wtkon@6(BHp1G zL~jLQNctT)D$k}t4Bw>U`9$wB{!#0YcdJF%_EBq$#48{II@LyC2;zuVA*8dMuiy`( zC4fuIrQ)JL*Pho`L^n|?9O4B~1y0I_jpv_k&u_RcW6AL>-f;3Tiw)sGllk-Q#Sy14 z$&?cu<0pV!Wl7jTMbIs3RUQ19FRg~KBUb8SIfs&KFb55W;3B2O?Ky-WA^sv%Y}fT} z{DKQWhwz`FB0AEgNCwUg&TVhc6~k-=Pug?*S3BEm{6q1){q`BN^P7}clqG>(>aSLZ zqK>JsA2}8_jXB9hQ_%$uD`6=YPR*4E2eYL-)+&YV$=>uan+j`HoTLj{9Ts&^6#*&Q(j;PU7|DYwyNxD|Wgufaj5G$u z)Mflh3*v|q6cuRZ$t$rh7zHUL>)CMSwQCO?^Z*adfw(o3h9-Ksp;n z(Vgg<#c}Gp4u0AKL^=bA^U&O5sK;l@#!&f-h2as<(!{*+Gvjzl(C&Zt*NqIIY6!g_ zEM!?qi;JhCp6C_Rq0lF7m#d7y5EyEheS|e~+yIMOq=h|zrRIT#`1lK=O?xDLm^zmL z-m0arq+Sa^k+MfPQxs#7@lTdw3?q9iW17~s^-%T(F;0`AbtEMI*afikk6%;4L6*^M zl}Ug)E#amKywm(-M$=XN4%IENxS)jMDKNJV=O1*lSf1 z8c;NjU5ZkiU@;Ds$m*UXyUY0dY5wF!1|Z4H6Bk3>uqqSyYcI+PWt|7hsR&g_3t^@~ zJsv1QiUHxLE5*g(8UKoe`{iCH!YWO21wauuW;!reyrl&h9#K&?6D7k*Rhnf_Q%b^= z63yG*5KTaIFOg}ZuBXyPo=`(IdcXOof^0=22_XQ=&aYbuTx)p*cH+)Va)&^P)CU`I zBn_#P-8N$}(LXHaly;vuh@|qV4u49wI{6dlPST_b`gU9aIEn}Av>XpO5zO9X-y}%M z$?7zg{4toyz55dvhow-RX#S40l|CQ%lSpvsKiiD1W2Dn&EQ6 zX`JuBJ_~2U<~`H&4*xZ>6UCr7M)MC@nv5vGb&+BqSjN9m_sjK9i5?_XO~R2dlZlzm zE`Wh@jk+So;#Z2r?EUko4|jwfB@zb+B*H|VL_j$J5o5WkJo+Z29EcrNh$BgU#=oQo z;FEe^T6R(?1T6$t(S|k?%>R20?RCC`fDQ#???9TMA&VRY(E+ta(IQR(&@OT2Dmfy0 z!y|oK8r5+KhKW34z-XKW=|SpiSXMY8Q-H*&Gk@M{#2U)94%;USP>j&!LRbWx z0>KZKiC0cI)|G#&<9AJ`BS;jb-D4`p=oZ4+;^d6(f-;+Qc>&WzGc&{DwdR7@e~7GV zBE_JFM_?0poQ_qJoBy9;(*%tK%~;A2m;@Kjgl?#$3+_!v3CdSUkbo`caTZVYz!hcv zL%Jktq9oV>$($Zoj$XTyOd4t{8#kz2nkE(qfsj^hc;z_NQ;JIVLyLAin4?tbgB+XG z!jiI`)MHNO)~e`1k1|=1b#44E^Iyj9&^moEH7M%<2}c=dXae&H{uQ#0BQaGzgN>dV zTi)0xnXo8nYsXtBV;BXT-e5vp1*oBamK>YXwhHQ?&9Ui}>DA1{4u7^46UN+55_@61 zLJ4ZAxj~fZgI>45&DV@or{XK?(P%fEx~L~ErxGKMjxDcs{;R=89HXpEd0QGz(^YBs zXQxLSI*dWUL+zPnR!7p3Lh)V2lC5HBr8E+zG$vk56K!{!5${Sd)B5Z$ds@hLT@wYe zx)rCiyLcJm@}L`y&5^1yY*wJ5LMD-r{7SGk&hkeqq3I~HDvt2!%|S1sDip|-xz>@B za=CWklo$u4S8c2w)j;h^tV=UgfUN85F*+@yFhtD9N!FnvoU)c|WoIU-GSA858e*UH zV3ednCU(=38WjaRfw+^y^IfS!YqEqKmvR}NkSM4XLj;ZRK+KhRY+E*}CN)g2-tO

Q!M+VJ*M6)&xQLa>q|Oj^X5 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�GkFlO3WcXyX4lSTLoe^Lvh1VI8vy|tqOCa8!7QctS`rWN2I((TA;DpM3xyb3dF?x z`h*b=TBQX}XO4CBV4x1mR&K_h-$Eq}fi-qL%H4qI%&J$=u$d6Q1~J9#!EGE?TQ}jJ zu^U_wrOF~|KRpTqq8Y|_96qVzuhp0TH^&m`TcZ7cht|uDMKBMi7@*__?4s}sJGnn{ zh~9Zm6ElPj^0IBON4x3aGuUp=YhHSq+XQu_f(hKBCgBl^<#}1$MUfVGs4KoQqfb<8 z$_^Q7dTzuPL`8sF|Lx*2_*MH8a@oOc@x$IP{6~tc_HvA9L6^HOsC?U2Wx;$6Zk}8cshRST3j?1AHrti z`<3j1{Gr$b7j~NJjSmV}%@Nn?5%O$D44l@p2%EeEow~U@4Ai7(9m0|(>lhw&vR{if zQk_ty^+02Lu=o386BaMMaMxl^)l{gDab;UQw1;!yXTI%W>^i|k@Vxxi$?JClb?`9L zc9HU4dEgZ3gpNE0slD4lbw4t(h0eX37?;f$k3_J6ZRj^P#zU|<4 zKG7#*w(~sQ*i`&xUBwwyI~Fz@d+Am=s+W&k-SvM*-Rw3bOb`WS`Rxj(vSi$%$NezM zZKVVn_lfz2UL2a^w^9OE$`f~;$5%bE)1`~0nVreXwnpUEz78D4mq!);75E$OTqrpByTis2h_Gu7?HI)w?xMf(aTtbocjyja{?N4C>EpCkW zjgFN6^X;m?A|@rUT(7aq7X^+(_llgiR60m4h&XwVqd%-zsGYqS4$@%B<8(8AdE*gR zxKtfCNP$5q09oMQB;0&}mq7v~Ty!j+?PPA&+p+8qBHhrhKjS>H5z3_~`(UK+y4qsm z3^W|1tU4nh<{UbD(4V+SSzdjnf>I09m8Mv!QOf2KuE*ref`N|MWW$3`3ewa!IKi~r6W#}dsxb?{M(}Kwe z!CD2Ahh%5vrxz7t#aE;s zLz@wFJlJ*ubw%8*7J}_hRFv#srP#I$$h5c-f)$@&SGQ=7)qoFd*>$+6*k%LDNU}XFXEYGf95FfvUys7LV~w?10%%*L`x|R35H#f8=GDTF%06<6u1}+31v_P z3Ek!Pt8C=`p1rsDF4WwNA56Zfphsv-dU0-1&wDd+i!m)`QsT@+AQv7{7Gm8}jq8$Z zuCS=;jjJ&0Fd?#}K)Z{9Wn&>#m$lR z#>y22O3dlG_{u=dU7;N~*gL~(NCF7=k?{Xxb@jZiDxG2?FZRCH(J3egBQ=#@&mO`W zx%}MFtK0m(K-JG3Llr0uh;fbOznYrF?8o#VUQjMSI8=W#W!hW4O^vCJ+T9K-!l_l? zhuEIO{Vd6#0Qs~k14^>(pL!~(`tZX3{yvfN=6YvawwC#{ncOkMCPZ(n8)%h)+7@R>8b*MghLd8r>>rABu8)TYkrx zlG-ZR9d-aV_WJx#G0#@QaVy26abU}hp$S6^@b%2~qp!U-yfej{E9zK-W>dX^5UE; zrl!91&eR{RE2f7I{bTx9#fxVi{@~$RgZrk>&c5k~IX70^F!%1)hs|9%{Mft(XT3GQ z)%4_ruQy(}XwUIlOZI;nT=M+CnlH_{Y4y^=_8%;3^Xiw&iVi>USl#-cJQmD4Yx&n_ zwO{^4Uf&g)`#rSg`C-AeA5B=d_Nr#<*Y5l9=(>k5{cQb|F8wy#|J3jee_6DDL!3&Ja}I6V)o#V+V?P|&+~KPCHh28n$Sq}?U)(bIiN0G;`F!@)U9T?Q zdgS%AC#Q7{Zu@T2?j3K<`2MMxmtFEq|G8)G9zMOz?hiklxI5IL_WsP%|FQqBX$uZK zc>bu;bCdTT9C2>i;fzl{d~W_rryQ-feeDYmjd=g1lESlIUUl&Kmp^#q%0G_!@4P>* zn_1_TE=~UY%8;RX$J#cYcWnE_7mxMNUH7MtzwG#GcAt5#{?ho-*UnAb^V$vLC;oX# z*MomvJE!oC#`C^-Bj=fRZ(jXV(aMhSi9~9p6=7-Ja_50}OZ-4%% z%YW;BeEq`f|JmiU1)m(xTKLKC?t!vdLq|pb*|{{jx9*to=6~2#e(dWb<=s=KSN!Fa z*%e3s=jO_vt{hqU-lU?+(a%0t*(~+q&-Y(&{PVSQ8+`G_nm>Lq=xC!avzm4L@`9{> zU;aCg{pF?aKk?;rUB3VFaLeHTnV(^OwdqffeD%@CU5_7sGxzw!Xx{PNSC0MqmaCur z`ocaxe*H^!=YN$ByZM`Xk5B)mwC~DqW^di{-Ra>6zhCuBou4Yd+woJoXV&~@Y3Uh1 zpMCSlU&dTCIq=VUm!Ep|89h(^_dAPDy}NeaY2kj6)2{i~z|&tG(E5zbX?LD+cD*HM zy!i3{Gv2@SuV>!gZP{73{d~hY=biC~bDH!WU8~7G_trY6>4oRrKd1hAn|@q)-jjuc z&o8g_pYty&Id;(x2m03O^TwJ><{nC~o3V6F-NLiC*PS$?WxbM%zN>fY>#OP??EFH5 zeE%h8$qj3N`*HB$(jS8>Kbh5N*VyM8ebD8XMiZM&Y1;gQ)0(yE z+#~6Y`d=kI8M(N{#~Vvply=(E@{5M2U$x-8?_14!WqR_qgMUeW=dDI5Yg_eA>9Syc z%46kATjzbeG_^tKSnBZT#aBPHY+t*jm1nje+@o+CTm@cFTS-Y~!C>0OSkU(n^{Ew#J$U7p(Q!M9uY*i(0Dub;ks zxzD5Jb^5-0b*p~2exKE^`K(3#3VMItFKKw&{==JP^$(4m*8gztyaBi0fA4_ulD`dn zzV@dBKd=4Sz~?{67-`{ukhv}t190^^TaY5Es+XiI)(m!Kt<;{N>+vnX$V<$bddThrR zduK0QoS74Pt#j^o-}lOU^rA%tf%8`sJoNFraknhH@$SFBkbd_ssh{8d?1*;?-+euM zd{Rg2o_*Pu+`Dkt(h2h$U2=bD_L%!SzIWdPv!c&FF!8;t2Y=V#(1SmJ)8Y?B4K3@X zODyZ&WSFA*mX!h9l!dcRZ|GuKNoQMD4lV|J;w;OGUS?U#anVRS{F#HR5dMXma~7iA z`pYeAG0wyc)v~Nh>R8tN(|`+i+B^XG-^0A^uV-1`D-YeWmn zO2@I&$4>_hz;g)wZA4#>U2It|;%M&gfomhiRSuetoMTxX@$6;b`xzHXe2$CZHe3O` zO)TqvjQ0}I`wZ}^{~BA?x439wF50#Rjs|G+9q2y`?QX#sw}P(b7$fG_b6$82{_QcQ*L`1n@P*A)HU(_NLPS^9|-K2Yg(IxxEu(y%hZ{1Ki&*j^>bs zjo|fAv>DjUvR*mWvNAEY@sNer0-z0iY1j>XL;HWA-4DQdGy2Ou&$13CS=Q4yh5uIY z_ys&a8}qXUIR1+{NX7f^JuT~P&=SFT%RtvvXuAl!`73ZO2H(2%hFpN3-{JXPkddb$ zD?Cd2Q?y9}9fQuYtn0z23-P=Nzi;beS?{3zf_C7|g?NU3W`Oq@n4jaI_kPeb61<9Z zx2$5!%|OV}UW~sfp5Fi*58?M@@D)R{jsv#^Sz3#+-iY=e;@<-Bb{58R0pz7OXv_qU z=YXD8;M-!1`C9Pf3-J7Mv}uR&Zos&$?SDu6k!M=gQ|IA5aD4*Oz5)#cA?H6}9PeXpd3C`( zcy@@zAH}n z%u=VF$_*U6IgEGq@iwMzIRBGJFY|QmfoRtgXQJ}(*9jJeVs&$`1Jg(OdQU|4UIZrZ zV~>a*Mhg5r@)T$9S;+&{iKje?)V=H^Y|R4bpzdfWCyet5aC3C#!KMS$u;hLpUZF#% zzWAFb35RfwF3(84$Jzs=J*vMdjHkn9;D0<%IE-6cghSq=9e+ELx8}E$JiXwb50eIJIRS0=*IR`G1c+n|1|F{b)NA)toGjJww+aUSJZ_X$=Tps9hFhONYCT%GIs#*spIN=M;bd>%kOR&mHZaG$4s%PGooXMwlX5O6OG{G$%=Qm2qL z8Auy@k-BEldsO^uXqMt_<~4hIP6j|SMguReAI-G>2OwP%fw<&)?;Vh+ znomS|$~&D_rhOc~L<;@Utf@J2vsr&d9Im`a!#*`>SQ87i0?3+Ig^XgR?>9P(#{FvA zxcc6s)s0x+rgA3Yy(;_lI@#*`O$ECC-eI_rReV3BInR)V{P5o^GZ+Z3j*rlLk&@qs zFZB=0r6WF=H^A!wX`lk}f)N^s3D#ktOY>4eax?IfVIw6v8`h1N1<_mchFS4~TK2yL zczZwiIAx+W;i73nZ1e0Ma^Dp3a?lSt9{8y&nXqC*5xA|D5bb1 zhO!*i5DZ2x*DSOuf!@%1g1R)w%?hJIk9ZG3G!V%BA$ZuTk2D~Lk$zO9hmM@G!Fy-e zop7rSz^kPsF*qsJm!)I?Rkx~m=Dpt|mJ)rx_X|KXI5C=dw0PBZr!VYrux*e(8`60qz?`SSppnCaTL7vaUgi;1Y>4K@N^&6vt(7?=bAiYZBtb;? zCl_}zu8(I3K8{ZTuO^CT>JoG1KR{%J9i*TLgDHkm)7&!$Bl(GMSGp2~2g3;JF?!?B zo6yLJfpsIX#UkC>HjoBwdYOBX3M%<%ANxW;daHLHP86wESHeEz?*sYh#K?KE4lmxy z#lMW@Mxj)}-z27!`>*_Lr^OAy?&@p9$k$s@T56TJ8$BKRI`2$t9{>y^Z4P=ks1ffY zo{u)wI}z^!+z>f2IvF6038HAQdGQs~f{;}oH6z2C8dbSr zSleyhh@wL%3zX3QI#(xXwU#C>arMR^>6 z=b3nZ5MYL3&@qSq+PEPqk(IEXL+S(6MKB$>gcp@oL)1*iK&G<-#@-5H=a@i9N$V8= z-sGJj77^*s{5q=Virf$vZ7PEf@xs;@Kxud#mz5l~H+7}Fc#~T$0(3H|F|QVsxHwT_ zhtwJf49{cst*4I4r$gFA+Oz_w3Debk6Vr1PNDK=?Rnsa)R_nTesa@I=eGYI# zGW_6fF(rgrt}ZJK#7~q9{{0bZ*M^={g~F6qZ)9>M%H2kIcXDV(sfshpKgE!+kaY{t z8XDtAtJ!P{+kAi-r4|8Wct;DW8SGVp4FE~?iWWg~@JD=$%e<~C-I3V+ZvZm1!vv~6 z4RkA1T0a_Ly~W5gPZXt8NOgU$AJ7?o`NYw=G`i9x!=C}PKA;siZlqOxJk)#=1u_Vk@cg4TSZhqTb`Q9;kle)zs9- zT}KF1s>hYO{sqnwC1q5*I1u7?MdqQ@HC( zb8&Q;zDourO7#l%t!UoOb-DgPh^EBbT-$M?FY+V<^MIxq8@oA@X$|;JOv+Pe+M#Am zb#Hp~Ve3@@*EfN^7c#@uQTUw^5;Gky`>(-DVCR~Fk?HZ4cfvr?rDiCy z!HO&;V7)<0kqR|f$_rVufMn#!A?b~Q6(H`WG@1e-aXD*wl;LgYqR+{7q2mRHMndm< zKz7r~AsYlbi@^pmgVUr@{`chR8;Sw4kmMq3iWTE3C&eoSR3!$E6cAbja*?(Fen8T+ zszLTqKv+XV!~Rt@H09Q9DVjo+l3Y^Tx;9wU?dLX&m1g&yAB84f>;)sjB5M`URXO!8 zItkyHQD!TNVz%U}4t3yFAWI=YmRba$bu-Nt^MY~8KJ`$H3|U{8AWULNmb+#r#4CtWR7UkFPm#7Nlasd!Eb`ed0{!+{-?nK23Ehlv?nzho+_>%sQu>$|j0GH^a zsA6o)>D>!}<}QE)L+-4YH<$hgjf1+eJ7MOKM_vZ+pd0zsQ%iZSa>!&b`Nt{b2*TDl z06XOL0+pnbf=C{S-FBHf$84+}rYXgP(Fe;cE6v8bP$tXMIkvox11ag?(z9snNNsFU zZZJ})9zKvi0mPA7x&^r?Tq*;U9IpJi5rP(%4*}LzbE~hK8YYXPRCEQo$|X<%%(<|z zKgQt{?Gp1$0f^;P5gI$hRo9raQVfJeRtdlyVK>1<8%+6~hvq}|Soklg)&n)`mQrHk zByd$h%1+)m&wRhdONSqEtj+lyw)Z$>`z_cY0>RjU){L%EzCw1`s@2GjR$@_xQ8J-dGEFs~7J{Gg zSkM^AQsWO>Qlbpl8~-9?KLB}n1`gi8aMHH z5CJGcqe71?>zt%3nx<(vuDU5jv3~AO_mHj=yoiNY@_l8=3Kr_eKCvFz6ht&Xe z6wL%B{|CZq^ssuXxLmK%Wm-o8qVI_0O%;f5S9GUSPRKrRn%c7UUjeZOn@K2O{cXc-Gb%|16mAY+nc5|8y)K` z%gx8AhlBAZ6Ypd}tGD9eI83mNEuMIB`pjKsrd62=%ACmCPngLp#>2Vb&9?w?B-U%Y ztFj#3n|bC`YnQ*jc${7YKqrvUfVv8qc!E9vn1@KwW)m!Sn$AtKt6DT~{@Sa5j`R!( zKgXy!3t86#h0`xl6spOhEs-*5yd-fnfEDUCwx(apoK@c}Go^H5E`Ed>00lX_Z|Ra zFB%$lsY%1CTkTTsWU)mmUsBa9v{nNA>iE6)1wY;8E!}%h#1O^Siva6<3Vl*puf~ANc+O)*FrO}%fAK}sT=z&*?l%vcfa0dAP`C>!qW+u$F|G2u-s+;=Z`k@XPtB}Xpx zddd}5QYiC=3Fo)Lhn>G3jjk)j(s>;^fX*l8o~-ZajTV{KTmV315(D&vg1F6>qj~$P znwwMXZMgxUkm|%e=_%!OdYSeP0Q*!87`r>xZ0{Q7^YMLTiuuyC@zgN$eGhix`N-_m`_3)onp)_7&3G5k4f2Bcb;SG5KjVpzE&|djwZ@Col!`qZ2*FrxB$_`#isv5dqk}kQ%XUF`&Oti9<@bU9n*^+%DfU1;RFOZkA zjR5TAeGuE5`}t=)7XAnXFglEjGwd&HnYO#@6Fp#l=}fo^$EP6`NebC|fh=rY-d53( z7>%j3v6a)VKmY|ujKEBE;ziu)K!ItiDGDs9X<% zx{KMTSB^!q8@*RK3%3J|@jIoBCO`^^TK42w*s2gF#``>(6;_5Zay?0uIFgQPV>}1jfckL;zQjO=@(sL5@8QB!d$n(VusXRjj2} z-UqaP?!==l0<>cj_bOEm9YdL)2$L9hnOXu3=1V~-iCl9%*_0|o_T6X%j$Y_vyTEa z?<{PP?=x7Wb9XIspMkC(@!K6b1LiwLV4EmUx^*8=r1()VJ(FFR@wVBn0!S)B!kArm zVU2UFmj)>ZSxT#!D2;`LyaS-7@qtXs^)EDR<<`^8Zw9E5{t=A078>dTn2E0V4|n4V z5yTo>DW6OPqUKVjlf^MJ-WUZ#Cwp!=(xYhHrRv7yAtM}amdnJJ24O`{P(ahi+Qdgd z(zz-m=3p4OOPcF0ymmc|V00-$x|_KC?mFb| ziGU=<i@f%A|T=U>XR+*grY^wRu%2u zI8UZoECuoEu`AHDzrU$D0p^5J)~MgI?<$OFiG?4x0MMc%3IHdEkntUdp=$~4fvU^N zp>j=OUmzKLB1p`^yTheuyAEwQ&<>47O9j3QyrG4SH7B&PRD8}8OJ($*tSA;jikQpM zv`zrxwtmF4kA-L$Rva3SpE6-|dk5CHHn92&6`KPL3{~QdYX>laduU-tm)?$IA|_DBE{{zqaA8(Y>XS z>8m1BcSdGTD%-a$&?qwJ;pn2BW&37TJaPyPgZag{fIT{OF@7%JeBjfPhXd`S+tx-_ zY${*)M0D!h^5xs3tENO(PmZoseJG^mk3CwsdOuz{y%H|T`Ax7_99bEeJ2NtAPg&{W zNXfynLr2ONZeqhb1X!I5Eq%$mV0WDEk5d{4$;I#XVU7cE25uG(w9GI1>fp?ZBg>*o zca-fbDciTjzP+S!&7&0?r$(kffdLVFT4e82ARG^5>=+ov9z8HOC`X`IPMTGIXgh`w z*}f(^b#M9B9Tht!mhInQ!bYa9s$912+Y#CgRj!b;q;*ttI|bmmv%3<OVEfwwE-kgT_kuO9D%bo?^H0+C)P)& z9WFckcy!iwX>$R)S5Gld@`w~gwk?f3ys7LE`Q?8anLDd|<4$?nGrD9|^yx*msLfeY zc4T!~X(>58b$O)pKt;(U@HV<_9j9T%mh#d$6-TBxugbR_1}-wcu}FbP5yt}Ym1dE3 zyDFYp&o&K7`Mz0@XPo*ux)>Kr2GLLXw5ib*PxAeBk$Bxh-T)L`xFb4qqPIbGT1j-p zG@&oBA^^dud~9yy;GD?8MG#ext3(udD@z`W?AwQ7R<0;1E1fO!tCVJ2WZiZ&6bj0> zuP&c!D!+|LQVGr@fbm=H0d2?W4Wcy=f`={YPjf@X_advqZtw&rTq0OAJ$qBV@w{bq@zSR@ouqBsz++eM}>iA*`*l_p=i zpy0FHnf>`N(bUuglWJELrnxht+tzzAi3&#!ERY^;n#4f)cpxo$L|4p;OkZ98%o<29 zc&jvks;q1Q5P^j}W(rxlXsj3hzrlxV1%56kw=hKUi)q^wLj7}>s`U>%|}r$8a#=)&Vd8K0I+ zR3|9w8#$rGwl*s_T2{Kg?C^5Yw7}GWtwBT63$sE+z0z}YM?RaSy(6;b5o)mVE?PlNG|j1A2Oog}Y9R)MgJ>One0{}~E#Aq| zGb3CVgF=y8G?f5##Lt(PJ{p-j)qR{=ad<&w(t3x@&KsE(;U}iJV#nmj)U8A`N}7P* zK2W@bMMHN23L_420pf9Db;9;N0gOSWF7`fe>AqnHx1DeYydlA!KL>lu6z{a+F;QRV zjMEkux8vGQ<;a#ei4Ld?d2`8G?o@jx3GLenQ496vFT@nAx{2 zh{K?Z3JOEEbcAooE*jgvAS0(255kY^S{W_bSh4*uoF?^WOnPoOB$igqSY(~jr4@89 zl?x|AGF^qZ3l>&gbO3@xDkyl zT!=az1cyw>8(pBIu3iPWjH4)sKtPe}2uNho#ER|v2+t6qaX7ztTuxSFs8i#x0uRfH z|9J9nps&Ms%DU|;Z2!v+ajhHmsStNR;l8IFGIqzoiYd?7-X63&4!;gfwo+i&2$5;_ z(4LG@kpuMJwnUmhQ3wE)AJ`vxe2U{Z2$$xV93bET@sOb*O$umbTSabU(q0Ppwspb2 zJ#1SOXym|(uqf;3m?9zz3;?y2^QT63?6=Veg?6u%bOIh&2K%iJlb|S$oh7vm$o;1m zRm?_=uoxIYspvUhCM71PeACmAE1Lu}E=Tk%aS&&6!PLmUndRG;pbuiK6U%=NDolIu z$RWuGfD}zZdim<@Wk()&CK}HnaOIn3L=McUTyDoeXemLDol88qAu?w#$FX>S<>PBe z!M^R0>6Y0vY1iH`=kU!w+gR@u2qCnqqI(Ku4k*B-82W}~88b*KJGiQ{WPWt%VW0}Ciw848$)WM-xy9*4 z$QeqeffA~yrwc?Q5<`0F=b+j<_A4e-5ls?#;Y9ZtI&{#G;IP5{3D?M;fg$RO2VB!J zJ{wm%z$D8wuu0Fz-u0M4Fip)9Ew<~N^egI}_I`GiK*w;nE|6%Fy|&oNLO~o%9ED=; zRwFCyV0!{jRIIwEa_Usm8MBiZv7;*~N#aa&Ljq}uAQm=j=Agk_5fh{ust*I*R{=*s z9n}PcEio#c1UeX|=*kshbb`aP!^P>j!68M(nb`$u2A6?37$=o;`6@8CVux1gy-!uF z*&3O(uj0@liUdgCwGz2kkrR$XJ84?K7 z5Sh9RjfFrvMwH%FkcwW)4<3l@oF_sHrX#l|3sk|0oaqHISA3v-(E2W(1W3_>6sV$7 zFu9v51&FLVO@NAcQti&y?ROZ*@u|74*f3H0cUVb?>%>^0Q>2P$0z%^9*cJ!$LeWr} zNc2H;tyP2pi|$AS25)OJB85qN#Qej=BJCnT9{_r2Js_R)$lZ;^j8P1Wk6 z3udAE$7ZT_LQF|EIjgrbATU!$5k-j$12;%RiThFRK#9hVedn%VGN` ztqTf^L^vVX5fohxK31_|a`|%R9?&%wli&}bfwP~8Y@8wO0|x`XtcIPa3<}D}>6&a) z6ucEi=`M^S_964Z@)=K-Z=E5xvN@@x$|$LKGuA_h%1bwvm!fu~eDacwq8EIt^vEgy zo*nm4iyEjNokjIZ=jwCs!YE#PV%}F-iV)ag-0>L zWf2qrnWmULr-rN~CvdG&J&B1$3v5N)A~Dd2V01-EboTm+BlF||VhCL2b7$a+Y5)WE zL}$Et706|nnVjfN&~B_~RKW@d*xvE{kR)g5&h&w+lHfQF2h9lAIl6BW$}OlDMpo>s z*v*QB1XO8G!A2^-C5RHBr;&A!@dwp8;lXkbBudQ+9p5yRTDCl}i__%?7eE(4GzQo* z&ilxuO_5zCm;~t{vTnb$Z4##~9cIvruxXq|(K+)GQdZZhY~M481HG+mNs_{Z!%hzC zSO9SZGK4gHNVx?WNNIzePz?z_pu{#gBRykm2)|_I7K~2MO&(`g0PqACH;uzT<8q6$ zvh%~zuyb%ka&ks)D7_GGk~8>MGH*}i54m^NI$MJF*gC15Am}>jNnPHaCqm)1oyL5s zsy0ULhz|=w`(|+YVn0fdC25`kBlqR-@Q-iYcmn2-cP+ih9j?L%iD`t6!YrdyE8=4c zR^=m`7%e;Eg=!hA)nYf0*TRWY2^a4KIw-Ym9aYtW=mtXMf{s^(pqCxkC_?6VAqOft zZ6iFmq|xF;fI;k)C?ydVGX>Z^Lza>%m(40auu;h>a-G>Kf0k!>kNl5$zZw}$G)w~! zfTdWil6O)lr!htvuZl8uNpk8={7|`dsXS!2&1@l8`G*b%2nJwP<3qKD?OMgOV}yv9 zT3IH4>pFI8)IbiblQE{?!o$n$bPYs{nU%#N;aghZYV@l}fZ?n-^kj6_8oC<6F;dbWP!ADD;1r#)nSwEQrpx;CDMB(T$rJ;Z+ft_DK}9b| zJo%~eEfYynEr)%us%e2B*d*S=4gyzk%@EaGB{u|6m$x>@m|FzSV`u8eU~3W_Xe?7C zzRk&6WGdJ-;S1@dRR~DsQoakJ3C00-N}m!=y6cxJfRYKv&>fWoGFhOK0NG8&z>@7K z`)zXT$!F7RZl6}Mc2XLMmimtf#mq^8xAH%%BQ=2Sbz7N$3qyzRmDrX2rS8OW5a1biOnJ5l&-MDhfEE**hk$RCt_V1Bb z@{qK*u(>sNDqbqeF(}7wNWv+o17kG*m=f}OO_V*zKl0JB>E#C(SL~R_3WrMVoobY= zQBSQ_u7wK&0Cmti%}7%IZ`F^oJZ&-G%19;buL;@m_)4+ukjP48p?2C+zII~y@}(GO zpaHlXH<%;Ec32VF`FO<+EL(EX-VtkbD-|(dX4@tn!Gzi~utF@YmYf|03N&PIY60D; z2RaCeq>n9Pz*$9c3d{1*6;DWh0DY(AZ(mut4CQQQ40r++4+4c&630|iTAjt4mSzi` z9;l@!uWFO7i@a?mk4S_2P+pU?&bM<>RTG9=fTFk^HIr(A6Yv2*%^(^X1lhGH1x@$O zxl(?{R$FnRe$uWG#j!wpgztLgAU2q+D?eX(89>U)Om}P z8k?QhDt%Nf)D|XGAdG^Nb{5fTTcdO47-eER`FF|e>ikG;yo2#tB4vXxooYMC`1zB zjJ!UE^QEF07|6(xL*hKGbF_c3L+<#RWBEcpD^@3-`(~+j4XcSOsdvmGbt4Jm=+g&?~odqs|s0{`v z4qz<`{n{HS)cz1G`fx*}$~fUu5N7=rWw?bdLDHEl7JuCB>9&AzT4oj^&>{l}nz9PJKEpaI3Tt z3=k2u*J2YF-#pmBds(u>%7!)-Yq2xfUT9jgGJ?gll{2tGxN`n_yI>ob7qF*N`ErN5 zG|Xy^<9~z4BViTeQ>FJKVGJ0UOQc@q1O3Tjm}Q;MSKk+~K%P{bY;U8|21x{{>~E zO|iv9l}%}{bk?61xDb|8-b&8TL7i*MD{Sh4xq+@~PXITC(I%A~l$k+#r2-k>zOc+t zQ98E4Wj5wF1=uDwwxF=6F*HG;(hGiFb93B&#Z%NyAPIKXmm;|Vee4oKgS4^`uP%ZP0Kfya=&z9009tHo-Q z+sK^{^|gslTgcqTVwO;JjjWpmymnc{(I~cQXeWEl_dUw~IWih(=(J|NRqfYsqCCRa zlV=j`IBnG%+C`Cj&nHBD(`H!LLHxLYiIP&+=)|3o_4ei)DMxv!B1o!T7*RzYMo}tH zp!-r)3Jgx^#~ z)e%O!wv9oWy*~o|#qFckh+%gsWlLuKC#x*-V=deDs?D)!f%;xt;<=pf94_$g;8NB? z5RCxJI|EHABTp~FYLYWLSS4;#o%9I4FT?l1u&5Kc8q8rY?P6WV=bFRn9((<5@i1j)HbYZr=u^8P`+>TU0C z$6KM4WeN4I2*m@w7Au4JL{D$=3)JH?C6K<|o${THs^QTIP1*(DrzVwkUbS%(9!-@5 z@StMbhRD3>q^POGX6T73{&J7L^z9^No*KX1tGN~y81D72RJb`yq}GfsoPf#pEBY1s z5Pz(f%9G*eu@;^@Bs(iVy{Nb_6b_s(JQ3_1O`tRO)r<=j7G;ORK`ajAYXzH1*fl*Ylx!IH>2#PEydf$WG6Vu2&FlY66VshdC3{$ZWk zv!?a}*%>kRbE?Q3bb_g)V8KkW8Z6U8+NcXwUS zYNcE(t<)){EE-49n>$XK1Un5}!{%%pyxpx!irG@TG$5kxNYrOE(Dlf=#foo1Szd7E zQN}8DN-c@#v)>%cdi~f}hXa;n&BLGEArr#bNRxuEYSixEu=CU4P&#%2x5B5M83l#8 z*`r$p?QavWZ_BOsDXFc3-Juw;EZ66UiuvhqtKgvG(fDZc#?XYJ1v#Po>qlREZF;+m zcJ13-cTL++YKQBZ#CBGUh(8ZsNlNcSLPe*5sD`8t{|)Nega7}r@l_KiwoRRQf5W6l zwtO3y{QQbPP2L>*e)8g+E2gHt^v={Dtt+O74*g^LSH+8G9{%9rS%dqg&(6N-hdDP^ z+%Wg<*N4qrIsDkX24}rBzt!~Qg|9bWxM(}o4@aVdSFa2!& zlrH@?-2c?@4S!j*e?z1DHf`K{?A47Iw7qUqlXDJj+SP8w=3_q`*xcc&_cnL@+sG|t zn_t{A_=&z-Px*ZI)?KeI-g@Nqv?r%^4Q~5x)9xK_&G`PQnU`JiO#iuO?jAn9&F&9B zoVYvGq4xgF)Bmymu4xMnJb3=7(sPsd9vpFQ+Tn~(K74NeOQ#&Iw|(sk4~=;LrINz4 zUS4(Z`IkR<I`X?MPL&HAhU@^-sF z{blE;m%M$-y&d1~HR0#CuN{~6*G2Cv|7+yddVeeK^1!?QzT@S0^A6qq-tsZ`d~ns6 zO&=8A^X7-m=k@#O=Wl=hsLOxretiAH>;Kv1vjv|V&szA&?(TuISwlxf|Jk`Ty0`9_ z^5%coRetR2Bjw#wr&s*tl-U(W|L5k)pROEP`QD_W%F)k0SJ^D};?MVAaQySNa~pi| z#hO2UG3aQcFSD9;`|^UUeqa7Okp1PQ??3V7b6vjw@^H)G|Cyg*eYNRNk9_sf$6b#f ze>3;^#Ax2}-B*tN`j)Go{rbW_KYsm7cISVU4!ilAdXG>4rnK+MZ)R`Z^4;m-2fttS zOP!x8zuWOsyJyz?XKCpfKc9W`$X~`>G&%6kd6%Dh^%*@+{r5YIPQAN!-f7`}k<+gE z*TBIp>}6hjW_r9bK!*J@?i+ zr|E^~-9M-Pd7FM*c;1tRgU>Io^`G-EDmix14+r|z>GQ^#OXeO*ubZ)SP2Iw?x7VFC zqGi33i@vLO>g%iOAME@>gMTNFyL``kb1y%CeCg#$naK@nfBSLp;nE+2D?gdlXxG^1 z8hy~^mqrttO=;TvgVUO|>D(jfjrw0DJsG*U#m5^Q$w=bj@w-SGLbf8H>^=jmOJtzXdP3tym7ZIyYcS7 zzmR_SFR7p3{p^T$3g3M_dwfzy>z;ktm)yH>*wP8}8(ngLX!e-6mX(8(WuG|9vZA=j zXE_e%YKK2_aP-Q*aNOWRv|EqMYZmvgtWYh>x}=U}%|8veaQfT>fd4(r+x~i%_5Jmh zwFCI;1NVJ6eDi(a=zS5cqUvs0B`|}_a8k)$9CLmLjs;DvYgx6=w5;FZYKC&$ue%d4 zTX8Yh;NRgr&Ix)8XUC7g8OiB4Sl02=K?Cp{LVp|4*JBr3){C9dA8>8NxXMA(k#j7o zBc8nsd_UvtozHQ~@CMxYvJ;2&-;eQL0(zeTUiDvN%lZ~4w9G}@*1*vKZN3BjXQACK z7~@vZ)g0pv0#6EPco)CdMxXoeo_D+UL;D->z6tQ{!kGVt^Jrf^AMbf`D){#z`s#bG zWqsBK?{TKb)#z&(o~MIvwbAx$j420g#xw^k`uno0Wj%BT#sQcGpzZfK>8>^Y90vb` z7~6NCp+4}3z_T+zXAkhPC}>$fG_b6$82{_QcQ*L`1n@QOg@188=V^fX26L7JKCZ*u z-ifhZihh;>?r#`JbI8I*@OmiP48*DKubgUGnHbx6$iiy@&<4IV>;}G}{Xfv|2jILJ z{biqLSqE{V`O`QxXvWhV`10hFyG5)4_egkkkgx{0FR}9HI4%`-GX)VTjBiet6 ze+$6dSs2R&keA+|kr(LA0X?n2x5XIqwcy7W;Q8ff(+=a^fN^cXygY!py$bwGZG`JA zdVs&6Z#U?9yBB2f40Hopn?kntVcu>4A3g%U+Q63!nb`x{|Bm(}&$O(k&cl1)`UIqX z1sVoI&VRr--pAZtgSPkJ*+KMu5qSCo##sye3;fQq>VOZk!25dOcM^EF9{m-9r%yrN zJ_EkFkf%RktfN3fDsT=5o+$Y7BYvI<{=A3z9gXq4fU&MXn*zv0CiwIf=CU2=N&>H5 z!8iti&Pwq6evIpV@bX*GyAHhj3^MvL`u+kisZGHT=-)#8z8cT}02+PVIXd$&9v%eG!|ix6508B& zRA2lphXwQ8xDZYh+5x0Jsz2V|JORu-64gC4wH9Qz&&eY)PcIz&>yVizJ1zmT8Vu3B z{z9KpsO3l>oAhDLsES3z!?%dUdx-VdK-Rx{e#Iiwhrsy}TBp0eDIys`I9EwHeKH)%xmvYow{>fA5BGsqp zP9cTvl#aq#jyyDNtm2S+NF-0Hlas&XoK0`5A>dw?vLr=-C0|zoX=5)^*DQLEH~aw2 zj9bUOW>3!vuX!8|ygZ;R)A|`8#wGGz5SLuz^8OFsif=VRF zyn2V{kNU6U(MJNhr?A&1U@}m|--35)J#*q+v}g)SE!oyeecAGkuirztFf}O&eF=dp(44RgPGn0Gx(~ zRrc$3veoxH3h4TKhtU%wsdXUIZ+_|HO~2!vP1N9aBL+wa3)@(;_UBR-fn zz>B@X=_{a1^HM=_GazC9yA{nyP0Rij2zuN5!R6L?O}L1wK26&_nDGvP2K}Jpq1#lJ zOvv|8tCzJHjc!leD1HRMA&gNDLX=Y65<^)IOCLKN!ta+Ob~kJgL0uZ;G*DhT%{Wva z)ycbL{ULbR>IYOYjP#?@C&5xScvdpI6K-YJgp$PIq*Nd9y9B7ZRmC&!nY6K#=sW3- z1I^&XXyTptX_|eZiN=Rl?Qy`o<+wXirM8LDN-*JGBkXc$A^;87%2zIf%>$H8Y1Q*s zV0~9L67O}jCJOP=B2OV?t{oB&!J9?J=1J)5f<{IRtQ$#2BGRpG18LBvm$?@iMQ&eZj`#YnkTa}rsEx5MZV*s^;N-Ul)4bzv`XBKo(_ElU8eOF01P8- z4thBH7VqO)d+$X24y7MMTyM$l%0nb%`# zyUkOa^%=@!u9oUIfXuL;H9;mUq!h`C&9zX_8g6pj8(-+uo{E>DM33Qm`~#xpym<;Q zQR4_{pf=o=&?kp5OPE!YGFJi67(COlmupe{$)au%YlW5pnK5r{N{=Sl6^E%E7R}TE zJm*R2p90J<3_9lUUv)44u#g{{Yc!0x>*o|u^sPBGV&l=gx!2cImt7e$wYf8vYg+0cOI)Os zV~B}>rf?6F=Guq)14`NkuV61j^KP!ot**JY<3wNNNe2E3G|hj)v0)9Y~014Nks62>d8nG)nEIb*fL ztTocz)1ptXx?ux=5|wyhra~n~k_RN0X{Kn?L;S4fQR$bXfph6R8|X5cws15pdH5eK z)CB7(H1FZXNBc@GrhNKun5O*t{SsAyTz(yh8oP)lfF%@jiaSv;XClHgFGGr}oAD*{ zA;t>)JA+(OBC8x=jA^;q~Xsn!EE>y}btqV7{m zr0nF4^UU{`V84z-haYjQ&3UA%<^wUdBBV?nbQ%*C|yH*OVC5Q*-+a# z)=QY5XX1a9Aw6}o0XkZbx@u4wO7Y>^V48t(RaL-E=;qd%bYn`QNs!o3H~r;X#sKdC zxx+OR#2mgE8vY0{6%o*>#DY;tWUq*>*;ZE*tltO~j`)KVK^AJpDY99Hr9`<=^u5*y zAQ=2>5R6g@4TtF_Tw$vOXjLE>JJ6cZHOg1W4qH2bQbjAVC|RGSteK`-S8e05fZI<~ z;}4q~a^f#S{*TEkZ|Tvj`sh57%Z(Ed^3W`C^`QtlpqEtKn~obaHs;Wawr0E<1_Yit zJaGi^=WsmGdgkzC(E7z7L$Q`V z*u;+PDPoK*7)xr`Hv!;y5CJGc zqe2j*Wm)GW$I&!R%W>6BDWXN_iDF2z#?r>Q*bVB0fhM>&)M}1qj-N{0EM5=20qRVg z2}=GCgw^O_^;U7YUZcyj#sJ81(D8vdiTZBWrHPvBsgr<5)~j%*HJUp%(~CWRbBQK2 z<7xiSxYA%cgBy|}NjxW$mYsUeJc;?Ex08_75#Wvzh%?sURRfOj0uDhqBr6iqvyf@} zcD*Pw4SxsRI*C7Bn6W|AM(`l_qKce}p5JzIYw!13J@h<>5 zbE>t=-(NgV4@&X`5*kofArnu~0)Tml6m2%aVyEdCfK*kB=FMMw_0JKl7xQ4@=NSA( zSq?P!^h*?lc<3#Y#!C{L0LWnub1xZ}8UKV;0z`C7-DvHwmIAqInlgxcg)yqPIObH~P;&0|?xr-jEtnvMO#85O(~MWYauK%fM&k}O zZS3DwG7rE(0=sp5NlW$WI`8uS3mTvLP`;;ntp$`mUvp1ZjQOp}g>I>q zHYo#L--*YA&6yUDp*KcoQ^^33suoXAxR6SN=sLpjKzr+NftCX-@*&>^g2*g5Cp(?`p%Wt}9+ZBd0*ZMmEIthSY zQz}$(0o7Gj^+&0&jLTbG0Bji_FrH~?-v+iL zesi;$atj(l{3zJ6PQrx%#)I4rkPAL)6{LOP^K6XiZUA9Ey+9^6y-FwL7b<88z%ZXK zn1As_)Li#SQ|>npF9O9)RiSVfR7Cwb1!7v|cfHj`yWg<&HBfe|8YM^0`WtIYqLM}~l9 zKq1wMd(uGU$~zX0r0HDK)SShKxrlsjF6Sk8QD+IVW1`FV4-Ha!oCCzh-;_=mvQ~{5s-&=b!OJNZ$a`Dz(Oo)Rc-uCTM1vtv1zLTRHfW{fxMLc8-Sg>4`O>8 z2aCa&BczJ38OjgP!N^n(z-PuVI&_a2_7}EH+X*0!s|U<4or(LAW4y9w7-&F8Vl<}C z#-dmV1WVOBLe^~<$TldwZw;`AqW@i3f&kF$Dvbx@w z3lw40A!`GgcNR9t>KTi4?yhAPt911!Ppz*_Q27oQ&qaCCtzAHo;zz;sjOmzL^%;n1 z*4u1907xo9!g%N|tWnhT(jes^3$(}<#IcZ&S{)EL#|JVkR~s~J<<`?!&IsG)NTOe6 zY~B&f0zeNJz*{t+&0x^rZd@TrvBp-)Cp&?txs>T-amvtxFn5CWJ%CgF zz`Dr5Iv6EENOu#L>(Y?BCjycbliU87pimXKecqeEJp}MhesH?=e0!{6{7l(P)PGvX z8X_R!`S!{$DI((Wpk)RSG>fGmUOlz~P5b+sniF777-fz6E&Hy*aKO}Mw*WA+6AAz) zhmi3dhoM8VWkA*C9oukbd-=l$@UG2+Nw`G);G*cFed@T@9ek{Bu*bpS<;xD?=tbO2vW<82;U$k) z-n1;ba!+*Q41o_uN)MDD+8)_C)#;8PEv4h~M_1##U*0UR_+Z7Y7y-iJT+;E0M@`P$z}tVsJ_3R-M;54~J;tu;z2|l;_m1 zIhYnq#s!n^Z!Pg(Kmn=2l+P%MPMRS=wTVhSb&&vBa0Cv5y;H$NcFy4Ou!kRyE_h7Z zTtL+7DP~a~)#amYOQQ=9M`q!O*jbQ5DFQ`4+&RN$QBLV18wudqty-XoKtlYDc`sEbqL9URP*Ru zxj}EvGfso(f{DC3pkxx^wgn(am5=Shp|+7Zk5rUAB4p|#Q6JufyHg_js7G?5Y2@%q zk!v3Hi<4W|Z5IHcMDDVciR4>_5z<_CV7Wk{%gBa@iE8uH<c{SKOn7Sx6sxBui@(h3Uw4+(vZ5y-+9`IKmdkXf%l{<|4}wD#fCJ-AE4mb|lZ& z$d-EhL~zqQ8EC{hRfEPhVO5}!BjX)3wss%bDt+11*hVZKn(jOWe#Wx$XV&lzGUaOM zhA47kC=P6edfQ`GmsdG`xi&Y+w%0hOUs-BfS@zEm!{@rE6lW5=4Q<2jh&hzJa^|C) zw2rRkI)_y&k04^Ajvj`y;`MpTtBKs;{#OEbfC9=51-M8+Gz?w9X@i$a+#f?{AefUCT7<1$J3E@p;3tE+x+&b$f^Fv9ZPxSC?B`=(9B z!F`A+Gy)AFL{}W%RDp?BA&4-QJ>MeGUO+sCg+*u5R-A+qm~r-X@bM z{hWwzJ|V<~1$Tuqic;Q+3^b`R*8mKH5_FgnTmUY=R_WUIZq8hff}(uL zT0udsYr<_I-L#fNo5R~l<)CrTrIfgPgEo}vqCuA=%eOB0j6JZC);MSU{)^2W`WKhl5EY+VFV#J86Jy>(Ha%p8VW`^i0_AEw%!gpd8bM zG8zAux_5h$G^y?czj|1$%|&L~8PiCskwi(b2GVRpMnvAKm5US^m#TogkrCP5h>?Xw zMMP!>6&aC=OI0PaHr9Z+32~FuNL(8bAOsR1A&sCx@U##6wvTJJkE5#YHrt2&6ZZEz z=eU307w(Z2EsfdQVsvGEe)r?YkDr?#C&TRdI08DN?5ckn(!;C=}JlT03eb3{vW^c7k}dqOx-)|_&03PKJw|uU;HF? zShiJpqt?BYm3!b9HMzq!(!sojntTmaBwE6e4-S3l!z8?)hM@4g!Ye&d-=tLA6Dz!X zvP6)+qZ3&We6l+;T}okrDOA1mEnQab;g5XBlShL(Iaa|mDsbLsPOEj)ym@lG$C|%lYtfWPA@EAf{Q(|>xJj0RdJPt7=>x$EKRKg- zCc@JS&y&qXSN<&I!ax5}%z$rNVV1T-`Q95_eS@dLN^!L+aXTwSu4m_yV~n-BpErb{ z6#xGx-?q=sAe(!i=0;k~iw{;T*V_M+KBH;b-0Onvp8A6vJ^te7AHNT;4*djJfC8ZH zH=h|y=bhor=4{>@jEPeEHqxJdM^9^QgMEGM`>*`yBfty*f$Q<9Y!;JYUHn9 z!uz1m7NG?d!$_sU+J5RE{_%%jrBIN^#Ydm~`lFA0h}}6>+WP(c`-#oq9Y6f(SKs^6 z;~#xO(HCt(Y(`(Itm5MAQE=r>2Bv#*phqY9+8@4zLKuW;6`tA^t$|`K@jIj8Vry>( zm>z%nGmpOdV}l=0XL*lvw1KNgsqyv|S@A%QXL4LsofP2ImPzVba@8h1TQGzyFo5fAG(W{CV2mA9QL5hXPK*1zZ!0kA4C*q&}g9r$R+*p3nclHy*wC zq7n^Hd@xBsRqn?$DjQc53o!rqh^mj zOc11a^?#V2Qs0bz+NL)gcOm-T+<}th=Km_H&ZoMa?s*>{XT$N`&hV*AQxG5p??7GR z&*gA_HW*E{;9GCL>#3)@!+vLioTs|{=c(afI^&Pw@BH+?T&7%o`T0-4eBhOXXv zX}WQ`)3-pq+Mo1C-Tqs;cmNRe zM5j+;rtjF1src@PG*7kd$kW!8u6QHND?fTa=jZ4R`~Nk={%?Ne@rz$m&H+!B$>GUz zpZh+hykGpQnMdmv0IFg6Fdg|5XJE_1J8Xr4l<}hUD_{P?%inx2OaF|A;NwsIDO9sh-}oivGK_2d z5?_vm0$6Z}75)XAe-q~XYho|cpEuva`Qi~=HF*B@5N@V&O-g_vFQ-lh1zq@wfjDO~3l-KccemAO4ON2DXiWw(pm^B?y+>U4Xis34agG z_;+5F+FWYUrK)Dhpp9MkH-AV%Dny25R2dwU@%2<1R?ai4 z)nnn;gPIFsDrSSokax2u=|6>|4(w;h_6wLz51n3VsSA6 zFsqNg^YvFh`#JmuWxyz;MF)NG?y|bT70; z(pu;uRioj!Io2^z%V;jn-yeK0$LSikCmR48ff~W@e;p;DH3ZFD&|J9@JpTCm@shCH zltgSGK~7g~NXHTUhG(C#J5#8<07mKKY1WrNOa;XUz6LOOKOl&98=kNBPy*THZv6cF zmETo~Y@u5EJUTkb82uJ4gx&^tb28`+`_t*oN1y-nD_=JL1Pt+GVVKc6xf;pBB$P|y zf<31z^0G`Ocdj{&wSo#|bE*)b1}5N`T`qG%vE_Hgx3rO3v5^7_|3qn|tUBQo`k95n zd*CZAjLsa3vDlVdNj+9Em)v?TRMXW{MpO|XS`GZiH(!2{d&iCnXH%v?K^k%6btyOf zMf$+;03a!JT&#sA8)>nfbd>1qOk@U(9+ z@c?3<&`mvBfBZqy8Uv?Z_{6JU7Gu;~JY>>kxfYaZg%yFg;d@YSAYW0{A3tLTH^Hapbu{ zcpv@bAF#Ea&=YsdzgK-Z>{>W+veqkR7A#X%z-w*|LE=?@Sy@(N9P%DPZ1T5q`TrCL z%wNg2;LRc4&cROB7c~1BXA)q%ptn^ND=9p%SnDm>=|63f92Vz|;P&2#?~-sv<~YU* zKj+k`3#y?Tx3Jfo*^Nq7-KAL~j^AqfGfC2Kwtq%d6ICDm*&n_9{cjsTq1piLghPJa zKmOzkAbpmLq3PkE2Q~{lr9Wv4$@ut?V2b-g7DO!mThZdWWpfC#w1r?^KbTPq z%J)Bk@b-Yehu)2DBA)Y_13(8OZV zH_(wrb}<~fAN}a>U;WUhUj5W}wF}x7zxM(b5YiJ!r;=2n7_5+5MP$FHZ+z}Xh$N}i z=Cv+FOmFg}YWrfPs5zXV4EdZ%dr*f?nY@hAW4)en3O#b8^t1qK}vC!e%ql3DGTWa2MVbYG>D z2*h>tx<`ZE`QIAiuLDg|48hbDGfC&1hT&;7(8x`KEFy8HKdr@$3A#}c4+wg}kwmez z=YK>V{IC8D9ulNO&Z$fk{cr5`XPv>Y-!pH->G^mvdm}D!2%c)2&d6CF4p9m>9)9wL zmtXjdrg8xPi}EU}($7|OH2)JnCKHAg6Yn1-(yC)-_5YwAE_D0-%=;7xZk|m0lLNTd ziLigvnNHzn*JI&luuyak@Y=#>I>SM)JfEH8PR0QKri1Z_UJ+9YSwDDDo2Fzd6_&UA zQ|fVY<6n@IdErHD*&8LTgK;YuvgmF^+Yu@)!13+)N(vtyLgJ9)J%)+S3mZdjC$M&L zfs0VgHTb*b%&ZLS{QN?lbS(cid(LU{(_lIBH&V25>c5t>pH@&1y_O0B$k$LoZPQPz zkOEm}wF;0^u(TGIhyanxIZ$GAt^W8MSsc!4mi^K_`eR=E;okB~m$s@s{c~DP$NGkJ zwZvB`JF=U}teP^!QSckTsC>8E zu_C)EKgkYoFw@1wrOemmn87eHEOjbC2?Rmi8O@OWcdR#x7Zg&GB{d0H^jXuhB^PcMBdR#ws2 zH>w1CNxRE#JEZOY^t(=eM>gkEWHi-uq(6V!&S>!qe)`7G`5wL7m&)7!L)P(2lf(e^ zF0}VMH{PUN%42cd(QeH%gRJ}yegqL04o9*mp49l!VN0l+EFh|?bMHmT8o$buNBm(H zTyitW`P+JYs4hd7eF4#YlaMN~L*W&tw3tpuKYHoWcR!`I$RB)(e4`>1`~MXxpo}|A z=Yk@B0uT?yALynt>6>pqYWupdkc;3;3Cq|=VjeZ)?Bw6;uH-U*>q`qBt~Uw0p6FYDG zCN&EE$!yS{-h|ZGx0`mtqvf?dOQ7LT~0I~Zo{Qy@AVPpXhB8&3=#~=SgddB<^ zH%i_NCi2rCef3MYr}}A4(2D^#d1dswKL%8^1FW2G40z%ZsNxZ&W7nH>S`6(I-Wdl& zV_2U$7+3qJo%wK9d)OV$dw}VooWLr_(;Vh3;_0q~S^#|ksMJY)@ssqP``h2cMwM3vkP2U2_9!^H__)06 zlryS{Bu@V=)Ei&RRw2U*iSa8hfxX7L_{xWP)kFzf>Ztgex|6~^!rK4vLA#uWQ~i4{ zV76S&h5bREL3V{4+g3mbB(@mTY)HA$&W`x;SH6H74?wfWUw9E_DtKXe^{SCQQLYLI zZ|ddK6mu0#X}Xs})Tp9^-R9eNZzMMo{_kJozx(UBc~Dv_6%d~xN&Y+BMMqq+_y6nU z_08F2Fglwu^>6Y0h8KS-dHqQ<`9FX4-zLxe@Be-B`dv6VlukXY^K5-a2RXx5)hK^}d<%5XeQtDRi-;EV1v;qQ3V z0WYQr+S=h=-VF0aDq~zf9}o3g2cacu4>j$$5C5`hpXK@wQ4gfII_DK!bi|Lrss7C5 z*C+jDSr}1R$tXI)bPy!ReJn@+e!n%i=r?Db3p`83WZaA&oL>1ve@19-c5WOw1}2bc z@)nvF+3#O)U|kWAPUcetL8SU8*`Yl6)_1qtEr2fBsrIMc$>5UmUYe+P z5Yz9y!QDw`a^(|Rg``zXTBW45p0sck4%8Q6p<4C91l_cnSk)0CA-%niCl4T3&ilwB z$1GoXW^X7r{}C!`j)M+&CVjg|xtsgO+Av5!T3|2|B43^XhIi+)%tUtHdX70wI-}`j z2abKSMvs#0>7YL;q}BU+M!cr##k9KIL@##^w~~UNDbw zQ@PcV7Mk=g$I}7uIK{7>%UCmMmCVqv$dju^FU`9K4>Bt$)hrlAT6eHoJEv)l`y^M{ ztwW@nT{W{vP0EM8-b^3;Vjew zP~hHif5a_xq%OK?HJ8)PtXF0T<6$qTau`WF=syU`U9)zXEi{IbY637$3C9d=*zdf1 zoPp~8V3hqln~dj|Ibih$xLKXYJ87Dic+=?);I^B7ojS=s3&&gurJY_wkX9A)8%g&` zw>iSJ{IHY$o~6HA4Rn|6EC3A3CC&>PcM>D9uwskfJuoE;66N~(k{Km)%qPIj3VyYRF)(Mbp zGQ2`b{dVUuhjkZx0nU@g$bKAku6Um^sZA#1Ng|p{QCQaQ17GjJmwVX9WApG6ubg*A zXZ>DM?s1y{`8D&fOuN zyX|)fETu_FdkQLmPdBgK&a8Wmm7L5H`B}rl*wm)`oe8cN;_7%6ct;S|W!-%CU^+!h zM1#2pn`PQ{`kt(yYtRzwqF2`(<^R&bHpjy`dm@j?>14k#ZTCr5DVr^<6VT!m+@@^H z3a=$-<2~h*fEMH<71~n9QXEM0X{7P_ny$696W@RqRz6}{d)V)Sk?AFeqxx_@#TzFS zX7Rm`WkHan<9@e4;2Q4sPr>UMKTNK;2OJ+EpF80p$W$+B0P~YOv)QFW3jma}R=*DB zm-o&p?IbI{E>?o>>!>?^}# zChh)1u)G-Nj!6{49NcTScd4%zfx`hp%rtCzzw3)3lb>gAuktqs|bZ#hurQ8&O!&B1|!S3z_7ked;QuNta3vXu62_?D|ushhIU{Zj&L1Q2*qv;q7 zTmY~;{o$o`6=oWUVj10YY_Vr?1Kl0@VIejhrh@IdOUdRc%sLp48dP<3z>3_Dj1`kO z9e@_b=79sY+pmqza1riYdA6|q%Hn1Zi!7f_9udNt#|{gw>KH%ewnbRuL=cti{#0&Z zGfoYVQcfmkgeGsfgE0hkcgoZFmN*y#6E7}fH7*BeE^O&wY@`lciV(V>NP8*=ByhHN zdP=^K`>}hECs^lxpOF%RDmyOb(=seS7z;NMR#g0T(LiGPQ*vO`Xn|!MVzynd05}uF zqE8&8DZ^zX*y$>A2g`)>gY*F`NU{UlMA#2mI^Hu5b8IL1T^g1+6s1zQL)&M4NWY+c zWHRp$CgTxza^FJXpc7*jrvV+7%BtO~rA7QX5H@R|T3YpJf{UV}BMj@3Ee5$K z&A}YPd`HBupB1P?1kbP_Hl`4UI}odA>;R_?F{dBWRE_2Y=F$!v@?B~k5kA2TOi3G} zS!FoD*sUv-@pdTD#do1n>Bvkq{8O00%^#);T?mr{V@|AyA%A()8D32X5#ZR0)<7(- z9OQ9gg>4xWy%gon=}1v9j5$DsjYR@zg@tVFQ#Y&#)!J_gaF*hu<;R!DmzcnCZUVQPw=Zf|_>?Z8HQnY7juq@x<2vcc(Fp z@8G@h*t^9NDyg013m9R$*3|OnM97ORs)V(o=O_;K<2K?`MJ(o)wW->>^F~##(E57c^0^ZliOhIL1 zYA_q+t03AeD6H7fAfsTfaFCVf1+hsR!z#!5^ z3s%Um4N-DN+%vErp$&S_+3&HnH-?1^|w0R}eInJT%$GTyuy z9<>By89bsHEUO6Kp(;84GPY9{Uq-r}zA!yhiER`lMG{QfvSJf%x1@~)RG|!7lIfGg zFsjumF(DvT5qR;LaHIZah#pem*^AyPEZe6|C^H#53>v0GBuC!vm_owsGn{Xo_guMk z^r<8R{We~1=fV$@cgzP6wW{WhEN5bJAex5c80K~C0^0H~u||nMIMQGKutIY)2)<`f zEjpcr=UI-&V;b0Y>`)b!i6GO2(`P$_8EJ#!n4YN9#F#81670!PmgTv!Y2vgaSW*AG z0>v%7x{{OG>9$HgiBjsUux=TLO&j`g{|qergTeU_LFb0e+Z%paJ94!!S2nlpDs#p#PNYhIxk%FyzRZwX`ErP~=}f!AkwsR=HZMRd+XT)z&t4>(#ANxwyMt zDVDdlw>P%dwkzxP_4?LYWqZA{xxQQ6DiqghTgCG3R;jeRRZM_Zlum)-skTyf_e9ZF z77?#nP?-gJjK&C%KZa5|m|xtrpGVY!fl+{~5Of<|6!j^i{sR_8z9=cE--e=&MG|@x*1XnVjibt2k zX+?wzcp*ViIy<+t=SbQ(5T#8x1rwZ*)Zjb9oA!Hv!^(A;{SBQG4pig>15Ae8iQ|bM zU_vHF;O@>YCX`1#uX6O)UUfQUmCPYI}o1zwQF`W&6$g&$x0l%7F*wAtdPk)?Ku zC5NY{)DKFELk&yA1x?ZPCgAH37!00Gu09Bm%&Fs6pSzfoG}J0x}q zzC@apka^0qdN`2;*Qk90O=S9p((cpYi=YPh9C2MU=~v9j7+Nyo;0a{f=o9`!&XD`? z+0I1FrRB@Z;g$GIF%N!yLqjsf9dKeg)$&wvPrz}Zc3e%_S0iQ8xxS@&v?YVMAAmIP z5@mx(3Q+&h7C%YLIxJ1#MFyOk+MF^tPE3(~9a5XS{P7!KBoR8tc1aIu;>JPRh1KV6*ORB_BawqUgd zmyI=rwKaYYw#N7Ru*AoOg4&2&f)s&85sM7~U`&xy2e|MYiBfCKwtUIp*y4tO`aB&~ za@v5&lBaq;SvpPRUpp#<&@qxex9CIeG98WIx`~WHSO@GnU)&|kI8#hR*pi9@^SO&* zT7BB?4JJyxreq>PEi}`mNec=qO)}T=M9z3{qepXW@UEo-4{&76X#z>AW8~oZu6}nw zCcH5nljqc?l6`mGIFrVo!gjhJM(;Qoytp~*CMWP@M04<9>?Ozaq`)C}`St-EE=;SF z9E~sMIF|dy7=uRp6r&fa5y7y;93y~1ZIVfvk2@q=2QcAMR|Ojg|6o%0;au9Ea2o`x zn=j&5D1tBGJV_8|Q1tiDu7nQCzfbl#d$P=enfX?n~}H$p;F)jIP_gvNS!zp zYkvL4G9HM2GT5|{S+J(U{*l8FTMHaj^0>v(hM>xqxengh`33E?g(N5*-4|Ep_V3Qe z@C*mT>J8I$X`;SCNsb2`5>y^+_iVHOHn@&-rWc)uuv~$+YvW*m%>w=%PzU-%R>HWa znHr~Xd!xsDHjTxU&e)!BL(2tC$BuXkiEm#pD|%kjF<~DA9Kaa|=s=x#SdP51Qs;P{ zn`@BUcFXA06-Ejje+aiF)80%o08!2Yj_knYG`X81Ynq62s)MO_-0Gz{D6V^sE((|! z1&dTM!_U6)@oiufTxn)kREXd>z>311hHSr7#PLk5vvHeSK!$SN%+ z;-o-@+O*rb%q*xw0Z<_HdEW2dqpPBv=R;eRY#+M9ENqm^Lov6vEOS2!PRk@c%WqW6 zus_i}r6IwdwJP9x$@<5Z)=FSXfPD`B)Ah0SHxa(F6|4(L8fj9SRx>m^@)3Bc{~eb~Y6GHuIspSCbfWbDR&$Zusrax(;0Q_mXrIG8+p{(Kz-AKH&vGxyWn8RzmNpwzE`{FuFJ`{Y)YpVNb|IA zK66OGt2@x3n|f2CgR4K~tqM34#j*QZH?{Ku!T5{l^`pNhoIb=KWn_h~GGME&Dao7kM#a{9-os^FM@8u90&;y9eKUsnFiv$43iackakPEOqBj`{Kg=^$Bf2X74@xY`^C{ z3C;yK&MFj}P+L+>0y&H)MN{1A_3Y{10}TKnfR?(jw#M1}@Abv+T&@7BH9&n-C8*d+ zYb>BEZEoa0H^F9lMUvl>87+<|cn?QII8-@(lX#LnguiqTtKqyNl_nhUiZG`%opq>+ z#32G>g`VSjZ3ip?gb&J-=ftIBxFO=6(0i2khMkhDgA)u#c3W-}@2FG|$4EG=Lt_z8 zX8M;#(}jbL*<-YmkR9SBD9z*9M}Tr52~`U+xpbQMp@hQaBe6A)Pn-SU<@hh9^iI7A z8U`QaN#AjE;gB0sFcLAJ3d~V*uvucRY+$}A#b6sqsa6M9b~;h6X`$qb#jroYQ3&fE z4mvwF>Y2e(4Lf8;mM)i{3X2n_Pi_=bdU7E>e}i%gd7uLaBpi9y@IR%RsmU_p$Kkq-e7+8(&y%mb}SLjlBh;Ss5Y+Z!8xmm~N7*>&~ zElFP5k4!YIl%0PhS1wFsm&%W@>9UsgNOBUbY8i_a1x`S15p{e&DJufeNiN&~`4gX= zQb23&8k9)OhBHm`7Ki1#(ix`(!$uZMEH;(aU2&SqpS;5Esb9_y*|Sq`8xTi%b=#o>5A#sc5JSEk__QQlbTKd_ zI$aYB<;v(8Fa=O>Nk%n<&NOGAnx_pqU9_D*O`P-Ha}lfpVuoW))x1LSVdR>~qJwkn zEjz!=(Zf|FN%Ke>wy$0x*}@)9VPQ~H5PC?|=>IHq6iF3mCQHl+C*B-NcEo=avTN*& zHjOrq2eKnP$0>No-yP8wgih}}&P=E>VmMX>Pna&cpNJRzj#-hGwI|{X7ab@bwhC7b+h*He2SDysS@opM7OZZ##B_gJ+S<8&#W^P3KtX%g|3v+ z3AVce&rV?3b^GB=Pe4mECdxC56qaG+n?RfD?^xSbgR5P996QbP@qE~mrl6ITzJkH- zR5~MA-yPBi`84GLWWW&ck2z()LXM55JvNBD8SLP6fI2VJhL#L_qS481a*D+;-f}T~ zQruj=0-FQ*1716~hFN0EE+JYXz^X=8E5jJfX@{*a0s0sol{ax5C zy&TwpYmpE)5x5&rG|eyI6gYAP7B1`yIRQA1!F37-Ihe=?=iGrK&hYARQXf17L8Mj! zmmK>YUXE`-QwLHH??maWJ4JmqI1TI*igB^7D!h(y{qH=nOeT=cS{?ozvm4iCw4?A27ltfa?PRV1gamkh{c=1hck7 z0V0?67YxzlOJ9^Wn6MBxyR)cy$0t0j| zw<*GG&l@c-qI}0Sn_fAt2uDM*Nwf{Sr42KlgVRhq^dk#vlZwl{mYy7r&^)1(r-RoY zR?0!i=EKHX-YHl5+5^ov2DeWmfKis>I)-5f+YV=s&o8s3{houS|A(0-s?)arq@&F?n5M@*ymU^y0qZ!9>MQro_bAvwa=p z_Vkdaa?SQ201uu83lEn+@l{5=5VxlYb8PX`+&9OROK>|uwxLuJwoa0#xstjFev;5R z@ov8dISg4dCKxqVc>qeWF+5R3j8Wr02Qbx4Q1dj+BQ{2C;e5<2)TSigH}eCCS;skt zVssYQKvaN2tO@VFVcQ{k@7PxSx*`i>xvr?8 zX#}F=*+V!ui(({4Pwy~87?NJ32O4LhtP(W-O6YKt7SVRpQhU>mzM4|r4dUqBQ>*oFjp(P{c;)kc`EpP3eDfGOz5T&_v^`^-s8Izey^OQH2nxT%lbfw?9B)4C3iMAthX&K6pOV=x3;o+(2 zd!4&|2Kz`4Ro+}z?BxLnhMOFP3Z+mvKzJ6cZy@XwLfs)N+)cJ*;f8IGzz<-*@DIbA z4DB~!vFKI`+v2qi*Js#Zd32S)U4;V`h5)~vaqAs)uA}?1WI(QfS&TP8?$eJF{nGpo zov}pdYj=EzK1gt0f8=uS&Zn?5_JKf%971gc*a3uh67Iu6!O{ZGRz*UbymIdm+E_40 z5R#ZcZ$!E00f2xJ0J|_6jiIUqr!;jwC&Gik{g*z8`a3n6>o;_tSOon|6TJR%W_SI9 zRb78jNv^*jUWtR5eu?*m{uC~dtcciyv6S=2!?!! zJxJR?I3=Q;Q7vz4@gk)+Ep@G&L$8GO(H-15$Hs87XyGs>Bvzg<)-zeu&t_?%VRwii}@C zBEJ?{^dr+I8;O!KN(iU&8`|EB{*be!;V6I{;}ne_P>O>8iu4jL2pkgZ(pcLfpi(hG z)qremd|1A1Al~odWgAS#%L}*;q1uEvdn1lXaxu&*@{*Ezrg7LZ6LV+N-+*IBex>&s z<&)PqK_t6SelFx>x|fnX75GE$=Fv+B1M~tPy`}6*5X*#M6PZTv%L`9op+k7)u%#vT z4n@#DXhRE$+LAtUUG?MnaxhnlxX+wcQ9}Hm3-2vUo)7_DsAMdL3^a4ayWpk}mDiycOwOduVF5aq@ z3iaZ8eRF+lbE94hi@Z?UEn&QcQYERZS4st}LcOxFTdm_dIu>bdeS5dEyRo@l-K^sO>+8khT7uE< zRw}iP4ZvV+bE~$oR@zwG-q={%!eXth1GuHF-TGQNsTQ}lZ*6XutJ~`vr6Q7R_09UN z`r0kxOubmHl-G)tt;%Lng`eEodKn9H3rn|I*#^R_Zx_(hPPM$XS=&vjo7=U` zjq$fn!wcTn`t(E|nTDiJgs8lx#8}-r}@;6KM z0wz<~x>c{$HrLm7*An(vx>emSmP%`zgaB96T0?uA8#VN{wNcvG#C}NXYlY%w zy^c9<73yUGtF~EM-?~MpRO%bIO6$Op0yb1qFIP8i)wgcd09I_B?d@8jP+#9Itglro zxXoY3#;I59>l@${@dyWRmWu2^aW~lTkhH<M%mg#Uz(w(~H2Ywm zaNA|f7v+wLO}QK%lYp#ZEn01)QULS;Myrk65D*qGk3_JLX=C3F${w_!X#U!s(< zsZf%5Vh?Z+9l(QeS-B$|Pp7z(lD@9-q>?xrYBxwM##|B0|TsFjav%~rcrKE7RR zwd=>_{o1pK$8S#-6PgU?a?;qVwws41$CX<9==kt$wF;^<_G(xh6}GI~I6svyvoN%9 zrp`TfpGdjZo2Y=o4{lu;-WMgt+S0nvdiEA5CVn7x=zKaZ3u5ClO zzH9fQ_@PMnS>Mo-E!_Z&^93E}iI{OZSdq}eC3oQ?wUCeL^rV9EU2uI@w%{ujN+aRK z0URJtIAANXLd;bwNl{0!@ft9=R_#QD`0CMa=@7E}z`1uVEu5eE6N}qkePX$VJ-fWT zA%rM+=9kw%LhnBKxC^goab1AU8Y=h!j#E+k#`MDZ*0B22!UT<` z>8IGY)JKY*uix7n5CMnEu{8T8sjB|^Qjtc`v^9~*R4ooy%<|G^pT#u0oe3Vo z%4DW<7KM}UQ%0e*Yjg0-2(f$0ry4k~=?vH27|GAz78P3LWP&HBOkKwZr3@Ao7zCve zLbi=0%yZBWBa<+uQ3Ssr%*E?4m<|Lah&k${J)jRQpv*~Ym@iGoyzf#E$SWAQ70Ir^ z|8cp3VH~Ly2#+nGi5&>9LSBMm%wgLW&MM3o>u5}F)ksTW1&alF&EW@E7KU7ldt4#f zW`A-4gUxg{=!UHuy~G}-jc% z+{Rm;Uak~d`W$pzvW|vxB0yLN*pX|a1yAl=0_(^R%}dN5Y{k{H?XjmFJQpMvlhz9N2Zf0 zwg=?HBLgRskVx$0icM!Qj9Gk_=#_@W^D;gC%|nxl(ozM^WF9 zMGvPGWg0jtMZ%G_(_E$I;fR(tVWWEuEm@e2xVUpLpTcSInqySxC}<#=$jEvsYqBKM zqIzYf8%{KnxeOL{UP`!bhy+HI$h6_3Go}l*c*rTKl)F>(>X$oNh{4FSh}Wq&2{A*{ z-KuYqg_X5Rc7)pqN8gxEVfYWzt@d>~C0oI;u73;8OJ$J`!a~;NQD+b_H+$aR@sHS1 zP%x^s2nH=13>M6+JphGKz49+LYS(ET5I=P_wXt{DQX zh`|DL=*+cqspvo-St#$tU6kTez&xbatNfd0*OUptjxPOPQMUm141`aPY$3yMzJnF? z7W4rNr#_;jF4o-@Y8WyGO%bf}Fe>ifQpK;ocbwa2P@WW6BWOh?LHvp$D_jd9v@;sX z4BLrE8~Gm%nhJdpPHNde_#TT; z3B@jRBmoGC^LvPs*uAp0GF!jhljqLp#HD`Tkc=3EDE54sKY&!O&$GPC&cy~|uOie( z7mg*XRS>4)V0;*HWwLdQV3<={EiWMv`64x!YHSEaER5zm0vs+6-AMsrS0K^@obc6` z@5x0S7A!T%h}L28;137Idvdbu?q=>X9z6AyH4|Ale*dnaqLq8PO}}=;AtW4txafq* z27#~>@%fh_FRc`0;F(8_d()eK~d)(VU)vLY6GY zgilmb`$lmi`FyV!fkc-Va2;SpWod(<;Bd?}VKRYh5*NTl=V06N)V1Sc zR7Q@URD|GBo8jE91bfK>1*3qX%oA{jj?0*d0vJUXbJapI4m?uKNChr0 z=<6wwBL1+Z2c?{RkwS()*GXo1(_AVF6G$OE5Zbyr?h+N3zg4ME)*X#;Q&Vo|_G5g5 zyd(i#rLb(eZDS4qMwh~Y*vyphgxNVb%kI<-6M>Ndz=Zwydbq6`c7*dyoVk~ms0}-p zd^rXx?yx$j&eF3-IWNLGtmo-rbeZ5-{=lKD8<-${EQ19@kEw4tsK^2`2 z9j}RjcWE~x4tUKhSH_cKM19)(;QEy+iXq0zOG-IYp-VJb4WCoT%<9>r3}_Zb z$thS@!@*sK{t#n>CC$z!;~5@@q@Kaa9ea%ZC-Fzciy8Mn$MD$~F&Hc^3aaRqXp$GF z(u_U${m!9(PE%aq^)SaY&EVtH(%Nw5T~tu)%gpC~r#r!%Fb=A9Q-BD*crF)w@MM6vw180z2J3c-<7T=}z z!6BTO4o?oM?K^OVI@xO_hex%8W^=E-+c<0=Bfs4|soYUVC#}~wc&5D9sJ4&F%_h91 z!kR~KuQUrqRNgOF+RxOE8})a#>*dDY$#E?~(qXf0B_jEi<98ml+BNpOUv8meH!Q8_ z`mWTD(~)9W94S1xYSnhRc~EG}K~2u>GJ}RQq@XDm5tWOd`)^;OYaTuJVo^WEg-# zG+VSb>Q!@II+8&AvQJ%lwhjah*0D?R+78mnY!(b30(h1m;H@>7g%NOaD z;5mWkvP5?o0<*yqkO@RL&eqS7`{=yag=04M(nhs^cP^g#@<55k-U`8M)PKp#u2IHmul7uB zFR2_>K@DonX8CrFRP(re{LVJ!YOa^JkIP4Qn#r@}rT={u~vJp zaR5qjvcFq9wv@`@-pT%fORIsbWy|n8U*g~dF;dqO`BTt8Mg3FKKkNEuL;q~*pDq2f zt$%Lu51P?G(h5A`^-EeQtVt_{HE9J->Y6956xO7b!kV;FT$AcWtzKM{>P4+yENEUq znl2V3Z(S>_YlU^Ku#WJw7!qzq=pU)Kp+(>w&%{lw2fuawvP2o@rjBz{N4BLUx3nbO z_EDjvFvB1FVk`J!EBM1!@Q1U-AI=tkI9vSTZ1IP)g%3Ojh(FqjOblPzN6b!yh(=KWuD6!Gk|q zUBH7ctuElfmsS_>;7eN(@Zd{Z5%6I3wG{ymzO)qq55BY&0S~^k6#);vv=splkVjh) z@Zd{Z5%BQZg^apH@Zd{ZS=X<1`_lQXOPji7HVO(J-7@%+Hg(J3OWM>egD+`Cw+z0t z6+xnnfRaOa0(DJXaf3Z8<3r=Z}$^Mz7G!BbH16cjuK1y4c2gZma*QU{^n zDJXbw%SiGRJXk>WQd+`z%y2XIy6X($S$X}93Ajg)eU6wF?EUX$!i)vG8BD1+3&?pB z4|F5srMn!#E5P?g;|C)r(iuzIid#Xa5PGW`3wn9 zK#v(P6DW~&vPmmbnBsipZCBoH#WBqwTQu-FO|g!Q#YB$4EP4MVO@&7WcrRDl=@O(Y zj(*HPL5z6$`^;d9tM|>>yf+x583v&wJ2#l(atESm4&cv)Xxk5vgm{si?qTye{sIcm z5oUETg;>qk0UM)iJ9Jd5_U{+g10+!12aJiKll=ll@mCqD@k^?nC6o>>Z699Z#XcnR zu_Ne`#d#CIG}J2@4E`KnKA_tg8A7P>?staI=0Amt#|xA+$>Z%@a@hBI;c}s|g zZzy@DnEp~T91sQ}+`PM(iZWVM3-}5J_se95+mKB9lPkigKVc0^*Ur;q-l)A0Ye8m! z=4tJ=ovIg{SA|!5ChGK-enX)?IE6tYOfff~G{YoC@5n)Lx(=va_PdPJl953qe5TZb z%d&d5coB)z0(+2-5^j!Ikl7ePDnR@sY=JQPBD!JokaBPnVSnNsKWrKY;{cD4m!V6s zY_l7LS*iRJ%7SPX6m<$QH}0zl0?aWw3mD4kvPXImNvcAZMP5*cIwUN!0EC?`g83L;$?3-ui7c`FLVQ*@OZDjd7R=o3WV@o} ziMz!6L@#VQyecF12de$_Rx~>Zw@bz{5*HLaI~dP) z!w#lL&TD`usId&5uhBq+?T0W9>dV;@PsorL?1}}E2wL+5Nv6l}l6bfTdQj|m`shuu zI$%9^a0NT4>I1LKUJJ9~Dh=uyn?Y*5kMd6u6t(5hLz{l)CosB8I7@LzEME>VLPT%k zWe2^Rv)`HUO_oK}wuTaa^0=eOZ=f(s1M#X=HG{%#o8(UxVfh*2(w!aIp&QQH?nw6) zSi6hPjA1lfiIl8+`_PqRJ?wUpv-0TfqZmaWaENU|^LS%i(V0CszXV7YIY?0(eK=Z# z7azhEZL~G2L~H6dt1$`qCXr5o$Zx@mT#~bbu(q&8t5?b3auWD2ZFm`T zBPOS`7<}%9sg8h0_IjS0rS(D#aVZ>$`>jaAO`fTl=NZ$ClOsf6!gi#)`5=p|GovWByblh zSDJ6wdF!oqTWoy2@@PD|x){$9UwL*u26r&ztIvo-#HX}tV7cxioIT^pK_vt?TUuz% zlBiEb0qe|tnum*z6O<-{R?NFJxb&2#OIGT?xLUDWs}+NX*D8~~yIL`reh}deVBoA6 zeAwGwl(!~RPEZhLG{CLY_d1RwA*zTjIB8zVs(=yBq|tsy3`gpvs*A*=XG!Fab*a3G znT)+p#e$sjjK?83zl&2pTPnA57Ml?TF$SY1R6X^44G8gF9sqmcw`an<>rdQ{9T zki$5+a2mxX-I&oVLrm-khkgxDBQlAObxMobZclB++)RS67AsT)q^WFM=;5YLc<=*H z%5bOQ)z3`I=qmkapBm?+uU)wjfLU=r?e9inKVSq=rFR(@ciXsGnrZApUXIia2AZcA zQV?Y){oR2Y_w+$-7Aq|t%qy#v+g!2SipW}65m^f>B5PqqWG$SoHV@d8R)PzRPOFt$ zk;cGYwOV^Ct{W^~tF^Zxaf8ilrS?iIu3Krvb%RxQrGD2}g!B4}a9&?=zOaX`G`@`$ z=drOOUTv(%2yUzhmyH$o<;IHZy8$!LM7^l9q#0@k32+T&%hWO{wew+F0;K|4B8R3= z<)&&I>fxcrA5BjoNN1C&#)@RL&Y_(>hp0hSpG6th*qJcAn8P0oQn=-QQSV5!1IOKq z@PP60em#E&BiyN-o>JJpza$%%F^BVrf)6wu%qikXix9ypk6?E!&Gc(X!(gE1m%~Bz z5q5OA5EtFd?N0Tmf`3*x#_R5t<6xu-GJ5B{f%c;rrh2w^)%+KANWy*1`~Bhgl8W?H zvk{Ka9Gt_9V~AhiwV~>WY2%zfXjl&q$wmN?4gm~*B;Vs?S+9u4d~~6A$3q8X_M>gA zMif?~9V?Qe71^;B4??gdE6oAU8>^LD@t}c=$7=1ZNO<8E;w&Vl7oD!y!HN_XUOlS~ zc*TU-^_b`l#SEGaIBn?c_SFoN?8!Z71T*hR;EBN%h; zyR`7VzVN-V@V)7{V92P_P%187mKxR2oAW}Y=yS|=2k?jL&A zbjQQUK?Co!JnG4H`X^TP`+Vw(KG&Q1(?v{_E~Aae;9k!Ldfjt3JU~D)i-6x7!=RHEXNww)X6tWg%Ktp4;wmUgnslW<-t%Mlh5XIo*l;i483gwmy2+l?Q_}^*1Rq{yG~==zVcO3oQ4>z10DWuVz6>X6z`%c0CVLPedC0R&qLKSy z8NLI9h(>0G0$uOYxF|B$WZ7b6N?5o63~Wj>BSKs=I0im#va(zlmD$m`Dh!v9{pJ*T zLqqc9W>U7WCBUo#SD>^A6dkiUp5BC-Hv=_XqlKG`y`Vd%lB3ybQWHx(2(02GJVB+H!ddP42EuDEjSv( zFGqm|N8KijP{WJ#Q`Eus$*_Z>#-SjqMh5=E`V*s-!?HJ^Mg0f=alo6;#4sOjcW&F- z3WezBZF#>{Ml0+*btqmF`WKfND1+R4uYg>M zBh~pC8o6ziPsNslH<(y6@)=mDF|0v@F7@MC#{-)Es8Ge?2M!HW;;e9`ZU%ny(#aoY zy}Lua2WrG0c5Ry6W4ED>=68n!L?4TUTee}k4${^Fu$l)2-p`;8xM!vuxNcosazCrH z1@)|ptUm~%DUN&Df|m+UVZPG>Vk*!ykc4g@xNTbxyOJ-2Axc@K6(-3#@@6USh1?mO zA=nxtn8EkVKA>5_zPArVTe-x;Ff%L*_L*7EJMiJdY3llsXD`oz!A8QZ7_Nl3u^H53 z2qgRh{+`g-+Tdtz#bQFmnN8$_C+XYD2{~43a}SpjaCwVC>M33ch1YHzs3j_M%R4dV9u2m`c=SYXk#IF?BxR zhBbl*qSfbv7j^|M5nq(@87(b_h-UcA8CaE$Etp2LB}gb`jf{P;MkrF9;RZ zpC=4zAi+UGu-jtmqbu+j;KQbhCQh2e?ew79j+&^0bWE6D3f2P5a_*>gUZ4rLHm^DJ z;)gsJq}_xq0lBd12=SLjh)CWshYO$V6M0sHaZ4vxB*R3U`CVoUlY*O7I0EgC5=3qx zE-(fn$SG8tc8@M@ zBLS)S#SEZ8XOY$9YLkFGMr|zFQqf;5%vLDl+Y~+*7+>=R=x|=#d)LlbgF&m zQ(T{n@mRXVy@R_k&_{ie;c+~p=P%98vadax=TqqTLzBPH0fD7*f)8#@jdoxSQ_>BAt6y^{awfV*!0Cb&b+k>-<%j+5e1=Cy zX%pt1SWpn7eWY4I1t>~Pr$nRzpO2Tn@aO?2&B}D9Jl!< z!0#F0j@yc1v4j4DrN@Ee6|O5owwGp>JCput*t*MI+J$3SCK~V{Xu%D^d?)1OcXVjx zG96!+VXPFa9b=u01Yn3NK1;k4@KmIHcsgJh1&utoS2jm7+)MX^WrhT|sW_{M|m;KK}lyw?hf0I4nKfeQ%s@4a_XTDnq6IdlB(pL= z26cdk;!}vV(h#!%)-En*SI)(dxVX|e*URf(yrM#OfnXAU1;7CZhP5;ANn2155Jo6v z`SiS`D>7XG%6P#MJY0BRcgJoLXspy}w20}F*>W2EE*KN!7#VzX8iC*N-gQ*Q!wY=I+i}%B{ocLu zEnf+%0eMFyCTDK>C%dZoYIPx9NcJLso(XY_eB)fOWAea5Sng8pCmCNbfW|H zz`l~H#Wl4lv<3LXDCV}$+$lCaA6&|z0Z{r&T5_6^aTTgyhoC#%t`gjrO+Q3spMG*f zvSzCFlBmgP0c(wAG65(pM$aUZ1U@mX<<#qbP^uBeG3eH9~5B% z;$}P6OLo!FXLw)ID>pNSb0i~#N56b|>2p(`zDQ$)$H7e zAUTQ`pRasI*;QUT-&lc66Xoe8TJc>6IfP{_w>oHB!nn(#<@0@Y-5g&$I#!Gap8XH9(|@ zr8^N?7JCK-^IfpInI;E4GskDr*jDCqCOm^ju}sco=C_s-xh_Q_JF3}vrl^`qG6@r> z%Dl=XUQ$F1n#!bPo5b=n+xl5H*6Qw}HmDLTy*2QQwEBm_)v!NQvM82ygCTMkZA$D6hvPISjxoWeXe6?n6O!GSJC%;6MS zHAV)c92)|V0-;J_kSAxZ7m)1xKumSmCK{7PqboXwO>am+>yB7v*c`Z)BJ!byJauuu z*f5ml`K1WvOjC3R5s}ph-FLnrRE1IZz5{JvPzQzyaYP6*%&r8hb&sw`*noDuJcMZZ z6x#3-U5K6V)=`*32$YwVpq`_j z1QZaR5%{$SRai&I@SngtbR)W4r0rO}9WR%M_`uLQX6jiYk?vHkphpgCn#(1OQ2;4T zBv?=uYxAAM>zXYAT24)X>r3PCfwxS4E20fR)LvZI>z#$#!ku_39d`n- z15GMCN;*SfrX)g6SV;Pc-;S36fy@daLyd-*DPcbe+dLA!EnbtUWjFvmfT|vxu?G|b zgxYTr3LZv6J)L94uH2`5MGGNRKRGuM(3IkVNMl=m_jxKYmQxG@h4oEft2JOYBp z8Z2DYF@m9n(Do`9JhR}O5CBz*?~#@mro@_ClIt1?vzO;AZE>Hj715T6saDOk8(Bfo zwm-R;20edrIr=p5^wg42sf9zM9@84Ck*||uBP<3}!&LMPv|okVWwG@6BQxQvIW(vM zey5rCP46QLqTbGAf-%Xqzfhd)SzMZ7agUpdu}Mm5K|5uuXPj(AwPf|+?HJ~ zGw6h_V%o72R2>i(By7Mk(HAq9Tt0Mq6~+XD9%7?535lPvnReBzzOlPr+FIYPY;KoI z_1$u{T3@eil(%+o-CAGUUEi$imdlmm_I9nbwYy!e7k0~qO086@6t>nk*KSodyi#c? zozTP4;UVJH;8yIcUxuMG;sMWOx{iqwG{146kh6`XjHK-sr|F|o3u;qdiS|oEjWSi@ zG-c=G-b?#{G6@2lMQFB$K2cnWxK&|Ua7$q$q9)wR`z)U#uVANDQYpi>-3(rpwhx_a zO1OLBTq9RC;`UMKE5J@o?fJ`)G#re{U4`lN?(^e`bm}+&Jr0Kmu1)?TQqVUNAD??j zgn$K{L~$J%4V5nu0+=4d2$Ia0Fm6@7?~%dYYdaH!m)ozpVwOwAr#LFL@Aa=3EiTm|AuTDP1k-ERiMf<;amOP-5Zv?q z^doH{P1}Xs0n?@EsJ-Dp;!Ego(>fS{Y!tQ$7gc)2w8n^J5(3ro)J_p(Q{2YT6sx`d zS>cieyE3tPG(#jks}61*W_x^?Ehgwjb#=BUS(c7uKv;=6Pjd8j#)J&2`JgUtZ-*sp zd*TnwYfs4LQ+r{NLkqfPt`T7YGtmp7kO{|(n^?q)@yHweBB!__5l#*Bw**#-m=|{V z%(Q9o>5QKbi)dpU&15{ZQp+1macQ@I-htb91Pn%BfMVt^7j|kzP7vxb?yl>|g-3)4 z7;b|EP@;r;M%aT*iAmoAQj{qjX}_C5epd~)Hij(bAz4vY=Q9zL?x z)TH4_47n0`4&sQTdPy9L{WIaJCJ{9XK0Iq4^ro0xcXSw1e^}8egebbc1Hc%0f{&nXv_L->>&UoHS*I zhM>usPe$?$-q4|wVn8^5s5HQaL|sg+DhR*y(4S=aV)HnMlfLUAXqy^EGRj7<;Rykn zH|!g669*BiYe}%L!9XsOY|t150A5Sb#gFW9T-{lk=Z2_2&|*t_&~4l3Mf$KY*v1Z9 zFuY5hJ;EXwCbVx(nHA{+`^L`rr3alI088_6e@dPTL0`cDuqbxDV=AulAOxVi?38!K z@pYNomo5YDL~=YE2BREf==3K-u=NEE%c2XCqAv=;|5-gw=m>KW-y7m!+UnfHD;|(? z;O7rVYIQOS2EX$J*E4x2G=!lpQ<`I8^7voC3s*G$-tcz_@0QHp~leG zlDQ<7L2Gz(bqZe+s(`gnK21s~8t_q0E0QK;@-a~g!H$M-1fzRkc1Kun+jXAx&9&oG zGmY6<0ldZ*u=hY+EBQP!)o|BPpyBw-HGO?i_P0}>gqlmds57AHLRMeBg#2!B#{=gn zda;FVk`j2&#ACaNF{6*GWh1_D=OlT1TL+P`QjI`}>5SzgFLyqnoD)*6Yb!<2upd|7 zkw^d^xAwwv*BpKR@=;fxNL<3gTMtyYEGeN3j+dLSEoG!O7$`6q-XXM2*?UPKg8nUP zwM7MWb{L&AOtk^?M7I4LN=M}OL>#8Zh_0pXy=uM55H_kuP?o6(%jDPM`m$ml#3d`I zhKjVNr*KnUT0`n6lnl4GKzZe-o^BqWL?0|%359&Q^X2orCW}PBE0{xpoNoR&>fJPq ze6exGF18jKryV@2lv%AwUOP+p&r!3W9(@|_WI?Hy`n}0CZP*3|9FDvV;K-0;UX1S9 zI8>2mYMoR!?i4*~%G4zpET@aAN4$bCU_jV~MR4VuLMm<1O1g~nw!%Wj;=m2qGGm*J zu}_J8x|o-Wv3{w^{mR8AS1t{c>!4w*gtZgoFE+elIrBKi87?|Y_dfBoJk6Vni$nb+X0BGBJdAgZ5s-qH4D zDmnWK&_v!(kq-3avPKP{ zn&9+FcK6i1HK(Y6ok%T`Fh1t8&5JYYuh**YCvR)ol4A=c&s3h1uZMgD!S%$SJHI`+ zl)G8Wf(@nl!uvZrNNa<7@SjGqXkTRPs^;$$)miwU#-!-$CjvKZ7`biy|T2r2Q7<)r5|EH`1X zc1Ia5=pAWDx0~iFerF7!{Mf5p359=v zBzgU%6GsROK*_oFaFjM+V@fDAwBWmdNkg$}c|qeYBfV%E7V+dC!U2P#gu5rLE!qZ- zZ5s8~@QcYJVGJUPWFyZ?4ghh^sSVQ1TW0e6Qn@bv-rV! zubgpEL|*&_QMdKGU-Z8a;R-_srvQmke=zU^DFim;6reO*lA~Ae%JBeJVmtup1hn`b zSRZe?3(OKmjaqo!U2ls!1vTEff&jK~Z7&!y^qo@>7o+Pb@;fpWRKWUWRJqHGLE=Nm zl%C_C!geIS5VV<4P{ZgDF1BqUAqzsNErDc0zH#Tyc^S*Mb(K$C;5GA^*hG5MFQ1$V zXpruPd=U&eJG*=)^=B}I+tl)Tl!^VE^I1?XBNn~d85h5*-c{qdG?(q1K7YLo)Ajd59Nz~_3j}IPD zWFM6sltaR_sRU0_rJ|@C2+eRHxP^PV7#__hT=Ct(jJ!rz%uy2X2g-7VMfO0g zAun;cqtpv}U0;A_OV~nX(k&Gu$wtNSkPe^|=AVF_Q98G*1jj_oQdov92y_I*%Sx~X zA_92KWkulIc~4E9Lgd3|`K=iu-wsA2o+?i&=d>Mo;0^v#ho=x(J41~`5_X5sc)T3UeN`O!U_S~djc$pO?FgC@tK0KC*s|fRfw;wK`V^4ORmAiE= zj}=8?ETtvzKEU)IWwq9MU!Zz;ieIJ!@CR%-a)}IZcuF8bxND3SYaEW2_x_~1VQ+-) z;pt)@VL69?Hj^h;(8Y^fbKjvyc63p}NQ-e*qXM!B*uy>opTWd{P&5`GX9lb35glwm zrd%TM$H%@QZ?q`x;N?q%ItJrQn$&(Lu1@3FjwB+c{m$albRr=NF+$PA?Z|XXL<)k{ zTLr|P?#D+U7#%K*G`_Gcrw%?#ipM)xUW-d%2NpsdnKM$Zzc^ojvMyzZQ2}j6`yBg1DK_Wn4=lOC*H^Zz19qpuQ*G#c+ns{>!evDyt z(^-O93-THV&y@EX)%MZx;WLeD?YP}~=TWVF=eX89*=uRMpy{l0ap?w*?KXtU)T@B_ z-VpCIRiI8fdV8hWF1K38jop)0O+L5U&BpBmG}}0+A11Zq2XAHR=si!k~`ut$7$^K0E2&B`PJC`lrjYHkq8spUdfc@E$Z& zxm^t(H#yV4-Md0ldJniFWTE*6?(W{)lLzj4t83+ojX+Q zOQ>Cff@N|g9z;GE3*vK1T3_O($SZ_wk)DvQ^{LLgT5FXncWTvkeGl4C0Y&Bboky+q zac!?wZr0ir-1DeC*V6pwHr8&n%guu}G^U5u#=&ic_EtVkTWKBdwQ&igQGcfZx7<28 zu2~1ns@9~ROo|o0Q*PeLq{0u}RxguSt?h-0Y037shG0QCW zn+8VR8R^2+4$8ZGwRW?0e0ZRnBeQ<8IgiTFlv{g5k%Lw{leAmL?h@3=r#B9`4PC}! zL$*sRhbQ@3ncbW1#1-eB<<5@e5t-WMz1z63)Vi}DDd6!Wl9$1vY_8CVV2qNQfj|W~ z9u`7krcCYNncChVC|z`f$F)l1s6oI6?W9=^STCN-wwcY!gJd}@B!{#x+fe0j{|E%M z+LqkhsFgy5Nhn-x+^#ik0|}8pny!=Lic8zCHJjzzMi79h&BozDws*ft!pWyunGpbW zL9&(tET0_!EuW1sk_2MpLh2VGoG-kHcqa!qFIu$88XV2UO<6PN80+ zjbzRfYmR=07aE$e_n^_MoR6WF=2MhB+O+l*O}bxQ4qMdz5M3r`*{^P8GR)+pA98MH zOX?AtP3b-GUtp`LjmDwtlh9Q+_jS%Ji4_U+;ss4X6m&&He=f&xz=d%9Y?+>rBEaxY zs#Ae5QHQ+5LR(F4J&st#pRl$ z)oc-8Om`+A-NU2vGfq)~!D>`R3})0uumso=yC1xm;bVXRPsgW+r^=u|sTBC@{@{|= z$prdbHUFSzkn~K^o>WF`2f;rK3Z=;w&F@kvmQRHKP=jOQ`qMjRmCLF8JsFK*elVBn zfd5uaTaM=zqn6kP%t*+)Q@8^X;eGQ8#sI~jJUko2$Z#IHZ@d8Jh{*8{gkh7}Mmk=s z?En)F!M74-Wq?m|5*AP%R9Vn4*DDja;lgaw{xLH$fh=De*41T9om7^U@RN)5pl$dP zF_i$35MezpBDI+%1i*GnFaqZX>kDQC7g*7xX)yJX&~nZ)ngM_2onQ;OY@Wht>-yq* z1NMfqPDE-Hn}vTZIvB0iJ{d`5qhtRlY*>z(?8)pk6wJI&9cwN7{A`#nYXo&ukb`$u z1Wd97YB-ooJ3f*x)F`Sd<--Ba;B1BsIT_u)gUlF9K7?Pf&+#z!yY;#}sfxSpuw2GG zy!W9Jg1KvHZg&Dz_>$ZTNCiWM2hb3;Ev#=apwsFe=5x;QD1tc-bUw%y2WdGe`V-od z^G?!mGNnY@o}6||h0TILRjiaj7zgf-9Dz}kHgwNmbTEhoeL}e8zGL4FodSCR7M@lJ zhv!Qz$%4ChW+~v}u|M#zPg)Idq^!f+;HVp)=B zR1fN>xSe&UH5DE&Y?WxZs6Y&%w4-D#tZs`B2d@c?u399)?1Mw)6(BT6=!929qhcFb~ji1=p{gt`Qug%CRGkOE%_z~1(#x|>HvSaH z44W_CaFDnQ`NWCZF)_{>eT{@VN-G}A#L6YdtMknXhNna`y5soTh4L-flul0tvM*1K z2Mr;BY~&qyrA(e?(}!4wc+f}VN#j&6BlsVP(#p&AxMMNJH5PC{G=AgiF9MmWMOPKe z@NP7B#)Gk^Y`jpL;k5R_%?xm7$LD9Pe-%b<)~k7`NzhZq%nPwerbSPf&CP|Hsu*jT zjzKizVCxz%EA;Dg#ud*t2qznup?jk7iguny_sp<-X?Q#FY8v{;PD!+tVk*Xxxad5= zahDwmPfG^d&n=X>P0G_DL@BtXOb5Nbn-$@eB48Dh4~%>!YtV{&gWxEc0(S|xTb$;B%J$_<_^Z!G$p*Yi&Z4?7Q$Bi zbl}e1)c}V9Rm<4w$9S3{BlTr_Sbm-Pnx-35S@m#SjVVVK@f-km2^Wbu!{m_eL3;Av zO9;&%MvdNh)Q38)3=4<7%5n1=T zY`h?NU3TgEY`g;)PCvn*D)83Al~vqv#E0s_?H~=6Bf*{BPHcWBH zfqHR>RmA-^J7?Z1=_;C($pR9fg5_7gP}dl#-Lz&8)>mj!MV40 z00a~HN*ynL{|Qs&fN)B~$>#ya!H+PrpYMO(JNV}j>}nrQPV3+0m;z3Y<9gDD-+q|g zB&xt?yabhGuib9pg@OhRh^~MoTY}#9TrR(S{BV45^zroQ&^&@c1xm^<*_i966ZBk{N|gxvMA~T-2d5+xs1_-mPvJH=-r4Hy&NedSMHYGF;Qi zQ&^1n1f00;Q0LYR>rcs`17!IH?#4-TMu zBOUR1UcOZ4YEF%Be&Zn?3x?8lji(~v6Y4%u@>|1PCs~l%H+P=@pe_~&N++w%XL-li zT?BDen3u!O7G`e=x6|L{Q7WiXgoi$cBU1?nLmL>rVTb^3e&9!j$d6r{FY!n^yDkQ z1Z*|9Q!L(7H0M0WIbx}_uL1k*3Qpa)YQY~o^!p2(D?TD+xzep0T&d*6xBVrId*85^ z^9(f!#XD>=UiH)tPe8f0KflI9Z?J4p{}F%O;R8$xupf>ty4Ww?d2u&6Bbt@?g`s*B zwDko3#H+4QN7<+Rw%XkDO&^s^4dMj^x-5e~bN{!a|I2q2&M-gWnOzSok))$?QJP@k)bS3`4C^`D!EC-#N#8kZ^Gj=(mJN$cWN z)qZfKE=_F#?JYcBZ%g<|vLTjzLxOm`MEI2kb4UsEB|_{(x~y>p(2F6oO-I^;jd@17 zU8KO_WA6U>@hJtO`^QEmBswlrsY{t*2ND@A+EhhK3nSq#)yv(T%5hh({A_G(ZXRyz zZ@t;s+S)(dJJ{HHy|cA_u={fB_1^C8%k8b5t#`Yd+wYEEzTDq?jk3Liqn8`o@3!8& z!*R&F-C^_S-RmvHciTed>x09WTN`g)9vvNRy*xTNc=vMq_2J&ut5);{y?%!;4qtA( z-g*7qw`2>^-X~LY%Q2c*8#~)M1kFnjJ&O(slpj9v2f`L6F1Ex8*Kz(@Hq7 z+Gs+9$PjAYp1_Gg=L5v$p+pD7V@ywXtzVPPui97e9s~Tq1)lHF(cN9nj<6}PE1eyB z`~9nw8*oR2_AlDRxKN+Yptdml@XFs&t8#EUMFeYX$CbO5yHyLXyI#+lTTQZR`xleO z{3jYCSD)g>=%YQsCdUVrqy0)ZbcjHAbay$shM^XnQ4e+t^h_ZRoS8UBg*b-{7*q{* zKE;(gI9fuz#U5EsI7^aSC;dgsfM}J}id1EyFx5ph~|B06f<0ti-xHT1b?@6&>cElt=wDN?%H0WG*dhu4TZY1Bh2v9Y)Wp69zJZKBe ze=?bny*^f?iuwNOlir~sZ6h^o8>H%dy0?Z z9w|TLgQ&?+6u*&h@i#?%z^GnK1}Ir_=pH|1uEj41OL;#hGNNWsEi!}tw%XQZcE>ef zsojD(;`c{!M2w$;@9{4&e(7K_k)w=SBQ%C_f33ACYMfi+W3qqz7}2q45=3Ue#1JEA zjF&N622jO~kq5g&L7mL{D2i5!RF1M=@@_!xX{^rpO)Ep$54E#G-7ebvF)G?}XO5=i z^PI8FOl@Ph*b%z@j&)&{rUHqz%4kUIV2qO}*4BY3gT?e|+Y*)=Gg%8XW~{bcxM5v4 zgwX2%Xmt@UdfW20Z2;d^=9fHM7#WN$OKm9@3b>ARq^d2p?N?;tZk3o?TswWSY`-=D z--R{+xIC{nYQQp_a(E7q6Oxzer};Dsb&4uID`ANWc}GGI!CTUfuk$hO1VK14Gg zeIJhtWGcqpcHwQsoQS$3$AsBPH+@@{lm=;j*A*9XvP*1}_#W-&Xb)y2%1&00;Kwfu zs3Z-!lt3QfJbo48xot{qBPIyT^Jy(0FU3|uU39<}%GilGHtePPgHgwtHA*0`9hbhu zQ^+6I%+WcDEYfz#c#xAjnOnxz|3!^mjlH)BIULNQl&FkFA^I7MP3D&4j;fsVR@(~+ zQsl+?I4ay}^pv(1oPNW5B_Xqn&bR@`rHshaeoZa8&2oX^sj;70$Dx6!$(XZk+4UR7aY%UGaqT**<9tOWxEH7Ho~L|*=p+VH+x9I>&k7*_3yj4Hfylsa$%BT-_Jng2 z<|o-bnQtVH0?rWV2iD{##PYztsBVK#ws1G;=MQ!;QLJ-*TvKv9zOh zjh#2BI_|B@Fbp8a=#j^yXS+OgI8XV6*?$2&$l|V{f&GC(_9LA_8PUYXQLPVtI z8aQ78Mz}xmw-JJPd=d`F)9$8HWx?tJZef+8Av|IclWVDzm|xLPecLwov#KFlv0xS1 zGSrmwl@z$t%AYNX8KXhwkjJ&ue$*vj?*Aii&sOEuh8^Wp1?EHnTQ>v=RpzFB%p~$1 zUrHhiL$Tk##Vk!%G{8Zc*m1voAsp7!%16@o60f(nc-oA^YCnKY1y-6SzqaXI$y>1c z`W0m6oqE>!@3-J`IX{AZzeHoag`0IY7EBoUAf4QAzuaotlx0sBQHxT*qvzAd#lsIq z3^yI&S@zJTU}Lyvff3VeN;Dv}s?05f zH{X`X3s{}tCX-IKALF)R_r@!q7GFKW_nsbL&8HLM*wC={h#g7-(R?FD82UY2mW#zv ztyR^t+gvy}6>$P!^lqDk#meZf#FcL(pbzCRc$;CQoI`N8mu-WvO-1%vw2oX)5nC8_ zvVov_2Y;2!=ky4nK9}lyWK%!UfXEf6GT62SJ`nwgrJ^bEM(ODQ_Z8mM=LOj6C(a%i z$d}O@g>(4uH2`o&VJ_EF$|R;pz|nFg0L~xbSalC(r*&A;K|J}Trx8LL?Rj-l@p*Rr zbcHomV6G1?9xg8Duv1d599`diJdMLxFo2&;|GcMVT8@3SGm_zR47W_7eSr^`64hp@ zGljD%PIZ)rY@!YnJfoZjeWFv1`o=KDbQI^he7>@U876Y7t$UYG7W)Yv-nX+Q0tMk= z#oHB^Bm&U$K5%-}W)1FNsY=jH9@s7UI5(DjGgx0o0FQC#Uh!q-SG z(FjI>oo)68-k)20kBhs&J4irn#7s9U{5*I3Ll@Ds>L;2u&b1N-^s=PUhm=)Fx&=#B zt^?>VS9A1Nt?bxmR~BVUzO6hlv>44$y%U58geW&!_P(WufKZjJ=_Z+>nUx-Q^&ac1 zMSn4>N-eHODMY1BS#RSRZv8C_geFzBbL7>em5Ocs9@AI9Y}^*ku~;mh#VCWRMloDz zWmsFGzlcZK@yd6gU5H{*!{d`6oF16qk#P4;vwqoJRh=0@1|AmhX12`#W^dwj0;d!Rm6%*9;I{44#>OJs#oyCC975?grxM8_pTvQX{({IF@0~8Q96v5;!xkl#NlV zIiWlBKH?TirQGjZ@@x^Qi?7^R#n;Of`eL`~EOYvsaVBYN1h>E~LwdzY`1nmPgr7r0 zx`^=VjPbwXDd6lD-yo)Xj1@{HdIz^K%=d_E_T{lXMbHWjZ?QG8Rq#R4N!?S0FBE7g zqP!FyW6*>6ArHTqo3h(<5?`x#xU2s=d`vE%xin&F75%r7me|#f72*~m=GHTIUMmlJ z)56*CiU4guT=>eOYZ6)07zn&8+_aXgq~oncQ*-ei!zKWahAbHNjelB5%qZEHv?bMc zp=)}CT7g(Mi?5k5riRLWSX*-uC2d)~p=nEP^VAmTHL*57c3EkWY`fYLTL&PAlQPlD zd+4;ep^Qs&$82S3HA7Sd%CnZSgcmRm7pbI)>(y*&#ue4ekSAp1x((cP@3Bg+9hx`#g9XwEBL-g^b8OU>)AbD>~P(f6Iw;I3U>x4y%qk59kgmA zHcJf;KFI5^2yptACw{9qku2~T&AxptKe$hZ%b5us-Tpuh$=k1t?CHvt8?Sf?)pRA* zPoSGi^vznCDb-t-V?xeL(Mn{V%V;2FdGTW+m)^3qjLpxVmh*`FdCu*@GxR&z9qBJc z^Y!Rx!i@x*aDy})ZoPbY31zGL8OiMcg8jh2x8b7V$v2VQDfR7ayad-=EU&)Fnq^85 z>>p)+FYh1{bqa@7d*Lc{0T1<}*nmtWEW=g{wW%l$XB^`{D{7$=ZvP5zc`S#|cb5yq zY?Rg1KjzZJ>n1RB&^h!1{6AkE+-mf`hXpRW-G7z+9@W#;cMSE3y0U3As*>F6{y?|f zl(V);#lz2BvXVKD_l0M(#5#*Fa-%w)B*x4&y6ar5b9~KYa}dM|P=}6$?FM&Et{#0J z7T=X|K4vu6C)^muq*cuTIy|`T{;P=tTgv@e?!Q#-Tj3oT>>3g~EaZ|e8xf|$tpTS# zx?^Mxg#WjNNSt;d;#165h^{FjN3O$34E=iy1u}^98&|E-2Xt}<<13T{Ge%(x2L%H- zOI!%K<%PKXE;kBbPG|{aW;p1}N|`ob=Cp4o%Qdx`lA^L zA}(EW<87uBjN9e}8^<<{3JvsRTAK<_cL3$rXmkgPJJ!6l}PQ zrEX46!AYbJ{yu_`!eD6TLDmT_kPnI;CQ-wfS|j%;nTP&{xwvzKnOo$~aKr#s02^cO z@v`5K0VWpwK+sHm@EX0*6tcW|#hCR);C1D^5q#h^l{mtnwE~?L*#%aUM|70^%$xyS zVw)dku}|3WsW8M!g_1w@6(bRk%z@{32X=lxft_FLWl`8aK#LKRVZ;&yemDm$YVIoq z;}48X=(#}zR~nbL;zI;ErrE`6%finIIC5YeQlmBYQ*#96 zLY7q~Hih!j*jgZLl~au)CNezxVa4I$%Z$@~;CmK8kiTKVc?9K6o=HXI3Rp;W$pS-; zx{06oIyRZy6N6*ToLr9w99Yyg@yepWX!uYZxC@Tja_fiMsmc=D1vE^w?FW3__0GOzU~?e}=bH@PPXq+z8U-d)HM zp1pqs`6t2^f0ZgY2G=mY&Xi+hiuP13+4ecb)_nEP+3aEO7R$BjQbmZk< zI%18uRbVVJSGaGsd`=IIRXoWeSfA=Y*s@r366+x^Fkx_WndhR(*l%~E4b_|$-2ZU7 z3TnF7E&@qwm$)u!OJP3OpA$kjRNyZbL1P|SzjY8o;_>SJwz?nr2RuHho~lmWA`!<+ zP*boTKB#z}ibd!(K)!AY0QJ=tm1@(9EWI(LO;ja31seAZzE<#1hBT76-L4)$+kl?H zEwAs@Hn2g%KHOOkimWGb6sh}~W+=Q)Xc5bsGJBDPm&2gzm<#xWiYHpDzqu~0x^RdI zY6(2a2HI;Cx-9mw8oGp`UNZ(=n#`dhMjfUV7>}ySspzUU2`*5+$<-yQ-jqPxBB(gA zZA14vE4Lk?8=;Q!8#Ch7(jZQwYqZ%Cy!p3z=D>5rJAnOH5G1O3TOnLW^%1`t49qvs&iYEvbHKfUU9k?Lsi_;cf;tv)Cl9zUhM~vJ5oMVfShV(YDBG zl3_PILr$=eE^#fT{hS&Yx^Yjo#b%}fu+FO6gjj=xQ{pqZYJXZC{^2a=kNFB0F;5?# zuIG!>e}3W6#ZuY-C_v6tbB(-|}Sr$MKEITvH42 z3Y#weU2p?bD>X*&zk+XTg+YrCG=RfwbJ&2uS>hHpUb&T}`>(^r-36{whl0i(%qKMg|$W4tuk*jgg`g((ZmIiuy_iDupG;~}7@ zTi9LXDg!QI51hjn%NZZBLwE+ciIP6E%w?0|V;td`(}b0oA5()7Jj2PixKN1;#TFz) zt=H9pSc%fV-APrpc@H=O^i5mlmVmcz`2H{q1<-5w>WudbPm~~jQWEm1s$9N=)?wd( z8rWOG1@7C$UqCs{LiC{@!{ffb_+ian)59Vf@PiGQOmPYSkdICuSPaTbsdEF-aC$%oQO13 zM64?%6=lHg)$P;u4E+mKdDg|x$b#L+)Bpc$x%lW^&?8>11S^b1*9VPc033f413Jz3 zm@c9^9Pq;M{o?*T?!R3>$6Lg%;D1q*5T_T->!&c-UL!mLszUo_%3Xb$Evf!Ncc6_D z!3^OEgZCY=W4OoGPCG;^Aw7g-`4D9fc#um>;zI+Wu!4Y)*C5ZKj-W5nga%qUxzYP? z$O|fLl;MP1uv)_;BC-PmK%J`S2codWB5&_@grV-#7hn$LohQeTkIXy6uqx_B(buCYc46G zumRV~;4x9K0`qNz|AJ?TV+~{bH-19xhG6vOIcYEfj^)7w-Ibv)vPZ<7hnd9Cw-2M$ zkBbLHJ>%>!Znh&}!?1d{QOQ7{H{K$pJ*X*z$N8!$`~z1~^#cyazQN#(T2L*3Dh=dx z3TJs-pnSf+ihi0Eci5lIa(gdPjq@>sVkVv-EEyy8U_^3j6f4?lVEYMPR(fKJ*(=XJ znq$P>#RGKMvxOZD1M{fMISktiygT9ezK3JjXJzZz^)VvIpmh`nC1(zZVcy~H_Z{ph zK`iFUC{VpTyd^RZ-PB63QL-Zx2sX51Bc#Qm`svdPpHb#f$m59*nPt3?RcNlmP}akM z!NGV<0%DNY83yQuc>NdHFGRvR_^`y>g@sCHUsZDc-7R-m&Lmbcq{mh8uA>T0Jhwi6 zmkQ<1HXH$Pe|m`^%7tKIy#bFV#kQ9qF$n;igv=C=^YcfzLJhQI>1OpGTjyfcvR|*x&wng0 z9ySJYww&_5B}Dw<@{D?rWhr*whO=qI*)&YP0-9Fpq9rKa!0&50vxXD*K)2P{0;5XM zU>gX9!br;%-kOvi!Q6<`d1S)NN-{t>tf$wKmX9_C{g3B(@o3H{OQ`CJl(}0;#-xc? z+vh;8@0VBq*b(Zf;SsY{cF~ec5|Ax|_FxAa7g8h?N;lp(XCP&zn=d^NDbM53wYMA0 ztDJ7U2WoLVYW6(nAiH98*&3iHedn@>d0$;79B!bX&924pvKsEW#WNk=6*(Qvu2jZx zy+%JwDHlub+kdvTrSX#Xa71S!kgco___@siU- zzTVgfhGBAZkR26+DSDpu@hwv{xy-u)s*S#?d+8rL}&#z#NgDO^Q zw}RMkN{>GK6VMt6yloJR96jd$*mCkU5iYGJ_BUyV=-t_L1R`a~N6OH*^X8m#5^%dQ z1(7n8BV}Lg#*{|NOM{rZ+hjx5@=9s}-?887rfqzRiY!~!ESDEBP1_jcUft607pQR_ zpH8tIC-3QnhII7bhBIx%XR9hrZ;kA57N*c6CyXbYvAaz;c)Z+#Zx%Rv`-;n7HrbN7 zgBfKtj~e&7?Y1}<)lZ1=hqLL6+fSRH#zO>puH~AuDnZ(XI%mL#OFVi2A8A7vXB`|5 zAAVUs78GUc;Pl9`<-x_`@E~C_<*FBWPV79c%0Pu%{$Lm_?5bg#kZ`a}3f;F~J4bED z)J4j;2PvEEJHbagJNUx-ZtNYsLO;nZ?z~nJ%4Pbd&1PCVbYA-|-DzCL>cpwUDf)H) zgay(tX#}_4=zyK^RJcy1?yEsA8i`rb77*C8zQQT=78)sH5mR|bTp?(up1+SpsLr>J z#`RFS2V={SlGM>67$&+T0V(%#P^&_9)B{w8CCBs`$H?rbdZ>2R3E*Nf#%IF^v>6`1 z@fW7IGd_4+^b`EWmWYLvAXqyn0`O-ClMZ%~8${(5T}Y$GAjm4^!`1*}@bGwZr$67g z+z2uB=6n}gh}=Uc6ofucQ_Z#OClbQ~=0}`QJ;C1^C+S8yata=-QSn0t*3bc_QAG(i z#UMRgS>zVc6}AHltOPtp!}J^8&bg0Njkw~yG6Ba$U~RNWbG`!u6X+0kF@_#k7x}(C za0OYhki8OrVA0hdRPC7o-IRMnM)>DXQ?p}6Z4@Rb3*al4$D1M5k=ht&YN5_b8x_+l z$18g|Ji$8Q3{s`l7Vmg=08pi?A zY%X$E&DRRRk%WQiE=d#AvvhJ@PN)3U9HgaEXn--wa1K0#0%F3?mYf z+>h%v8e9hx8w}lxr53@{UI+@)6|}^=3useIH(D1Sd~4f#V@pxwTZKScJof!_TaMSt z@8|GT5xBOvDr6;z#$df*siMBEgwq%T-IRcxP#chfwavFr#GTp8L<-;p^?reqdvWjM z9W$E#j%ncLdWhY-Kv^tx1ruX5H`7o*Ra|V6>o4cx;R-YPYQTSxHh5$a+|60c+KddR zFbF=lx1@0FzI@1bp@&Cc|)gH+W*j5`TvuR<}I&>t$6b# zvycfq{v$=9eV{r1Lx%b%yj4HyyCz!N=8BSqS}&|U#J52F zAsF}g8`=jpTMuHis%Qv;5^NVRjcpx=OC}g+RVfKCVN!)GtR86$FF= zmfs@x%1?o*!TF5_Wtrx}0|Qa7@4rIl(X~GKa3l7^NKtu(>_W=+!$>)=yo%CSUJ5gn z@ilZU;8o<_CKNh&hk>_Cz?O@LZ@l)ZI>GS3A|BBF^^qu+R9~)UMbuzBn=-r#gagwD zglWAZzYJE1O#a~*@fBu_^o0LKz4Kx@)m0e#Kynbd;p#gzg70yhFkGk~6As|SC)OAi zsuiezCx?f)1>^hPdsZ8B&H;*c3|2 z;a-!Cc+DEV-QTO3CZ=&g^h)lxnM0QTP@a_i9rj!Hpc^KiUWE(mrLwb5Mcspd_5%ZF z9X<)lsYThg{mTQ+XnFD{v(va;4ug0^(h!c9JNKxu+FqN1b7Zl=xU>rSg*k{R^2yBA ztQAZTCS;0o$tEL`)(ssQi)p~M;#b&k@kCKI1kn>|W~*x592SVhj7Z|x8D1)5htUrS z!px`3E4Q$V-Yp-4JW&$|Up&>!4C7mF+mqu?BBA|A>tI3Pt7{B&BTj zMX$!rT5s%{B42EYJdA?TbDWRv&iR(Q;1Za-=d}gk20b)M1kzw<-`Xbd3gSG;J36&bBWGj77fo2KAGIc1{zexlmXf7Yrwb2_+cO0jo!E6L# z9Piv^2~t)Vm>AsX10~hIm3-bQ=Wh;Nwn5hti_XR$pA2rh3EMo55!3+@C-ksM8s&Ue zuS~?syoHL!bB$qnfe=_ah!C7%u-`@BpYEXuN~}uTHA`P)uVosKdgwsOt9c&hAuden z^=H7sz&iotoj5B9JM*?P;ujiH5^9t6OkTjzF zsx3f9HSqEY7HcE7i9as35rGoEoi3+*rE0_iT+V@WIr(W!`+RrAR(imgt*WiTzW)>G zN(yz(b9V6wy9VG+{Y8Y4u^7X@&$!EtmQ??Y0gTT8#*KL~p;HDTK&hJv(GTqCAH{}9 znFNZ&@(BCYPQj_gQPekA9j$aVN>BKT?yG=2E2LbdX zh8gRIGz08dGiB1gX{Rb=p01zDe$gMYq@U+oJ;#E3c{TZmqN;9SP?_Ow^5_;O%Jp^i zdkb@z<|FT=&33G~HYTHx;xkBcY-LBFJW~_lbuG_B z=t>LD5sohAiBMN?>TQAtDfF6fNP%T6Wg83R*@girn;}qy94=%Xw$5HlY(Q`VZEN#( z^Z+SeS;L^Q69x^W(5skT#2kJ@P(v-yr{s`>cEq?aJ>@{3u2lL%u$iD?4H$`tYB?sw z@3Ou|NMY(y1jPX7kqLXoTqF#ZS0iea5g7LFuhao#dQc0w&@Bv4V^K_(4_F1RzE?Ag zYKWPqVfcdCUXMZvjZu~Fy+3phiM~fB26VqTc{p<+B(0H3rUSreB~oWuM@PO$CY^5#`Z(2$1#B!v?-6-IecG1Ld($5L3U!slXtPEP(>{Y6t;bp za-~A2#VD#4B&$S6mxLs26j(AWg!KV9sztMPtrp$PA|-BbiyOnf089g6lJ@R0?-k6a5*F4n%aDj>GE!a2FVG~qWi-HOYzMOK zU#cgX!AQS0!C7~`lo{?-IL(BC6(&TqEox*4 zeDbp`Fh)-*o~Bu=g20_Bmix&XIlPtGvgNS4Ktj0y&Eezj?97$S#x3yVPhE5VP;JM? z=<085j5b3aBV|$;DdRe%P?MnGwaa|unat-GCd`>EUsG_|N8-dr6as+a$dXkJc0>q( z&63=zI#8N$7c; zNe5N^w#5PVKrO0%<6Q_;J-^U``1POxd#r)@b#bi-y>N$I!E2stGoBlDntbF+$^o?v zlMLd-uNx$I$>{-roQGA|3UROKePxdW?4TnT}ifGx(B*(Kwg1k7B78u1}r^)B+;rnPPC zBC617r?9aN)(R^?+lpshi@39oVWYLGr`C$0(hgP>4deO<=%3<&Fm2=mp5@URZ2P_% z7RE~WYdgM^mF!)V!BhpbtQaeFM9id{XYI5cJB40{Mx}5*Tb_s*Xly#E7xIme{ zysK5?>qvMtwQ7M@8XSN@wB;)~QB%EW+?cQ8%mwE_J8>U%?Z#hXvBR|`IQPN^3f$Oq z#s1^T)n`~l<0sl=@FCupU5H+?qbJ9HHF%Ej8cUi6hYLiPkrjv1O5_?g_RNGx{tvD= zXyEFVJjSfOyAfLL4sj){fAxqrDTi^+1{M>>C-;W~9g^_wI4#ysC7)JNi zAdWM-N9Ck~WgHhXqmMe}+})A-7s(*e^Q>7pu>>A)y32s+Xnb1(bX?rAn$^Ev?~Z=c zBj#>#Z)Xu)Q3XHK-ygUynP$T3#X1{n6wcTMr8lyCaL>-LdblPv`w6i_&n^>zl2kod zyRW^bTIwW|178RWe;nQ=HYO$gvSe}??&GS_)^rMZibYcs>Sy>3!;W$9h zn#_1A6|UrX;B2AC1w6Ws@JhH|d58TlxCR%fF*3Bws&#GG-|%-SJ%MDlnMmZxT?2*M zC< zeWcp{xX?FGz!~387#|Cge?}19fTXuTLWvOgQ}O0+%-C_s1tIPgc3;82tyyvs+$V z3>}=es1l)^dkPmz1-o;-X{(jl)(R+{BLWA|8L8G zFSq26=IjW}_KrYp?@Gz8=IvUZfD<3v@`XAy+-+`J^ZgI#+L=x_yIbg1^ItN|7<#KSh{$`2ucDVngbwBYt+InV=RS++o}N!~RL#6KqhZ_r^l>p|9!g$8%|$d62^Y1DDrH)I%6#IC!jsLoVmV|3Ruva|_eU6c^ei}&7W#5%45W4R5VOEs%E+j7w^(DF|F(04# zG7%Z8i78>{23~jg6jx-i1e+J)9bAQ>JVBwE!-XThMS~FOrjnq$Xh<+Y71tOVO`a>upumVrk<=je)FU1N-ar0UY387lE5uexa10mb z*D3?t1@!V`7VqE|#hutFKl^evJYGRdi(9)8-kec#stX$mkE!TQ`;OF=bmGJyPHr;% zscnS;AS6~J?&w_04IOOpFK+MfznF1Vzr&@^Q`j!hRbF3Zl1yev?wu@2*0&@%-;y-$ zS(0tr-UZ{73GwrgJa4C;VV>_lYkY!{ znlQzX?&WI`_LeQ-jtu7owKkmqO|}N`7+*ld=Ixur0gJ=Kut1OjfMcU@WINvi=T<~O zpxtI1f4zbhazDS}OsB5v+XLAV`M$z48MM^Gu0cHfAc@uX{7EevqpbieF5*qE;sJ`j z7<8`p;AZuFhrOOU*t(}S3EesrHKY!0wDz6=WYUg_rW_P-gLyV+X3jY%~n z4VD+MulU-7S#fXpEhB+REg}|a4l`vJW$-AjrX93BWN3@P9~X>AKk)BX*2IPT3`V+!?zfA@_b0cu$+iyFd!;(#>Wl|vUb6qn=Hn>Gmm)6K2Fr;f zck3bVF`;SqkGRGX>Pxy7vyPXL$XhrXD)>Nq+#Yl`$?N*8Ro3x7gCBZZ-JU3a~cMrV4wjz zStCw;`=#QE+_Me}lhRIHnnsLOj>IuYxX`wRB4L0UOl=q%;Jl1=fCB)2us(_zimTn~ z{2Y7W?|f0~o-3vC4p%=*V#MZd0t4Pu#4%bj{I%%!(UhIv#9d(FTdYNZq?tva(XBUa84<))FdFNP1O}n&p21nIWVlJo%eCNq<&4NU!6nsesZM)@kuVF zO57EBn42*J(M*u$iM~oVag~I}zqA?a8*W~SQ4L*=vgGP@_ete6m_$JYz67l#| zut2aG1w?-}d8$JJrXDQiFDn5rzFM(VFL1_VN@Z11hU|~5;Q5i!EwKj=SuE*-G!Zk zG`qFrYv22TiTIY6)nw^PqHV~{A3E%X{wWh3_Ua3L)j%cNft*8P3qT=?6J1(RD#4frd)IzXR3t6o3Q9}z-j*t@$#$P6fQxbjGU zdW?nMn|!_i21cNb>J=otc{=E8xU*~Zx}|di2r6G4Jmc;Lp0L147QAj?!w0@;kq)OY zq_}u$9+yr{?UDK_VcCGce_V9+zm z5A2?*9|8v3;Mj_WiL-QjmQA7^9ty}vc3;z?Mg$>6Z9`M2!%_tf2(mmwDZUWdR2k6$ zqo#42x3BzGKhAcnPNb|(q^wS)tWKojQ}+i#p7jhVYR%{)QnVLn3JMfcxME@TA!YS# zg!4O6$Qzr=*j+b^DS2^!lJ6+cKMPl&7x3xvB&;hq?w6xEh)r<*p1%rCm;uNOL72DCk*TCk4`aifHVcy>0OsEke)FpLdE|Pm{O(=&;emKlULm)>k zcsLw0B`%^q8Q5s%e#dh(jN#3xx&qR~C)lPJcA>zExD{R8dTT5Lb}ng8&C z#|8LLq6rW!Tj4B%lyL%5MmeO6a$9l;*{E>5ifld6AXeHW<)F42U&!cfSmDs^zo`m* zgF)xy=676g!Lnu=TG17)5xUwSUTehMHSrs*xN0eO0eF69;1L8XYIk!>WE4|koRwP# zj6Sx<<1PqvjqWfcOpOQG)IZ^VBzP1{yYBHtYxj|xF-1)_Ja)c8aFW%Zwatu#$V+^{3^IKFz9@yax8kw_5nOs21D8Ccl-n-$D zg_KL8IHpFPeSwro4WwM2ZG=4Aki!AAyBpUb5yZ*iUNaC0d-W=A`XC0JORKoFhdh^C z(dSojP>z(12r28;s~87T{w-2ob-28v*o9uXqEqhZluJ6irK4Rff;GIbUWHIc$^~XP zV7?Aiz6m5CW!*x`y8kBbCL)Dv^%5w~C4te~TvDjZ6&(VDmMB9ikz|@+P*`!*Z47z= zlsqzSB_lury{P|AY$;v@Cr0Hbav4TqVuimb>TL({#wQWyfbyG|!2|wS`8eiEOg7hy zMzSCMhxH4ptCXT=@T(F{xEc`pVv{2(Wv4(Ca~cK`A$A_*tdb?D)H=VuGr$x!pETi} z+)N?8Kl7C$1s1iy3wO3q(M{*hug*GOLZi zMJ$)^#Bjs4Tr8ADpO*@VR}Xq@!Xs2siHkwES`|K!$MOlV@c^wA9!8T7rPEXkBm_p#^Q7IPZJ}2b zvkG!FI<1a<*uUUILmR8NB;(eucm)YsgV@FuftCFjpG_I5p(H3)&+m%Sfnr{Pl5H9I zZSg~0K{*%IlA-*bSMBs>@djQkCikZQ`^YO zx#(6l5aJA5XCBnxQ`W?3bq#ABE7Lu`Z}vcVNI;7lh?zy^VL(t3$ov%ZDe`EdRc2s` zNEl@*lm8N8j((a_%oWOQMo3?%jOJXDVxT38DHxg&3KSHc`z!icgp5f zDZ~aWDjucLhXh%OBt1eEtWgf8(uE2zyRr$+!ds6y=aj2Yv;bpKG@Ta6)t}&vy9*h( zEKrVd5GFSM&+mDu%+xKm2->&{y^WGaRh9rNrl(B`WLDw^NSdR~a3;2y&Z~f2{=zw<37&10_eef2Vlm?=opDY?oGy`%yE)GqOuocj1JZNbS z_9}6wJc2dv*%wV?XA|jdHdf8aA#GVuV_g_<+RN9>+)9sVG+zfE z2rjq0QVO27Jn%D&)`&ePk4!*Z3aU{zG>%WIaF_z1gqikGt|g2C+XYuSXMPQnJWp(( z>e!KlvNW}N=?~A=U8fGADjfnRKIqC(mJ@Wc;co5Z(x_xoq!2~Se4NXr7Ss&?Y^Q6@ zlfZz28M9OHI-D`2-+r$(Gbt+fYJb02Vgf>%W8F7J4sreIIaTC17j>^W25J-G-2xF; z8%D8EqV+0o2X9z3=prmJcY)TQca*yll7|&ywDr+4P-y-|Hpc(VAND{Y7|o3Xb+Q@m zxlD;~Q4k}%>Nmsg;se27nk5{R-^R)qxvqiWSyOk8_7CXXtUD9qZ~tU-;#c=4`nV=2VR(U|fP}bK?fX&@an}m`jjGjC#z0m3*uSvfz(%G2i6& z(x`1rJ(#A%93(DtrW46d)CNV8aXrQq#o|T3y6B3W=1IE(g!Q=q#Y!8{s{#r16i91_ zy{$lisRAMz>I483DFD2DJn0RP$3Q$dgFlF#6HIVkIa!SLKj6+7%><=01$S^GGVzsg zFQm#Ag|YAz-7VP@6b>160K;}66MT)4mN`L8V`v6cI7^t}7Zk$a3d&NnV~_rk#f_Y6-Vj8G3wxQi3ruKE!?f_3*v)Glz9?eAy&~H9_2q* zqC~&JyQ*T7hC-ot*o1Ez|Be%)pppG{%BM}l58Oz8LSxn^_*U@5(o9_F+58?=&9S?) z24yCKdRJIpWS0RRmXE=wQ&`sdT5et;4mh`iBoe2eZ_#b^yqjeZ%-Lo@8+r;|J^E2G zubpbYJ4I@e1NsVC7RKdk=qYv!LnD%3!Em(Bmcrb@a2Cg!znJS75 zC4-y!u$4F<1buTEi|zP-h4_@snxrkKAD z>v!N=0Gmcufp0$=IAb36h}3poV~40G@wN@P#k)f=3cuz=-JirVjFKjs;d+j1DrVSZ}Mc@ujhhh{ZNkoPMm)L;l2yMUjk$?l&2!XIZ zLYw*~nfz3^si{zM!Bdp{#TXTw=9sgsHhIso3IV`BBYuPi_rT|xfqUE!m4_idEfA*@ zS0cm#M1))HXllQ#it#ed_n)p>A9`XZWpU?#|DN-iJkTOAplnElFKpw*3 z#SItx#^c3T5FV1gBsrmcPEW_F-4wlR64gR9S42v_9DMN{pjZ#OIUL^plSk;y%rD9p zx3{!ZaZe?7q{07+(-^WW+JK?*fH%9{*gsxKXiq~5NTU`t{Y!_V^&W?XSIz8L_{nPp3$;Y$v-}X*_J3rhz+p7xxIQo42?tjh? zP=55Uv-8vA_aFDpzI;BaKt7(F?|nJ@?fmo6+2`Yr1caG~`ou4ZuoG$T-qLS~_OpX}X!gjoLoKfMyRGQ>B&fm(xbg66WvFy~a(V-SMT-;+pb|# zryAr{7q@Fvzcs!hSd69-iPcC=3GbZI>1*kmRNQKnpA1dT$8zxE^6eZk_E0z8x8@yo zNKC$@ipODa4+NWWJam5ltQHa=lL%{&iRVlt0yIdqhnDfY5O)JplsgoZl66qOpB6h( zTZfg7qnNQ%NqYucoT_s;3{NH%9$3FeRIzL8-1ETw7(#PcHAC!70)USo6xEDujTZ<^ z6GWsv6A?(^DMYwsms50lQ;I9%{&ST{ON=R_*IzlAC3uF$VvuKgerV)yz2u352{_QH zDF!Md!f#oSDL2T?v0HowgORP_Nq2wo0B(zg;_9SC)QI^^?>`>iFpv)xZJX2e83PVMcz0SNge!@@iiqYW7>w2_NU+ZUP3W)kJM{H7Xln?U!L9cmiXkM3SM2 zIZ~AsJiTOM3@P8_MnEIv`PMdk+IjmxzeT)bsCN{LNZDo>Df)P|znx!=I7a09nsQl&1V~cBU~s;|tXa)zjF#fG1WOMz3xs}ymbS!uoTH|TpcIXY z!c=OCd~&pE%t9sAQAckYH~PWD=7TY^%7M*?Q>;T1F+-dXo6v5w{w*iKmQxgw#0{-m z&I~0~IIn}?utJnetr}C`mM=D^mA(#xHB!bBNEzcHWsFx0!$>(C!-zdCZ_RiW`lw#M zP@Dv>@L_M~figk($aj@%%@WqyWzu+&u+}nj^+8oL72yF@_><`UQVE`6LiqSv9(k*R zVDlAL{YbhgLRH`3MvHRCCp;Clt0!BF&O|@@4vwz;;0U6X9-y`aQVM$j zkku2etn(nn1U%RYPO8#7s)Df|**@or_gLypj-Nt$Ud}L8WsNoR z8XC|5Q{Z1Lxr$dsp~R~2ik&6YG$ikqF+L>hXE4(Fh-=?a_JT}MHHJ=DW2m|+lX3if z#yJB>X4J#TA}1LS#;*4XKJ&sX`;Ydl@_0l07*`M&J;?P$`-R7#{23>ig=jR~!C9{c zLp`iQFl_mC;ZU#mjEh|Rudv2m@O!E3pt|^F-SbT^IdIByZ!Idp>UgzhcjZ3-vv}B1 zRoVUr>QmK}`yN?Fcy;k`ArAq?Nd&umaJ{6xw}yy!yD15M7v@!0wWJNeCQlX>M)^0~TH4GzD5ae?f^T zViHp=Xo#zCP(-ntz*C_RXof+mexjPg&Dq8b>zFvCMMMZzi!PRGnXy$A$nxA#Jdc5w zzww1AW}&trkFmzpF`5KGudzh_HDemqJ4{0ui{v01Mw^SD#f?KSYrS|{&SfqDGY{Ar zFc)~apZ$D9HT<=!lL!}!ha0@Rh20id7YKe5Pkq1Op!;RWXHj3&vSpzYAfIWhsGThX ziJOB5?NS^$f^nX;!Fx*&H3fng9yls zMr7ndS)$_h30L~-On}*xl`rcez>}80A%NuVY=o5^Sv4mGz?~d$5xMvj%XM9ubccZ( z_by?ls!N1IvM)Qt>E8Ty1p?LO!p_$ZeB<5)w2sLL3G9u2w*vGXKCc9Hca4@)PbFGXFU4B2u+VVarMh!GQGzIwPZASMm<9 zW;8UOMfjv*3T+%lORSeVFocTr70|xVl_PZ73K$2ZS~5@|RwF8doI2R_N?7~X<6&|g zE?OAAfr)RNQ%zj!iH6-#j;Iwl9Yr0AX~M(w+OxZ;}Kht#SP4)@}xCZ zA1nv@w5~fBX=OAPXjRHyt{}wKdd01n+uaFgfFNICZz5htP?S^^GS!Zifo=9~p&VY* z*rstDnkB|GGF2FDxWd#$g@iY%_=mYu`BVm>8PXjmUGf(fT2v0nj$K8hB&F2Yjdlwh z%TuS-lrQH3xstOKbXxp)B$PH5D!x@qRrdtJsVL;{>Mqi?{tV&!KsqY(9(K(YUB?bU z3^QOQp%bAM^PGiP=;2~TNrsq=-({HyUDM-ebzp-0JOB;q!f36bBpWak<6j$l3v zA3Jn0jX4&$vnv@;nSHv*1Q9zJ_)2z|IH6Ma6#Mo8$j6OxwQbTMXPhIW(~C=Wjt@YY z!3VKi2 zg+ug{e;%C&sQv!)x04S?2fytdf0SJUoxlH{TU(=SAm7|2p{a1+EjHPfiShd+&}um!62D#>vMI<~_mA zafpXU@AkfYI6FT%1va&h((w<+?~dr|A#N4NAK#aX1c&ER)0fX5Di01;d3b!fxBubj z{P)96{M(kQem^<;Q&D>CE+I$%Iy(4rrj+;+xyK(tbTH`EC0357E0`m$eyslE@@n`d ztI@+(aY4eQqSbkLF#jyfv`fq=dB5P{^!U?(=(h6n`Ujf$wt!3QaC80R^XxD4mOP+? zE&{#bnGqQD4B|l=x&9$reZ6uweEai{Gx@ExqfZ!k*uPN$1oB^Uki*K3ZE+YrKorRX zG$v|9?`I~DS2qzA;Yur32CVH z&N5zeS>CGq$7sS3QWPs_*_P+ppRNUNpN*pzxbx$CX`V+f*ifl9Cp``fjKu4X__0sL%FWvr)Qy)B<9=8Ve{R|H^+#xveKWDQC?FRiFAIwD%cd%|@ z4ZAxCb1+atC{iGUpw1&G*hfh=W?np-^C(`p#1% zuL-g@EL$^JB-yBLHLLFdJ_vVj4Se>5kD&760qv#N=67iu06q6U$Gak-x__=W(4!Ky z29L51Y#AD8E?f?^LT?_lJgPw80iGbKLWj;gF0K~0auZ8A7?&S->41?p5Rj? zIAeFQ?1#e&kI0Af#ia}dJ9jb8J1`Nu{w0d$TzUnNi}{(c&-MeybKmofbYx64jqHvFR>QL zOCs^~<+;XOpO}1;Y)to6m&z7g(=6BWyOlxz;Jf=swwYnNk7|InRIFWab zBm48kJh8ckZa(J*X*;DM?(#Q1}&Fj|fF zAo}EiGR|Ew5JH^ZT9|RT&;(KGNV)of8A|sSRDfuy@)uXUab~A4IDJ6JP;~mh%Ux7A z@goWc4dlvC-`zqrL&8G4T8-$YI9!YT-;7u0@#59SBogkfy@pu5KrleYj^F*fnq=V7 zn=!=3jj0{#VTtW`thiMDS02y+zlqa*kN$o~#DiIZt>v_6tozIFB9 zY*NM2s>N)<)?kpJq)ekkp82yNW!@)9nU(}~(_9CTveg|aTiub0`VcS1^eG7*FPhb6 zv>`#{H80xmr*UyFHQMlZW|0?d_?xrHi#Fsb7^!b7+SrP5Y(*PeF^=tMV>`yN9c^sK zIJTn=yly0SU^)44)7Ta!dXNt%vPMbLCic?uU~$G9SzHad3X<%nP)J31(ojfd9B}vS z5vI@(RVy-K$TQU;0mi8Ba&Msg4>+bf#f8 z#TSRq_ZN5bs~0P%3)&s=@qIz$tX^QwK%R8_u^s@m_rIs-puJ@GV*Pj}+H?mW+T%Dz z@As!Pu?Jo3dBrGiJtUVBslNPN`X`2|J$=#g{_6`h^1t}Gx^|itABr`@E4w2$!t0w| zjQx!<+Mc4ges(KUNKp2LedC7cs;|Eod#U2{6NLJUU!%2rVOLIG93uEM?@_TMV*_ zZPw8WM)fK~iV2az=~sVac3#g4UYtQyI+c1r!{vm(o3?H${PyaZKmNL&e_#9>=KJE; zFb}tX1LpDn?2iOsEpyB8mruX0>hW(3|3BaU&u@DlPk&vR0-jZXu>GBjZzL1dy@BC{ zyt{>^f{Kv%O5rUBnG&=do||6Eqy|HO9&p1Awm~U1B1*+LEIqG(^AqPrIby`1ly<5GYz)C&M{NLyL(l+xwE%_He_Yk}f$9u@PH1ogkt0DXTYzaRMb!W2*I6Cm1oWaVy zb68}9mytfvqXwjut&hd2i4Lp-^JXxu@zx;Hr7MGs!F&#&f?eY^Ows1{R;(Ih8?c8u z!nRdV1_Q-=SlMY~ka6Fe0}XFa!|?-r2lC2!xNK5}Qy9x1g&o;>SqyFca8oj=ljRfM zKs|Dd^uY3BhTHGz!bANf7PHaUf1Dcpwp6BbK9qvG0itPtF7Nn!hWU+m>2PEG8lK+J zb+IbR`3ru>Uw2540~f^-K#uQt_{onaW%3XTI%GR={Xh+QV*OKJ3&kXyBd3rW8Z@?2 z0|BLbs}cwt)N%kALPOzo2+0J4Y!3K`9B7AQ&#eKF5zud;b@+ajn z=%$zUMD9U9VpOkz=zRf#QqHh)CH5vRo{gAgE#*Pu~y2-Rc~(mJcFK>Q#=yao!Z-&bmA+u<<|<$ozLUx|A_Ht;)$xYT^0^uf3^Fb^uUHU#CCW{sBg-um2f2v%vdCMqkk<8Cc> zow6!e(q5RbPnE0rJ*1nw3a7X(zgR}&-2xkHR_ooGO$8`NrhporgCa=SoOKUs>! z;tKQhE4+EXsqIjpl5D9f_IIc1t)o>oxdN;2+_*HuBb4k+mKmtwXA@T8N0Tf<6*lv0 z1O#kz3H$p>_VyBh1k0oJO=(%IiO1^I3_~bMi%BDqXa~iJEO)lQka{PLZhT~(76@h)grx8^iWP~{arahBpq zmO`!Mby|yni!s}JY#;PW`EAzwX&&M1qOWvq4^o_d;5t;jnK~}OvDnoUJo08;M7+o9 zIf+{vi>R*WwV)JtMrU1n@L_%&+A5E39%-4~sOk1r_e2`@iPP#l0Bo zSwUVxtN!c*{^1QWIW&;f%1FocnXB+>z^T)wT&z{kxQgd=wvMIIG>;wgdCsnQn)$_O z<++4ta|v4%RKk>BEHlokE16^w3g;+xNkgbz{bcE9Yz?5{a)rW%Lm(zugmTScJmxmH z<=#_Q9!&2t))htP(`m#W^KzJ+UfFGS_IO%YeER zDTt*B!<9xdB_XXaCW>UqQY2r0T-Z`!RqDvvTz(H>uJXa{jZ#;eTlMe=3&FFKp86RF zVofz={Lb(dVu{-q2?-%z_DlIKK)D-XGyDnjhWg3XT;607QoXW#8tau}!MLDvoG%S2 z8dg3q)hZ0G%9UPd+>TKn##t3qDDf6tLw>V-%)Ms|l^FrPrkGzVH02XFz$+^8izmO1 zCWu`Rfp9XSiI~{^$0dUIF>WJq6kF=EIqpeHmZsP+K7c}ON|&WI|KV95)?^iQZ_!_> zy~}85b9g_)(Xz(a^BqL|HolNAa3BRx3nWmu0PBoF2|wsm51gexiPupC6-R2TM$8ZX?*=tGWe(+a5`Cpo*?Z4cQBRl%P)4h8R{7t12W%EQ*u|D5QGEQ#XEP=n0VL zQD0ya6Q!Dv@oB)ax_2<~M8-4i5Aln|hjUo7__DFAHFO%_Nhtq%oT8tW*7EUOvSmSB`Y2zrA-Oqod0H1mO7YTm{5 zg43bNB;?11R_cqI|C07`uk`SVUb)birH#{34q=R?HtjDv1kMoPP7%Kf{uRLTi)FH- zm3&Px@5@SVatk>*l=?Tz=?P|`RCMCUPcP-CA=d~V8PVp^UwFjZM=EVc4qiOvXC!>v@9D-2K^ zj7(LEGR@?qwS`jkIWTW2i5aPH>qrC&jKNpw$^rqCbthMKkB}PpJgGHw$!q(-O_yo=if^14tTx|q6ku1jmw8245bC5h*MGW+;%yx(RpaaH|y)A zU&hMye}}P0zJhbAn32JP)4U1XDK&QKDfzhqE(O^a=#<P?EYwW?_H z734b>QAC5XeNEL`T{QXX6Wmbb9oosTBA@U)A1ODw2szFn3l6Oh38j@*101*28jqSUIcLE2;&FG6sToM{9uXn+;fhx6Tr3Eupe{_`K9!Q z?j<}%QbRA`=3UUg=*xdzZ&FX=Uj~rdKspfeMM>)w7ykFeda>|6{%x-T4`jiR56V@w= zE-b!}rt}HU+HAdb!w*%;>ThhsU0zX23a7Zp^#T=*GDiK#3qo8^I**FRIb2>`R@TC} za4kOZjhe0$I$lgB34W_;N4eF{O0W;qaLBMZDOWk`o3aELO;L%wy^x3IX;6~z8DT^h zc_uToI)55XdG_9q6^3)DOpkcH;wxwd<2T+m{4l#wMy-Cb^iOEEiywc^uZ_a>ZEFHRPopJDMv?{Qm6Q1ssWGcbVjJlsJ%s zjT2Gs-s5Ogj@KGKv(0%p;+ispvuCx%+6USx`(Xr!6d?yg6|(mQ@>VE5`1}G&%{2un zkH9Ic!ru(r$+P;(d&Osd$Z=A$$t;tQm^DjA;Ba*Gpwfe04ph+zF0L&I=8OeZlKc5qpXg0E6#YB-1sFKMY zq30gmKXI?AKZVf@(K^@6_-)HKu<`u{vS=`m9^3sGGs9=qUT6Q0=9){`pVkuKoCUivX`YJO^Ccc&0oD`uZrOH5XSyPc~oF>1;^s>Z8 zjuQzh|06Fj^Umwpg5_M4iQ}U93vFq<8qOB)ZWk9861Bh>;R#p1BF6P(i#AN(gtKO# zn})kkL5=NMpW44%I*EgXY5Ns9YW?4YJEGX5>-M<|e1nq*G_6fp5X4Y$ygC+l7TeK_ z290x9OaX*)pxl@(gORzaXrEcNtbH_a{7ZJi98m|r#@~UcknCrF1*Nf+2xG%Jme2;q z2%oqL!HW&T12$}nnfrlK$hjKfrpWO~MOzjkBn7 z8TE1C|J6FEp$;izlL1v+A{An5hK}m|tJ23eShQrex^;-MPR_YdX-Lk~pJEV$bb>3v z3L&r{*yYcEKP?zKLOO6M=4~dGzhu&Qb$&Mf6_e@!@gA^xh)&{Z=A&O`;jA6O#wE5k zm%zf(O8Ii?)Rq;Sub7>$MWc;Rnp&?bm6&&<=+;jDBvz;0T=T*G^*-)Z$L%ud)>?2O z%HRKj2l#&*dL(=Q7hkD-{@WjjmhVg3|L!Z!{{P(<$yVcUS0I!haYdIje}hSyX3fpu zVNNwlCP1BYeg%VImOFM*kgw0LR#^?#v!|p6Y#ox^o8?84JAce_|NU^Ezm0xAm^4L|z-J3v!>cWVRShy-f@$5iP9 zP6*WpoY1ZhI3Z^ra6;ie;DqRXzzHAp0Vk}n25`(ReZUC=^#Lb5HTFX3OjLUz#nmI6 zd(0$+ea*7A#L3cHF`Z@mT3wa6vrC&bHpSmu0NaqWiyCqfFo)k^7-WHGMep5WA!nAlo zjcM_OD%0W#b*9A=Dou+g)S4Dgs5UL0P_HQ-Q*l~6q2{!BLe*TCGaaiboKh?U7||>N z7*nkUn9!{Sm{6_-n9!~Tm{6|;n9#2Um{70b5IHqX zRHkJW9ed?Y#!h)qF)uGf%*zW6^YTK%yu45_FE0el%M1PT@GthW98iGY(u^)qn$jjpQ~I3p zh&dBrR*+Mt6eh%p!i+Xvn3Bc|Q_6T@N*FIp>EeYcS-dc%iWjCtiNcH~UYL@^3v-G* z!R7*22i4^&k!H6dI~38cP?%A~3sZ_%VNMY%%qe1pIYq26r-&8i6tTjbB377F#0yi3 zSYb{PD|CwZeFhO&oFMkBmr$iw7?fE9pw#ID7=^k3PNgn@Q>qK#)an8_#kv4awJv~D zt_$GQ>jM}Cy8uqbE;M!>b^r<`I{<}} z9e_f~4nUz~2cS^02N0C(02E4g08&c&MW3}4O@MOB=70%>Q^1VU8DL8B3^1j92AFa{ z2AFa}2AFb02AFb22AFb43Yc+92AFb82AFbAv$->wUU6Rol5eSq}o9w2?92S}gd0n#UT0qN2@K>CCZkUo`df7K!K zbobTDgaXkcaX@q_>=1ndJ4Bzp4$&vCL-eWZ5Pjl0M4z?}(I;&{bSdi)eZo3~(^caj zOw~fUfnc6HCdY-&RY(K+Npa6T&WbtRWGj)U*+$t(wv%&`?KGTZJK-kTPOV9{lW3Cd zbeUv3F{ar@fl0QbeUk0CuCk{H&x05@M9HmJ(5xkRi$v8cg+%DJ`bu8BG$HhBhrsOQQxRp;ZHu(5!(;XxG3b zG;ClJS~f5VO&gemwk=Fc;|3<7bpz9*xvL^6!M(6c?Ft|#bphzmIRJV@4uBqo1E5FV z0O-*+0D6QCfF4x?phwaI(4l7l^oSV%JxZGHkrLAN68Cni7DVBcQl67B$~#o_@*WYr zyhlSX?~%~UdldBY9s#|)M?Wv`kuSO(kx^&yNQX)1*cFs6(>*~N=~3U)SN(dC^~`a zP;~;;q3i^zL){5fhr&~+oXQiZ4y7khIkhcxgiZ5G7f+-@7q-JA>)nz zpgw%gOMUp9tNQRcfA!&WPV2+xJlBWMxo-`8%7=aUoFn`2Id8u62(atf(j8M>eHjb9 zQkt<`l%@>kr8zr!Y0f-enzM?R=8WN`IU9IsPINELNgSmqLA^95pO@yuifilZiIstM z#774-BV!Jl(liCl37mrF)J{Qj(x;#~AEcl;Q>36dhoqo6yX2rL@1&qPBc-4@S54yT zji+`g>~PQsAmf`FAmx?{Am@<^Am@w+!)9uEz%rT!#;)a6LYl!sUF>F>=-RiYZ8kQxdR@QChH+Pa3eCO&YMAOB%48 zNgA-6M;fr4MH;Z2LmIH0L0YhsKN_%{JsPkz+%Yk6Hmu?0>-eIA=`uxu>2pNDtYL?M zS;GqfvxX4@W(^kv%o-L5m^Jhdm^H*NFn!7g%o?%>%o>{aE!V<1ZcxuH0uzuf!K0Qw zy&Yr?xgBH;wH;&)u^nU$tsP_ysU2hur5$7qp#!8(X9rnBW(Ucs{I~3pEl_8Ur+^vV zGr*Mk31H3x31H3%31H3-31H3@31H3}31H4431H4A8DPpS31H4M37~ThU)QoTDL6(M zAfAkl11Q;N{1Pg7=kGu&^ZX4+ehukN z^fw@li+&C1Z1ii$gpbxRPgRt0%(%E=lTmTY8+CEQ5Os0F1$A*k`nouwcU_zixh_s9 zTNfwf92K`Ttcw%E)x`<5EX;g7EBVUwvsORN9;(2*OJM5PDo);-n zDOpo0=1|$Z>_Rq_?LoFQ?m#Bg?m#AV??5IT(1A>Np#zz4MF%qBj}BzQDLu%RXF8Ax z_jDkGj~F)F1Fy;}0u2w<5Niuvz$ok0pQXH60~mYDm-@J2{iG^415IwQZdRhKnv@u8 zwk6JrZHcp9TjH$NmN;v*CC*B1iL*{y;;fRC7;Cg8&I)ablio_?uG;u<+6v#3aQNMl zDK*C5p>#x%Y3Z0MlhO%gCZ!YVOiCvdnv_ncG%1}>YEn9()}(Yov1#d;YLn6lgn{J+v5jD(#V`rr7jo|q$(KGq7+QXPzoj#COu;%_GX?9g z%oMD{FH^7%vrNG{oH7OLut@`!^T-se!yr?zoIB(-n6L8Y1DDt_ldb2Ps#5UT91>dTYK_y?&qL zY0Wua%EUHtg}2+11$YY5VcP^O za5a5<~Aa6Ml6{B*yEH}IZU^PEqr@-B~zVRHT$!E|_|hUxJ| z4b$U^8m7k&HB65eYM34$)G$3Bs9}2aAHj5JU&Hk1Uc=-x&n`(OD@X>`;eZZk#t1oR z$`2`M&K4ej0F>41l<<=ZFXV)Ay=hqxIXV@Gz=hz%J zXW1M!=h+-KXW9;I%C$Lc&bB#h&bQ6I!9*0EKHc2Rf3CN9oj%`PF8qabdx*Z(+!g8< z?A!u%csT{k7&!w>xi|sLSvUdA`8NT~nKuEpc{Tye88!njT%Lgh>{g~~Z(3YD|R6e{P9DOAoF4OGe% zQ>dIJrcgOQOg$kJu{8zha6|%@u|x}&@!wYc-1J5R0!>2gHW(&q*TS;GkqvW5#BWDN&6$QtT9 z$QsH!$Qr6U$Qp_VNT1pcvWC(Ql2ci|MCxD$i4~hW&u**49z%J)Z0lS>j0QNX%3fSeGDPYYx=5{9Q#>ko|Eh<#(6&npZ#ZJMzIOvxb2levepj}=Zl*@~QZh3J~EiVq5b&8#0d2!GyFV3i? z5m)zNhnOwr_g~jg53i<+C8D)4l-5qa&mRt-9&YDX7muy>P{A!Gv2;|uSObtTOnXr; ztZi#+YMH04DPf@&C}E@)C}F1-C}FA=C}FJ@C}FS`C}Fb%sAaYmC}Fu4C}BM1r3Q4$ z2WRuU8LYi)*s_JlSS5vOL`(+NGD`xLa7zM}uuB4!@Jj-fFiZlKa7+S~uuKA#@Jt5P zGED-Na7_Z0u+4OrI^~yXdBzI~Ov4E+Ov?uiOu_{XOu_>VOu_*TOhW$#CZT=aJ(`bo!y?|L0x#@Ry8_5bT>v_C4uBq!1E5FY0O*l70D80yfF5B3 zphwjJ=#jJlbm$oXJz@qxkCJA2NN28FCBkwvA#^Fv$r$AwDtdX3h+f{Kp_lha=;b{M zdU=n4Uf!dhm-omQWXy2t}u0C@l; z63ZRwaRwi1b&QX7Jj4$)KEx07Kg18%FvJhIF~kp;GQUKa#{ROo{W{&f+uV(W+zj%5vHudp^&9_v1gTTb(uk)?8nV)*F$Y~5 zGtQ+kzg!x#$)z!OTpBaQr7JuW6gkBe#1<6?sJxR@#dE+kEli|NzjVj_K*ZVy;1 z_!jj9^$qR;(Q3eeK^-WfPYVjU(|}^)G@zI@4Jf8e1B&_5fMT*VpqMEQD5gmZ3OUk% zVuCcFm>uyJtGXxYFasV$xQGe?E@VTGiwV)=Vovn9m=--QW=4;T$CdYpoBqlFBZ62Ky&^sta89W16v2a8$K!D5nhu$UtqET%{Yiy6|vVuJLr zkRKf^rbh>xVTZf0M$|Ab$r14!YuwM`>s9rTC3#JdF=TFOjpPmP6+%xjTf_aQ~9J3hX!5qek zFo$s_%we1ga~S8s9LC8ohjBK{VVn-L7~{hn#tAWpaYnS5c&YLzYN-D%Js@Ki1jqpw zdgO=^J#xg09ywx1j~sENM~;}%BS(Dckt5aw$N_hHS!{Ad*p}rBMqSvM2z? zB)rvr}I(*Z~1>45|8bifgHI^ZmGD%&KajU(nc zQ=n!DlTmYQNvK(>B-AWV5^9zt2{p@*gqo#CLd|j`p=ODZQFE+Fs98!R)GQy&kQw(P zZt1u~JmgFU&5$91=D6UXSt>YamI)4;C4z%ydElT~8aQZ{1rC}eK?2Qjz(KPVa8P2v z@?mWz=*wxlU*5|;yEj;=o3>BbzSr%Ox(*|}U+i|jZMNO@d?WP!OS|$LBd>AVmWK0U zAM601iE_Dk>^2Jy;D^uu2Zwr(M3~8~H!5u}xpsbD?7p0@9yZ(M{>$S`{C7)P7(L%U z93F8^3NFH!o>VYMaednO@stIr^~EQ1SS9-pS|uZ!&( z)M>Mxj%MrW>6yy7`ZjHULygTT&nVo!q{zn13li#7^|WJY`&yBYI3U6*zp_i!Z;S5o zIg;jqcLrho>=@yl>=2Q9*dZcyuR}!YTZf3$u?`WbR~;f!mpVkG{&bA+&UA=KJ?RjU zy3q=uN2=$w+hGPej&PBt16-)@9v5r9$HltuaWMmWT+D+W7qg+q#hmDIF*5>O$d4Wu zv!utxTnPuBicJk-z>^*ov7|$V9BEN8Lt0eKj}{fPqeaEsXi+gUT2#!778SFiLxr4Z zQ86Q0RLqB=B~@`EKn-{iBO`8v$dDfqGUiBxjCm3vW3EKVm@g4B=1hc)c@rUH?u5vY zKM^wKP=t(m)VYt;aH&|-VFnzEa1nz7T*#ju7qh3w#oXy}F>`uc%$pt;v!=(zoau2f zV**^rmmU|hrN<4p!daG6>jL*RPK)i$^u3?^)$BPQYs9CMVulPmC1%XI6JicnctXqp zFHeX$VCo4m2i!d&=77y7#2oPZgqQ=ypAs`}11H2Bw2BjA#_i+=2_9(_M`nDbc+fZ+ zNYn~yNJ#ez67#x(#00J&F;go@OvwrobFYHLBXn`%f%n+g*1qk_al__&+4jX4(# zuK{YP^DaFgW7P-90RwvEhzLD$#D^X^qD7A!v7<+hNYW!mT>d+|4xA#0Z)KB z1CK)!1&>1$2aiJ&36Db)3y(t+4Ua<;5066=5l?_R6OTg^6^}y`7c+E*Nrfn4Pc7~k zQ;B<)l;VIHr8r4m4Vcdo7mYKy#%y&=!14x4nlvYqOO{ zdJ8a}@_J0K!44CsvBLzq>@a~sJ4~R}4il)h!vy;6FoBYLOt0w<6R5kx1UeV1q+Vyp z3a^Bbwp&1_>ITs3xdsGEt^t9DYe1mh8W8BV1_X+&0fAO)K%mkF(Cf1X1j?)dBTZI| zLX9GQjW(^X59P5MQ~ZGzT|QEx%SSqN`ACH>A8F9#BL%vAq(7IB)R*J~?YVrUJeRxf zWZE~~as3##jJX}QT5W#&xZWd?{dD(Xv*{*=aWoivn{Cm*%e*0k7VEP!>+xaKlhQ8| z9j;S{sYf%P5P0<)E9|7qe3+g#yCr7k&nLmJr|q}p?R2reo4UJ>!>m-Ta$M&EGyMzd zv3^#LwqK!6L05a2>S1h|kE5iVj!fD1_y;6kokbQ_;4npEPLAuS{z zM+1p?QA0vX)R2%3H6$cL4GB3=Lqhe}kWlkABvg6>iS%7VLRHt0P|NSP({{qz$WD(A zS1Qi3SihA#P;M!YG+W3+wHESFr-eLJXdw@^S;#|G7V=P!g*;SZDUUQ*$V2rN@=$l# zzK9%Wl`4nOShI8RK+!YsNbh5KNP{svWW^XBl4J}IIWvZb6dJ=rMvdVi!DiqQ-^TEe zeq(sZ#_Pp>=f9~q*Z^YY^`L-n9VlX13ktc_fEw!w2*)i4J2Yi4GEc0LqZnRkdOg2B-DNl2{m6s zLao=3P~#0G(sm6AHC;nOE%)C*=sik3Cf0X|3zQz=BFzW5Q2hZe3V*5XCBv2ufH#MdmJgUV@MZk-0`=(lQasHNzYf?X>s=~ar6tZ z1x7bRUT*fdjPl#^Zc2&J$#b)@Bs}FRFf}+N*9x?S^#W^Boxs~rC-6qo3A~kb0&f6YXL>I%YIg$Xawl-|o{Z31)6_5!QfPT)1$3R2Csf>g7u zAk}OuNHyCEQq8u4RI{xh)ods5nr#KCW?Mm5vsZbYA=S2q_4S&eRx!Dz)&gMlHOnPz&$s)55#z^zgPeExfBt3-`KwaagYI*uQKyJO+BR*y2N&cz(0R z2~wPB{VKFQic0l;3Q{$20@8GH9O+s*j&!{pN4h4DBVD)0k*@9INZ0>yq%-0Kq;cdp z(phsH={&OW>X+qu`c$5R4Z;&wVwtidlo=l)nKK}gxy~b*YdMm+UL%=nGLpIOBAII| zl$m}anQJ7HWgV>Vmk)-()k+xa-)s(R9q74~7YPe(3<0i^5g_Sh0LaQ20J4S#fUKqg zAnR%X$O;<(vepKGthx~(>2CnYN*n+RP3|_j{JPvezQfhwiw9kFff{>Bsl!m36c|af z_5x|4x6zEpIN6@iNqs%I9^J zFejulr#DMyR(pn-)uCZ#HEEbxeHvy~tA?4?tzl+0?3g({8)jD9hG}%}$HZ(Fwf*#J zxqDhIo;fcnb>HC-;qeI_^42Aa-cHz1vtKS&yHAjPUf$vHWe)5>!`Lp!1k0&>Dul+Y zQ0SD3go#U$Fp(${CiXdQ6gbtNGNn_M8d?0NSMe_3b&i@&$B9P0I`O8 zP@t&}6ltslg_>(Yp$1z}sL2)-YP1D~nr%U$hFegm=?)ZWyak1tZ$S|Qc(XRHfSKb! z4+&Wi!g?M=uz(2xEaE}{i`WprB0dDLh!FuS;zR(8SP{S?UPQ2f838QfMgWW0;m6>* zuG|G76AEFk|11o&o`jLElQ7b75=MGW!bqD*80jzxBh4jYq^~Rtw3LLAZj#V7@@l$Y z;07t19kpKFp)~1c4xtK~L70}t2v^k@;rbdQTxny3Yi^8i^^Fm(!!g1YIfF25juEcX zF{06{ef$#JqNlAiG>ch=x4LX(l@@DRt-ngvXs(hqI;&)jwklbpr%KjnsFF3hsbq~- zYFVw1O4ewil4TvNrnE`?mDFtKWoi?VB*J zd=tiX@4~R^O&Hg_3B!ti1@7ZOj4h|d_1|FvHME#W8x1B@N`nb?(_lgsHJDIS4JK4r zg9-K4U_$k^m`IBaCRApF8S6AJ!pKx4fYps`{L&{(qtXsp@-G}dha z8Y{N|jkQ~XhUzUqWBnE&SMclYcB7Vp{QBeX+&>X*jo>u2 zw}#Wu-YiZ-d;2&I?G5ELw6~bk(B5=TLwg%K5p9j>G_<#>)6iA(`c(78X*Zy-)2W8d zGOvSHO&Hf#j##q6Rell>~gv8iF)H3qDmFoEDu}! zxS%iNn&`IR1_*4Q{=PiyU{^LBRU~eBFo2XpT@0z%5JGA~gpisOA*7~72&tJ7LTYk^ zkeVMMq^3v=saO(1YNCXYnk#^C``ll4n>G3fnD?9CsvZJgqkF>6jfvWSwV8G@%fQaw zf+!`V+~%vDMJau^_--HZ-K<|>%YlBhyT@G~R;#P+Ypgmy58iXL+5EyCe}xzyGvWTf z-Nla4hk}Nqb*!eNYK0Xq8)41JMp$#P5!Nhhgf;&fVa>coSaYrs)@*Bq70()B&9FvT zbIZpLawqI!Y}nFm-CI>$s~EP^z8iy)2mB1ogd2-0XW zf;9S!AdOZ-NUhrl(r7q>)O!B7UVP?GeEHnVga*%hT6OR#n&AxU(~IeLaj?_*+8umc ze_d~WTNn3tRN?47f(u<7vO7FIZMJZP^v(=DjCGB3exH;#l0&!0#8$E{aVSU(jbf!9 zBo-PgL!t9A6gm?_p>r@4I{QMQ^DY!R<3gcxEfyNfLZS036gsn9$hr0F@B3+;99E&^ z)d8P8dU!Y8W0_R$BXai^`yLuOybR{$mkq8S;{DjF+fbCXI}*>wb`8g|la6C?(sC?L zdXB|O)3G?|Iu<8w$Ks^#f!H-3i<8b{and@(G()hL46~#1WDoV&Nh)1M(yXdLT4<`5 zmWt}7rJj0esit09YN?l&O6sMhj(TaSqCi?`sF#)s>ZM6P+?8GRk3~S%MjdEMr~HdH1F@@QBsSd) z#IB@)*tK=BUiwC)Pj99PhdtN(#d3{Vp*s{rb1e*(eR-n8JW-~0n=)DgAEtZU)r5^0 zmXP?Y=8=gJC#9R(+KFi6%V}t*&uM69(`jfY*J)_y+G%K~-f3uO;%R6n=80(I>1k-E z?P=)D;*wRM<`>wGHycYNAYKa@FfM>;UsPwWhlL#LU@^HmSj?&p7SpMN#XRa@F@ZW* z%$yDuQ>KT7T}!+&2o4cX^zdKNVB{iMLNQ8aR%~OAr6b7&&}t02LzAs z$M`*nGfVF{&OE!rI7i4G#yP_6FwPNbhjETDJB)LL*kPO_ybj|Wp>-T*p4DNTBcu-F z#GG~yHn+)HXI~nN7E~U3wtn5W=&3`X4J_C#cjP2A1Ww^rka*e%G8r2|W?Uo4v}y#I zJB=U{q!DBmG=i+^R*>}B2(r=|LDq~2=oT~`r02FiiD|1+!|Ga;u%-eftf@N*YpPAc zni`X^rm`fgsV50*sz}4?+L5rPVkE4o6TK+quZz`Tk_C~nep*>uSAo2uU@x!hu#-3S z*~y!_?c_~8ck-srJ9$(8oxIUSCvWu9%WEBV@e@n^8n)1;UM;k#O%JW>&_bJgCrF$ykH@?JB5-BssgS69SDeYBRNCQn4(m;`gG|*!q4b)gj11%QPK#7Gk&|xX< zRai&^4HnX@z;6>BzNRdFD726Qyy86|)wKoW>a~DeqZW`W(E@V4SwOBT3&^!&0l7jv zAk~2de7P zWO}bkrDepBsX7CUM%_8G%y`JQycl0%}}0MH;eHM&7|YpoJl9qbq13d zJcCK3p1~xZ&tMYuXE3QX%wSRjnZtOSnZcxHG=uS$WqXxd6G-?lkR@G@<*MhQ+;lvW zyM9M<*X>B|dL7AKrz5%Rb0l|Nj^wV#q1<#hlDqy!@}xW6MvgDuZeC%BdH$-B?Jutc zpWw&*ys_)toIfpbBcSLqX{!xY4K<;rl{(ZlQHOf%*P&kHb*R^J9qKh( zhk9++p>NQn|=GsYCg6!j$rU2_r8$rCvbM@BYse%trUvWBO|K*bzNS6%IYEGict9^BU zzr2OHV~WvPYqF$<(xj0&gwlKlVOkm^Twh~^Yi^8i9gY#M%`w9DI!3sL#|YQ;48pWN zM!5dR2xo#?Ek3eg`|%cAmi?&pNSL)M_dWNqQ-d3);5r?pkW@MXB#n*$NueV^(&q?} z)HwnqZH@p*nL~io|lYEg$wcIV+Rya#h{BWt-m1K4aR7 z(xVS#N$q2~>U=0Sg^%Q}?UCG7J(9bgM{-y4NbVXQ$z8o8x$AZ)H^q+RuGNv;RockW zVsSuBc5dmhjm7O(zijw4=t#4Jlv8JYYU;7xN2w2JFoF+QF@g_BGJ+2{GlCB&G=dKp zHG&TaHiXA~8^H(k8^KRxqh2?*Gpa=WOZ$R4#;M%wVugLtv3H%u)aG3$61ItTB5#{m zC(^fxbs~$KSSOOXiFG2Un^-4OyNz`k!<$$q61|BP^PPhxoi_O#-o@_am&N+QmI&%D zat1x%`FxCs%#fZ5( z7vpr^V*dVP)XfoofW1|uQwdx~I+?>oq|<3!L^_?xMWoZoTtqsZ&qbuuDP2T5oz+F8 z(}`V1I+@!=q|@nLM2Z=1v!Zv)^~==~doIVbp8;~f=`lJY^AH`fc!ZAWJ3`029id~w zj?ghvN9dTMBXrEo5jrO25FN5{gpO%ALJ#=2+a0=3WY^}vMFSZzu7`zG>tHd*TG)VG zEo{K57B-+$3mfpLg$)SQ!UoJ~VFSu^u$U_?Y(SD0Hp33T7bKV!2{l|k$BP0pBt^!I z8IdqEbV!&PE+oth5fWyG1qm}lfrOdSf5OZtKV!z4Pna3CC(Mk_Zdl8Viho>RRzqb)Bt0BDqu#L3Wzm%xuM04b;a#3 z%gG8Lk5M=m`fq_ z7^`a6Loht#RBPx_c6IQ1hPCh`ENkG$nAX6Lv8{n0V_XA2#<~W6jCl?G82cLdF$T8q zBP?v-$C%i_V>Vj1riRGj@o}-$JLqZ?Hb4zH7$YP4g~*V35i%xSgpBzXA!DjV$e3jj zGA3ArjJXvdV_JpCkWmpbCR2ord322{Gx2?QxxkmBX5PbQY1kTMh#PQegpJrVz=nK^ zu`#1!Y|N<`8?!3L#=MHLF|%T9%&iz3vul72`4wYhhQ-(c$8=kIFi_ft^%0LW9;6|2 zTpDx5qyu)CbifCb4j5q4fzC}j(6UJfdNt`llP-;QXVQVTOxozD-0fOwrwng()5t2# z)UsMHm8{W9C2Mq2$r_DRvPK`3tkFg#Yjjb`8co!)S`U@1(LyC_bYS;T+R^iuU#8oy zFE-!P?SI+}^9+JaX|R@s3$U?eByB^U12npeQ)!yl2vliv0IJnG0M+^(fNDh#K()pP zpj!0s3yY*RIy?Js_8KRWuD}Hoz_Tmz$ZA`$qfAYxV}|!zABmA_|$9yv>l|0 zb}${+cljt4FQHsMTx|`;3zs$3!|scj0+$=zE?Srh17YUh7+@X95a7HU0TSm%fW)^E zAaQL3NIV+>630e>#IF$`acc;0UX1{WQzJm)QwErc_Z}g|_vH9!s%`o2Cj*M_EONO|HCnH?Tlo77r3h%AA0a`x*#z}N;75wwPce5@cb{VGVzwh9uHtAfN_svt3S zDoD(f3KA2ehJ-w*AhFsjNUUW&_T(%1ua-2LHrwZmo$dY!I=f?025dMXTEvj!(L&Z7 zjTSTMXtbDJN2A4zI~px!;n8R@Gml1#*?Kfu%;4kELRKG*7Bl^5w3z)iP+QN2MBoc7 z@A0d^Ybwxy3fW4nLZaICiz^9nBQ|TVrtLDh*>=sBPR4*jF`)FF=86e#t0ca z7b7O`T#Ny4)g?(^B|kE`z;#Msm(*ttM!3Rt^5+$O|e3Xzwvr%F~%|?mY zH5({FXW9Q)MP5yw8Yp~SIIZ8LG~Q=88q_K7Vij(uvQ ziesPHzIeyxba$~nwN;h)Q`=G#=cE=?$2qOtRB=vhEmfQo+ej7X#FkOTIk7!daZYRn zRh$#KU&T3*@pYWjcwWUhk=<1s=k)dMV!h@PKfal+#*tnf8H6W}#xmt%C^ODQGUrw# za}Gr^=Sn1VPDC=-eI#=oM>5xCC^MZ!GS^KcOFEeLdOw?a&Gq3LODsAU!M!>odDh7+ z#&k1>aUIQIlCEYjNoO;dq`MhR(%}py>2d~>bUK4cx}C$gj%P4Q*E5)r&RkJBS50l z2#`3G0kmu7u2{1WB1xpf94}H}DebjaNCVXs(m-#8G*DV04K!9r19cVBKv#t{P*f@H zwNyw06%|s~kAF=E8zaAAuMU^z^G2SLFllK9pe=0-FkKA+uC5`#H8upe(uM%n+YsQY z8vGAXG`Q5Z%+%5JC={h};0jAUI7+0%z zh-b`qyw!CpZ*|D(2_to6VW5vBj1-cDtw#6} zpZ(;Grr62q{Z$0$G&BI!nuk zP^iJP;-G8=(s=( z6kH-=?G}iEY70cx>(}+>H*<28Q|>mn&}1NT751W}yH1pq)`_yVI#E_rC(8QiL|HMN zC~Kq>WmWW|q=QbBE58%fwZ7I3ifyfz!A-3RPWvb*)#Ty4Gt^RqK_guJuZkYklY6nS8Uw_w0V#Y`<#v^#s?1yNcy-xGp7rlyI}i z=P_APM{%aLV>nmg5nR&e5nNL45nR&r5nNLH5nSTH5nLj~5nN)&F`QH72rlvF2riMR z!X0p+aJ^qWe>lkUvwULW^>-T-PeBfNGe{Tch&@Ae$e|HBX3_{9^J#>RSv5k(+!~={ zhKM-UuD?Px$un>1Oi^{a4?WRpOXwEhL~<1Bp0QLqa0ekdQq! zB&19Y33*aOLUPoQkP$T`q(K9TbYDY4h1ZZ!%Rad(oI$;=15m8-7#Qe11V-AAfFTDW zV910B81f+khOCHyAvYpm$dCva@+1UCY>9v&XCmM%b97!NR0~HZPq4<#^QXhkP^iVu zv8ch$5~;z?a;d@2(y77DGOEGOlB&VZ@~XkkQme(zv8%z(60E_-91}CA`6#wp&Ni*o zhm08Ts!0c^h*2FX zq7$UjGD2GrK2LeQcMwEX`=|QR8WLh^1?Y*K;gR)g6mdt;gb2`LQ_ZAQmT8 z#NwopSez6y5W9Y2aZ*z(uC(Q=9(s2hw3a@1wVcdF#8hTswHA*-3ymIumfD?%R+^rN zR$8BjRt%VjR&1DuR?L`(RxFu^R*X3UE!i^p0N3~s;CdbdOuIvX>vRY>rb%B% zm7#K!ii3`7wgAp+I0KJpIsuPqJOPhsJ^_z0AOVjtApwsuA_0#vBLR;wBm<8yB>|5y zCIOEzM?SnUOB?+($L$v#dPd}O)I4Dfb%ZU7Iz|;m9pj0jj*&!B#~7lhWAsqeF>Wa8 z7%>cWgcXW9MhQiAd^kLhp<|= zBUq#10j$;Y0M=@I0BdzVfVG+*z*_weU@Z#1iF)X~xa#HsOxwIN^@y zI^mA!JmHS$KH-jVAmNU1A>od2BID+{k#I*il5j`3B2$q$qUckbqsiZ{JdFRPg|V2#nE^5 zZ69#ZzV8cWkd3Ox$X4G&WT*5YveWz!*{Oes>^Lw)c0?E=J2niF9VN!dmKQ^0M~)$~ zWyr_%cG_)LxWt7Moz7&0MGC+5@_sqprS*{K$ZnVec4RvOuK6|(ZYVbkZdo@AZizPw zZn-xLZs|7*ZW%ZWZb>)`Zh1HlZm2j5ZrL~s?g=?&oR4(XAU2n}NBAKI7LiV8Vvh6! zj7*V!gqbPQk1#Yv`Vpq4NI$~Z6zNBpn zt%H7Ru+23Hk8wJ&EyfS9T4VeOr8UNn@L6O02$?m;k1$zd{0NOT#*c7VWBdq#EyfS9 zS7ZDLbv4EeZ{IAZRqazKNGHO%9@haw5*lF4IR(s6OaU{DQosy>6fnaV1M+@FxIsKW>l*HY7!S|;Shmdg9SI>x21p8#&&V;&UVxOQ+jy7!^OiIAIZTjAJclb z+^iMR1-@G>*Ju3aCBSDd^?<{x%_BlS`Smckt&2A1*@x+I^KH6XiLrThzI`}6;(X0Z z+-JVW-iYPh+4F2eP; zk=OVv82w>qzib{}f4`lc_9zM;ex6qNFqGZ=&xV?{aU)v*^rzQe*t7n@p(8 z{6v{AP;3s{+esqUA;etsV=uBRn3cpd>X3xF#QC}`mnZBzzdh_HXwXQ+LsR&2l7;hL z&rqP82u0O4V@pnW0w!ZR`}#ZPCb+dt&spxy{yfOSzkUAn=@0+%>7Npy%ZniXwLk=! zBIb|(RA7SK3Fddeu%ceBro~n&Nq;enZ*>}yHCGkn-jrmCC8>*^!rMLP0E|KOn0xR6*&0b+S;07J%SN4wQ zPfyeOE(>0MS+Gw<`$gY9?X%!|4`MwC!TxwyLgbpH5x`Eb}A zcFgCy#r{?opnBko#ohTHDSbwdp&b@;+~eMG06-?!yZg=d@%nkaUwqF8s(bv!3QjGES|+;czm~5Kl`|?dz1P+h~iVJyIl@_gv*We z>WS%SYH<~OnErmi=c;zEzw_=n%kn524t}>>FCPz&$KWRxE|JmGldrK{Zc=$i*+~&! zz?fOs_K(FnJ!elV_RU8aVC5*w{trYsnkjZ|u!bKOdz!h&-jLDP zunCT;{u2EM1_jXmX0b(ib<{mlaIy4gTb|xpuGXt(jA$!t-M@l;aI6$ME0V1Jy4b1m zb$Z+PRf)zJ_VRi8XV-1H7vBu*%HPqkk_BV@% zvujjP(U*LfZZ{ae*Hi9ZZnn!u9q=(N;VkX_0v94EhpqthM)-c(vM6Gd!0bRT@J$RF zDH|;{OwcejaQN=*S6HR^G_N41Yzpk$j%m|?ZTJLJA$li9jXma?yUXb}$-~>CbFeV8 zSuBy1javH4X7ly@3np1$e=T{9Z8_VV`&x+Cy@G{tzE8FncKW~+p3Y!yV@~^DlNL6; zHJeh60L)sh!0`f5Hun0p7a;cPxq!Y|;S<`hP&Z!>PpH`Y<#$b-?|-*N~<^*UHgU%IZq`(%I3=)rLKw zqLb|Uc6<1o%9b*_J73==%X=r&q2jH-ZWm8^QihsOP}r;s%!nC5GepS^eK(SIJ|X4Q z>tXhVsWp&0=}JqH`({4>A6Q}3aLU`+&x@UwLaHP^=?mcmq5le>-MQK>Vf`&uXsPIF zkOG%onS&H?GkxFF*P|JB_Z6Wa@3eS4lRD#D7fuhdD8 z^m?FYQjjRmaxN)ip6cb3XOKSAqdowciYsSk9>9U=mym0 z3A|X};YNDv@IFkNEi{WdxY$pRyK_|Aok6O;%VOo8QUj!Cx|W%|4oWPR<@+t&9;_bH zq5!gcc=3YL-JO{ksovd?52 ziY1I=ehPh?&4r}dg#yWhls;##czpQ<`=hCL8=Aa8?1^VPbk z2MdJ)#L|#Q&ofdwtZZ67rNlUXn8V^uvuxc$RXi!e#SR7wT)cN@S8Hv7`i#f(dwA^Z zNrnj@Zqf*GvHRQON){T<-Ysvpo89Jq|I@G2=bsRqGV{{~p4zBp$oy3LOQ{$Rb!Gjj zU+iKHzoeMINhmV{OJ#>n!vUVRB(kQ^g@fZyYAdER9YfD)3M^lQDG8UHVp$4BCxSLD zW{!0b9GTeNSx1}}j!?(cIJ%k?ne{Gn&76a?oKCt1m3@+qd&+ejukatd?73)Z5vv{%cm8iPbwkL7dNEGROsfp3c$s5-Y_ z?`{`QlPAZc;Ak71cBqT}`tTX6e0xlA{~i8fjeX9o^ zzvr6Q2O3Ntru|{NW*<`x0J6Yyi7A;FBT`2}(1lum#29&|t;`9jxjvM2RfQ%csQN}S z#zRkwmW5Fdv$BWh^pYUBM>nsj;}pU^W*RM7pgLX>NvY67TU(U6!n$vI7CCCD4D z-fx}`D-gxrUOy@%y&2<~G@0TZ9+WX1LIPw2|H}?83C_yo$IAsaJBXdiCV74P1qW)R z!+?Mw|1DAVLE+YLsFu`}3Tl!tQMiswl8_`R<`}{GiAnO+Nt7Nad54emOj|Fht~D}# z6iz(bs9PfiDP{2mmi=OND>eViw0?kjExEYaoRNO$VrYYZKa&r>h-Iq;a`T7Dv&)fA zt732;N~}?}hlc{jh#L=!8%r2RIVAOr(W}bVK*U&Gd*e&GKuMpoKB>to`K9@^`qZr1 z10|STV{6L(8GVuM)F=apFxx;qTN1E>%kg3jC#k#z#-Be@Y8zL4I0whDr$>4?g3`IW*o@>}?keBGUM=reRZ(m9~V`@`=o4{U$u+wI~R zftwf4@P6|_V6Qd@_?`;fiw(9hE!IVd^|NJ&Rd^6|b$^fY6oLt63*HW&7Cz5~RT=F+ zKCUO*FhdV)vSukI)JtJh_-r*|~+ zH%{#O2`?~#rj5$NMx5kG^~a>s#ogT}-DI$p$%8$R4ygzEpgY>MP4}V1zF49oRDk_t zD5ed!lk}J>DxWErxf$H@bc-yu7QYLWlu&8xA$;S4vR$an`V2&jg_#LB@oh?Z-c`+a zM$FhY8`W{6iWo~62X(&_xBg~Z#fZY?9ri6b?**$!ewSJ&v^UXt#EF7E!jim&H&u2Y zYJHOlRiGm|+anc(D)`NCty0=eA}Ss!&0S2D#UNPH%EnX$FN~}dPQ12fojD`{GZ+dO zL$0>RL|AqNVSZ8c@&gfF3DfP@F3$DjoraCF>gR9+f~JI7lWuh-W3RBVO2aM55%ciG zMhj{~F_ZhcQ`!mnmNK)qlL`^&OUNjnY8w-9ZLUZy+fT}YE(H)r_r4N5FV#d@v(k6J zM=vcj$WP{kJKQ6iT&Bk1`e)?EU9B>LS-<>ZwYmNJa`W`eY2WVb`pfcu|3V^O+|%a@ zcMfX>Pi?QB=99T^M`5|NCU`FEE#w`#fTW2Cv#_MJT8Sui%6u(D znW*JQb1N{-21gAGln#y98xN7Pe_SuI`&QS5^{qu5*%gUJXILUuM-nHEG`a-i);%o; zZ}ev}!d%ct&7*1bcx@H^as781EpUgb1*M@{RI3d3{BKa<@X<)+yj*NC4|za`D0Af3 zi(<2zMhDS$)`4k&t-!rl!rzRE>a>-NJ6KmwG5E2@X=VvwH4phGhfVbT>d~E^;0l1_ zfqRV8Z%OoKiyPzQm*_}-eZ;tOk5Zrrtd%#|Cz>#{^Dif?YA(Og(Ti~zjI{Wa=^z)& zE*F>5>*eaMbJ}$ZcQQo3*T?ml? z0i~`g2Ps^1C4pI2@~AJqGrnv0A%e0U8L5;pG)C}?1{G!|ufKs%+Ge+BW}OVAta+a% z$PCp6MctrpGd~l2SS)vwc6V3wKb3D20@xgv$}Oh5){R>PZpr2?C)`X7ZoY-prSIZ2 zT4RYJ?QoN-O(vC~KV1$LTjMW6)Qa{1sb_yMD_T1Tn4eb3F1A|rW{+UBEEmckd?x>5 zuz$Z+3v&o99JPM^Y0 z7yQ?~b3c`yx_KnkTo|*bfs?5#vve`gHMc?<&K>Pjtumq(&y?R_wj(&$7pNije(nUw zDpR&|Rh_dQf9jfX5kRx)3>(T}#6QC2j0qFQq_-duUoXsn`6X3Xf<#(H)0biAh-%nJ z$(?^2h)LuT@)~=axfe;NHQt~-zgVR}7XXKt1h}T9tEnX1r&0w-uT}?Xb}Ce{(WDCZ zfkV4sJ1p|zSytoI6SkRe&D(Egv-t<&ABI?#OxElU)?7diYo!hsG{~>!AD@HhiLq8ZTc?@E`Ho> z#2azv^2JK2qEh*d==$mrmMmlkyOjl*Tp(oW=@||w%4OS{4q|ZEE-(jOJu_pzD-DD0 zwaztj{M}C%xt@MH4(3wFPpF?TB@ud6juBp)QD!6wUC27$&m|$V{b35Mj*H!!<@f3C zn)}j*Kz-F%E(u|JK1PTYFhb;t{>c;-<)lDq#a_gKAhR|nr9GG<(Uc*sj%bz`VJyj6ci`I}4X^pc}7q2)o@oR~0qHNEI4 zgYB?3N6bs?#xM*<7g)^6jyz-gC>{;PHsAmBE`tq_@4MPEVKdg5TQ~-AvPSkf-EfCJ z#yjLazZvzybA3o$3`FFdNpkxE{{G?jIefkU(;w4Q&db{aPVD_WedoTAn+@XdfmWYB zXrl73^zHJCRRPBo-G=y6bbv{!RYrjR`r|9?nA+>_-xt5dyhM3E?r+rrhMM$Aq-a8} z;ZqZf@8_G(u76u%|M3k@`Qda`6@p~s`DTMj3#t=#%+2!ltLy<`?RpY7o4+i+EwDpv z`LtJn;jxw>&pp6uyosk@b<%g52K*ftJ2=yuFfAh`I-)tY19kykJC>hy7*~KnQ5ovP9=*UxCXWmu4P5H+Nc9v|i)_DoUSl2jR;MU5wnPu`p<-9T6&6Oc zpJhL^ouJsXMvD+k+h94$SW4aLqb6j!OftME3vinwwj>auzp?}dVv2&_S%8l!mD$0D zY;+UbRq6>#h7{ zB6S?=g$h85iwrj}K!{HcfT|OMOitma=&1Y#f)Iz)aUm$ug`;Qn8TNq;3=O-$+-xU&PlRdFL4WH^~n` z;>@w`8IRI!z{1EO`ImMnVsJ18KEifLKW(OjnlPDAydam_6g0N5Z)ywwD$PFT|IIaq zs3+co;A=8c#2}dHiybTn#AGe_Z-yiHBESpK^HExd5QAkJ+OXI$Bv72@;mB(BKNJQr z$I(m@#3|Z@WUqN&DQ4`qMR{l_V{LP_;348frFle@hUl!V8YV7+CG|r*ZGa=6hO0__+RDJmoH!Lz^Uf@%VVyb4~zniX;Pd$%0sj$YXheaYI>J z3ZTo}9<)02L8h0`DowW1*J^uTWj9?AFFjbcyy$dIdwMDtr$lJYZM%;!MfjqO^`L6G&`*M;XC>9=5_S znN$b?@$etG%Y%y(a$*I24R%lJ@}ayDKhgbSOug9sb-CRG%LM3nS2v=~B$YrEsr%47 zJN}gynNtAE{EwekFn3_Y;RX?S5BuSZsRW_wKO!L{an&L`x%4Wf(2JdnOUv6bcl6S6 zeIt^saVll`LlP)YJZ;Qop2*W6_CjYa!laa1q(p2L9W1h0FHtz1g~3xS1|exhO6;@i z`q4ITl?VhiAS9v9A7hIR&HO%8vi_#E)s!Z~(p395(*u?OdHj+G;+_*%m~SVL4aku4a@pMudf^P_ltYW$Dr-1mN^Ns9yul05#ZLRO!mLHw#vrZ0 z2aZI(6Na5@dY}ZBhpdO96;5zeDzl_#5UJ_gdJhJ%x6lci*eP0G3aougC@$oIw;2ss ze`uSMFzZR>ehQfl(zha$Mh>*H4hi%Ft_fN{aBRJv;FzTS^9DvRp|As16wr~dUVNS8 z_r>n#<-?b2KPw1L|FZclKf+?ty3ooxQ?-ju*kdl1Yi}OOcqLne?e{$Ootl<$*672h zqOq}OFu+S3|6mg5*fY$H4ERZ-kIp9iqL)X2+4+N0G)^mQM%Ol*-|yX>HF+%W%4P{8 zf^I>h4$K~>*UM~Fw|a~cT5_ykF1EK{bZL#07)zO|J8Z<7!AZESr9ItQax!KgM=`An zxvt8w?7MO)nUgtr96vQy$wePO%NZXH7tF$7hGzGmx8O8EZ*4P_S8^ItDX`tJ%(s_a zhf9}WKNZjGs?e+j9Tv)TUi)=j@Odm03)TxI(3MqEPW*WORi}Ht%N&V;SURslz7lQY zIah&BNT0$vhv?LdaBVdJLr)v5SiIMG<0eoxvw%|G8&hfHa8{%ZP|DH-rSi+ko9nO3 zr(%-|T*)ceYZ_{lj*%_xXY4N^qn|w$?Pl^|l=9=|0~WlRur$+Z!m)1m_a^AV^FSUn zveX2v0?H1rfu_fc9oH382CNUcfSgq=y++>6?AyS#BU#>gJ!)kLi8x-#2&FGC#tRKl z#z{8>neMcypmIZ-GXsp2P3dGvYD`X_hW&Cv4-qhn^(%?b&0}Cp$U3194;->^AE>o6 zL-;%FbcBv~PQbik#YHaPbA(z*zs0s^j3TeVbM|VdL}P z@)>!!LEk;ea=z$99%UjTzwV^Rv7b!MPh;6KGW)Y8h$!nDD7Bvq)%^mRr2l5PEx;E^ z)*-~mrU0_Zx5rfhL_n9^V^meX&YNNhMG?DPnzg2p2BNpw6CyQ?=jx48b%EW; zstAIa2^cs$-qfu?GRjD%@zgJ-IQ`9V<*SliH`A(zBMK)T?XKvDMBz;kQK?sz{+F&X zI8G?=ijo(aGo=k94wHc}4Q78Nn~9g~^tzz>bTje(Ex03dl}lb}#-}bx>a7gm+hBt& zOj@1EoEYZ>cHhf@A=`{v>~mP1d+5kHu!>8+UmL?C9Cu#73*UlyE7Z8w7&=iQVr>BB}SeqO4Su6Nf$>(8mZ$lB?+yu zc^Xlpb2#zHMQW|YmCsFokTWPsvm$cltUy-X^>UA?h4_o>z-tYJOgz<+3Cz)azZZVt zcm*eLm_Oo=FAVah9#`WpIdg#@=)ZpVC;j_}-|Jsoj|0oc61j%{F_pqHup{|Hp!@+W zi(Z>))+@6PLIGo-rrfMf-LbZBDJ<`wO{ZLmPhEMAr>zHFA6A)Y8+v+5Cyl;s&2(nU zlI~=DPc!BGkk~ z8H@3;4I@q`t~wIa=2+=gXb(N7o1GL%@nub4atunCeBdlZ^Q84;1t)nPtJ^F-%H*rK@8g9)xBhNs_5 z*NT{?6loFyppFh^Wy`+!Dg>T1UOVfyJ0du-uNU{?ghtOK-cuiQV7X`?`WIc?vj;Qv z^-1XI5wZPY!oASqv4aZ(o#4$zcGog@c5DpwDcfG?_|YURJ#q)rZ?I{r?=Gb9tHk;z5Z9qF-(Nd&*KGlN@zqmL(ok(rGEp0;V zpp{qZ+woM4U9EvsKEp!r!!Sh*4~nN6H`vdHi zt!MFy+oosF#6@y>sDS$M6ml(;iuN2#Lbvo3(>94K^5uk`d&&UGs|Dpuh|^6()`(3t zMceUaY7+QklD|ND-33lB+~siyJr#YP-ZgfDcH9D9YfuGVDBWEIAsuH{MY%gI!_oq* zF~9QvCLvN{mar0llQ|Vc3c)@Hvv;6a5BYrvfD3_0CnQs+5$1%84d6p)4-p z1QD;ElK`;P_I$hV_YGDMcxOA`Pmh!x(-${t(RAhB13$tKtXI}l31s~NdqP1{IRI?$ zv`yDL41(-0WC!*G9)8x%QVh4B3`I7uE`xPN8KCnMWjJZTaZV)uo8flFv#k>`!8$}R zi|@P!y2Ml=#Js}dkSwLVQ2%}TT)ao#Z*Pz?VzHTNYq@%4rbQ6z#3U3 zm61i7mK3{s^_W!wl1oJwh%$hPEYe>L^R+v5!*C_ZP626c+cIA-Wn8#T?a*mXxeXH> z&jz&u*2#G!U<_FJe?Lq))$+N1vvn<;Q%Z^{OND?jj5kFn;eBY3-zH9z=|al5e*L)Q zCOuHXjYSeFKN#Z2rX(~yh(zZ;DzM*@2A6s6w5T#(;y1(PmQLSQlpZK?OV6(JfiZ=N zB)<0GV}`$?31!rT!K6%92X!tH1QTseWIHDY>qa(iB(R%)DF9nD__=DsqZT>L>r+vs z7Vc&BM^>f%&b;cQWtmdRqV&K@EZr^abotAoaoQ&~sAlitPE2x++T_q!>Z_X4%`W$t za=qmWS2m~d$d^Dj4s~^LPaX|%>-u$DqmBG>dN0TDPPtk-LdqP-G{dPNMt9{;dcdxh z&lsloO49~@6?KFs)(_?Q&suyf&*55=qFNpRySj7tF2fMmeIlQY)xF;`wF)}HFw$mkY84m=Zt;8`_T*ASA+QEgWx&r%^ikLe9Xgt z!8*p}c=tT^jzi6N*fP5-f4jJ-Ftg^)cYk45fQ3fR#`zZ6!hu`gWDdntrYGN|#2a&U zYQ+KHY;D;qadj`+c{ky*Hu(h`1dDuVaJ&K!Cx=kWObL^zFLHWifwQHw_ux2_1F~Da z?xGALggRY6U?$`zPICao+1KBs(+gw*@L|H>jzRwR*(N7m*o=n}oZoSUW05(JxI-i# zT(f+$#!x7$OwW$Z^h>|_XQVm%}w$@|OY@Z6IMo2^JS*uZMRI8FdQJwjL zH7oLK`zv#JhH#1`*ZYgu48ff};9vtGIH`uqRk;cGBThJ}D%^z99H27D9;{qzzN8-6 z47FtVk-7`DMM(`WkcJ4sdF6uS21A!+Uw`q_QoNE6(GZRs2ec05*?!}m5Ti}n%#X#M zHEKzk_D>h=T<)kUo|xlbo#FZ6x*0HQp=cE;eLtYMLyGA5mbX7$Fx@|ciq+X!uA~2? zo{oR}$(llRlHrF^k*4$C%Svi^|Gi{LTw%%H68sl)?C3( zPDv+O*jML_3+QutVse1=e{{HIc%JOWIn{6ehs_gha&vw3#{@^$P+d$uAZ;?S(6jMvWf5noK`qf z#hm;EybUtk$$C2c)J!uo#NuXB`+nI8Ir2zuEuE4riK8d*?e=0=W7r;jtKODV-)br7 z9z&YlKN=BbWi&C7vdQHmm#VR4+Tae#WVdQRRal1m;9Loy{xF;o9B39zDrL~4 zWefMu22-!%|2I2Gvc&L6?XZrK>+pXpb!rTg?Ft(`_1-J* zy_yjAE$RLBu>BHVV%^&4eu>SNhPbe~a#++R z#0e9hbiFx`SvGqXATJZaSuiH8({u3#+E0ecEhPNGFOG;%en>1h5kO*2F!*N!X?`wt`< za&e(P*($X@kVNLTOYUxFR9ct$sO>t(93;QgAD0w>BM8ia)}*!|_`y;w=kp2F0}~dH zWhBo#0b?l|a_S+=gAYIXroT9v><5fRPz3f6#xmPB7z+^*I|*Z%Z8MC8h=`qsvCOt5 z#zNc97z=GvIeDtC0UVOjv&xWO1DUI{~>V^Uj)|+#ISfZLa8Q zC6c1#oTrUaw#5DV8x8|PO>CahJ7|3||0xBQG(Er(o<7-~v!bOepLHdI8;CS-vUSW) z17@JsYrY`?*+}Q7EvHz_vHb`|U!FlW1mG$P#KoNI@VMp<45t?WJzDakuw(EFq4xFud1ab!f>?zfggrpG_)Emz=Z zT`|QO{{qW$@^1NRjH#J=m**lw>0l}^ zmqF_f*B5tlDdu|luojoiy1XG*W%!te?wi%YF9+JkK?{{Z6I|fMRD>p+f9_MN)WH5d zq3h!8wB!{m(9`x;_tF-hcz~8M<)TDRZnP=mtS=uZK}*LCM4XD2`$n%lkGRL?fK@40 zGb|4e)mq1i1EeG+!<5%$xo>b9(ysHvv3}Q{^rtr)WGv(5)fNe>!gsPYs;LV$@03`= zYI7-@gNC?Xgcg-{@xvf)CTfo>uExWE4w z_RKi3;d>J0EPwUHF~LS3YL^=iE~ot;{u%2Bawtr1UBsQ&xRjc@o&_)k=?=jUEthGR zF+%|ZX(s+EAEvOr)%Rsmc*)KhT-xB09yD_Fro2tXe-j~AAAd-lONiuM5-a*>Eu{s# z#l!Zkz3Y(_{#3ire!B!T=0Uh7mth@$NlTQ}rM zKYvEU9v2g7-EdZoPuo6Vn8ewvcboObp5&)wN3L-PA!HV%o*m!Th-4Q}@2>UY8qCBQ zS3Yuso&aCM#gpDsoE~o$yRZ7dR=ZkZu;HhAcX&Pe3QSKYwBV;_w+1fm<-@$#V6mCF zvzS=>AdbV|CKnjCg%}!XcPSYDTt054k(inVi!H%Ps=dUc)3C9i63GPs#zyVE$I3n_HqmNMn-;6l+{9sZ0Hgv=B&4 zD+?e3)^G=E!wg3oy+{hEB7_*pGT$OU81nP9dV=HRH+%|LGFCoQuAfZA1`*A7^XMCJ zh_v5{r{y!{R*fkOMp%O}k@23w-KGf~oF z7w1JpNVFqxC`(4nJCW@kGRUbpzn8%C6&pUCTZwaRK)WBG&Kf`QiT`z-0w0*m8Vw3Sg$twHRh@-d|h?3N>!rK zMl!}rHPgh2!il%+zQXrH@m(R?2;2o0$R=Hb7NLUGVTWB?svqNj3M+6$?4mIhq56JK zbh<7GEUH6^&Es84B=>DYA!Fh@4BFGCh~i%qf0Vsj#$aiQCxe-?nID#j?v(HmVI=G0 zrS7ASl7@>ek5vHEbDo-&Lg$1OCJHa`*zm`#`^a(9sKsa`MVw6R@(o33fxU+9K!tRU3*aq14NOA znQT$yR#e1lI=3kH2*Om1fZ_r@Jk?b{k};lI+HPgCz1JZbQ&yc+sT=8P$qElMx~`@I z-w1MyrgTM=R+8gn^78fCA8WQ+$=hu3%dAt3>$j`TXMCaJGu#cB+DHlhY>sDsY58A% zhc7KR0l#Yl{?G>KRRZ2dXrxNQtlBOxm&7`T)X$Y=>1D6+L9B_NkWy!mI(&G;Gv6Xt zJu_u%t~_}H|2-#ASPs;Woee}qtV`UShwb{3qM<#gKwZe~Y;SNsC3g4s<9j47V&gMm z2YjsHTlJP4SRi4K*u25=q5(wUklFMrzH1KO;0}&qIa93_5la|He1SM4=dUiG+qWi% zJL|T5dlD2eypbsV%F1gbMl+pQG6C-XSs$M0*OrOx$94L)jp<(tbqbE@MOtseh>KLj zl1AZz-#Lp(YixAj;cX$Hbu;h^CBg?mRv3C!1jaPW6s69pfVW&Fz*p{c4(c}>N&`0j zvysV|3#hhf2>7jC?r-4>JQ8nb!dmX97n6Lg7?*8*(%UQvQtuotPUppz@a{azER2U! zB@G-mpSF*CEVKabi>uK+{^C|o((b7j|#yc#|Al0F&EeU$xmNRegHm5)gFDPSv2D|(B2 z#KAuFAgX&j8&7420uwcl5P!b^^1Q=_)=XCV1aUS(ZUL5D@}o@LXiM`^IYgHq9Ay)n zSxB7hQ5reo@ZGJ zL1HKi(E7xB@%S3TqRU0ue$B3&O5-)c_+YgxmXV)&Iy_PNtuj}tiu%u!8kywP^{nIF zJ<^kgTs(d_$VUjcksimdktqD=TP*C_9&rzl)cF}J+>eQl``YIL*0G_8Hh`*?VRG;H zq&kso7?q`mPg%NWDUGXJkxanvC8yqc`I9lbc8CN`GPb$;45pl zf~Z4?vA!Ld_OXet>qCiUX|>A~bB@3!=q;-9`SJ6{osnojT(aicx2$WT@rpqPBXPN| zgM`|$-MB_d(B%Oszh505&`#6@%)vIe>YNz~j6`FFoJ1Ulz0*yqo0ay&lLt0HqCc?~|CnDvXF>?81pLl~VcBsf+$ z7Cx+{u++khiqa>Swg{Fy?MyXS94-$#8YbRVmhUgblA~gQLf$UN48fLSa89H4PT2xm z|itmQ`v!2i5Z9v1Z?8CH%);c`UNL**@POyp#fz0YqpU zF8EAT7f7VoNNceqZ5F8Jqc%(;bXe49CR09BZpKYfwZhjRnCJplXrK=z79;y&)fA!) zBaSs)jb-wHR;Y2&KsEOWsfvr|vM_J4?QMah@E8;JU(j=88H|7;qIHcjE@9O9%EZ}y zIOjdATvIaN!NtzTWG)(S(SM^|eSCV@V$ZS(@ZA#LMs?xec*n#V2e5I8`);?uXIiC) zO>4#iby3bi(dRNyhlnCjzCp}kBZY~HskeJ4b0`X<0a}D3_49A=(9j8#(!$`l$EOo{ zMrwD4I>4tibs>W*93EM=bVy<3>mwe#k`E!F6X6^s&rlI@r|t{ZvkN2*+P%`D%3Mr- z$uOWjk6F?ZZ22nI9UqK4tXTf;%c80D;Z+&I^psWO07mc}#&W0Os}z`KDzJr#rC(un zypwRX$5MrBoyeq5jB;*Xh=k;+TJ1Wv%M3v#rc$Yr05RGwKTPcA`LVB@mNZjBboyo= zsL>k5h78SDG3IFic6fqo(_MoTg-I8N*UPC=M}~t7!makp;4T^7uHk+h`P>ik zVj%Ix$OMTQ>|*i_DK6f4{ux%!M{K|FTw{wC>^Q8?@zAMJDMgSePJSwCMvJRGg!T=1 zJ2pOU;b~dyCU+0XJz8x0F0gUW^rqdNyA$#g>2!fzvG_VJ3y=kM_zRyKN>fVtbUzt4 ztHHS-!jEfoePSNVqxYBcfZTmsZVvcpigm0HXqe_iv4I=>EA5KRBSO(n^ON_mPZVP< zHTMqkOJ!y?nQe2y%iHd8%0+i@`h#@=YIPd#`6B$3B2*>ShL!S;>gv2#wgrqfqIJwD zPnig+RXl^U2qQ;w>eJ@5Z6bOD8k=rP75+xr_(-{n=eI_hK->P#hBz;XnYbjibY{W> zkV`(N@RFZH#}-cp+9=6p>SZ_j6L@T$<6%eg7L0TAR*e6pTKrp_s_CK-I*eKyIP zrkFiQh%gB5DzOBg>yTFHQl!lb<0PE)$o5fyqxGBk+Y8v{gu>?Z)|GuH7Y*6sLufCQ6nu5fT zy$@oRPFPie=_kXY=@=^ED9h;fu09p1EUuq=sThF!BwtMr+i7Aay2jEcst?y?Q6viv zVjUF&s=d$J3kFM1j1xR8=UU}sona4K4=chu*yOg+RjdsUBptN3M@=F*IR}A_xx0gY z?9hX}DFfwYrDmXCIY*ktvC@>0@k+-?;2yE_KWPG%i`ASB-l6-Dg$V_$7x^w5uCNA` zLJT7x!!R8&qAoXAdI~H*QD#cirjQ;eakC%=@^xLHXw$uI&~ph3_^zD+)sNcL6pj<#a7fkeV;(_8n97j=}elySZ7`gEDaYr}|>1v>Ik z=a)7gBs!Kb4sTn~;di@)*BLu9#AHzi#X!b*7$NPpLK~#D>_PBbSj4uX!2ad+{XMOs zZ!xDzfGgXYgBxBY#Pt`1@teWixsCgVB}&EE?gzX=`W<%L4|KbD z)Z}n^v;5BC1?Ivp_}JYo1_7Yp$&k5kP98YR7U^%9`?@at9L0W zQ(iweo4@`ZryiPGL+I~d(>A?)dUx8uB)KEg{_qFc`1DOrnkEg2=wFScT?r`t=YN9q z9Mi&NHj{vL&n=uBv@4=f7L4@P_o=z3C9lK0MS?4mLDJ`Ht2HIBNP}{*kAwYC6wdb+ z!-z9BuOKkuEPyFA6|B9{AP~;@a=2&uGv-0%OhY}~qtsqCGVbHXU+r);G?y_~T3z-W z&YXMyd)Q+blcq;>A4;rE^N|3ZlPn%C_Qpg$MVkdM1s*Hi+_EFNUf>dKKgkjV6OCQh z=(XN&Fz1&K6HTxY8P0>N*xasWr*u&GrlX7*vE<&a5x{Jt$AqmpzIs-8^ z1Ngy>7@LDEYvCFblsgSu_znJZ_mUoP^Zju=vXqTV{KltlagZB0E2~()t?eQPDn0_n zfl2JK$_LN<(iSv+OJ?o-8H`;@(ZSlWEY?e*T-3y6#hlcmBJ^@=@g3`& zl-ory^H4eV1FvxEmkmL7r@UL-Ze=SAnXZUur@PIqZ>A$@_p zSULg_X&ol>;r&4q6!-+?+=F$(MIxp56-obsdxsB;2TntMhF^p-o+H%xh!8EdJ~IDx z)M~Rd#c2SLOmH(B#$ADC^{vKL)?vX^VJfDs5nGz zvIAP0`f`9cWb>m9*7_oen`$hXWyv#XXTCrWOx*RQ$Q!vQ9}181?j_9Cu0@tsCR#Vg zeew#3;Ve&?YxL|$_v)qAtv>0%tZ)Rp#&SqnrBc6x+9Fnu?W?N7YXhPd$fJ?NXmRxwff{am^d2bFkR%f)vkeTn5;4od6%p*9DkrpFpr67m z-zhDju?6~IW8-k431qORdVZlqsC1n(&#h9TJ_Nl$oAci(J&By3yzc>=OnLFF%e6cC zgFt)D*()qF7K$pW7a+3{*fpqN$;%Qj{nQ$l{#tzCRLL{G$}^#R=UxDxC#E9_Ix<>= zs^;OT=IItd1%)Sxg~O^vJ@SU#4pl0+=4ZWlklFm}HP`y@hH24lPXow&h1y?g4}kS$ z%($@*Y>Y|frj!&FG76V{18(FxxQ>kqhZn(W)RtP(Mh1LzZHcYxcms2$rlypcXRNWu z`R>(QDm)F6AD`~B+cPa|qRlcReE;s^K5YqLLiKxD5}i6QMQnjEA4bx}v(U?ft<+Um zW?G6}z}n^!VLqO}Pr>RT;*~blOIVsJpm@dA{tE_#CX4Bkg*II8TS#J50L-{lTv87~ z@+$l-j=eMV@EHSMX{*;E=-YJjIz|k@ndghUE9?oyE_=A1JyC|I7e2>w8#8pbo><#> z6-Vo|AW=R)z#4~@jN6f(xXD#!f8{gf?-#orFC{?5Wp`>AsC(s50B$4Z>KF)?3|!nn zl0#W4@8p7OktcWZunS_{x0JYmg94DVWhXcW|qY z6T@u|E8NPohl&1k zvT!EOBP3|%GHT<53(Siy8TjDvIWd7<-Fq)z>dB`@zIT1CJ8i<0{8XnD0kc(j-Hzip z5$I9bvam*j zlGlUZu5H&Gv}B`%_YwjC9c7Z7i;Sb2Zbe8>Hr*k!7PWexvK4dNM1)!_Uk{JoS;6vY zgxY4w8L}kk*~RIGOE~Rdf+J6GYxLO~f$Kf^MGh5u;pWq$Zd9I!Ae)JbCsw} zJ>8ya?JVtX*h5tVAp;UCit^hm>0-?Wf=|?PaVEdaPQl5+M!ksr#YEqA+5Bs(5fe=q zenk?T1o%E{^B-;(rWXz;Wm3jg9dI!%lo7a0OFx3kvXnz#kVNjGc_&<;M0=GmW;;sk zqs*)6WR?$Cb6O(q%UnT@5PNA7o5Ci#J%;bHf)`~%3cgw2EbKDo*EPH~lC15K=!D(q z1^;%k2bByT*OE++>`lE>A|!Kf9xREA@1@Lhy5v9-i-1a-LSsTtbqUR z{fDc+K)a=VB@N7bNL<~h>l@5NC?O}YL?vCbq-?`kY8Y4{9fdQl^ko7d2Ua{F!A?%@ zcfcI=K~^m46>&Ra!z|6jsg7hG!QM=db`Y}%@?Jj6-vjqWS%J04O@mm0eVAX(SrDtL~Umfgxr2{7-`4deW{miH9ZF2kI_ z`@g=!WnH%aLHL+u+~>hVxjf~NLbKK2rwy{m)A98tFTAAN=1>O>**VM|hqPsM&Cg?{ zHN-9^4@3AMf~}V+1IfW;$BTNwhl^LO%ocD^t#*!?giJAKZku2=r|o)e9$P^kg^Fl+ zXW5@o!cl}7@KYA**TGQV`*})uPdHBqz3J6`MN@7`#+z;U)xMu;*(*`vl{iJ!u>GE@ zVj#D4T9_4V*R+Goa5i$8(pL4|$Z-_(5zDA_Qc5Qt*f7j`9n0$80XaE&Z)W0{{)g--It~YyE0KuYq+I`yOEJBa4JGjifQ{tXA>tYHPTNYrb z-Uhjg0W}Xde_tl5W^+8dTi9A4!G~4j6&yzhv%b%Euyfc$uCP!L&vdX3E*vc z|49*a4^I6QR^@rshCQLiotb~{5dQ6LPt?vCrmOpMH zOs~?mG2oJ{Ka2}NF>SQCc!jhtp>7Vo)fY~#D%rT zFf#+4=P9Xi5lqr&-vs`CAD<*i8(M6CEju%rqRq*mM$=g`k!=%!Ss$N@4K$JvZij09 zf7)&%fqNP(=GvbvYh3+dTe{RqBJOhI%o+P>qdw**7&%Kp4kXhMNl$1iTKLM6t`#E>kLJT+ z1)%M5R4-g)*5BqidkmAQ4oV#2Ug0wg_5Ro*5fx@Pe>vd3s_v82mopdd?%^vXC1Y}IoT))t;l#L7vENF&8cdS6%-YC(b$>Uu=Z zfkNQn&-VMP13&qPX?vP1YVSw8H=~^YzLh9Q$i}Q@oUM~Fj15#lB5>vUE23;*zk}`* zNT?u<|D3jd{MPzsODf&Bwz8$Zs?*n$GDg=I4Wb^zSxEK}b}(bY{Oh=+^ks`X_WujJ z!n0peFKLz}tN5@+ik@6cI0G>2Uxv_+*!YKwgRte_odw4u!mG$7$Kz28A8VUx>HG}~ zl!vWir(3@UPzXyV!8YR=BXgG(^M*zsmcQ;95J!yGT@5m;jJuq4--YD2##ploB!Xk3 z!b%uBcO+s{H**}-3F%X|MI}YI+GsJ@jaIn-&IYLpk-vq|xu!jl48bHtHa5zox$I98 z1Sb=gh5OXQttPBm<=a_Dxu%%l4Z^xCcguQD7YnKd6Lmh^$EzsiYhLff1@UD;th zO};Fo$^mMeM^2iXIRsLYRQuJUVRM7cEP##JXrH6$ZW)Y7Rf9;OS6l6lDk-<#4HB|m z8LV<6f`$!jwYSyABW?|Wy+WDn{|Ur9U#7*ygt8=~dD1P0+K|&)dvT=Feo}<~q{_DK zcG3>7l4#PNcZq8oeDdBojh2^$9nD(pLuqhG4devq_>oUcpfTB&uDs>RQ*+Q?|4^!u zccbaYm;~qN%UEkZ_@M{<4EF$`f4x{!;ODn^Evr5OaQ@qZ(@TEaSQ4pi?u#`VWwzm&V*Mm5 zIdtma+Pi1uh+Pu4rptLxUkRdHz|Yi%qOKGKwf8oP`_@U#4`@~ z{*EzI7(I?}mQR=ia0drkASPJ&{SC7_-X#cQQcBL_RHi1kWLtyp;m=wtF1+l$Q|iG1s;u0>;4K&o27|#Mo}s*l zq3EHhQ57WO>FUzhvE&8l0RN-Qwfm#D3*!UD*K}y}-So`>NQD#r|Iq?|0MFG|SRCO( z37WB?^o`~o*>;1*Dc@I6-N@;GED$6I2x5pJzWbF{o`mAF>PalEEabfkcFom>u+gJi zV<>UoF28$pe)~n3FOVgv0T4qs5Lvx`s2n?FfiEt>evJiR!d@_Rp5Jm*t{OlLn`a;- zo!IRQb}ic~Vyg#lGvx)FTZ{J^tTq<2a!{I+Xye@9fxgucN! zh2K)sPfV(aD#2g6g@RD|i10<5bXxw-5m`eoXivcn#sP)3z0Kmi{((`s_;%p$%YWVa zH(LLC`Q`qampm}Z6&4jgYabbWLPJ~e4Z=31R&^{dCO{d>4&+fbf`G=J6eOY^NM*Bs z_YgnNu=T@Z@VnIw)yG*jJtT0 zpa^{M04%-m#ir3OFyRnvrWeh$Eu^NRH@saT6o~T#y3TfOfg!LT6I-caiYU6=DR=_Wa??$a?87K*RAkR+#DlK0_Eg49&4$lE(P@eG3un zfB%GM#rB8YS)t53-JN3#_`{XgH=FN}CWBe$nMHDO3Wte6!=sv%T=KI8M0QpfWtuHSYJ34yB?!z!5fuisl%LpvCSFm~p_za<(;mV2fZPAG} z6y}BPO}Qb(#Y6-_s7$ZPb5_Cd9YLOsW$aP333;wWV2d5gx;)*Sp0UHh>@X)3Q3EH;aUD(3)VzoU zmOE-ycN3^Z*(!%Ni!~+lv4;_{QPjbACdOhLy*Yw3imm`Jz=@yr#b_P887>yL%MWKN z`V5acAOhJv<9KkgAL{0gH>%vwxB)`0~7dTLM=4`=184p9I(Ui5v5CY#4Fw zHR!>XV<^X%qvv0_BE9)+q*iqN<`gKr1L=iygYW=$tOYedTL+d33|(5F6;a%P)~h|0 z(QK>=Th77U3+(VtHiO(eiw)mkK|Y7dbhE@ln)N$UK5q=9mu!6wO_!~k2);K1dK07 zU*Iaf*(_gypYs{o{J@07Z8a?-GKOepb9e;`y+#TM6-Z3HG!GApKM{iPi=u_8fsx= z#kzseX7Pr24N(Zr8c-hrYMQk8>i0Eft`m;W(s-v?_CyX*8zW;_hXc8Y1=x3doeZZ} zxH5^uZC`Kllek(65E%lip1)h&LJTLGPFZs@#23@*;bm(5ZF$}GKznz~Uqgt4_%hV) z<4_67-fyDMCaU(aqlFqV1``|0;@$RQ8zE9ejvi)h)`Ua6ENcfXzV=+Y$QwuO=MnIT zSEcQaqE4X`F7<}CD@gR})M5|<=L~l`cF*N}ds0(QQY5TinBb5DZh$!)Dm*pp*PC#i06FC=`CULjqfGcV{oXKPE3aAj+3& z3*ma^GS-QA8uta9${~}=*f1Ou+qc{Pe88Ip5b?{ZU+hVYQ%G-2Yzi4>D42-iDvu|Y zMeZ@p6HBup5^M^*RfOGzY<&7cn~i^2B#PcaMT|L60N0o(M-R7PXdO{-I#r)1gMC2W zhQS;z7s5m%(J9pwqUZ1qTYxXHbVAvJH9c=uCR`iuF46AGSn01<2neZgs+fYYHg7iD zJ4=QZo1%;DuCp>0v$|UY?PkIVOeSS`ccZ2y_aY?E^MmAO4p6maCFG2xUi>>GHpgc} zCUfDSa!vQeLce>^L0_dlR_T5+l60z)6zKW<2ja3gX`r}J%`@s)@$X>#mpZxf;1<(vfS?7M!|KBfLQE7UkB)wQXt<{dA&^S03T1ajxw`#KJFsLJkdNVP zYUNI)z27G8P){Kv{GHxCt4ME$i!~P>-eC(q;F`!%X)4sPSS`7pyvpji?;txjG88ut zp#Q_zuM|IN3Uc=wyV#N2b}hG_<-|6oMzqG5);V}Yw4g5{N!ZTZCpcdTJw!7-CL9dI zKaAz9UFy$QSic}R)a(jD3nTVv-m9jolemu94slaUyiDwLI!Id0GNY=y@p{NSKBQMU1bSk-W9_{uD%s={E2gBHQ6;-P zYm*`Vas*?8Eyc$8m4k|id@w-qNQmYrgyO})EE#QDml&t`aV83cE$i-Jfz3fw8B4Hl z3wrP|_CsO@HiN9Bt%!j*Uix&#ly1*RSpI_R8@soX3Omo4**2`H9`?A*StW!SqXSdD zuvJm(psLibLyOso@rYNKefm~A;;YnYXW9K!~SmkMekcyAJE*o8~mP9V{M&n z#`Ux0!Gu~XU1B~JZSdvSlq>2yfZ0}+SV~u7(vyK5llfw zq;%T$GMv(^ubDAuNsgyX%@iK8jQnCu;o9^sC(&jFS^LHMqET)ZVhqF)%t+`379*|M zkqiUMR!t4h1|JzWS1F0>asw8$KLQ!hiBxl(wuJdlzPfe$aKN60F7)nRYjs7)n#V$0 zbJZ@fV9?CppdS_k{R?d`!Fach{vk;@(pSA!2pP4$`ebU5k)tQhH>Y9;6N)2}n1IZH zhxxWVr`ArTSC+Xy@dyhSQL+$EZf>m40X>GjoALT_9B$KIS)C6bPmE%$*>zr3m&x`D z4PzBqUAFP!xQ?26Y^r=@O;b6Q`7-aBm3#Dt8w)O5QmMmHm_;S5Hc2>j*$g~bV(IJz zxgJ{A$=x8YMMzY7;FC0*pgCg&jrCniFY-q%B1S$%+$FFA?Qd4HWz~67?pknAC{8-X zNItyAuv>pwgK8xxdz}!S_JB18q{eSESp^5#?!?)~$_$r~+ClW-C2cJx>$Rn32ws+& zoNdMGlO&AD)^)d(Ar=Lr;9@Poqm(fuS)08pLoP!n>N6Pw0ai^RUvb?k;LEdrQIO&= zh%`gYaRhL{4=KGT{^BoYTnr_S8|kcyo~VJCbEu#CqaW8=vyt4|VVEI3f?OJ46QZaD zNJ>4tFBD|*mM<^+G~}~^u{wcTf7)?#4FUe-b#1_BMC)t{`O}#KIK7+&sJ`P}4xrr- z7N!;8h2m0dcL07|e)rR3(Fey0@TcVv;YI_9j|Boq!AQ8WqwrjWfmD#(mxx=dwaIz7 zLP%r@qQwu?v4aSU2YMl2%O0%Vz%kE z-!UazkC46DkNaw5R1!zMHTasW?w*QylytPb`@tucCKGw&jYZ|=3yk8E2-QKt;3ZG`@y?Gvy+tUT(EF3x=>O$X#sUYUormReVYCjJ`EifX8gT0i)--dHQal zJFJ$XtFnqbB>#-e2Rr8lPiuU8<*MagoYJ7VQnG86KdLn&UZrmmOfp3j1gsU-@Q_&k z4oeFy*icb$&bPV5GHzg~1odI6aavHE9)p$qr!UXO zb|1hOI)5@gd4DgtHksyiM2;Mfp-Y(Tn&V6iXJp@(&BBRRH3}Ia;$yB4a$LoHnydhQ zpW+h9JBU%PY88*z+`Nl%cB=WtJVZ(gO*J2j_IH>N;R(kZqO>FULYC&*Y$;0zH_WxO zz;XV>J%brlk;&rgSMm)fdoWw%#N$VdO#_76SoDS04k}9gz(xk^fzMo|l^HWinjRjO z*UJXLs$LjhX;m)Jq=w-6MUh5{h7t;+XL|)7%QGNHw>`1$Q6x=JugW;vxolbyU0J&G zpj|UBk`2XH6i@B}IBJ2tIhI_krI>4D_VJappR@#H(VU!~O#54;MOBS#R-qRMFK3#< z7|C98BlROI=-QSjas? zU8R1TmuBKiU&bKLfW_LT$0w#aRy*5JSiFT%03JYCd0yl7vpYnXS(}kfN{wpePOB`p z0zt%NE0PDVp$0PcTzUd|?!)U1Tzgg)@)A~-O;iv@f-I`Ict#6laLIIa!$5LxqS)8x z>*Xz`2y8Vay!*k&#Sf)H{jyHNRT!HjLxNFh-^8 zfd_V{H2sjdE3wzrs1z0Qi~kQ^jyuEzFyC|nrrdPGe~7rT1;j4@7L(rp$a@J_F4@=R z@C|N4YLIJ$tj~^Vrxs^lzQGx#>8c7o=Jt1CciVLl@Dibw3>=@Hn&c^x76xW}*}^Jujn!zp#FjKwiwgik%)B_lMhq4YC+)|XnOMm(mfk0a zAgj_Q#0-Xz$2A$b^Kh^$eAeK&@jS5g@!?v8EyB7)4Z>*Q%BE>$bfDii6KI^3+eNDq z_dGwr-o?1LzCZPKUR5-BIJOv8cnEV26J*nE&C4_0?Vc`<;gl}6i1iQeV#C9}%KT}n zuA$y64VtUYxeUvCPU_|hsvlrqqh8c282!k!Tg|_*v18pFw5*o~0ubahG6%j!X$US`if;2}nBfBk7pkd^iI|&S0vx zT{V%Oo+gUT4M~o{xQ3Y|4sesC@>>XTd{Xedqigzfik&{K8^FTD8gXK#Ndf zaQ2{7qq_&I8uUF_!8LnO%%{hXtfNa!o7>^rhdDoK_B3E>xW@{5C)b)bnSrhWm0{$1 z3T27ovj+oO!Xp^aEQrxCPqH~HfzG)f0s6!qGPI3jfl!eCC4A^k{}MiQm468zTF)N* z*|BF=sCxdd^FlfOU+0A)^}o)G0sUX+g<`oU?*z+>N}zDFdN({l3|M>UbBYt8)fG=7 z^s&6d75KYF^I_+7Os*xEP#O*vWTH8OH`Na-^Z3vGa^90A7!Y)T5aX=&McbL_4@kMk6QXWd-thFioCk%owi zJ2#646pVSMB&^fWs&(q@O2g$KkmxVxWgb0v@SJEf<%3w{;3a(xI{z!u7&Bxh1(U=42!n7$$E+U% zG3V)j!}O(eJlLJxrBco?FJZkv=zbs4^6?DLkdV0aP=LN_HzeR)32I;H(i+TaS4()c zFf0jzjhLz~bEMfFAOdl<&o0}gtFKew6sPeFXxqRs33)Q_4JjXKw_u9qYM^2DD`=7? zs*!k-i}@vSm|v2D`GuDwhogHr{$F|o0G_ggfm)9na%SsibMPl_A<@1J-zMy+_%tcA z*;b$DkUn#aG&=Ed2m;)Ik~rVUjNa=sTJrl>?Y2B3`kQ!rUSl`{a$+E403CjD9@pSb zfYC~N$;6!!oT%O1!!qKxJK$V#OYh%Erly=epTA=Uz=BVc3ii%7Vj)|YEea|w;`^nu z60678*|*EhH8#)?3vs(jav`cCfpIeReYL$Gsup3oZ#P?rE8M4P?tO&^Fazko^AHzy zqGEJU_=SBXaH`MKjj8`0H%G4SkjH%mMh74@Jn{BRg)#NcM#*Mxh)-n@ zVPOM3rkEHKpHW?0(3+;|#j-OWvRHkY{d2l>ZCdEXMADB@bo_-5x*0jOVX3bZE#vr_ zXC4c-o0A@qCiG4J^i8trXWe$j$miSESa4)g{cw+fUZ#mqHyt$s^TEzKOgEiR2QN+& z(x%~_#9zgcM!@j*RonLDfzC2tn;kDD)!6_$R-11<^q0N8$Npz;x*fMv^kxU70OIKE z8tmfZ2*&_i6qSk7k>xj(zc~+$6279v7nRN_L z0^2-E@2lS|%@Z z8&NUC>-HNQfGKbxj*~wuF1;jI8N>A_U400LN*nN$hrV6F#tS1auOXcQgkBcyOLt8N zm`!4P`&oN>-txlllMVI^P^;}Lo%fLvzui@z$hGr-`x!{*+pP66y0xX_Wqt%<96(j- z1`T{3KCXTi`F8PTiO1CanB0NbLGoJJcUZ4z;^Iaxe=l?|wb=&kELu&!Il{dWmyHLE z|Kh)4v9LFP8(1Z*&F~aCn=dxTbC2Lv31AC5&Ex_?e4OiIMY`q2il@1DzoEQ%kQ-rN zAE<@Jsjs4^bSuuEUl(^LS6W2;Ct1`^q$^ZX%Ddra1E`lys}X#R%yo1Un+X+qni)o= z*C|sO#x-OPCV8V{B;xw4d;}3^)omV;Tvd?D>ER_`UvE3nCC+3UH?7XsQ6Q5(iVwB9jeVJmK$~}~YufSR*40_Lb!7@(BzRFC9yu(Q z{ssySq8}C(wZ{#pwy|rZ?K>P``*z}PsG0>k4>ikoF8N`v}7o##1PIg^52#7qjJVDA`2=J5J@Clh5Q z87w7kN+vs{S_`q|*YVnB#!=ivnWP7HUoOFnNQ7ai=1ODo!c}3L)D&ZJdV@Q(X7({{ zc>?U|krMtcc*o6KZQ8x!ajF`=4>ys-T0bsU!&273)6FZsp=$dfWGKaZeP%OcrW1G^ z@1TLWzYUN72!_}4_D9)5`KoMb?-sjnF!M=^u%D$MzE>xIEj?VPW_xRUADD=O*>ncx zK?TqoljRiG=daLGM_2KV+=8+z*yBLG`fe1d_DgEsaqdfuz%Uh+jB@OL$ts*9`b2`k z&r{$RRZZA4R)y+3!P}JEGi^!(DLsx91H&6eGJ8}EhoJ3wx7wC)uG@Y;mp>JWIG$|C z%h4W%_1)szl1LG!=YY}nRe;SX{Y3jV`hmI_j=h$2=U`WN2mmjq$-WiEO5-fCV2mA=+@|dr_t$sLm#2I!D5e8vLM&23K(~ z_Rbat-`e|M_e)rLkvHdkbzVZ@f6DP$UIGthX{<&I`^hZ0Si|g`8|4cpCkW1Ij|c!= z*L3Iz=%R|oN{5<7BScuKmTlK5P*=J2NyoZPqX;)I&VG8JWtgFiDYwRz1ai!NjO(2E z47!RHJLM|-D2|g@m`Y{d1|8wW^ZTI}*hcnpn2PbsgH;@Q;d(5Sa2PQnng8CayPH_~$&lmW_2-<3?!suAsLC{2F8RqyE>p}H} zb4A`BO728fZ<+;ulQD`+;?M(5r1)LHoD?z-@dphhib1#JHGP~(>Qa6f7!#FOZoDM* zOm}c9063||0C{^djmP~&NwQW_*m>|5*!=g8(ghH%xFuJ zOZs1bEt>inj&7&XZPaiK2?4s?&`Fv52;e8kwrjgBG{Ipw{0_OLo7p>$@;XYOC1Sb! zhJpTXigdL#mtFBs`C>nC%M+f%z^bM^!MH~26xfZfDy;+^r-gmm z_tdb%eBI*h1AlSa3wcm)xd@?PuoF!l-qRN73(%1q@iBl*1@YkF%>!^>1-K^uhWPS& zuYi3hb(@D7GIk*Mb>Inkmx9YbSeh^JG>EyU$(|s$pgtvSK%OH{SSm`L!yGL)l3O|- z*`BFzX-_J)O?y(YhV4m( zSn$*dGKxl}S92JTmr;5*S4QdK9L5FMPJ$V4Kk#~pxDs;^XZ?qR^Z`xW0w$;83tJBU zQ8);kpK+Q~iAtI%f`-J5LUES(b#fZBd4w01*5)dvH3=uGQ0&8kn$n-aO|I$B012P_ zMOte|z-vqv+41xOhC87xZoXVD+&wL$a+?+_eGOCA&YE~|c|q?0oZM_&q*2?@iDs9; zA+!T5U(>U-)Bvb<^l+^*5RrTb#M4x5=~HSW1>{fNN zoa)=_E6NtR6BIR_N-EX3PI2kPQufRZ4bc?gxrw2HxpAR^L1;YIHkS7s65&aB|8ARy zX6S4!XhGx*$T1$)LFva3V8e5vx2vcEO{XvqV&J=eq*AL-rk=u+#ffG!xju}8;l-yzWt~37ALWJzhJ|tQ$RY_ zy^*6Hy~YC1I9@ivfrbpUVpsPL# z=UnYd5`(uV^q$%FCbrje2ZK80Imy!1qxQfE4B_bOt=C2!f|d99MzU)RNlJ&yDhtE= zMSH6?mi{RLhKBF)-XvBKiHheFlrWgDd_*watr)x%d(cSNq}1Ff8X1|de6d;Kbr%OG z@ohJ~ct8?jU5u;yUp3SX9k}-9URH=#W*#c?)ngT~Bh|bKg>==uv|8MH)n2;X{?sil zy~PT$o-(Fcu-MOAu$)wRQr$Hsmv2}+U<<~jcQfh20YINnn+rrl@Id>n%n{^2*28UJ3he%>7&|jv5PLoq8t$3QF`jMUL7n1>kH}bY16&A3y0R>X-O*FE z2X}dH4uxa{bN12(5#gCv=$xMil_Mh%GW{??ax-0}yA?K9L1J6S<@+TKZovGvFUEF& zMh)u==x}<4!3Y4L0jg4=kOce`Y`b3E%5%~s4<7ftp;zHWRUziycB9J7xd)*=!$WQO zMX`of7r&eo;LG#>{DrK;ajE!_pB*0xBi+xHq2ohh1e1TTx4r&(iFcqP93wfycTQT7 zRf(N>j(z>JM=?f!U?mxp(7 zxMg5g(Sj2yoTN%?vL&roK{!Cum|*J=RtMAbXDCE0SSg->1-r$Fn#^rVTT4^Iv5^_k zns990CMeDnsFOk9(o@o%iO?bE6Jd>v#EGF{b{@bzhJqeGh5|!PjI&BbCpFRmbmaA&&|Mdg82?FlxRzJ zanRI9abW$$Vo4PQt_00B4HFkRAeuoPY;dM;$GZn|(YK7bI8_r5E0>H~{z@G#{x^cMOj)-0(IcvH%4(+wR+T$mYq zY7h*TGH|Ia<^YDQO9EwloM!36bjBr#cDCeDYQ^~aM#}X)u%VzSlI;u9F4!zc@Vk6NWvd8}>G0I#Y5i5co^ zy29YZ%=R4q=x_HO_{AaHxd(FXt6Eimvxe$BUyPdJur7!_9e>wpSKb=7N&ZT!$l-#mwRj;(DoQjg9nDqlpo~sS- zRusa{fPtgqj|tP!IjLaxYSkRkdN13u`a1GJRN_ck^W4Za% zx`Met%GgCRnB3iq#Wq`+UkA$R_?%#L%XG@VJ|sx>?BS++QYoEGVPPOVccU;3-~ybF z2rfn;`Zd)k&t_OOBIKnG=s3I-)t6iKoGgRo`$>2Z#ZSV6PV*!@9gaSseOQs7RGy+U zJ)t~5y9Ikzw9?%9RmU5j@kWPPeat$%v!k3Ioa0plOt)3#+;G|#9E@{&RDyepcyMZ` zCu?mL|A-*CxE1&3a`EkrUeko16|JdkqaBr2C&i#5*rNh~TrJ>Aa?dZrMMJz@So|qw zXA?)A#01qzS9KPSrL&yCqJ8D#xoY;R1_;94H{_3t1pJhZ9Mb{@4R`XDgAhJ><5~&k zGSrISvbDr^I24(o3l&l!6?H+X#meJ&Om=?O(#atagG}VcH2iW z9CPGIz+6FR7QH@Dq|p2Fkeb9`^_^;s4%RfQO-8X;A?Gzi07{Vw&9IO}#>;`=GNp-w z@*E`7%V?%7rrWuph{TKP3#V}{<=SSTYs5aL6fw+TrX8Ldy`1nDV^=`b zEQ8rm#+bq#%Ns5zwWj_;3epEvl>RgKNma5P)K^Ljo#`b94&uKS2;AT z=@V4E(xfsNyf2Eows^Q5{Pl!FNeR&P2}O%$T(%}Cju|ouhZqI_mv*uY4_Vu39-Ig1 z@IiW~@}YT*Qk*Kfy9f2h&JRzn-eY0Gaiuw&IZoycqjCYTJ#|UmfBJ<4m^Z|{vtT!- zj4qHd8GYWD46(2;`QOnrzr7HfQ0x_WP|Ag)HxmM+=MLQN#5Ns0L_YKgV@|WTgb4*D z@8{%)uKDH{g5}w7Fc5#xB+F+NI_;CCWGgZ=_Z2j>xk~HR61V7bPR1DY3-0=Xz`@nF z^AID5C!23^4IUN;`=v{Hs0F(2(L*tm9k8SNr`<)VN2Whir`Lj(Z)lO{FgbFK%8q#3 ze9-q(fJjgen;kS}%42}x)shYj03hTBW)UeUkSeC&^7TLj9HV>w( zpV8_gh6MLb*I-?3$E)hDE!$AcraL7Pfsm7ZW8!m+jP~4{V=`uqnMU@{l&(jusZ8kEa{F5tt;|2OCtDvNk)ekp)E|)(~ujs|{ zddpJbGD%GY&UEmuygPVV>NG|fL10qoaG;`=Kw%8U2ku$dL2QF&Fd}ryeR=W!XE83;3rJ>H& zekF&hw%cv-e%amEz_V}|Xp(7iqhz~#@Ot^>{+c~7&vH{TTiE%wItej+YDdsvAbnka zRQYT>qQRVoQr{ny7`e8*-(ThHWRdJ8?6$RIA~n4?QXQx}I+dYXI`^0CT&z>NnKujq}f-lP-UWKCcqf^`SA93{eT)P!KF+}GdYV?JYnbI zgS=?YU^^;LYz13@7;%i8d@Vv6Wy{qrR;A1)0ruz518zsl(q{^8!8k2TJ8e+l{njPe zVR}H%C(&l6uV2`1)HH?7tW4g(U6b?Taj|&Mc8VORXByMeCDr zlfZcI-c2&m(@c6Sp|a9u%SGgV60_@l0#~r@{axp%7Y3sg?&N%5MvcuAqZUvQhmdn{ zMzZ&WZCZ~pp*v5PAuHYnso`(Wwh5 zTuQO$z{~z9J7h7ndc@?Gt}#|jrlw5k^P4o%@!c(5#+$}q8EfZ7=H>#oy~oC_!O!Z3 z{iH8=zUS?Wksm~ioBRAvZ@4zz!D*Pl&_B?41d>Cyu-66i-Bk1D`EF z^?T#q_A~}+3r)hpF?AV_l=RZUNbi%;1K4e@?J4(qa2&%|EN^D?R5W!A3|a7+ttS(3 zV|h;iBm1%fZou|?HRwts8}1mq>ag5tjklUS-dFY%lJ82(tVhVG*&)Ju?LiKdy@lKU zLamyqo;|10;}M**+n69Ou$`uS5Ii|rh2$5fU1&wsbuC<>yPijM?rd*M?a$)lpfW9{ zM(b<@1?%k=2XgG;*tU#&MKIhbQSPds$9oT*$_#Y0e20rnN6SN$jXMCVi@LsedcNJI z>yS@HMmqiIDh1NSS(}Jxe@&Lr?tLmXd}2RszHOkvI_o)R4GKlPZM-mA9}{+(W>qX$ znonc3wNA~uymhvi0JtoEySzNY15x(_0;Dp`0^BjwxF{nR*0MgrXJ{?Ua85{@c<_C* z$ppI)6aWcz{5-Brb4K_vD9uC6m2Q#PY&9NF*fV><+EkA>%&>xV8)lgBI|z;VJqYHO z9yH#2KZ8NF$5Plo$jt4A*Ky@2h0f)@7s!9!_c|#zVGakoVSu|G(&2-qGhX_`REzS+v17C9*tYwVbEzg(m zE@DNTNgB{VW1>UQ6Kfp z4|KG3&+%F5iPKk)0r)xID48Jon2!E zvaVo;YofHtW^_?2P}heo?t_)M#CUUa2wLIzU|^;A?lv;+-G4<$9(I6I)6PJ?ZJQra>f9$n1m{5Nu9#1r$a6DW%-zp;;czapF;GM>M5g z!C-KQeuENa7ZnI&-h)iLhA4w{@NlnoGeSabX&XxYMhJAere~`KeEFYWKY!LDdnr1nOKQ-eE)pmhI6Q$Ox>5T61=)~xo}6Z)^~-S+;L>lzVEGl;n0DnZJt=!^3lAzj5nY5kMuRLdgybE}+ z@T?2Vs!Y~Wl&MTA6h9L`-@2D4^fsKUGQdJ!5i|E8h??_l52W$)AzhEURKI5kb%nn$ z;P(g)C6%Qh`h|aY#~i`EjUW})b;f%)i)7REb3})LShSf$`X1qJu|d+UF!MywS^k7xi8k4|M;YwwXcPto!Yb5< zQX-dM^VRtm(2sE#iv2tJ=gsE(YPa2Rf%{{Dm)y`XQQA&54tLl0Ov3bd;E53I+@HD8 z{(QRoTp1-akV%KnBHQQ6tZyS-5X!GTNF4$9l~+b~A67li`|zB3_M!B{wja0Wm@#&2 zq>WH~{1bBty9(TV2IIP&%tyNr&nZ+GBA87h7~E0t-!0ZxIBQwLR_t?*Y^JvjmR2(e ziOUeUZh`6za!4l{b1d(XqtUPi=oSww%5a@EwTp7Z3V=k=!PMdKu(_bm=Bb~!x~L!` zZ>W}D!28>>Qj2S!u&9ut54E%lm@EKoJ<^h%Q*j?P!IPe)sIjK9yL8#rO+%CCzNUue zWk5N}+RsGCP89K7hq?pq5p!o8UJKZHXAucz$A@uViEUn-q1_~@A9WBECmaYrq1iDlo-h9Es zEqL$6Iv;tg--4Z91rb^lGwh>zB9Wy*HojS0d_7)ozbp`d7=nP)N5N2paK42h2z#23 zpmuT|-e;GS2@N;yxHp-GnS=tZRP)9F&6+-pI4i9jJZ-mzj&R&eSi0#$fAISL=JwTB zX#e01eXt8qVnA|G9uB}z?73J1 z&i$gX52Ay>bw!w<32|kBiscSB(IsR&i%%HODWe6#GS@0a>wU3@x%6H`EkvQ@B;fJ0>%kzEPvM#PBHLk$|JW;r7O~ zY&HN!O+2&(j%Gv^W4sPjZJFkDY34@<<|t(-Xf|* zBX3u$SC>w`1>aM+?%B~J-c(^{ZMdm5OQ2HqfH8CgF5_y7r#C#*a=uIkW4EmyBtGBT z$gLEDNd303htCTPev^Dxt$LgOKgw%s6p23;yGwJs`wOo2V2+rH(m0dbU4XKI}4-BzIPzrhr5sv zEDN6O6wX~OIuyI@kKtenh*ty=orT@=SQK!fy^0u+5eX>j3bthIF&7WaiJt}XVX-pn zOk_4AqKSKVO;Y$KbnuP`Rma*^aD@oW40^J<`!GCn4@DDFK*s2#!;o1g#O1!q;LP*Y zddrKzGdEJhhvNVN3fBnWBqDrP6SD;ODEwuUUw4%E42JOD}Y8SNNbB z9Lo`(mD^G67q$6A+gDMB0&(xFx9O?ynoAQkZQ=&sK* zNoKds+4O<8rajfhi5Rs6Ox%f1OL>DBA>>%nZ7GlPp0wXOqk zXo4aT>UxIN3+%x?YT=j~6VMuw%OkhgU6}#yQO9Kx)60~ZTW2Q{dDh`%+nH3>pgggD zD&~VXXPnTZf0Ss;O6m-eG|s|Pv6(EE)Mx3sQr8km%u>4Ooq>s`u4*=2nPvYOT;0j$ zxu8w337dLEp;E?Ya%j%Jx~VSrFkNdXy-KLtOlC14Ugzi<=Wj;227{}mxG4dW$}RC8 z6@-li3T2F|wv}vsUAb7k0T9C{VCV-GC?>K=rg1_omYCXUB4HhXyY&0}8@z~odIilN zd(b5=s1Dz375Cb6E28zzdqG0E@|q%p^?19i`cVP4x=ZTp!AOI@h%%}$#yF^=*aI`N zQ`)9Ww(Lh`V(iA+Fr#2z9>2ST_|JZ*<(8*~F$6s0$IxPs*%-Stuf$SwcD{2Oc?p(j zj9V93Q8pT5#NVqMG?9N~I!6!~yp4f<<9w1ehZUOYz>p&Ym8YNzlf3DOAj7+24cpf1 z#zgWscgL9?ir2@?N*JNAly=humz3-rn=qfsyk?U2URWKe!E_u|K)GGe^vo_G*n|>eWRy z%Da(Fo(||259%%2JwiFzq|W|eS18##Gq|~cM7yn%+FxN8n>k2dppV8h8r(e~*V3j- z)Uh4TVzM_`iyBX3UW|f`vLX5!QuD1hyQ!@seA~gMq$gm0#QpOx;A+c94dx@x;o&R@ zxd0kXOA2qixqgCOnppwAFNHJp;b|I_4$a;n%NXSxZsh(^_usDwdkvNgqtwI$n1<4+ z*;3AUromX(-Xe<%(}GZDh(AA;ExSaoJgt3|C`s5gW^dhY8#kJ8Mk{EQWHu;oRx^%i z$X(Hy1}A+r7n4tEY2|a`Xtn&F@E7*qBt;B~0dOSz+Y%21q-lc99gUlSaDThG%~v_F zYl=@3&0RI!XVe;^OD1U+Yy6M<0fAvVcZi(Z`gUa%dtU}`+524i_OL65mvpg?VlbWE zc?({8GWxqRpqH0zF5BkaA7c&>8-;S>lS5j4GPP8}wZsr@;cvLz6O_Z=dp2|T$z@Y- z0gf;y@LYqjp)Vp()jPLTQ18q3M>&ubCOXu>h(~rYt5z61*!3~gTB(zu8qwJ>K*Ty3 zOrWARYhU%PFgGVxK7u-qT(D8JtpU;PEM>W~U4|9r1Mfx1 zbm*XR8fS^}7f>^bhQ>K{56fV+se}F8y9S|koQhYllAS&kb~{?;atZd9k>ez|5w~92 z%!TgGwOXJ)y6@PMI#e zV9-^CcX5J>3OF>XA+Ypvd2tb)&^U;BhFv6glA)Ny*3P z3rlSfXH>YPG9Ek|Js4V*J%KYD$!T}8;fWi(UvYR7gIRI9s_7zC^vR4hURYXJ5U?ha$Z2#4 zg{0|Wrb^Rn@OiWkhbxq-BhpGw5rMp+lS-g*_4yWxC&jYb(Zr)bp`r!;YM;Q0d(rE; z25PURYYzRcr#?)ln-E@LxEn&jqg*@O`2LM`b~#7{xQK;u%h~k32dN3}I^R0m z{VbgFe%r{$I-%C;B^!)M{kwM13&C;rq5|g>M=4z3>(=|sg>FS9x2R3JQ1mFG zaY$)#h{;JpnNTxfdbB*l`-|yg?j>|;0Ioo}*e&c$b6hQEds&Ph}_yzh2oZcub1Oqao9LHVA-`g{dlZx6H3oYu43X#@{FB^Va(I;~4FM6&8WpSsa2gM#?-H%Qn zu@KCGh}R4kid4Q|7Wb6sg3(WNH`X zz;Lr}oesq&dujDi#~6kyT%3UBrIjY?sZuhJr$t0_P!klu|=JBEG1YqFwI$O~i z_hQUfZE*^UF!FE*CZ19=XrNrl+;Z{>f%tE4;VhTCf#2Z5OqU$axp8V1821$F!7P_o zB30@ST{FQ`Y0Ng4xj^YT)5!;6iVK=H(%SZgE&?Bi)JtC9!}?4Yg-TRa8Pm@uTaCM# zXLrk6t)IuR`hhu9i{hrtRaK}PUk~Dr#rf7PXEIi)D%{}V9dX{uva_!GLs6^fiGlKp z9!nJpgXq0;(;-X@xkokB+DeA}K@5&qBFI$z{{HqGc2Mxmhn@#aeW$}z4-g`NRsYDJ z%5YhCc!ynLNIkn;bqr>3y+Xv(x{BzttO#iFR^vRh-Pia4A8*BL$5MVW26|5s!F`Z zl#WKU{ZhV}B+lEN#}{DN6VI7IS8z0B);6zIbZ;TL z4{(JWW#l!J4&`-7`}X&o>KD&ajNmy*`wds^@BZeb0C6XPglko!5DpcMi&$!aQ=X1i zn+DEwMZbbZNLYsG7tA9z!d@+I7dr3_KvD@_nNTs!+s}1-doED<%(pb|%(A3#WAG-H za_S3lePR--PpG==j?4;fkobOM$R5phqibJ=A=B&5+iw6r6LW0q$W#jLhjpr~^H`$P zf6vXqavj2aGpFDVGYb~3qij}Ju=Mz+C&Gl}Riqp&rlCV28pw+|sxc+yOD)P|MZ8OOl6CKDKJZCn(GKRRh~&K~H++!SW(dlrLH zir?6)F+LYbX!Rluq6zkMDmX|DmTZwQlg|qetfts^ zi*HM+cd`zkME2io2RIXxRn~&36t?|8n1Os{o3AWExg=Y3pz8*tbJvXT-=eo^SQ)cz zyhcf%=?Z}4RE+G9K0ooiEHn^MFV+$CMAsfY%ECL$zi=g5FU`P@S;G=@4fPM7pcOY* z*9$-)#pN^Q%WM^69QD4rx`H!-lLrdDlaUp15M`tGfwG8eMsDo}vAc$r0W7<2#5^Q& z&Rr=mBWR0k++kU~Px+8o8O6U3QVDUks!=rMIwVqOc+cuvt$>O?E+*nMu(YusG|LJ# z|L{W&7W>EQ+#R~77-Wip&0K#WrQDeo-{W|!X_=EW7ckF9uG0*dEeC>vcjr~zZYhc~Y;6tJdB z7`KX7s(pUi?*3$hyA~^06})e5-ZCOJ9krCu0JGwU@z@pw-fq z#S~DseP5btrJyKX;#c;DAJJz1&ayJ&L`y|X?>?EG38z2xTx1 z&a?`kTCapwA%kH8rP=6r+}*-6sf^7LPPUj^G9$Cep4V#tz!U^ugk?>A7)5@@mFMmW z&WfE;hE&=1iB9t-W5b~xBJ!nO*T>zd*W0@d4L@Ma9itNv<>1|RvxU~W`dPtbDf#pc zp4RXM-K>6M`Mcr03#0<0(A--QdP97`)>bGXDeMJ$J z&evFP9x}Hpu^fAY;gHK_TQl>ASj?M_c;I7@u;M!?drel+ig8l3Qm0K?Z8X*&tSFNBgME20?q}cJ+E&y zWx{0!5J{t^=odlKQS%|-f}J_?>L*}cCb=A6n0eH&_(h~qAq38LPr)LcLNPy6xXhTuuL#v>i?tUf|87X>EYo>Z0h&zSW|ImV6ing43BzW6ZQTwd7O)tk)) zY-YbNe?k;jwS0B$5xWBt6SULAkk@R6p9>9HlH5sNyx9d3_o9Z2Rp*=FI62=1gRgf>~@00ib6E*7_D5i)C| zaC{pNbaskz&hSYnBBU9wqMTv4df?QwrhB_(DUl`4z-E~-n*~<1^X1Jg4HB>k$(7K< z0vy;td4VLjPO+^BYi9y+K*7V^9#)_Jq{`7 zjV}6;(S<86_Exa|K zCh2=VA+}!q&oWty+%bh~GTM3A#;TA^JL+t(*H97u8r;*LbQ1=(X9^5!I2 z7gn~VdI+0;=2HvJ^;ZQulr2g@ys*Z51=7(WmH1?ZgR*aKCpQ(@;>KuA)v$w=K$@@i zK84e~m2ST572qo#+sfGsu}H7BgVVYov@#Kd3Fo#s6xF*q`~vd`A`>DyGMUk1@F-2Y z?r^^hDgGO_i!id7WFuPDFa_(A`Ka<@@6%oD!3L>A`gA9GRpFCH5`KhDC!c*?ShV(O zl#^k=d!BpBfD;n;ND{fxn{iEQGp|K=DB*NhwHe1)utbcioWlIw1gXkIETYvN0})f& z;#t*L`(pNZBwTC26Oke8zu>WwbjpH)C-+w{uqN{-=!|$W2zKE(*YKw`NG2D$%P$;|-^xxu{~L7p%he zbC|@KuIUcG+&$wrl@Ot-fbMZiStN?Tp0%eSo#7!8G=88XpR6V>F{*MaC_Pq&hRyu{Ip(j$bhw!_s?eouDuz$3|6 zQOf4G;5G`~h-4uSsH?-%?dG=X_WNx@OL25AK)h3)NqFmg&m~Ga2t&hR^0lK*$$Cw;QL3CKBULoac>nfSwkf&#!uJpnx))Y8w#FM+WyiodRo5$`D#1Me6N;iHJYM3Qzk)BNT z_?s5dq8?HDU7qgv@6auw!QmccUe$E4UPwpsu(5T`zuj&C>tdJ}ojW6>B<3Bjkk(b0 zh;_whB7r7@8tlC;imP=s99O(0j%DtuBx(y!PMat>({f3e$;))p&-PT1NFGfyHREbs zeXM*qYAm!>oXx}axE2IaVrLMqd;5r-_7aj9Zg;6mNCA*Ybt(H;k!w!O-fB@Y= zRejmW5n!3#T!-yZ`m1FW+=rH3PK78WRZdpd+K1>HnPWb{I_Cmgm( zjB1}5_qdu&s_i|3tm2>_Z@KgBD=oW2vkJ;~W7aK*>=L8N+9Uf|~Of!AEm>n4L27h-X`*m4>{^S?i~P)@BzO|J0sP_>}Lm)s6H z_T@XljkD{;no%GxXp6ZtFJzA#S$CnT%;yBw_xjo1{m2sy+}kdTT3jl90TtLp zStQdpU?2OLOrF}Y_a86Zrh~20@@EJ{(P5M{Dt`{6)Zy*Z5;PK`bt1GI`Kqe%o{&Nd zGdQnj(ay>4JB~~WZRT!48SS(C{Rp;Jy{4#u*f@JSy^PRa&uJGJgO?K z6ms?H5%gTlI^@1Xqh=%_xk6Ttd*@Q50u$@?^2_}-EmdDnSBNGW4_{|G^*daqa&{nI zz8H!OZCUee@$7awu>Nqr!)mlVTEU5nM9#{5obxQ)D{ql<)nK-CfN_R1SY{~6wrM#z zmEE2OzCAeJ+G(uZE--|fqUJ^yva$+A+-&+{(_HVVx4K%BtCO27FSZi&jTq!TP&Ujg zRr`j|$uSkDk$}U8`!A3<@HBn9++5T5UVB}=T^LR8O7L-l;Z%n`wJeyZFNaI}Ay!#+ zqAMA;iei$>As8Xu{Xg!AI)}n$hzJlrt4mpHx{955s_MKOVf1hKhz63yX2YlsueTep z`v;mQvvwwzcIQ-bJHru!`rrt~FZ-csEq;|1tkF)g7PF^xoBGGI)7>A>o7x|%`j6*N zul$eak5vC0t)JUy&XDSl=hY3&Mtam4Z2YU1K9z4lpHW`a@)oy6?qDP!mk{M2pZ3;< z991imwNlWHGlLK#4n;+O@{YRCFYV(kr{Z_`WnxGkov3K$RSC@vcsq&?hODd@DsO6{ z*CRKT{&@c~mroooQP(4(K6#Lf9t+yi#WHlPwsmkJ)C+#8+PPF|v0NiDWL(eJaCRs$ z*Q-vvUR}ck)Q(WLt*9g^IWI5E&NzTj2#00sgUC^dWQu z5#KY?5eX)lPA94YM*`p}!o$iPWu2ED^BJawq-D&RyuA>q(R}t*B2iY{QMZ!`P#8y3 z%i%nhs_;`1lF1k9y5{7Gs_uW4qFKfsMPjnfSyf^N`#V?vQFpU?xAmOd+``8Q7t4mQ zsd7Tkk5T5IV|?SA3awoYCL5b0RW)NuitW#=@$J=}$t+bhS@jdTY-Ner#uLt+UNE=K z2V9g7rQOECzfg?4Ig6KyZH{poh$9_Hd%pHWXHi8>?LQ0Fnd^s_Zq|0;|Zp^cNZ_7TYdUfw})#b z9*Sh+qlpe8)+hKDS_><@5s;WMB(^hBc^!yYBJgjk<@&P18v&y{G>Chv3YA5+P~u`y z{$^3)?S>XR)5C~InYcqVW|oq=zxfi+Bzc?Xa-ro@Nz7$)2CubVU7S5CRf?2qAaVxqT z7DUdG{9rpFlNZUZ`5_7}Hf(_MW4}tlzrDAWol`NsO?H%TX4RQ-jnDwhk=LB=ep}zi z9T-)1hQlJ6vdk5ZU9(?~j`!h@?s3a`2TwuX%Hn23Z=;Ax?rB|;AU+KHElN{t8IA9r$JkTFaWDl3{wq=enN)X<8Cn{g_`wxjB6HVw86L*wG{+{fT=GJry^Can~8>Dj^7bl`PPG znfvtq48_Dok5w7v)dw$3N(N_esc(b{8sN?wW5Td0oI*Ci`3uTBUcVze?fc+xyZhJ6 z21p;p#yG;WnNkJ{&E5Zj0teLkba(YPt?Y~4QXn#u*sP;^%0o?wNk;T}5i?AP5m>8M zqB*9yDC|XGV|*1%y_~B^y*#!F%n=KpdSH$61&yo8d@A#XfxrUuW?Y9=Ady;%GhD<8y_H*Ss8uw4UcGl7qm(-xo8F{j)nbVk1(_N7%`)oFroUC zF@Y_E1+OYfSx;d@_BQrY)4}spK1c%hSv&gA7h#BWsJuAPzlX!cYGpQf1K5&&3~}0V zLXz5+PhiYf)8T@$6&2Ji>%pe- zFe1)&bF^D7zFFFv)3*nfm$zKhLJHO#XJZL(lRQj$w}6_BPgG)K0@^Kp$4eHllHHC2 z@m(yFqa{Eg95B9_*VxrpNc%wVyjCrr;v+TL3~e@FZQ;p)qnw`|s9Ig=dGUG){{fhY z?Kj*6_@h|gTVrB#_HX5?1yndIAAK_t{?|S1wiW>3n)0K=fqtS?6_B`A9MoNN4=L-M zU01+C(h3nfSzhiA(;u&54kEb0i^;CzDYLBy7=gv*2;;*RXXp=|hmQfXg?5E{1Z0gJ z5SuJQ)vU``H{J+LIB6Q;+jva#MmgukMIiAJn8&9|ruGTFoT{{rR{nBgRK&4~T#BVw z(9x8do3d!|do-90t0JMIthNS3qd}0_TN>coR z9vgc1=BLca6u8$kiCJD^Jd&IRd^|f$9}sx?``(!?I)h-}cs1{^ZN^2=8gQ|Ln~nj% zC)#$tTU9_7@rrLhz#mxTk)BR#F^=F7;|}+PF!^~P&coUWMx!%hq03WHE6kw>#fZ6J z^Tnr0GKMn23kFw~`HtSGEe-vU8Q*>c#1!S`54?2&oA%;bD_RzA?Xx><^~lobltKrg z7U8L>#Uif}W(jP(riAZ5rT5v%wjB~s;GFI-F0Nddl(}0PMXYKSV1QG_wZ7kgl&cGR zTVW9T{y1^5mv8~4UOLofcGdhKK^Z5OS$#Dl1}0Y&pkz2~F%TXFYNX1UE*(o%r7`N@ z#qRoF+SOv?El4^Rshu zxs2J=swDE!;F^`Y$@l3Nn#0=_rjKZ?v!?Rt%M*PG*lD4h4~R4gWsP((X3$S(7(W9Q z2)Q#?G(ibTOqNki(_x|Hicd&w6TC(QjkBZ}CqA_hia}tJ`RpdXhdQJ>Wp}7taqCw9 zyv4`dIU+h6wAE?aUzON1DOI+1ZxvZa(56~4M_Cu!#qE#htT$J&Q(S7gTJUJo6h2cy zGr5>saV|mmh`e*X|G4;}`I1C*t;hCS%t)UIY-aI*I}h@3bJmEhjg}If2@Fs~n3Ygv zXHz@~!C(dY@@^k6LR8|ihYe)vsxXZDKY#tpe^fH(IpUI9jE_E{$kG;tJxhq0qofz5 z94dFC@WNy1SuS#tp_KmOSlErgd!HomPX?tyg>om<-*9RPF;JBv1jr6V4_j?val(1V zZ})mo051SSvg6|R>e3u75{n~Af#n_Lb@}y`^is(!3?}zUsI_#NSu}TI!yrM3pja43 zU3nxz*_7qnBDh!Ys@98NQV>=NHtbEA{eH8;9!R}XdymOROjXizE4H<}WvxpqSSx|G zC?5=Jlk7Dq0EGcI0jg9m;+M$>V|r{XhmamOra2&d9VBb$7#1ulr}3mhO*@>s!Z_E1 zIVzwFDX{~ForSfNKi7ezgvdg%X!|Z}jV*9x{2GSVLpui+2*eQG?o(k|L*S|dqE|yy zE7sSL*CYNRuvE*;YurM=bFpBxx#&v^WZmQ>pIbgzo`gOd;+Gc0;~I7C?aODOiB%M{ z3ev>N;I6k)xYbbf5p37MF*mavxP^j;aBb^kGLKCXR4y^BQ+6kB22(S$Rxs5Pc8UTy zx!xeUU}-Wl3N@}{Wm2Vb_xA{l{^t7Tywfy59>wW(JwLbVa*mc?7vER71$4|X8n(3H zCmq3W>1=`U!vgx4B8i$Gh%}jIBf%91W|uZ6TiQ0z=niU|_U4<0v*k6rKo>@ugcz7& zi5EOOa8Oumg&Eu>!|~q*WtTv8?GmZBd1$LTy|{zGw1uS4#u(z(ATHKj>I8`9axV&^ zVE`Lm+-0tiKF}5WB*~GVcM%H*JdHj17!4A90!kloY(e3Yf@EKZAjok^1ECA2JN8KO z$>Fcl`99rUXf9-*j5xj4Shd^)?txrlL9mY?-Lc8aR1*)-r181WsSmTe}sSXS7{UqJ*-j^aTWl_nF(N z*>+SoDkgO*z5a6`I9U7l!(tdn3={oz6{uBR~mS*OiF0j36}2AV&chV zqVU*|+omO^aiY&&;$>NQFBAFd`}@)Nh_tEnhz(zk9oWVxR7AGhVt-Vw?)ONzNEuf( z5ZLKb?>Tkj)s!8l0-_=39*;Ec@n9L`8(^!;l&r9`Zoe&gWxyo!5d`ixtR526N=l#E zxGyXK%p$NJEa1#i7MkV7l2ff*&V@CfcDumLlHM+_+<`~2el>K6p zI<&8~tg@1$ue`J7{3}967KwJ2?ujR_z0A{Lg-Ff_*cL-RU<&#z?7($q7=9bB5R=&- zd-KHHW}`%ql*Xp0v$SkDINdPj``hILtPATd_0hFoz9!p?P<+co9SAA`u)P(t8@mpr z{c62Sf(JW-SU-G%*8yh=_~Y zG!J?Yab$tju&lDH=fAzv%WmApSd2tpsH+#6_hhh81?pvr#EB~4Q;_998TpB2V>F?? z-Y)Ls_VZ}zKz|SKXhcpa_?-MS$Rk3)+bwQvEp{Y_ilX$uTn=Uf@tagXPmDNu6KPVQ zzbgI*tC_;sAsVtyHnOZ@1nE;AhpKx9VQ$G#PBz08yb^laF?z+- zDQA(jt}VVJdM4I&bYm>uM=kv`UGRcFW}?(DZ}aE z9pAbDw>df?ZGWJBJs_fMB?;ur>Y3YorW|L<1Fna_*J``tlG>%Y!VHrkw>1!!GHtqK zvif}{L^C?pbBk1uG%@AXel7LJ(P-9giH60&HNw~RWe0$*K z2t@DNf5VaPWWO2_fZXTC}>sJP^f2K3QG4>u)|F zqu8zr-^pL57EqhYFQpyfT(^ZN9}b%=dXLOUZ=y#B)Hf^_ z`bz{GSE-L!!9U(ZeY{x~g;y7)(CLagoL3RwIp`~I%Vm6-9WyzWo4?1C_)?4f=6*;= zwn2>HTkOz@w!rbQO;!7TxqtzU=ViC}k)!)suY7*46b9qJ%<9OL< zVAQ4)Nn;d;2Y!8VPL&W0p^7H9NS0@g3x^j@80-jtkxH&6-eaw9Xo1)`#)`Ig3=#nP? z;wdzYAl838Lx$Tv-l13P?ZAzzNf@03It0Z!PD(izE;q+09L(!D*ycKw*%DUZZtX4* zE5|h3VX95U!(|gM&Kd>_h;&Vsqm}|ex(z#8)p4TJlnPLkloR)dDeq8<%oVP4m))Z4 zyBU%~;gZ=5M@ZU2IQyl?xa1By;r-3Vg}8pQYso?UU?!dJyC)$zNM01^R#Mo`VJHed zY++8cmiWmoOEdOu032-Wm)G}~;>DO76~NpuJyU}UQHjg{M`a5HEX== z3v9L|)ak7WM$H7U74CF7fqlpaO@MffHAjb>%{-+(-kySC+H9*iJ1UwGJ>ixc>k};T z_WRx9_7>Uw<4moKC0td}kSt>WY@^VUPj|m9)^O)xu@*vXdrd)V1vC&HV-U}~MiLkr z0ZvjC#P6CU)wc~COL0a&i}PW@Ow3!UriJ@&5yi%Vh<;>liGDmqcqeL$15=Xxp~6Nm zQ@W(!45QFI>C;oI!oeJ%BW#SGrI>@Vd5WotDih(!`G8>zE6yI7MqW>HHreY`@}6?T z-h(Tn>K_egjj`Sbqx8=le*J+Psdk5hO0jugB`ViMBJ)xIS1H z^Z=$rseP9rOB4g~30Ru<#AiWZ)Qm^)$q3v57!~r1HQS78yH;RS%Ff;AgvY^m_3{NdsfCc0;8OVOGYd}OKM8H0pB0OBAYy^FO(S8^ z`N0$)E@h10glprZ^^;lJuaTawJ~-5EffT#UIfklM982HtF7QN%o-I%0F__rYzd76K zyxnG)PGKH%wlv0!;(HkJZ0%)aT;o`->TLHETrIc7&ybrg28dW9cGuP(M!YvSU*KQV z_Gt>{!a2vozukO7cGVE@4Vtj$c))0!W5`=0n3sKnPgx6OQ@|CD6VIlJ4ijKvHwsnK zE7S>Mgkr$N_)R#Q9%=~F>hDO8aw5I{c80PigPEaMQJATRRd}gqRd@-*=3=vq)*e&M z)t)5MQ>Ek0sd%-ctiZHY_{eCF6K5RKB1}>K3BO0MSmq&XPL_4DNgPu`faPKq@-P$| zG%?Xo6m=0UoL#e<#yA>HA&l^mN-L3=w=Rz`m%g@>!nfkyWG=XXA`dT@`V)LGcf`+N zImt_ZAPaA_;)c&9JS=3;gWt@Zi>rW}7>_S^p74jLUDLJ2zaoQbh%7b_bErIPq;h@e zKo(a>&a7_Ft%^niIi$gX_-qJ-?@;WS1L-bNuO8B;mH*o*l@Gg zZd2Dxfix6vGPYufc^<^kzd(4Q;twwxO_OASL6XGsIX}8!vM9n&Mqb z#j!Z963IpI!}AZ*aLY5bz(;P9q?$*jCUlf_$|`-or?0 zXk2*2o~&MsPd$uyaf5m^W!T`^MAd*>I&h4lVJ%k7S1pKGcC;8BD=QO=pG#vtI#yL$ zqsCh_noV%({)w~g@f>E6E)qHH)$Dd&rQNAZVIH56-hSurAsCB!_hVoH)>=_Cc=3Ld zR%>QXIgn-=pL|pZU@0I@3t^*t##T6E2)yQaCE9VS?#EEbyyG)KjT=vqDi=wE`%Bo$ z2-p3j%49G;sMEY2=_@P}^w-t90*&nfwFS+h=k)iJVm- zI|RdxwKczR1@0;R1I4(TgSm2Tjv0-tCuMo}gzJYH5#0bYK zVGw|L8Ts4xS@~Icj1;doJ;}w>t02`8ZtP<0*E+0npuWvC3D!@$qpYj1;G2PvezeTv zk}V9u#|Slq83*Ig!ON+Fc`W&i=b2CmO?N4+IglxoPc5Bq|89O)g4j=#{CBfCxeSzh zq3c*WFD#Q17Ghr{a819%T%7DJ&yVT)mSrhlIwDBgHd z3Cgry70{rCDFr3+IO>lVtWUp;8GnOEp(G*%9@%!ebIuo0PKKkk0`E@8A>V(*8osF3$0tIPwC62~zpK{dh6@eub=S@8Si4{8be4GZUTaiaqLC!J?- zza4%HjL0k&#r)z`Gh^kHopytR1=@8Fv){W*kD1eI5OT}84>oN-JG;WM5N=CB(IQ*z zOUD6#Rqkzw39l}ItMTsE>E^G05$#WR-cG4Q@#gRu2Zj1k0c@}bf7o2840TnPR`lt& zT7SctH6F4Nh(-r^j$k6#uE5&d14#`~8XV=8ko;f@#}I)<86epvQhqWu1UxMsI7|{s z4aU!75zjlS>a?dgG&RRrR0OpFiLA zt@7dizt0$x?eId^TPm*1?zA%xCjju3HztHEA7!WjBffw6pPSF9i#=3>d2^K?cJ0PS zSvxx4zUlT5oJQn^(*k_^m50*?&F}22^T-))UGwmVlHbORzYKK7 zU55QU!C4j#iw3H%3{XMO%L;-l=pE!N&F8Ei)*O;lw|;( zL9w`i^GJZ=JPywr`&+Va(ZwO+2wT2DvdmeQm=bg8b|GgD2(>C{0@##1ur=OK<6jr6 zx(ad)KBa02@wLNZA(n!$RE{On21LD4RnI zrR5t05^(<<2uqaUA3Six!@IRkhg&NrzHUU}RJG_kRezf|3}R4!VCa7iM*PxgJt?Cr zHvfg^Zy)}6P9?$HZ|+<60W536`aq^v8y&VtpS_xx3CQQHF##G9cD}v!aCHu4;~JeR?TcrhpZWaqCL4xL1=7hv z5vogfcpCwcX9;y=XwD>mtzK7Zals2V+=F}#ejziMoe66&?zXWet|soNOk)!tm@nl9 zBc_iSym`)-yPN#hhYU}+0I~b@{uNTz>&5K=p}+?r%S1%N?SsnKjdW;b$pr1pH}LE2 zGR)3WMiV(G0~w(iSbxi5iVMC2sRnAsY5JWEty#&nhYko%pn722A}0gAd3VA7PZ~9L%o)&Q0uKS?)ai8*oC^a41b`O?r=c7#_$+A zcM9zB;c$+LE#A7IBmU~(Wz__$OuWhM7E=Ji=E~1~{5Ro;oC~~+=Pnol@nBt?)9UgYT+Ke zSZ}XMeYUOt+;JVYjX@=$kC&9k4AiDf5P%W!$p$ApXj!6sr+30DZnjKF4_wu2F22EV z?ISIyPH)(|2_QbnLz-Gj^kZ53Q}%^QYmTk30NWPRPjpy}Jda1H+Ux&?N@zK7LgkhE z;wx#W>@A*KMpsJp-1kmeT^x8sN=9VKbIHx(7!)_#FC2+9N`c}vd5Ci z^*F-X@g4SfuEkOwK`OkHQ4>_@Wmp4YfwU7}rGAYcysw7vC2A|B#` ztxb1zOdAo7i%!gY_1|G;>S%y<+kB{t**+cnAv1Lz!)MT7Sfkpi-kKATT@j!#< zAI~>f1jdb#gt)qpT4*X(S7Q;$%OG^7iqZi^?o6-BA2s^mRbq8K1Cx&o{4F}j%&MPrWnja)3~EV z*%&d1{Q{T;2SPI%0o+H%z!{P7{r2#71=Cd#2s&6SWs(0jTk0=Z3lFcaces4YXOnI% zhMcgyRPeD@V+4%ceaBT)^v`V*u_xyajE6HJU(_*?sfND!DfsCIVG!TJ1cQZRu!j-N zbtUfOgbdb#f#vyPx01v7pNJuW4KLOPXs`a5UbQ%QHa2p(!$ssUYrZw5&XP&3<~v?wy{Hp}g?0gM{BbNUMfB#y=5(icVdq=O9p11K;u#JX z>k2(mte*qvWx{Af$kM1PPF9Sz3VGa=WEa2X3W$|H)$<7)uPu+-vxe^s>tJ7$IC+Zc z2q302W6NJY%E4x1vikZWdnE@^j9oVj2zT3y?Rt{sBGCuE=3)d#*zH`Xdm}q+cw3)d zl`~04^5Nxn0h@UmAW&)%g)J5bWWC(YVfpw^S5!-Y_BlCA!&^)sfs-n zLw|Vroi|c zO;dW|R*l&U#<)6RyZjAW0oI3#a4AFL%4!H#URE<~QHC;rar*wX60oPO3yD{FsW7F+ z7C>pArp5p9q8`uh&#hCH%Ah8Fe=!aBL;ufb4>w=7YttJ*V^C6O(jYOcuMpV2PC<@r za16xPUtC3GBE6FsvS&Ep5!8?WkGZ$uZR1$-z4?I_#*yT_93VJ=?Kp{co!B>0lD&Iz z5zrDPb0d>lQnI7P^WmT0|6kQTJ;ND_cFqD9NMugGR##V7S5;S6pI!~biA>c(zhM`< zR5TjP8IHyx@t1>FL}mWfpQ_MTABFFWFs|&yrs^s^3xQ>=*382qZw`!C#0k6W1lnFz z5lYCd5CwaEFLPLjX3BolmX_zt>X+M)z)OMctp6Pihrn6N6grH?WLwLX7=`IBzqb)= zH@|ux>3bg(QHskVQGUmS_VTB)i-jg43;30=?Ghy}n~F#>G+oY;>TGyZCnM=m>4iGf zSCf2N%*0HWgpm%=@KfVE*E14w_PZbyE>dhSSsixvRvzHN(0EZNqV1<*&c>>yFT<&l9l)Jt z7t~XRb$Vr;E=_zTqKE61T|-691VqVphVwPdYvkFILeY0?`vV!2+)AHq^SC@HkxPWc zAyfnHD)U;FO|F<5c#)7(s?F@1{86pOb1r0mr{K_FJ1=CHPh}-D{h2sjT;;AshRFgu z)4=WR92K9=pl_)%0q7aG(oznC46;FHwDAUc%^8eu*46H~DxR>ZhaxH$8Ql2e>44?? z`}ak7D0~+qZ1z@ApcX<3V-m3$OCVY;aW2nOr7WJ}pO{1pB~Wg`bhwQuDVSl zbtzaz>;v~Mu^ly#CZdXdm6d?YytwHsd2?Gm;Pu7&2oGX~u*_wfr1?y3F=x8b9UBk; z^Mbu(6XW|{=NhSvdld0IFqxy(c38bSdpUU6&_J;9XLz5m?k=K0VsEWymfkEjk@#wP zh1--aap`bD0PBjmH=zW%ek+p|-Oz1*)%{CFiL4`QmRranatB8OexLM!^BGYq0$lwQ z`zf}3(Q1otkr7*R80N#i^wavINipwCS$L<{;tFCjx=)HXGs{y?3b8&(Ts=80eI)fg z+3mLwPoL_v#?9B@*o;r=&29mF_WjPr9h*S}NsD>l!kGHi@zQ@mpvq?Z$nFCTZ*;DmNM; zcTnOgUvj!MiZlUgQ^YEVT*CWyDT0FHe?G{8_2ZgnS!>EKTOB(Sy1F@s zDq(Pa80v+EBmmb6pCzRxp6NS?tSrA+L77-sWquZXxx5yU)U-2 z7w#+Jl^cg`!C-@>WYJ|HJzIBI^`!7qn%lN@@RT7=$6A zFMa!R^+fLLRbniqkn&xrB(hCnB`+JuB4Xqwu3QWe8b!zV^6L6&$3R)pYs8PJ z;D!KPsI4ZO`1`4emAGG5RK6eQn?F!vwGL=sr}1k-WMs@#j1-k^BV0SWtRKK+Xlz16 zfY9lDBe(XcAXcwPLYo4YGm)o}ba5At9PWcOY1i}5s>XQ z(DGXw%?QKh{>=xx!WBR6yL${1$W-OkE|TiK%A&VkHY3$IxF)_?U^A9<;A`OOzHj8J zo2jwdzPVB)YSqb$D(hP3(2si_1Tp2>RNE z%po5z!uRa**o^1z@$Qwav+FI!UL*z%RxgO+BrhKCPR(~WtH}1pw#0RocQI9-Qe-QQ zBX{=a7__CR*0^lW-3Ucz)kYZ=fTvu?3Z9H)LF1;k%O%L z{o5~s?e$qMM∓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%0D`hT81L`FUR}!;*O^x%c?D~_1Ak0N-dbQ#Yu4T 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 EGAd3Zw;8uG(>Wi} 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

rDc>(Z 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%0Dngo;3XsxG z0a6;-%Jum1dnw&^Ekn>yBPM9736p880h2V>fJqu`z$8sJV3I}~FiEown55wbOwx1{ zCewHWCTYF_lVZS!xeC6AjF_wJ&|=;!#|GV3b3pNx9H#Xu4yn9~L;9}bkg}^dr0FUS zskw?nI;{0eY)q zFtt@MNNW`gQd$LrbXLJ2l~piEV-*ZiSOtUhRmEWHs$h_|Dj1}!l|0(N=XnciqR+Um zlE%F`|9|%0@5ibu%k$hJgb)#|6#XddTj9!C8a%)c?;&t7NU z{o|Z-e?$aXIZ_J&_ujMj+H0@9e($yS8A#MRdv?Wt?;t zS!PT9Ht8#p1~_TJ{hI(qlCWVYQ7SeFrIC;gLTRLBgHRgD*&vigiZ%$Pk*EzqX{2j| zP#Q_wFq9~D8-&tG;0B>I()fNYjnpGP4iBOnJ`Z0@;x5R`Ta$d+oxDislg9IV8N$b- z`%^i&rfla^{b9GmZ&v3X(~J6kM|Ydv4=>`gZ**c{y%3jmQ+qg7_qH44?OmK`y$c%a zsYYcl(QkQgk2y}Sfi7xcJbrKKpmq0-x@Voe8=bSx-i@wVXYWSGth0BcTh`gT(JAZf z-RP2a_HJ~@x_d|6vCiI&&RA#fMpv}5HoUokn~1Fvs;uMSJ>GZpgq)4uws5?bOWrK; zNGquZ5U1? zR~v@Y$k~SBG;+6LIE@@`7)~RX8-~-!>4xDna=Qs|q8x7+P9xVFhSSJ-D<&T<=h^;7 zE2lJW&w*@j5Q3z7LlB}oZva9g$s2&s$nXXrG}5~P2#wrs074_N8-UQr>INV*Qo11s zQ9d^Sp^?lDKxkz0qqgmQ8Q-fxJd2b)Yu+YSJo=aNUgJ{^vt?? zMlG_Ao{f%JN6$tBtfOZm-|OhvNbfp&HnO>no{i+Kqh}*m>*^V$W*t2nnOH~9hU2fS zgcsuG=c>o~I?O8%`%+Z^)CfJ&wlhxR=7B?oz$2e?QgV+p5N0*1y+u*YmR8 z#C%Q9!s(0pd|hv>u;=YQOX{_%r`N-RTCHkMpXgiIZS@lOlw0OabXPSK>Y&1BD}r}X z?QW|5b+x>uk*Kq$hG|WIzf$X4dP>#BJ$-+nUk}7FZV7&VD|J;D{%(8auK+NJlkF{pEM3DWsr}1%I^>FQH(3vt#)0&K+TRnr=lb(Z5Wf^Ov_nR#o8!Qt8i`fxni*V4 z9nu}$)V`=u;AO4-$~bVBG*ho@Y-8zrNOn?29J4$rOje4uwi>JYZSdjajlpw{;C2;c zLi%T7hdFaFA2)sNO!mxjmXNQ_nmXX!gzCqXOUfla8LX^kh?1zg@DGQ zmi1dfA9pPiKJ{8Yi}Hcihx^8*e1gX<-%lisRMEW@>#c$C4x9as!eOUp{!@#f4yJmf3oLScpSsUKn@JORP>VmpB z4%Cq=@^CyTShigE)@#cxNZ+Z&J!#t~9Wtl2zq7uPJ`jB$L|WKgws$oSZ%o3w;?^Z} zk=e~~cYPzYdCGR_I@j`x^^JiCb1!lUx}!eY#5hH0d7I(L8ZAoc#qwBP=P7W#| z!Zk?1BmMuq{%=dZIB1>g+a75}Be1%)4c8c(LZ5sln}c-@v5j}Qzdo)kDKmTw(Pk|F zV-)W&3UOg$-Pb8kCFXtW8E$U|UHYJ}pCaz%*xhkdoj)JMeqk(Y(WY`2%oRnXcEV-7 z+84P@_Sm%Us_@0KBSSnVG7&A-xspJ5K3X%blPb&7RJ;pmK|BZ7**#|b8hy)k>f1a* zOVlB&zgB25MM7hFJnhvaMuIDdR2=u0@;npe!=v@__Grkg$mM8Z?<<;o9OJDpmZv&= z9+}^JejMCOKi<7==kxIXOTkH1ia-fkVX`zT?{_7Dz_#`B#J_L%G<3-~8? z`h}T>S?2w$!6p8(i>I{2V@9jQUyl%tlgA+MWBOrEEr^SeHoP?2O&#$wZd5%DX}7x# zFOIXj-9P?5jUV!@cr<=HT;n(wF$JU4YHG{pTK6wP*!9csJ7t{y5YB}o`gB5{j)bWl z2O=HrDWem%9oHHh7d6Oh;kWtfgjt(iCL#NdtEI0&;s)>%-)#FK@?Mysm3@xC|6x@+ z`o6}xuV3BoghmHCU>#`iJs?`6YiNV^JP>8n{O<#ix(D>D!F{fkDt&iaTlOo=0tMQT z9YIfqo$VQf2VV=?b7A+6a6hj0WJc|@)SBq?NYKh`;Jm&+(eK9<>yn$9Q_iWR-<+hd z#%pRn_s!cngH^dpDhqS;P032`KX^`%DCZkt^jMjmLuwPYAN5aLk)814{##0&-V*r^ z>v1lcC|OA4({>R>D9@?o%_w|j9}E3R-MptZ2q>a{WFW`}(TSgFQO7dF@>h*p~-tr|CvT0Rkaq*hA>l(hkBM$-pZ1Pt*jf&TCP=? z@n39~VQXCc9f=*n?(~^UdtvjKM%|Z;5lLu-1yhGQ{6RuMg2t1%B=?kj+w-Y#WxTJW zt7eCFW5VEI8&*ns#ui(Yh~1)8<+h)Rc1m$?jsM=fAjqf@1jt4?pwF0mL>f#{MF;fV z6eg+Z1ESY6{fk2SE($Hbk%~YO+!y??lUbB-!>j*-HEo}IMo(BC^c{gQZ8)z_w!@LM z$gk9wy$O6_vYs&i!|+$}+W|>C{yZ{Ek0<)qyu;N0iBSEGT9Y9;65IVsu*l6G5IjEt zjV7|ZClNm5&4;O1f4`vFVl@0m1_v~2)vf#zaA*m_7$T_2W(89)B}js0O)Kip%^a3f z^k?}LL}i&cEO?W;sO0;%cf^1eyZ?*kY|{?aNN>iT>aVR zy}TE=Y1X$_H70FYG!o%pYzvAb{DTxdi)om#HRMy*!jCmG<7$Spq&%{g^NTTaIc@>-vtjt= zuqMX#<}k=#ild+e=66CWEOyh(96%fk)|b`s6?W?9WAakg;glZ_()hH@_1(mC{&>AB zpO0GLI+Rbp6rbS~BO$1;30G*{d|Hk>KE6mFSA3I*`1X&8Z#iy(?~P&H?=L!^n6fw4 z%Lo4IQi}Kp0Sa92Y!=jI>C3Mq(Mrvu+2axp%xNsAlTfp3RFIK4`Sb z=dvte`c2s(_#j>f{m>16@}rVQ$9AR=#Cox@V&Moj7RLbp&UHwFW|+7g5`M4#kDF3G z7MGxgA%f@mlON$q2?Jot8Z(GN`cg=rCxotT?oDg7|;J3|GSiG*;e5Jq4)Z%SZ#rR*}HZB5$MYVYJXRe|< zBqHWg?WaK{^QfQ{%YpFO5)FlP<)xT|KSvbB9Z@uQrTK(Qc0^I!5!JkWsDIzl-$(jm zpYR3ewG*HJ`6x_H=j#`yarr%y7Nj_Y!>fNW%KPhHlzv~2C2LzZmlQ|~ z5-czOro|Om=ep$(J8x`Qw4U|J4m`N;y89DOTfBjLY|GL&E9|oiiKS?gbS1eh@c5ij z4K9%ekuKk$LhYd}wtOkrq>LpB*ypSq0;YC%9=laI9 zJY6}zaAMdz!*| zh6@E`iAJK%I*%Ss92o4QyYLT7#%k=FB?FoXM&Xz%&3D^(D~)7YePb$9QLb7A_qfl* z@oAE~G6zsN zCR+%=yk9Wjf#HF?FXO?)Z4GcLdxBsFKo8tbo<=TX2uez>Any zGD&C$I4kY+yGz3}+fo>ReRG9ft?#RUiAw7R3L7JAUBvDKX&xSIZ%jvK4(4V@rpAYHqku6{|gh2@LlsV9-qH;0A) z=Wc-i2<-b=7bcs-x?@eUWVqMFwk!RUTtCbHhuSYH17a*mu0NA+^!k~%OstswCKyCj z8Hp_{cb{rrqO&M%Ue-ssEiThRnbhvD;o$?DBOQIp9m#*|OYNo>$-|StZTAhK5R@akXx@zGg~iu1gb zTbn`!ay{GH&Ozoc8=+pvI9e%$`G@>!yEx$_elIIx+g7ORoC7@N;!qXz=!OR;sTWX(ksq(m>^oNDx z!v&4wDOmT@fm?dtiRJ2Dx>ByOpb1)%J&km8Sx=}p^f(5VV~%)h_|;Ia&OW29DAq}H zwtX+$p(bvb_O->^3)5%@wp8P{9njHCc-WDA`tb1uwX)jqyNz~W?qFom&9{PO#V+8B ztYd$k>u*ZJC9hg~Y=)7H!!YDQHLBqJEY2+aO&Q~X5%|NCw6>}$^J|H~*vDkY4&1)g zhNY`3rOBkb-FiL*U}#=|p z)QaeMX!E-n=Irl3LuK!#>A2sc(y!ZKvKU*Fq1e3MbBOK{CuJ7LetCWWsY6Z>yY;{2Lq_IHCM@s ze@I7zZ4M(Gw%^cvI9NPij^-T0?z0UIbk@4Jjq2kQEf}M5kec6(64~$jtbI<0?CJD* zLG!(eun0BkdRy0!CgY~p(r$Ees7xz~Q?}VUg)76u*|wX4`)D~bw5^Q`g)zQU+iYfG z8|i!Tgz7o#5RP7GIJlQJC)bvE1;-UDNCmcMb&|82thmPdqFdTZXvqSkzKI%TTUwBq zI>JnZId%zGwQGEQgJBLVmJO%YVHB{J66}PZ+Y9p{+&eL9ne{VIE;ooJxiyHwq5AYe~M-#9--XLgr<&m`UklaPkuMyF$2jpDg`m z2EtScrnf2ApH4(mx&I+{+v;V=ms9L94eP+5 z6iJ2c<|@c2?5Sfl#F+Rt%);`e3SyHN#dj~Nq1fum75!G9`nsqB2aDRCiZ$`tVS>_~ z%-i^E!pxFJL`+OkhrK5B=@;A?ymMi`ycr`WP6{VbEcas!goA6h`J|C?>>k9q_;H7D zYz!l5+bK3!*KIbFuovZRCw6fBQE!8F*lBdXI9TuT=*=wZ z_z|(cEI7=~-j<3f(0x8Th!Ro1ztwFEqvzQNJLjV-qW9DAmnwb<)6DmK;e%oi`#;cn zQZ9Km4I(D*O+nYshwH_;0EVmGC#F32XUW5cRE2B(CkIa$|zc z{fz6E`!+XTkfo@D#b{H0yOD8f+T^_1JL021=7{0`W{%c7r#r8El(6-dG%Wu+@IfU%gvipLN_|`j%|c&-CXP;Z*bOZGC%N-|d-wI}_V;-1b&@Lx;By>Xy!h$Q@R| zk*7rP{gG7mp z{#o~QeXZK@6KTG-H>)+>*A@3Uyf7BzE(n4gW3)jM>eauxBI5a1ci-D}q4j=1a@xHQ z8>ECE`C*Uz*<#F(z!LteXJEm=gGPVGl`ti&@KHCW`LYoXA{r0HdMt{UWMLjGG$lA+ z3sY;OVU?^ndj39}J{nAYH+oFmenJ>Zg2(}F!l>Jqq7W)`dx&h^bIm_pBWt*37yeG$ zrn8l|g)Ium_NGiK&VdKmF`$`AN+y1LG@f&wDys>n7y4Z!FjVG0gYj57h*rdNOWiAYD-;3mF3G?zs& zqgo5(l;}z#BE8RIN`hV5#Hl%zx>jK7B|Mmy2GGjP^Z_L^N^+HZ!f4!jMI8Uch)5hW zw&GY4C-4L0_&?soy~H#OicFghfQO1Q6EoG zyLiW@AgbT-9m;Mu#5r#*as{hTxrisF${h;WKVRLZL`Iivr4pK&HcFZ4dUh*i^P`|j z8?UXZ59Bs?@lK&@po~{7kvc8J-H1m5|8`76P9>! z`F6^{1>S#8D`$I91kx=}X7+BR6E@c>8N%Eram8%|r1b8PrC%vA?d5Rdh*$rB(PCqt znl6T5R|5Nn{RF8;`UU^r6eR8oN)jP3wP_@_jU}c6(Q$3^$}89-Cs1M);G{}8#Otdl z2vlX6JAzw+>iq8u`YB)gT<2qskfrtS)`6b$BX`TdL!e-Ao%?F9cR?*mf5$nDvOBf} zka$YmAds0&h?+3f)OMNX$qA+~`zG@xxbb2;UzVJkk!@QF_#{=&JKjrzkdt1ct@XjR zwjNv5kW(qM$ubp21gyaK63!g6wA>o=WqG)az0qXqoP&vDV7vU8@oyIuOpcv>(2(dm zuu1*J-@oh9Sp>DPFYAz6n{|No4HFoBaR{DCvC{+`8y=jBmyZ-K~b%tL9+98T_n zgm1ceyQcT(bsRpcNdH&=y_Qilrs)_X1K;bbkxVw-t|TwBE7b-8OengZqEiiyi*WLo z@J3*Dsz70?bp5kyVk2RyWU`_SJWGi}J+-@1My$dCrBvE?Y9`4?EBvWX?Z1_)R!%Lq z7ErHiI>azg?;mY-zAIlpq?{+hkNd_O*4?A} zKC>jfrPj~&!|z7wD>RmeU<4c8x*mW?Z?q>E87RLY^WpzRp z{!RVU|G283@zsk}`+8~w!qk<=<(+VeP^K$##B2q-`*owsDyL}^>l$wO zDG<{B#WcFkAS^-L4@eXEtKt%be7W41(z8opre4Sl2G5tyE9a4+915gG(HLnxZ`1|H zf}-4D8EriNv*REz3v{*F!V<`|MfCmjD)neukDPHat{%B8XD&}y)Z*hpRa*hWpk1ox zJdkE=JYZ3P9W=5vv){Yt02jh>b{r`8Mavn4?Qcdl)|TV%(kx-@+G=?XC99n8&g8p1 zDJ2F!lqTBN#+|O*WD_UfoS5~7&nWGcEQb?$51BAZr{S)R$Sy41!>>rz)A%Pn$Zqda6L3jr>) z$|;gY)>vPxD(EFbfDS3#9^ID+!M4y`uvr_&jFAQXbel2>*MtYci^3XE;Jfu>Rr>$% z7P}58hIER)Omut7VoHZ`+PmZBta2sW#vg%jWdSTgG*2pt8b5B`EYq6n!(3C>4!rB( zKP?mb<7!;(KW!6g9NA}nSn0&c$@FyZanTx1Y@0k}6Sw|Zblpwy=L_0>1`>0+G^k?5 z_$9+z8ph2ui;3xSqqrd4E$#8AZ4z_r!>j+hltk>^xG`M<Izs#|n`Mkimem4+_L1N;HH0^@q=iVpbgXRO%aUF4 zuJ4lT^ZczZH6p;4pM|r=nl^Gub$MIQ4)_*JVoaZDSQ|YCLOMFVHo=%DSK=yoJ-F2- zc5hw4@TBa*sf%&mtj5>EhxvCY4`}9D_SrQ{ zM9rl=-{wTy;0UcY0S7oGeS!<*#Ixljw+eCcDA(iW5?7agNOBJtFEnSMeIG&5@<5qP;-7*k&}?v)&I zMjv;NOky48OPfT)*f4oan#+%W;|!2*s-ji8s>7DzIfJs$$dAbT{YqW>qzfG*I@-YH z!`W%rD70n4au2;P?^x$T?KTq@5*$72;o8)VvhUU#(_+G4Z^PDD0dIdL;*d7DlSZ`p zvkl7*9Iv&2@%Y#9;oH49J_VLj|Ma6(HA1R3n!$t%s0f8~A8Yyeo~*Gc_WsEc#B^sk z`^aW;?qus;Vswb<0oO>3V;Q&$8)7}I_!J#bym zQ}QI~LN{J$PPTKooc4nLPo`)zq?GPhiWLVK>-)V7wG>yeWO&2g^)pyg8zX7H4=y~- zMbyR1%nz?KNg_9#k>MlL?a7R5l(Dr?O=ZKt-UF6ARZ=m37=}n%@Z1cJI?8VYu&r;= ze%Dkme+VBYuH0Mb(tyL|-m?nraZCu%IDEPjO(Oo=O0`f)C$Z@GN6AZDQBQMp+4IE^ zIiq=zNxRyYG0JSJDS;GTqaZ~7Zzg4%C|0U= zQ#>pPU#!O;8yos1C|;k|G4}!1+aXXG48PNu^gtP=?G%*R zq)_vsEDNjioig?<^Y_SD`3%`Ejdgc3#$s0ay&3!H?G{WQ}5gz)r2Ey z6^7Av>QJr*d)DjodEu}k^Tj!vr;Qmh*S5>wfP1>}HfK6Y=jDyzqcx@1wZ5KV8^udX?ksHzD)!&|%#Z5UL zM;z5*j;s}U=8QKU+puWYqPk$O5}Quc1c{Saw?#^me)YfnDWEh$@@`DdNn`NPLJc)Z zo0klhp}2F6yE#+ws{Y)C8ovIyWSa%`Tn>%u^Xrbvy`BUdyxVj6dRoYxohYv~q-Lv`D9kk)&J zOMb!Oc!Q)s^C>M4UzR(|2%}e~u{SCWHH;c+0)ePxYY24&Td_HRF zFLP0#6`QA}6>WO$?`pY}ARV$w8q0@1?uHT9C|P42a;Sb0Ml+UEAm>Zy#P|F?HQ^vG zGC`iDjxZlrG+#_b)1$LR0b{4ZysW;sWrS8izX7HAZPwalv(zuN%6<{;>vwVRa^f({ z@A-R*!b}-thG{M}rs@qWj1th8RiL6Y%#;)j+i7Psu{;ak@3D%z2|)avM;UIvZS&)T zc*KvQ;A(cAL8v*?$IX4RwOIVz;>y2ikGPyAR4tevXUtL>y7;(LD(;lLiwEebMgo5G z|H?yM*lT8J+b|Xso3&=8OC6x#;EVPQaethq7T~)+D|-|Er;{LOd#_1~lXk43 zC1=f&Cg96~%(SkZW2|jt2J0O2?2>THv!Hzxa`=`8h&!9o^=7%4U6{^k7~;M4=p2rb z%9c*&=Sjj$d732bNGlb#5rs&~e*9Ntrb@++x#4Uf*WcTTE__qk)X|#z!;W@o5d3Ww zw$1ZU)39^DrU+Or3wvNQ^VSo*a;KuqloKd<9w-;DhytQXYzI0%efqMBvDh zO+akYwKqKT@GQ;K@Xho0o}q%C`3&zVPETf(ejjQa+W0`B4mN=bqTqe%&Vgfj3&IM#+_$m%aNyXnRs;tti0FcS zsWp@S0Dk{Ft}P@5s_jOE2f28me<54iX*E0H%VS5U7&%`8uwglwomJJ;jL9%8*h2Y=gmBW;OQfTSL z4fsoDA=1VmJmqiH?269uezCCe+J;yExr_FFfD>IewhpjJzB^!tX$c3M#YeA6yeP!h zDfj6+GdIxK4m+>N~#RV-4EtLGcN z?eg)mx4%9xz9{B1bb!lxx>v{SlWP`6z?C#V1V_r$D_rjzefI3KcoqSa8atLlHswx$ zY8TuA%T#=P3-HJ7ww9`O)sD?Gv(*1SVVDNI;^@1Q=}54{sjgq$^z>r)Vpy-q@pL*e zo`;-H$25{C_~VY%hUB>?>rKf4p}WjJu@vPpk?%-u$ghg)&wR@L#B?ON=j)Cj=Pw=M z1P~4vE>n`ognA;lZ`e9CaK$Q@OauGDG~jQ}Cwe?LNg~D;-R<5uN4b}PC(!F0Ee=x# zE<|YtJ#JA?Az(R|yP6ZMXRCoxCZcDBD4yo!mdTT{k(raCl!v~?U8Cb=LJjb9$R+ zROHD1=4#U`d4bl--DAs$q~1`ut=?+2MXO<_xds%O#l1@9R?42-?+zA_GJ8o;&Xslv zOc}hSQb+FtSEHUi%GJ8>f=jFzfY8M%6~n84Z#q8_+DTWYJn}?_yPHfa61hP8bd1#aUGQO#1BBlNJG8d5q=q*eBU zP$OflrP9rEflCpvzqb#IiOwTao?E?vE?KZoxOutbmP(7zQdU&$mx1Q&CxI%iLTfK~ zJDWLbjl!${efW}34szqm-p=o23$FvcuA{mR8QTJooULD72*R;L2qE?QY!ratzp3xi3eOZ9KzS(9TWO0|lZ!|fgruT&NW%CP}ogC*C-Q7FrJ0Z?1T3=+fS)&Pi`-~e}ud{c<-xKy; z5w$RMjiynhrp3ta_7CUHFB^*GE~+R$;hJ3%f3p>-o)b!^>jzG4Hk#)2WHY!CXRnbb zoO#zfR0U<^eIMq$Z)DliM8f2Vsze^gr0(}o-%Xl!s?AkdS_&o}IDT=|l zO&8sUo~sruYw3G?#xHqV#k{ptG{bjznOXIn1?qyyb`f6Tlem*&!8$y}?7123nL%9a zrmLrc#=%Tnb~Z_z^)y*aQM%oygtu7YpuT#Ai*JYJPdoQ$;&JFY7&Z?R4+O8(QaEL* zs*k4mcms;u?xsi=jmM$Rfmoai^p{5Q)8o^MLL%!aI&J;4@Mrq>_u=;;$O5&sya!kL zh`GNm7*UG85f{J}tOgu=S9C-F7#(ve!gG2(&-6~bHe;!{P$hG zz&>n!m(N??)u;F3C;cX`V%#~|NMMTYj-Kc8(tx*9qyRi#w1DMje#jC3cjfx?O+QiU z^Nqt}KJAS=!9Lvu+r1&vXkeV;tL8A5YS;5uYgybt(JA{AkHu&6@-O*>EU56K=N-3Q zI@9C@+2s>!XNhZ4d|zfTGu>K6jCbQ~Q?y1Z5dD*LVDdI>BRRbK+iqb?CYivR=_G}i zqcbn?i&6RJN6T}E6Cz@g`3*SFvz_bC+MyoteGeF^Vn*ttYkk#=T?p7t+s~;(5KvqdCpBHh6rCE3Q*0|T#6D?Gy?P+C| zG?v)5^K0;Jt{0KKI@M*>`~&K2S6@B9cowR7gV=9aKlzlJM}&kE&njxRYqFejGjdVH0eNFal^B6Kw~$6NMX=QgbJ`3}1en;v+JdfpLAJB!?I904znrrS8K$%P0@+ny(H$o|dvh5;Y7Z}!33XN;FJep#@ zgP15+^AePxZa{c-n1fH^YT^ImaP~`aIGW~j1O>O(L)0Xe;nlw!J)ueTKshYo z%0${==7OxudG+~B$Bh=1%}Z=vJ7wg@A*z0n2mO zVauD!WFL~3Ve0vg-r<0gn{MZyn&l~Q7z=wIw!9z0_V*jy!tq1uVQ&$#x1y29pxs;( ze<0?msB3D+Aw5Gp1!?!8&NiVZ`1@2id>h3IA@rPNuKy9fRB~hcZkBWKA`X}=>{y5b zjk_FXf2qAx6p=G!*!fF+r-b^6sA4`r*!@dAx6*1Th44Z1u;Z8d-_{|_{8E&*{X6_N zzOQKn`r-A2{lARRTZ9KrujbAg-}m4X{$S8`Q2OjR06|hnX`|CVC{9{0gKeQ$fkVMr%*g$pY$RNpKjZ6 zr_cmCvd^pkY4ik43UY#$U{lGKMG_bZk^~qphAc~(OsC|@50P>9pe9=ArB{DDE@Ey; z`jJX;n+?tiS|xkC$O2aLvM-d7t3M(6P3cjXfYwSfw*sa8OI0Yj>y;wxA`sc-YVDmY zyA*c)5Y+}GeJ3ft>HOFJ?IGLZD~&~X^>^LO)Q*!9SzM!hCsLWPd2T+Hat;?TV{CLY z<+7G_HDer^^2ggLV8d6T+`G2Y6bQgr?aj9mbqiR{Oj@e84`SK|OSMRpila<1LP&lW zzwp#F1e3PSI`}3}I>9B*4VH*?qe|G9>l!dMZN|6`5u;-T8HD(jxdY+_{aUNc`|{n4 zW~H{e+VHhFxhIY5o34pRa)UH%Ns&RG$K@NtgG(B!&T)PqL$WNx2&5((d1fI}I1i4O zG+NtTIS~)&_;#fWA1oUt73Bs({GB#d}9szXqTYmG^>4a zpIz6%$Vj`Mcma{K^gY)I<;oL9YZ+YUujVSjn?w)P>6zaSer(RnZ@dG&ysrD-sxlJ;$?&Y#=fmm;_JVfjmevjvDauQ?fA0;T2s){1r2pa1$e@(h0CjdmIod_As2iP ze-eRF8a|a=EQ~2}dSFUV+L{r}#mUqccfn<;*H`}~4-G78!*Ze9Ol@NUeI?OL3}f2R z^=5h@ryZVSr^Q<#aX=uh&e2CBqSeglqjl-OS(?!ubu&GQ6tUo9b5W;82?qV6pnler zT`A&bGLnI^-I12M+l}b1DQdbb5Q zj;9`M!r0-1=gKW3W;p7+W}J7j!%)KQY-)dAPf>idw=5fmH|1OFev8;2DYtI;yHX?Ty(<H6a*%t~n6nbMetb^|hMmF5Kf99ZS7w z!+3BX_a!GhoAo%=*S3_+wJ57yl2VRPuh2z}2!%|tBX*fKDEppQTdwdW8{~9z`E0OV zTv7`&Jf@!~p^h8Mu)AC%YC_SJ_3u7*{rf<3&Xn{F`-jgy>>obEu&0maJjw0DidOWc z=qkS2MK}WfIGOx=H|qM5vV$3BtKF7R`qZ)zSM2aOZNpx0 zzzdE=K6Oph(M$TD&)7Hgjq{MLAM0cnK8Bfv2npW-357y^deuWjhZWbPn0w9}y>l;m zm?pv!mz-6MajJaQhTQ3=ocK_4H+_+8d%N#aq))uu>v*Y6%TYT#nZv`{52SN{38$uF zI08;dHtmh05cicMS6bv)`IK+=N(6Y$d^umGFjux6p?^-5tvzLt zYo!iqH!nbR8^YLWU6Wl{Zt~#h0A#Dit(;}6^|=za;WKIVt~iL*gTtvAe<~Sm`NHp) zqb&&Idr_t>BWAv7E>$J={<=)3$>{#W=M!#EiqCL(r6!YhIlL@NJQpqaw{Hc*(YzHH z_43``yk$k3WBIe;c!r{F&WCOLpW6^dWY66-RJ=fR6{fF739=Jb zVF&({d1Ya-&kwp3b@@}(>?Dn4=|aoJGL~fr6a}2`g8~n7w%H2SSOJTG82{TCK@4D= z?1KUBq{FEnA_{SA%uhE9UK(eMV}?Q~AqW7SrqwCv>Lyn0j1AC|i+wo;Tq z^%h){N7(b?%G6>p(#^b)KM&yu-pKQCNgPIQBWX4FLo87r&XoFM7%j{PVlARyx2Kjw zC`XhKPF|IK+W6End!#fUBOmf5B#qG1zI`t(wXmRX`CUV|BfonDcUy4ohucb2TN%l1 zC9R+7`(m`bc*NmUs(-~F!#k^e<0Q`mulNk@;10hu!uYSW-)k}MDrpmR4Wjer?MkoK z9xT|}<|x!gil0}}IQ;%hSl}75VlVVrKIL3V^ID^pG80almS}q;BO=Zd^s8rV-+4b0 z9DXlS0645)IMUT)RZuD#ohYG}5^469eQXlNXhp5!{t|+10dkHRZ3#un=ipKEAvy>B ztk!4nQpe+NkiL+VV@eT=;N^LbdKfhJQba)LfqQNbn}_j#tNc$;S1lo>KY&YYNKh>p=T>U z*G0*j2RHOI+l^=#){l&>S<5hE{~veVzU_GUiT;EqZ%Dh%%L1^OLl)9DTgdr)fdtX6 zNflceq}jMyCtNby|TtyJdHh!cJb8wXC@;@8aTGz6pJ}+fN42MMj%ElDG&GH z0mCaPXG|4wH{^C(!+Tw0Jjus^CPd-r0&umtQZY@W{2*FC+d5nKoL>D%0@uJpbXy>n zh5=#LFFVNLW!R1wQ#BQ1yCWbD`x{H-n?H{=0!G_i05RbXZ>P`uQ1%aG$nE`A7$F$K zRAZR6r^(QSU0%pF7n#f4!KwZ1mb;msi)72gw5jbZI3a4-OA_NdjfmK-sn7}*ZJ=z8 z+Y#Gv6~2w`@^@0LFg*oH^J)#XPU))AxAk;I7o;xuw=JKj#gdpP6OSq(pFdd;2GHKz zy6nlp3mT2RUqmm}b>=NND1|uyd#K8x)o=5050=Waqs2~?I4))e_YfnO?t-m()<%h` z{x%pQcqnX*N?@A8I#DMQ5?V}4fZTw}*JM05D_~N6gJL~`lJeQ5x62(6sv&GFtH+=WLNE5~~0SMhTm1cW{G>*8FycN!Wt z61|J@z{P?Of;g?~bmAUSCEn+rbO*X>L2Gb9)C{|GXB2k<9ViOU0Mib-GFsSYV|aoD zeqml1Vm+O{C)x9r2(#v1+PtG1uT4(Q>4iP;CYX5J%bLeu;UaF)@!41Zf{Z>fAq<=B z=k+)}A9_JZBIq0*1X+tj`TnK8VRs+vcm6M-uxRW8K_Gty+O7^uXk7mfQHk*prLe=< zK+L!UH#67NBQc6*8ciAI#9z*k1Vc~v zg8`RT!3eeB==t1NvFEp%zriixUsTVumlO`NLTPko5qt?VW~fX5TruD_n;Wd2d(7cT zTaaFMVR{hpabAMo>P%VCu0KK#FNom}6QklamznD-cH5ESauzd5qlJSh!Y|k9dW51`D?;=f zK-r>N$wPU&d_7h)VzbYcjpkTZlCvw9$N>&((Czbza$m^ozY~oi&K@))hOz7zS^z1> z1I68WD*z$Ku;jX&Cy%O%LNu@TCl&31AdAZE$}z@k&Ak!u z-k|kOgH1Lp?YfrwsMH#{>#^40;84jUQiLgcxcf%KMP^~#4&$Qa&Q^L6lk0I_%3-_( z|C!VLrCdLE4$%a5Tm1So_zZn45N{)Tx0>NkBkW^Qq; zK9gHzyzg3?5Fv(KRn3mL39L3t85t{6|c2#rwbUE=UuQr&=v{kXdvy%M1cxy=t zYz3NUheyK~1m#R~EXrU>&!=5GM@_Yr(_=x|s`&rFSA9qKw~%wUAcWbEs?j5$^qi!u z#tu(S2-38FCddv)OwYOfD>Y^E)7Tx!b!hfv`#>tH{4gHsb7A2=Y&fy3^?|R&H!rGc{#1zyw=KR6Aqn_ra!5_++fJ(Q_bJg`k&uViuVadP4CS&nZn;G zf$|6a`nkTJ)Tb{*FKf~45^Y&jkaI*&?4s???bOro?PD*~LKX0v*uFstu3aJKG8qI1|tm0~wsZS2cskT07F0DShJPUq5 z^E-m5KN`2;(5eVeduXE}62(4{T)`P+-?@2qbT9mQi$i=~tQsjKuDS_htyn*d#FgVw zr41?lVo|zz1Een2i(7(tRa^znb97=9rOGJnt43a5Dh=a%2IJt<2L&I?uZ|XXk3sc5 zNH?ShP3y)qQg%u7ZQ+^{yhK-)OvKwW-v%kZSF~CYFK~DPs#{bR4kZtp-Cy^ga<4rs zPzVw-#ZKMPX@>4n)R<%Y7`zm8&(Y8M!Y)VOgs{`+?aWV z)gK)ANTAmAPc?Jdj5(eFU@_bZB z-a=PhBrXBVyIPNSC1@UXIahM!TiMs@^BiB3ACLKbOF*1?uyJhm>S0p7n7JptQ0A^X(L zx#fuTE3vCBWM+>FPM)MEG`}wFzzUBppEGdUo=oyZ@Oq45C7$J)8x%CV?^iKqTSB|W zCKgV0rfHprP@=HxjNP@}YIXfzNAHDeD2cI&2zAW6@chbo8F#rn0^9YQ`(nuVm({9; z?9!yVvTiiQoLB4G{VluHlq%ZQ*g0eaak=Yp5kbxgqXxpU%0Xl=g8T87^>d=5QMO$`DAA@%2YxDV;ooi(H zg~FnHYEk;iZa=MTg+8?TL3$#+K#k1Ihl0xBb zgG&Ll^(;gw1Fmt)wXFN5Ou_vuLR}Uo`)*gsKXre{zg)R+JfE@k!IH9n;F#zBYB*81 zPxphTxOpMUUsG6Cc)^U2dW`nsE&n#i+!We+a#$%#lBn5WSy+~hKpx~@3dx!jV#3sA ztMosBx2CmES6?A1iSu(GDBQ@`EbWz;hQZHbABw0n72)~GC%>P9Hy)L_?rL^%DA=ng=pEG49qm$~<0Z>(a)*KQmrCA18Sf;kI)S zrEsOEK&(uzlKrxW!s9`t>?QqDpOMJDWAjo5yLij%gU>wxspJw1*3_shtqI`0@^n}mOu5_&v}O2W_I>qv22S3 zvipD>me@e0TJ~5CXq2dSUcEk%1l*3cjVz><@?rmzoQ6Yb)R*E6xD+pmaq*ZKJ zwjBVdcSU$7OX}PcPC0GL>I5j2?<`j|AIug#hc{`hJLodNX;e$7|DB+TLIZv;`GIvjw8L{zK6DsJe>wY6{mSvfV9iP>CFR$o zJuI?to$wEyF)6z=*=hxfB9gwYVL6_MY>&qE{T&&C;}Bk1{w_Rh;Rg8?$&vipHJ~cd^Mr^TJa8B|$#4jrw>CH(-az(kf zDBg`81{S)c$#@vuZo^yZ#?epLmRi=&Aa>0>oalqZ+TYb&>b!SOpFB5E#(8;$n_9u7 zRoZ%6^vPB%hdCa7`Y;-=_vjUy9HWjE%jlfL;|7u*IuEQ+xtg^%SJ~WwM`69lJGfpk zdK4Ne`j_w}R-e5=)2Zc%$vT(5!L#U#=iWi`XY!T?6Yk5~YSjmt-&#Lci)X^a`4q`S z_QP4{(c$gjK)+Q9?L@Sdys)xw?)4&d%wJx8WxgnFn<|$gcY`-!}fCuT}Du%wBrm zaeJ*u$5`giur=&T+v_>c#9dyY$h%GO0rL7q=k`Rux^;@L25n#cy|Y-maj60yYDf=q zQaFTAUDp%JMM)*DY3U&vr+aKlT@1uxmBj>=;0>JJ8mp2(IHKRm%}dHWSGYqJz(C2k zz|g`_qHBhvp_T$hx9=?b_K zGZWBsi%-XPaXkKm5>35|B+Ed}mFbvTAtr_$gPMb2qiowj#eoxAwk#cywL!vrAQHNM zCwH^qRnCIhaTGtwo)Y-sqsiKvRFwQaNWYybk42P$)@_Atly>0s=6oKGqOitEbA~hH zwoGX*r`ss<1Wh5>8(NUPu7Q0d{S)Sja$>VCgqrLR$eyDrd$PQ zp&O5+qo5m3tx>H5!l`Zutp1Q*$GUr2f+bxF7DMvG-EFHNT-*i6T5$r&O}vvkjjna6 zLR0YjLsBbRx4cJ5yIhVeWasb$>z4Y|>3ow}A8SdpM>~*QbXgC}!}`~o0Z`ktqRF-w zu)3v@_B=lvo@p{mK$w&nPPr`%Uu+zPryx2olnnP%%+jO2eMpEgto5QeKWnSk^z}n7 zS2J%J#IgPTeDzqlf=Wxnd$o&$%J!K))$4YLjc->6IVWq4hw>bF<`>tPozCHhkA{tx zKTFBHQnpgLclbDOU-N(dtX|n(h(jeJhgh!u#^FJ^RCL}8?@W%vuo<^FrqD=rPrUlp zh3Z2&Gw+e(6wc+}+T)~x_^vskLGZ8cHb-d6;-L%DpICroDZ3M3!(fz>*zOO1_hV4{ z<(+YlR%qP*<|%s7A)27SCl;)Yt>jTQv%l93h4a_sk(tKEcD7**r}am#K{hHE;)S5h8Ur*+cHhj&C zV2c!qe_r5nUEC1293O4QNWjPQ#`hjN+T5toiUO^X^ji^K@rdf;vv6$GIGMM90_@jB zxw5duw^^WKRT6r*yGNxHj#{3BwIy1x&3lWRZcCQag{5I(cuO<@-WvxL(~-qR%^c~D z5%l}RAe3<--lRoTX#*+fU^JBM#jGsU6Oxo^5L7&rBbd za|&nc+=$1Z7H3ePaxYSk!+51fAI{Z%u@v>|B*-$LF%AFZPmkA|UXPnC4sco12;U1{ zIMN=wGpBQyov^&7{;bvgaGIOr4%#fq!}v>m$T@lbOS96Y zCWbfi^-S%;47hO&p5ySM+O}WB&*}Gun(=o%Urc$*bi5rL=EX4-60t5;DrRFa3i&0M zu_e(s93=Z<$JK~k;C48}BUoX&pVhFk9=j$OZ3oX-tR8lfzaR(VdYIWcosg@TAJNqx zRw(25AHJ=*>ayj%Y~EpiI&9%~^AYc8i?P3z$HL)w@=2D3AFr7}Q?otWhAkZD-j4L; z0)k(NFQ6`zKcP>4IRPtTttM&x_C&BxYNfeXkh}upGG4;xgswR_CG5^b%p5~;*w}4_ z&Dd>)%N$y#JWkxE-hk$Z$xV_wS`x+ipMT*w(+T!=@~{>EHf)VAt=L8@9gW=fNNtoO z+{5(^??mb!(xp9I?$BK2u=9R96L|2d{LM>I-YEe1P@BLvwJWqD-dm84`AApDn!jj8 zCw?^%V{%oU=}tzr;wGfU-hpUR&9$>giOC3B(l4Mu2O$?+3-?6QZ#e1}(&j4LdBG>w z8uq_X8_Uq=5igvG-mp6d@!I{DfrRW^6J*b*_|pNU<6VV$zrmAfq}$6g!|eCsnBs2I zZ66q=cb7Xp^TH-xpS~yQ5w?nv{if_#R|2hw*4L%`D2BeMRlcn6VLNoc!l#}7>4I7; zN#E+XS^w>t)?`&{#Jz>7Y#{8)zYVb1cU*Jl=Fa1y6Pn_V{y!cIUd1KTx5WS4+sWTB z%eT+Nul0OhSAgi39sZ0fg5J`v$MozdPqzD$uyvmATO)*>)|#g=-xww~zUoeuk! z#%iEzbQ_u9!i>S^a~xqeu=r-s>~}%h@@wJ6b;foArS@MP;>ik3nD>M|Am z`=)xc2lGR;aQ7ngu+y($vH6^koI+1gd(W@cdqEuc7wR*o_?1iIpuyA(RC%G5u$Qif zU3W7+9gMyQA0&BD+0Cwd{gN;DuCix`>RxVLvNi|FO7N;VKLYL7OJU)n&ofPdM9kLK$`@7GTO3NtqeP&XZHJqx7fJs)`4-(k1g2r#IuBgh}SuB<1 zv(M&sf%)RAGgq@i>>l9<8fTP#Y&dFt&C9`C^E?gSycPdi#NBsk{)d`Rw~sT>;^!HI z$f}H8-J54!unqQ_gED6@fUy&9g#NrJ&vpUB4C_Khsrf!jw$yZdvr9J%wSkOGM4xas zrB&tyV#%>C@%Ndp^bIT4NVKj{)x~k!4$EgbEs4o4soRMsX(i6{>JN8@gZXc3qC1kp zP8e5Lu@>QEe)SbsY~?)+3fIO_l5||W_BQ(Gi@FcA;T1OI2+%A@<5z+{ukY~laeaFc z*P5eS$EEWxt1X`?Z9gZdC*_U&Tr<*~UIX->$?N6M8Tn;3?k|Mdi%5?a^@h@>i<+X% zF4G_PH0%3<`dm6)RF3zNL)oXAv(`o5`L-6jtYqQ-j> zJ})l~KmOZyMO1syJH`q|!YPD}&gpJ{3vb?t4wcnqVgPZrc#4pHB}4KHeSSZ@7pB8M z3GWsNl0=qq6d>B;XNF=)B{NGs9(xT`aXeaz3zPC znwH#fXh*mS_EEu>&$Qj~4BLJDFvD^~e(D(%aqdPVk!ST$vEcf>@qvC2BiimJt(aNZ z<_ltT4`&mZ%@c9Izg*;bS#Tf}qsvR+I0M$}%P3}CX@}({zQccNUYw(=wCi?8H-weDbRP}1F z=$U1rprqEkxVV8s|Ii?Lwybw+W@GfJsZ(Aq(>>0lx}c;KJm=M(o-4~&yS!iozs%<9 zoa6sPG1opWU76Q<-@R{d@BC3yX+iY0YCw3HXaSRB$~fl})^tTJP~!ui@S|}@$t8!2 z7cS^;;xo88oEZj-NY$U4CRr4gWMkx)obo4Kb8Yv8x76#P_achu+(oGlMWaE{ch(>2 z!1dr*S?%+NgHG*U*m|M0=aPVe2Fatm4hd4tFP4IrQX>=i>tN6qH`?pM+HfqVnQ86A zIY**HI=S+Ez6a{FbBtWqFxuq^0X)%$9GCa20K>`jd2OJlXob#n7(P4G`k{){a;Koa zKq{QRCjQOqX=|?Y6-28qij&cCif(n`Z_00@Gtq(`ks>efP>8Q(t@rqA3ejIw_=^2li<&%6<3N3~KpyQc$PpS~scI4S6JBTzz7t#aN_6&DWS~ivDO06V zS5J8DE8+1Vu8x<y8bk1XbivR5WSai-TD4%fSRjZua!DqycMei(lsJ>Iiar? zFKTR8#4?N218!K;yz%7f+yFG{*b_h1?GKb1y|yxaul6hbjaDzcf^SV!d)CsqoaZ2@ zuxve98Tt|~+7UIUflE->%_lT1&-_dV+efiHE~9)#Z?TD>w|Eq4z|`8LWbSIO}^D&RuQ{g|G1p+ z@-gj5<>UA7o zdk8th%Gj!wuUu_3_8F`JTfdmXOG=Oen+4pJQ$=Ga4n}^N3leizb77Xe@Civp z1B65Av+LrPUO7ZeHr(G0+M!H&-ChkmU9MvOt%}>Sy#RD&s@`wq$xig^r0&=+94aAR zi|h5Z=$qpBa0uDNQ?;}bao)?w5h=+Q=Ad_aZFYSHOAb$~FlTgXL=uF&{Xp;Ot&6!X@;we{Eq>hZ7BZ*ue3S)RgcAwV@v?a2;c{CU^a>}}YWUn% zIj|7x3RVhk$9G>4pI?^dvsNALwPTf_eJBmMDwz0i^!QL((fY)laGgd>ZLbL~-_WNk zeLYU)Jkw@Qp0x_@nfIIoFCF2&W!G{sCLu` zF%GO}a$W>UJyw9?*taePtfO1uH#eVwh(uuDh>)T@T zFLY&`?b#v4cG(RQjVHJlcfHRmWqO{-XJEHzux~fBApX6k@68Ci z%jamVsORK?=Ix+J{QbnI8Wl}!{`|aP**VCSh>vR{c!2iOm`BI!aZ=J;wj2q$YTt*8 z<>YpM02%eCP4598)xE>+?tVGvS-s*{kyCVG@;2)c4m(EV-^f6Aoq`EF zto0*#X<;{3rd{zFwyx<{HneK)FRM^feP`JEBGRPqc|((Jw9?AxfBazlYGkIte^>s^ zsU1|Ig?4x%xEjf2c- z$9H$hne!TTS&|xd{h;SbF7XLupIfaPs8#C*_P!DZ)tGo7jU4Z7KV_}afuZA>)w7DK z`)iD;%eJbuh

Yi^0=4gFSWc)o4pK7 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

T26X|2y?Aevx+E*0MK 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

NAwUtYHHs8&0%4_( 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%0AC;9C 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 z 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�zfDq$~0)SG40 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
-rJ{YqkYXCNcCE7DyGBKBakyDUWP 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

gb0&}5j>zP(y9x|q#XAbr<7r73e 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�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%0AA@SanrH%?oB}*cN#w1(xK9;jM^Qj)QUY5ER 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%0A%Ymr19QO05 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�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


()678 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
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- -
-
-
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- - -
-
-
-
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/PostSharp.LicenseServer/ParsedLicenseManager.cs b/src/PostSharp.LicenseServer/ParsedLicenseManager.cs index 209839b..d78ff12 100644 --- a/src/PostSharp.LicenseServer/ParsedLicenseManager.cs +++ b/src/PostSharp.LicenseServer/ParsedLicenseManager.cs @@ -1,37 +1,37 @@ -#region Copyright (c) 2004-2010 by SharpCrafters s.r.o. - -// This file is part of PostSharp source code and is the property of SharpCrafters s.r.o. -// -// Source code is provided to customers under strict non-disclosure agreement (NDA). -// YOU MUST HAVE READ THE NDA BEFORE HAVING RECEIVED ACCESS TO THIS SOURCE CODE. -// Severe financial penalties apply in case of non respect of the NDA. - -#endregion - -using System.Collections.Generic; -using PostSharp.Platform.NetFramework; -using PostSharp.Platform.Neutral; -using PostSharp.Sdk.Extensibility.Licensing; -using ParsedLicense = PostSharp.Sdk.Extensibility.Licensing.License; - -namespace PostSharp.LicenseServer -{ - public static class ParsedLicenseManager - { - private static readonly Dictionary parsedLicenses = new Dictionary(); - - static ParsedLicenseManager() - { - CommonDefaultSystemServices.Initialize(); - NetFrameworkDefaultSystemServices.Initialize(); - } - - - - public static ParsedLicense GetParsedLicense( string licenseKey ) - { - ParsedLicense parsedLicense; - +#region Copyright (c) 2004-2010 by SharpCrafters s.r.o. + +// This file is part of PostSharp source code and is the property of SharpCrafters s.r.o. +// +// Source code is provided to customers under strict non-disclosure agreement (NDA). +// YOU MUST HAVE READ THE NDA BEFORE HAVING RECEIVED ACCESS TO THIS SOURCE CODE. +// Severe financial penalties apply in case of non respect of the NDA. + +#endregion + +using System.Collections.Generic; +using PostSharp.Platform.NetFramework; +using PostSharp.Platform.Neutral; +using PostSharp.Sdk.Extensibility.Licensing; +using ParsedLicense = PostSharp.Sdk.Extensibility.Licensing.License; + +namespace PostSharp.LicenseServer +{ + public static class ParsedLicenseManager + { + private static readonly Dictionary parsedLicenses = new Dictionary(); + + static ParsedLicenseManager() + { + CommonDefaultSystemServices.Initialize(); + NetFrameworkDefaultSystemServices.Initialize(); + } + + + + public static ParsedLicense GetParsedLicense( string licenseKey ) + { + ParsedLicense parsedLicense; + lock (parsedLicenses) { if (!parsedLicenses.TryGetValue(licenseKey, out parsedLicense)) @@ -48,8 +48,8 @@ public static ParsedLicense GetParsedLicense( string licenseKey ) } return parsedLicense; - } - } - } - + } + } + } + } \ No newline at end of file diff --git a/src/PostSharp.LicenseServer/PostSharp.LicenseServer.csproj.user b/src/PostSharp.LicenseServer/PostSharp.LicenseServer.csproj.user deleted file mode 100644 index 97f75a5..0000000 --- a/src/PostSharp.LicenseServer/PostSharp.LicenseServer.csproj.user +++ /dev/null @@ -1,47 +0,0 @@ - - - - true - - - - - - Debug|Any CPU - - - - - - - - - CurrentPage - True - False - False - False - - - - - - - - - True - True - True - True - 0 - / - http://localhost:44670/ - False - False - - - - - - - \ No newline at end of file From 0f1bf80a283409c08a151554ef379d650563aafb Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 09:09:34 +0200 Subject: [PATCH 02/44] Rename project folders ahead of the .NET 10 migration Move src/PostSharp.LicenseServer to src/PostSharp.LicenseServer.Web and test/PostSharp.LicenseServer.Test to tests/PostSharp.LicenseServer.Simulator, reflecting what each project is: a web front end and a load simulator. This commit contains no content changes, so that Git records the moves as renames and `git log --follow` keeps working across the migration. Co-Authored-By: Claude Opus 5 --- PostSharp.LicenseServer.sln | 6 +++--- .../Admin/AddLicense.aspx | 0 .../Admin/AddLicense.aspx.cs | 0 .../Admin/AddLicense.aspx.designer.cs | 0 .../Admin/Cancel.aspx | 0 .../Admin/Cancel.aspx.cs | 0 .../Admin/Cancel.aspx.designer.cs | 0 .../Admin/Details.aspx | 0 .../Admin/Details.aspx.cs | 0 .../Admin/Details.aspx.designer.cs | 0 .../Admin/Export.ashx | 0 .../Admin/Export.ashx.cs | 0 .../Admin/Export.aspx | 0 .../Admin/Export.aspx.cs | 0 .../Admin/Export.aspx.designer.cs | 0 .../Admin/GenerateDemoData.aspx | 0 .../Admin/GenerateDemoData.aspx.cs | 0 .../Admin/GenerateDemoData.aspx.designer.cs | 0 .../CreateTables.sql | 0 .../DataClasses.dbml | 0 .../DataClasses.dbml.layout | 0 .../DataClasses.designer.cs | 0 .../Database.cs | 0 .../Default.aspx | 0 .../Default.aspx.cs | 0 .../Default.aspx.designer.cs | 0 .../ExtensionMethods.cs | 0 .../GetTime.ashx | 0 .../GetTime.ashx.cs | 0 .../Graph.aspx | 0 .../Graph.aspx.cs | 0 .../Graph.aspx.designer.cs | 0 .../Img/PostSharpText_Light_240x33.png | Bin .../Lease.ashx | 0 .../Lease.ashx.cs | 0 .../Lease.cs | 0 .../LeaseCountingPoint.cs | 0 .../LeaseCountingPointKind.cs | 0 .../LeaseService.cs | 0 .../ParsedLicenseManager.cs | 0 .../PostSharp.LicenseServer.csproj | 0 .../Properties/AssemblyInfo.cs | 0 .../Properties/Settings.Designer.cs | 0 .../Properties/Settings.settings | 0 .../Site.Master | 0 .../Site.Master.cs | 0 .../Site.Master.designer.cs | 0 .../VirtualDateTime.cs | 0 .../Web.Debug.config | 0 .../Web.Release.config | 0 .../Web.config | 0 .../packages.config | 0 .../robots.txt | 0 .../ClientSimulator.cs | 0 .../MemoryRegistryKey.cs | 0 .../MessageSink.cs | 0 .../PostSharp.LicenseServer.Test.csproj | 0 .../PostSharp.LicenseServer.Simulator}/Program.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../PostSharp.LicenseServer.Simulator}/User.cs | 0 .../VirtualDateTime.cs | 0 .../PostSharp.LicenseServer.Simulator}/app.config | 0 .../packages.config | 0 63 files changed, 3 insertions(+), 3 deletions(-) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/AddLicense.aspx (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/AddLicense.aspx.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/AddLicense.aspx.designer.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/Cancel.aspx (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/Cancel.aspx.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/Cancel.aspx.designer.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/Details.aspx (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/Details.aspx.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/Details.aspx.designer.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/Export.ashx (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/Export.ashx.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/Export.aspx (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/Export.aspx.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/Export.aspx.designer.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/GenerateDemoData.aspx (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/GenerateDemoData.aspx.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Admin/GenerateDemoData.aspx.designer.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/CreateTables.sql (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/DataClasses.dbml (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/DataClasses.dbml.layout (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/DataClasses.designer.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Database.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Default.aspx (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Default.aspx.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Default.aspx.designer.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/ExtensionMethods.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/GetTime.ashx (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/GetTime.ashx.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Graph.aspx (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Graph.aspx.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Graph.aspx.designer.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Img/PostSharpText_Light_240x33.png (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Lease.ashx (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Lease.ashx.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Lease.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/LeaseCountingPoint.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/LeaseCountingPointKind.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/LeaseService.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/ParsedLicenseManager.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/PostSharp.LicenseServer.csproj (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Properties/AssemblyInfo.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Properties/Settings.Designer.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Properties/Settings.settings (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Site.Master (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Site.Master.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Site.Master.designer.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/VirtualDateTime.cs (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Web.Debug.config (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Web.Release.config (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/Web.config (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/packages.config (100%) rename src/{PostSharp.LicenseServer => PostSharp.LicenseServer.Web}/robots.txt (100%) rename {test/PostSharp.LicenseServer.Test => tests/PostSharp.LicenseServer.Simulator}/ClientSimulator.cs (100%) rename {test/PostSharp.LicenseServer.Test => tests/PostSharp.LicenseServer.Simulator}/MemoryRegistryKey.cs (100%) rename {test/PostSharp.LicenseServer.Test => tests/PostSharp.LicenseServer.Simulator}/MessageSink.cs (100%) rename {test/PostSharp.LicenseServer.Test => tests/PostSharp.LicenseServer.Simulator}/PostSharp.LicenseServer.Test.csproj (100%) rename {test/PostSharp.LicenseServer.Test => tests/PostSharp.LicenseServer.Simulator}/Program.cs (100%) rename {test/PostSharp.LicenseServer.Test => tests/PostSharp.LicenseServer.Simulator}/Properties/AssemblyInfo.cs (100%) rename {test/PostSharp.LicenseServer.Test => tests/PostSharp.LicenseServer.Simulator}/User.cs (100%) rename {test/PostSharp.LicenseServer.Test => tests/PostSharp.LicenseServer.Simulator}/VirtualDateTime.cs (100%) rename {test/PostSharp.LicenseServer.Test => tests/PostSharp.LicenseServer.Simulator}/app.config (100%) rename {test/PostSharp.LicenseServer.Test => tests/PostSharp.LicenseServer.Simulator}/packages.config (100%) diff --git a/PostSharp.LicenseServer.sln b/PostSharp.LicenseServer.sln index d928456..feb63a9 100644 --- a/PostSharp.LicenseServer.sln +++ b/PostSharp.LicenseServer.sln @@ -1,11 +1,11 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30523.141 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostSharp.LicenseServer.Test", "test\PostSharp.LicenseServer.Test\PostSharp.LicenseServer.Test.csproj", "{6A706191-B0A4-4B9A-A597-7A1DAF32EA50}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostSharp.LicenseServer.Simulator", "tests\PostSharp.LicenseServer.Simulator\PostSharp.LicenseServer.Test.csproj", "{6A706191-B0A4-4B9A-A597-7A1DAF32EA50}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostSharp.LicenseServer", "src\PostSharp.LicenseServer\PostSharp.LicenseServer.csproj", "{3B511E09-1CFD-43EB-978F-70FA3DFEC83B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostSharp.LicenseServer", "src\PostSharp.LicenseServer.Web\PostSharp.LicenseServer.csproj", "{3B511E09-1CFD-43EB-978F-70FA3DFEC83B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/PostSharp.LicenseServer/Admin/AddLicense.aspx b/src/PostSharp.LicenseServer.Web/Admin/AddLicense.aspx similarity index 100% rename from src/PostSharp.LicenseServer/Admin/AddLicense.aspx rename to src/PostSharp.LicenseServer.Web/Admin/AddLicense.aspx diff --git a/src/PostSharp.LicenseServer/Admin/AddLicense.aspx.cs b/src/PostSharp.LicenseServer.Web/Admin/AddLicense.aspx.cs similarity index 100% rename from src/PostSharp.LicenseServer/Admin/AddLicense.aspx.cs rename to src/PostSharp.LicenseServer.Web/Admin/AddLicense.aspx.cs diff --git a/src/PostSharp.LicenseServer/Admin/AddLicense.aspx.designer.cs b/src/PostSharp.LicenseServer.Web/Admin/AddLicense.aspx.designer.cs similarity index 100% rename from src/PostSharp.LicenseServer/Admin/AddLicense.aspx.designer.cs rename to src/PostSharp.LicenseServer.Web/Admin/AddLicense.aspx.designer.cs diff --git a/src/PostSharp.LicenseServer/Admin/Cancel.aspx b/src/PostSharp.LicenseServer.Web/Admin/Cancel.aspx similarity index 100% rename from src/PostSharp.LicenseServer/Admin/Cancel.aspx rename to src/PostSharp.LicenseServer.Web/Admin/Cancel.aspx diff --git a/src/PostSharp.LicenseServer/Admin/Cancel.aspx.cs b/src/PostSharp.LicenseServer.Web/Admin/Cancel.aspx.cs similarity index 100% rename from src/PostSharp.LicenseServer/Admin/Cancel.aspx.cs rename to src/PostSharp.LicenseServer.Web/Admin/Cancel.aspx.cs diff --git a/src/PostSharp.LicenseServer/Admin/Cancel.aspx.designer.cs b/src/PostSharp.LicenseServer.Web/Admin/Cancel.aspx.designer.cs similarity index 100% rename from src/PostSharp.LicenseServer/Admin/Cancel.aspx.designer.cs rename to src/PostSharp.LicenseServer.Web/Admin/Cancel.aspx.designer.cs diff --git a/src/PostSharp.LicenseServer/Admin/Details.aspx b/src/PostSharp.LicenseServer.Web/Admin/Details.aspx similarity index 100% rename from src/PostSharp.LicenseServer/Admin/Details.aspx rename to src/PostSharp.LicenseServer.Web/Admin/Details.aspx diff --git a/src/PostSharp.LicenseServer/Admin/Details.aspx.cs b/src/PostSharp.LicenseServer.Web/Admin/Details.aspx.cs similarity index 100% rename from src/PostSharp.LicenseServer/Admin/Details.aspx.cs rename to src/PostSharp.LicenseServer.Web/Admin/Details.aspx.cs diff --git a/src/PostSharp.LicenseServer/Admin/Details.aspx.designer.cs b/src/PostSharp.LicenseServer.Web/Admin/Details.aspx.designer.cs similarity index 100% rename from src/PostSharp.LicenseServer/Admin/Details.aspx.designer.cs rename to src/PostSharp.LicenseServer.Web/Admin/Details.aspx.designer.cs diff --git a/src/PostSharp.LicenseServer/Admin/Export.ashx b/src/PostSharp.LicenseServer.Web/Admin/Export.ashx similarity index 100% rename from src/PostSharp.LicenseServer/Admin/Export.ashx rename to src/PostSharp.LicenseServer.Web/Admin/Export.ashx diff --git a/src/PostSharp.LicenseServer/Admin/Export.ashx.cs b/src/PostSharp.LicenseServer.Web/Admin/Export.ashx.cs similarity index 100% rename from src/PostSharp.LicenseServer/Admin/Export.ashx.cs rename to src/PostSharp.LicenseServer.Web/Admin/Export.ashx.cs diff --git a/src/PostSharp.LicenseServer/Admin/Export.aspx b/src/PostSharp.LicenseServer.Web/Admin/Export.aspx similarity index 100% rename from src/PostSharp.LicenseServer/Admin/Export.aspx rename to src/PostSharp.LicenseServer.Web/Admin/Export.aspx diff --git a/src/PostSharp.LicenseServer/Admin/Export.aspx.cs b/src/PostSharp.LicenseServer.Web/Admin/Export.aspx.cs similarity index 100% rename from src/PostSharp.LicenseServer/Admin/Export.aspx.cs rename to src/PostSharp.LicenseServer.Web/Admin/Export.aspx.cs diff --git a/src/PostSharp.LicenseServer/Admin/Export.aspx.designer.cs b/src/PostSharp.LicenseServer.Web/Admin/Export.aspx.designer.cs similarity index 100% rename from src/PostSharp.LicenseServer/Admin/Export.aspx.designer.cs rename to src/PostSharp.LicenseServer.Web/Admin/Export.aspx.designer.cs diff --git a/src/PostSharp.LicenseServer/Admin/GenerateDemoData.aspx b/src/PostSharp.LicenseServer.Web/Admin/GenerateDemoData.aspx similarity index 100% rename from src/PostSharp.LicenseServer/Admin/GenerateDemoData.aspx rename to src/PostSharp.LicenseServer.Web/Admin/GenerateDemoData.aspx diff --git a/src/PostSharp.LicenseServer/Admin/GenerateDemoData.aspx.cs b/src/PostSharp.LicenseServer.Web/Admin/GenerateDemoData.aspx.cs similarity index 100% rename from src/PostSharp.LicenseServer/Admin/GenerateDemoData.aspx.cs rename to src/PostSharp.LicenseServer.Web/Admin/GenerateDemoData.aspx.cs diff --git a/src/PostSharp.LicenseServer/Admin/GenerateDemoData.aspx.designer.cs b/src/PostSharp.LicenseServer.Web/Admin/GenerateDemoData.aspx.designer.cs similarity index 100% rename from src/PostSharp.LicenseServer/Admin/GenerateDemoData.aspx.designer.cs rename to src/PostSharp.LicenseServer.Web/Admin/GenerateDemoData.aspx.designer.cs diff --git a/src/PostSharp.LicenseServer/CreateTables.sql b/src/PostSharp.LicenseServer.Web/CreateTables.sql similarity index 100% rename from src/PostSharp.LicenseServer/CreateTables.sql rename to src/PostSharp.LicenseServer.Web/CreateTables.sql diff --git a/src/PostSharp.LicenseServer/DataClasses.dbml b/src/PostSharp.LicenseServer.Web/DataClasses.dbml similarity index 100% rename from src/PostSharp.LicenseServer/DataClasses.dbml rename to src/PostSharp.LicenseServer.Web/DataClasses.dbml diff --git a/src/PostSharp.LicenseServer/DataClasses.dbml.layout b/src/PostSharp.LicenseServer.Web/DataClasses.dbml.layout similarity index 100% rename from src/PostSharp.LicenseServer/DataClasses.dbml.layout rename to src/PostSharp.LicenseServer.Web/DataClasses.dbml.layout diff --git a/src/PostSharp.LicenseServer/DataClasses.designer.cs b/src/PostSharp.LicenseServer.Web/DataClasses.designer.cs similarity index 100% rename from src/PostSharp.LicenseServer/DataClasses.designer.cs rename to src/PostSharp.LicenseServer.Web/DataClasses.designer.cs diff --git a/src/PostSharp.LicenseServer/Database.cs b/src/PostSharp.LicenseServer.Web/Database.cs similarity index 100% rename from src/PostSharp.LicenseServer/Database.cs rename to src/PostSharp.LicenseServer.Web/Database.cs diff --git a/src/PostSharp.LicenseServer/Default.aspx b/src/PostSharp.LicenseServer.Web/Default.aspx similarity index 100% rename from src/PostSharp.LicenseServer/Default.aspx rename to src/PostSharp.LicenseServer.Web/Default.aspx diff --git a/src/PostSharp.LicenseServer/Default.aspx.cs b/src/PostSharp.LicenseServer.Web/Default.aspx.cs similarity index 100% rename from src/PostSharp.LicenseServer/Default.aspx.cs rename to src/PostSharp.LicenseServer.Web/Default.aspx.cs diff --git a/src/PostSharp.LicenseServer/Default.aspx.designer.cs b/src/PostSharp.LicenseServer.Web/Default.aspx.designer.cs similarity index 100% rename from src/PostSharp.LicenseServer/Default.aspx.designer.cs rename to src/PostSharp.LicenseServer.Web/Default.aspx.designer.cs diff --git a/src/PostSharp.LicenseServer/ExtensionMethods.cs b/src/PostSharp.LicenseServer.Web/ExtensionMethods.cs similarity index 100% rename from src/PostSharp.LicenseServer/ExtensionMethods.cs rename to src/PostSharp.LicenseServer.Web/ExtensionMethods.cs diff --git a/src/PostSharp.LicenseServer/GetTime.ashx b/src/PostSharp.LicenseServer.Web/GetTime.ashx similarity index 100% rename from src/PostSharp.LicenseServer/GetTime.ashx rename to src/PostSharp.LicenseServer.Web/GetTime.ashx diff --git a/src/PostSharp.LicenseServer/GetTime.ashx.cs b/src/PostSharp.LicenseServer.Web/GetTime.ashx.cs similarity index 100% rename from src/PostSharp.LicenseServer/GetTime.ashx.cs rename to src/PostSharp.LicenseServer.Web/GetTime.ashx.cs diff --git a/src/PostSharp.LicenseServer/Graph.aspx b/src/PostSharp.LicenseServer.Web/Graph.aspx similarity index 100% rename from src/PostSharp.LicenseServer/Graph.aspx rename to src/PostSharp.LicenseServer.Web/Graph.aspx diff --git a/src/PostSharp.LicenseServer/Graph.aspx.cs b/src/PostSharp.LicenseServer.Web/Graph.aspx.cs similarity index 100% rename from src/PostSharp.LicenseServer/Graph.aspx.cs rename to src/PostSharp.LicenseServer.Web/Graph.aspx.cs diff --git a/src/PostSharp.LicenseServer/Graph.aspx.designer.cs b/src/PostSharp.LicenseServer.Web/Graph.aspx.designer.cs similarity index 100% rename from src/PostSharp.LicenseServer/Graph.aspx.designer.cs rename to src/PostSharp.LicenseServer.Web/Graph.aspx.designer.cs diff --git a/src/PostSharp.LicenseServer/Img/PostSharpText_Light_240x33.png b/src/PostSharp.LicenseServer.Web/Img/PostSharpText_Light_240x33.png similarity index 100% rename from src/PostSharp.LicenseServer/Img/PostSharpText_Light_240x33.png rename to src/PostSharp.LicenseServer.Web/Img/PostSharpText_Light_240x33.png diff --git a/src/PostSharp.LicenseServer/Lease.ashx b/src/PostSharp.LicenseServer.Web/Lease.ashx similarity index 100% rename from src/PostSharp.LicenseServer/Lease.ashx rename to src/PostSharp.LicenseServer.Web/Lease.ashx diff --git a/src/PostSharp.LicenseServer/Lease.ashx.cs b/src/PostSharp.LicenseServer.Web/Lease.ashx.cs similarity index 100% rename from src/PostSharp.LicenseServer/Lease.ashx.cs rename to src/PostSharp.LicenseServer.Web/Lease.ashx.cs diff --git a/src/PostSharp.LicenseServer/Lease.cs b/src/PostSharp.LicenseServer.Web/Lease.cs similarity index 100% rename from src/PostSharp.LicenseServer/Lease.cs rename to src/PostSharp.LicenseServer.Web/Lease.cs diff --git a/src/PostSharp.LicenseServer/LeaseCountingPoint.cs b/src/PostSharp.LicenseServer.Web/LeaseCountingPoint.cs similarity index 100% rename from src/PostSharp.LicenseServer/LeaseCountingPoint.cs rename to src/PostSharp.LicenseServer.Web/LeaseCountingPoint.cs diff --git a/src/PostSharp.LicenseServer/LeaseCountingPointKind.cs b/src/PostSharp.LicenseServer.Web/LeaseCountingPointKind.cs similarity index 100% rename from src/PostSharp.LicenseServer/LeaseCountingPointKind.cs rename to src/PostSharp.LicenseServer.Web/LeaseCountingPointKind.cs diff --git a/src/PostSharp.LicenseServer/LeaseService.cs b/src/PostSharp.LicenseServer.Web/LeaseService.cs similarity index 100% rename from src/PostSharp.LicenseServer/LeaseService.cs rename to src/PostSharp.LicenseServer.Web/LeaseService.cs diff --git a/src/PostSharp.LicenseServer/ParsedLicenseManager.cs b/src/PostSharp.LicenseServer.Web/ParsedLicenseManager.cs similarity index 100% rename from src/PostSharp.LicenseServer/ParsedLicenseManager.cs rename to src/PostSharp.LicenseServer.Web/ParsedLicenseManager.cs diff --git a/src/PostSharp.LicenseServer/PostSharp.LicenseServer.csproj b/src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.csproj similarity index 100% rename from src/PostSharp.LicenseServer/PostSharp.LicenseServer.csproj rename to src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.csproj diff --git a/src/PostSharp.LicenseServer/Properties/AssemblyInfo.cs b/src/PostSharp.LicenseServer.Web/Properties/AssemblyInfo.cs similarity index 100% rename from src/PostSharp.LicenseServer/Properties/AssemblyInfo.cs rename to src/PostSharp.LicenseServer.Web/Properties/AssemblyInfo.cs diff --git a/src/PostSharp.LicenseServer/Properties/Settings.Designer.cs b/src/PostSharp.LicenseServer.Web/Properties/Settings.Designer.cs similarity index 100% rename from src/PostSharp.LicenseServer/Properties/Settings.Designer.cs rename to src/PostSharp.LicenseServer.Web/Properties/Settings.Designer.cs diff --git a/src/PostSharp.LicenseServer/Properties/Settings.settings b/src/PostSharp.LicenseServer.Web/Properties/Settings.settings similarity index 100% rename from src/PostSharp.LicenseServer/Properties/Settings.settings rename to src/PostSharp.LicenseServer.Web/Properties/Settings.settings diff --git a/src/PostSharp.LicenseServer/Site.Master b/src/PostSharp.LicenseServer.Web/Site.Master similarity index 100% rename from src/PostSharp.LicenseServer/Site.Master rename to src/PostSharp.LicenseServer.Web/Site.Master diff --git a/src/PostSharp.LicenseServer/Site.Master.cs b/src/PostSharp.LicenseServer.Web/Site.Master.cs similarity index 100% rename from src/PostSharp.LicenseServer/Site.Master.cs rename to src/PostSharp.LicenseServer.Web/Site.Master.cs diff --git a/src/PostSharp.LicenseServer/Site.Master.designer.cs b/src/PostSharp.LicenseServer.Web/Site.Master.designer.cs similarity index 100% rename from src/PostSharp.LicenseServer/Site.Master.designer.cs rename to src/PostSharp.LicenseServer.Web/Site.Master.designer.cs diff --git a/src/PostSharp.LicenseServer/VirtualDateTime.cs b/src/PostSharp.LicenseServer.Web/VirtualDateTime.cs similarity index 100% rename from src/PostSharp.LicenseServer/VirtualDateTime.cs rename to src/PostSharp.LicenseServer.Web/VirtualDateTime.cs diff --git a/src/PostSharp.LicenseServer/Web.Debug.config b/src/PostSharp.LicenseServer.Web/Web.Debug.config similarity index 100% rename from src/PostSharp.LicenseServer/Web.Debug.config rename to src/PostSharp.LicenseServer.Web/Web.Debug.config diff --git a/src/PostSharp.LicenseServer/Web.Release.config b/src/PostSharp.LicenseServer.Web/Web.Release.config similarity index 100% rename from src/PostSharp.LicenseServer/Web.Release.config rename to src/PostSharp.LicenseServer.Web/Web.Release.config diff --git a/src/PostSharp.LicenseServer/Web.config b/src/PostSharp.LicenseServer.Web/Web.config similarity index 100% rename from src/PostSharp.LicenseServer/Web.config rename to src/PostSharp.LicenseServer.Web/Web.config diff --git a/src/PostSharp.LicenseServer/packages.config b/src/PostSharp.LicenseServer.Web/packages.config similarity index 100% rename from src/PostSharp.LicenseServer/packages.config rename to src/PostSharp.LicenseServer.Web/packages.config diff --git a/src/PostSharp.LicenseServer/robots.txt b/src/PostSharp.LicenseServer.Web/robots.txt similarity index 100% rename from src/PostSharp.LicenseServer/robots.txt rename to src/PostSharp.LicenseServer.Web/robots.txt diff --git a/test/PostSharp.LicenseServer.Test/ClientSimulator.cs b/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs similarity index 100% rename from test/PostSharp.LicenseServer.Test/ClientSimulator.cs rename to tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs diff --git a/test/PostSharp.LicenseServer.Test/MemoryRegistryKey.cs b/tests/PostSharp.LicenseServer.Simulator/MemoryRegistryKey.cs similarity index 100% rename from test/PostSharp.LicenseServer.Test/MemoryRegistryKey.cs rename to tests/PostSharp.LicenseServer.Simulator/MemoryRegistryKey.cs diff --git a/test/PostSharp.LicenseServer.Test/MessageSink.cs b/tests/PostSharp.LicenseServer.Simulator/MessageSink.cs similarity index 100% rename from test/PostSharp.LicenseServer.Test/MessageSink.cs rename to tests/PostSharp.LicenseServer.Simulator/MessageSink.cs diff --git a/test/PostSharp.LicenseServer.Test/PostSharp.LicenseServer.Test.csproj b/tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Test.csproj similarity index 100% rename from test/PostSharp.LicenseServer.Test/PostSharp.LicenseServer.Test.csproj rename to tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Test.csproj diff --git a/test/PostSharp.LicenseServer.Test/Program.cs b/tests/PostSharp.LicenseServer.Simulator/Program.cs similarity index 100% rename from test/PostSharp.LicenseServer.Test/Program.cs rename to tests/PostSharp.LicenseServer.Simulator/Program.cs diff --git a/test/PostSharp.LicenseServer.Test/Properties/AssemblyInfo.cs b/tests/PostSharp.LicenseServer.Simulator/Properties/AssemblyInfo.cs similarity index 100% rename from test/PostSharp.LicenseServer.Test/Properties/AssemblyInfo.cs rename to tests/PostSharp.LicenseServer.Simulator/Properties/AssemblyInfo.cs diff --git a/test/PostSharp.LicenseServer.Test/User.cs b/tests/PostSharp.LicenseServer.Simulator/User.cs similarity index 100% rename from test/PostSharp.LicenseServer.Test/User.cs rename to tests/PostSharp.LicenseServer.Simulator/User.cs diff --git a/test/PostSharp.LicenseServer.Test/VirtualDateTime.cs b/tests/PostSharp.LicenseServer.Simulator/VirtualDateTime.cs similarity index 100% rename from test/PostSharp.LicenseServer.Test/VirtualDateTime.cs rename to tests/PostSharp.LicenseServer.Simulator/VirtualDateTime.cs diff --git a/test/PostSharp.LicenseServer.Test/app.config b/tests/PostSharp.LicenseServer.Simulator/app.config similarity index 100% rename from test/PostSharp.LicenseServer.Test/app.config rename to tests/PostSharp.LicenseServer.Simulator/app.config diff --git a/test/PostSharp.LicenseServer.Test/packages.config b/tests/PostSharp.LicenseServer.Simulator/packages.config similarity index 100% rename from test/PostSharp.LicenseServer.Test/packages.config rename to tests/PostSharp.LicenseServer.Simulator/packages.config From fc9705e1fc401526e2dac533d0c9b9278a4464fc Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 09:14:40 +0200 Subject: [PATCH 03/44] Port the licensing engine to .NET 10 and EF Core Add PostSharp.LicenseServer.Core, which holds the lease allocation rules that were previously spread across the LINQ to SQL data context and the WebForms handlers, and a test project that exercises them against an in-memory SQLite database. The ambient statics that made the engine untestable are now injected: the settings, the clock, the license parser, the email sender, the request lock and the audit signer all sit behind interfaces, with in-memory implementations for tests. Three defects surfaced while porting: - The audit signature was computed with the parameterless HMAC.Create(), which throws on .NET 5 and later and which, on .NET Framework, used a randomly generated key for every call. The chain was never verifiable. It is now HMAC-SHA256 under a persisted key. - Audit timestamps were serialized as UTC from values SQL Server returns as Unspecified, which shifted them by the server's UTC offset. The kind is now set when the value is read. - Foreign keys were populated by the LINQ to SQL navigation setters before a lease was signed. EF Core does not do this, so they are assigned explicitly; otherwise the license and overwritten-lease fields would silently be signed as empty. Co-Authored-By: Claude Opus 5 --- Directory.Build.props | 1 - Directory.Packages.props | 8 +- .../Data/ILeaseRepository.cs | 56 ++ .../Data/Lease.Audit.cs | 53 ++ .../Data/Lease.Entity.cs | 53 ++ .../Data/LeaseConfiguration.cs | 38 ++ .../Data/LeaseCountingPoint.cs | 19 + .../Data/LeaseCountingPointKind.cs | 18 + .../Data/LeaseRepository.cs | 266 ++++++++++ .../Data/License.cs | 36 ++ .../Data/LicenseConfiguration.cs | 27 + .../Data/LicenseServerDbContext.cs | 83 +++ .../Data/SeatCounter.cs | 25 + .../Email/EmailMessage.cs | 10 + .../Email/IEmailSender.cs | 10 + .../Email/NullEmailSender.cs | 10 + .../Email/SmtpEmailSender.cs | 82 +++ .../Licensing/CachingLicenseParser.cs | 24 + .../Licensing/ILeaseSerializer.cs | 10 + .../Licensing/ILicenseParser.cs | 18 + .../Licensing/ILicenseServerVersion.cs | 10 + .../Licensing/LicenseInfo.cs | 58 +++ .../Licensing/PostSharpLeaseSerializer.cs | 12 + .../Licensing/PostSharpLicenseParser.cs | 44 ++ .../Licensing/PostSharpPlatform.cs | 37 ++ .../Licensing/PostSharpServerVersion.cs | 11 + .../Locking/ILeaseLock.cs | 18 + .../Locking/InProcessLeaseLock.cs | 43 ++ .../Locking/NullLeaseLock.cs | 19 + .../Options/LeaseLockMode.cs | 25 + .../Options/LicenseServerOptions.cs | 102 ++++ .../Options/LicenseServerOptionsValidator.cs | 38 ++ .../Options/SmtpOptions.cs | 30 ++ .../PostSharp.LicenseServer.Core.csproj | 21 + .../Security/FileAuditKeyProvider.cs | 74 +++ .../Security/HmacLeaseSigner.cs | 26 + .../Security/IAuditKeyProvider.cs | 9 + .../Security/ILeaseSigner.cs | 10 + .../Services/LeaseService.cs | 481 ++++++++++++++++++ .../Time/AcceleratedTimeProvider.cs | 39 ++ .../BuildServerDetectionTests.cs | 109 ++++ .../EmailSenderTests.cs | 82 +++ .../Fakes/FakeLicenseParser.cs | 36 ++ .../Fakes/FixedServerVersion.cs | 14 + .../Fakes/InMemoryEmailSender.cs | 46 ++ .../Fakes/NeverAcquiringLeaseLock.cs | 14 + .../Fakes/RecordingLeaseSigner.cs | 32 ++ .../Fakes/StaticAuditKeyProvider.cs | 14 + .../LicenseServerTestContext.cs | 107 ++++ .../Infrastructure/SqliteDatabaseFixture.cs | 49 ++ .../Infrastructure/TestData.cs | 255 ++++++++++ .../LeaseAuditLineTests.cs | 126 +++++ .../OpenLeasesTests.cs | 102 ++++ .../PostSharp.LicenseServer.Tests.csproj | 29 ++ .../SeatCountingTests.cs | 51 ++ 55 files changed, 3018 insertions(+), 2 deletions(-) create mode 100644 src/PostSharp.LicenseServer.Core/Data/ILeaseRepository.cs create mode 100644 src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs create mode 100644 src/PostSharp.LicenseServer.Core/Data/Lease.Entity.cs create mode 100644 src/PostSharp.LicenseServer.Core/Data/LeaseConfiguration.cs create mode 100644 src/PostSharp.LicenseServer.Core/Data/LeaseCountingPoint.cs create mode 100644 src/PostSharp.LicenseServer.Core/Data/LeaseCountingPointKind.cs create mode 100644 src/PostSharp.LicenseServer.Core/Data/LeaseRepository.cs create mode 100644 src/PostSharp.LicenseServer.Core/Data/License.cs create mode 100644 src/PostSharp.LicenseServer.Core/Data/LicenseConfiguration.cs create mode 100644 src/PostSharp.LicenseServer.Core/Data/LicenseServerDbContext.cs create mode 100644 src/PostSharp.LicenseServer.Core/Data/SeatCounter.cs create mode 100644 src/PostSharp.LicenseServer.Core/Email/EmailMessage.cs create mode 100644 src/PostSharp.LicenseServer.Core/Email/IEmailSender.cs create mode 100644 src/PostSharp.LicenseServer.Core/Email/NullEmailSender.cs create mode 100644 src/PostSharp.LicenseServer.Core/Email/SmtpEmailSender.cs create mode 100644 src/PostSharp.LicenseServer.Core/Licensing/CachingLicenseParser.cs create mode 100644 src/PostSharp.LicenseServer.Core/Licensing/ILeaseSerializer.cs create mode 100644 src/PostSharp.LicenseServer.Core/Licensing/ILicenseParser.cs create mode 100644 src/PostSharp.LicenseServer.Core/Licensing/ILicenseServerVersion.cs create mode 100644 src/PostSharp.LicenseServer.Core/Licensing/LicenseInfo.cs create mode 100644 src/PostSharp.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs create mode 100644 src/PostSharp.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs create mode 100644 src/PostSharp.LicenseServer.Core/Licensing/PostSharpPlatform.cs create mode 100644 src/PostSharp.LicenseServer.Core/Licensing/PostSharpServerVersion.cs create mode 100644 src/PostSharp.LicenseServer.Core/Locking/ILeaseLock.cs create mode 100644 src/PostSharp.LicenseServer.Core/Locking/InProcessLeaseLock.cs create mode 100644 src/PostSharp.LicenseServer.Core/Locking/NullLeaseLock.cs create mode 100644 src/PostSharp.LicenseServer.Core/Options/LeaseLockMode.cs create mode 100644 src/PostSharp.LicenseServer.Core/Options/LicenseServerOptions.cs create mode 100644 src/PostSharp.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs create mode 100644 src/PostSharp.LicenseServer.Core/Options/SmtpOptions.cs create mode 100644 src/PostSharp.LicenseServer.Core/PostSharp.LicenseServer.Core.csproj create mode 100644 src/PostSharp.LicenseServer.Core/Security/FileAuditKeyProvider.cs create mode 100644 src/PostSharp.LicenseServer.Core/Security/HmacLeaseSigner.cs create mode 100644 src/PostSharp.LicenseServer.Core/Security/IAuditKeyProvider.cs create mode 100644 src/PostSharp.LicenseServer.Core/Security/ILeaseSigner.cs create mode 100644 src/PostSharp.LicenseServer.Core/Services/LeaseService.cs create mode 100644 src/PostSharp.LicenseServer.Core/Time/AcceleratedTimeProvider.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/BuildServerDetectionTests.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/EmailSenderTests.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/Fakes/FakeLicenseParser.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/Fakes/FixedServerVersion.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/Infrastructure/TestData.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/LeaseAuditLineTests.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/OpenLeasesTests.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj create mode 100644 tests/PostSharp.LicenseServer.Tests/SeatCountingTests.cs diff --git a/Directory.Build.props b/Directory.Build.props index 49d50c1..7c7dec3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,6 @@ latest enable enable - true PostSharp License Server PostSharp Technologies diff --git a/Directory.Packages.props b/Directory.Packages.props index ca4c868..0c0b63d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -17,7 +17,7 @@ - + @@ -26,6 +26,12 @@ + + + + + diff --git a/src/PostSharp.LicenseServer.Core/Data/ILeaseRepository.cs b/src/PostSharp.LicenseServer.Core/Data/ILeaseRepository.cs new file mode 100644 index 0000000..97003c0 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Data/ILeaseRepository.cs @@ -0,0 +1,56 @@ +namespace PostSharp.LicenseServer.Data; + +/// +/// Reads and writes leases. Replaces the methods that used to hang off the LINQ to SQL +/// Database class. +/// +public interface ILeaseRepository +{ + /// + /// Gets the leases that have not been replaced by a later lease. + /// + IQueryable OpenLeases { get; } + + IQueryable Licenses { get; } + + /// + /// Creates a lease for a user on a machine. + /// + /// + /// The new lease, or null when no lease can be granted beyond , + /// because the license or the grace period ends first. + /// + Lease? CreateLease( + License license, + string user, + string machine, + string authenticatedUserName, + DateTime time, + bool grace ); + + /// + /// Replaces a lease with a new one ending later. + /// + /// The new lease, or null when it cannot be extended past . + Lease? ProlongLease( Lease oldLease, string authenticatedUserName, DateTime time ); + + /// + /// Ends a lease immediately, by inserting a lease that overwrites it. + /// + void CancelLease( Lease lease, string authenticatedUserName, DateTime time ); + + /// + /// Counts the seats of a license in use at a given moment. + /// + int GetActiveLeads( int licenseId, DateTime dateTime ); + + /// + /// Returns the usage timeline of a license over a period, as a sequence of lease open and close + /// events carrying the running seat count. + /// + IEnumerable GetLeaseCountingPoints( int licenseId, DateTime startTime, DateTime endTime ); + + Task SaveChangesAsync( CancellationToken cancellationToken = default ); + + int SaveChanges(); +} diff --git a/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs b/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs new file mode 100644 index 0000000..26ccac5 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs @@ -0,0 +1,53 @@ +using System.Xml; +using PostSharp.Sdk.Extensibility.Licensing; + +namespace PostSharp.LicenseServer; + +public partial class Lease +{ + /// + /// Writes the audit-log representation of this lease: a semicolon-separated line in which the + /// machine and user names appear only as hashes, so the log can be shared without disclosing who + /// works where. + /// + /// + /// This is a serialization contract. Timestamps are written as UTC; the entity's + /// values are tagged as UTC when they are read from the database, without + /// which would treat them as local time and shift them. + /// + public void Write( TextWriter textWriter, bool includeHmac ) + { + ArgumentNullException.ThrowIfNull( textWriter ); + + textWriter.Write( this.LeaseId ); + textWriter.Write( ';' ); + textWriter.Write( this.OverwrittenLeaseId ); + textWriter.Write( ';' ); + textWriter.Write( this.LicenseId ); + textWriter.Write( ';' ); + textWriter.Write( XmlConvert.ToString( this.StartTime, XmlDateTimeSerializationMode.RoundtripKind ) ); + textWriter.Write( ';' ); + textWriter.Write( XmlConvert.ToString( this.EndTime, XmlDateTimeSerializationMode.RoundtripKind ) ); + textWriter.Write( ';' ); + textWriter.Write( CryptoUtilities.ComputeStringHash64( this.Machine ).ToString( "x" ) ); + textWriter.Write( ';' ); + textWriter.Write( CryptoUtilities.ComputeStringHash64( this.UserName ).ToString( "x" ) ); + + if ( includeHmac ) + { + textWriter.Write( ';' ); + textWriter.Write( this.HMAC ); + } + } + + /// + /// Returns the audit-log representation of this lease. + /// + public string ToAuditLine( bool includeHmac ) + { + StringWriter writer = new(); + this.Write( writer, includeHmac ); + + return writer.ToString(); + } +} diff --git a/src/PostSharp.LicenseServer.Core/Data/Lease.Entity.cs b/src/PostSharp.LicenseServer.Core/Data/Lease.Entity.cs new file mode 100644 index 0000000..1cfd427 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Data/Lease.Entity.cs @@ -0,0 +1,53 @@ +namespace PostSharp.LicenseServer; + +/// +/// A seat of a license, held by one user on one machine for a period of time. Maps to the +/// dbo.Leases table. +/// +/// +/// Leases are never updated in place. Prolonging or cancelling a lease inserts a new row that +/// overwrites the old one through , which is what makes the table an +/// append-only audit log. +/// +public partial class Lease +{ + public int LeaseId { get; set; } + + /// + /// Gets or sets the lease that this lease replaces, if any. + /// + public int? OverwrittenLeaseId { get; set; } + + public int LicenseId { get; set; } + + public DateTime StartTime { get; set; } + + public DateTime EndTime { get; set; } + + public string UserName { get; set; } = null!; + + public string Machine { get; set; } = null!; + + /// + /// Gets or sets the authenticated identity that requested the lease, which is not necessarily + /// . + /// + public string AuthenticatedUser { get; set; } = null!; + + /// + /// Gets or sets the signature chaining this lease to the previous one in the audit log. + /// + public string? HMAC { get; set; } + + /// + /// Gets or sets a value indicating whether this lease was granted under the grace period, i.e. + /// beyond the capacity of the license. + /// + public bool Grace { get; set; } + + public License License { get; set; } = null!; + + public Lease? OverwritesLease { get; set; } + + public ICollection OverwrittenByLease { get; set; } = new List(); +} diff --git a/src/PostSharp.LicenseServer.Core/Data/LeaseConfiguration.cs b/src/PostSharp.LicenseServer.Core/Data/LeaseConfiguration.cs new file mode 100644 index 0000000..c9331f0 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Data/LeaseConfiguration.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace PostSharp.LicenseServer.Data; + +/// +/// Maps onto the dbo.Leases table created by CreateTables.sql. +/// +public sealed class LeaseConfiguration : IEntityTypeConfiguration +{ + public void Configure( EntityTypeBuilder builder ) + { + builder.ToTable( "Leases" ); + builder.HasKey( x => x.LeaseId ).HasName( "PK_Leases" ); + builder.Property( x => x.LeaseId ).ValueGeneratedOnAdd(); + + builder.Property( x => x.UserName ).IsRequired().HasMaxLength( 200 ); + builder.Property( x => x.Machine ).IsRequired().HasMaxLength( 200 ); + builder.Property( x => x.AuthenticatedUser ).IsRequired().HasMaxLength( 200 ); + builder.Property( x => x.HMAC ).IsUnicode( false ).HasMaxLength( 100 ); + builder.Property( x => x.Grace ).IsRequired(); + + builder.HasOne( x => x.License ) + .WithMany( x => x.Leases ) + .HasForeignKey( x => x.LicenseId ) + .HasConstraintName( "FK_Leases_Licenses" ) + .OnDelete( DeleteBehavior.Restrict ); + + builder.HasOne( x => x.OverwritesLease ) + .WithMany( x => x.OverwrittenByLease ) + .HasForeignKey( x => x.OverwrittenLeaseId ) + .HasConstraintName( "FK_Leases_Leases" ) + .OnDelete( DeleteBehavior.Restrict ); + + builder.HasIndex( x => x.EndTime ).HasDatabaseName( "IX_Leases_EndTime" ); + builder.HasIndex( x => x.OverwrittenLeaseId ).HasDatabaseName( "IX_Leases_OverwrittenLeaseId" ); + } +} diff --git a/src/PostSharp.LicenseServer.Core/Data/LeaseCountingPoint.cs b/src/PostSharp.LicenseServer.Core/Data/LeaseCountingPoint.cs new file mode 100644 index 0000000..b59eace --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Data/LeaseCountingPoint.cs @@ -0,0 +1,19 @@ +namespace PostSharp.LicenseServer; + +/// +/// A point on the usage timeline of a license: the moment a lease starts or ends, together with the +/// number of seats in use just after that moment. +/// +public sealed class LeaseCountingPoint +{ + public required DateTime Time { get; init; } + + public required LeaseCountingPointKind Kind { get; init; } + + public required Lease Lease { get; init; } + + /// + /// Gets the number of seats consumed immediately after this point. + /// + public int LeaseCount { get; set; } +} diff --git a/src/PostSharp.LicenseServer.Core/Data/LeaseCountingPointKind.cs b/src/PostSharp.LicenseServer.Core/Data/LeaseCountingPointKind.cs new file mode 100644 index 0000000..65094a0 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Data/LeaseCountingPointKind.cs @@ -0,0 +1,18 @@ +namespace PostSharp.LicenseServer; + +/// +/// The kind of event a represents. +/// +/// +/// The numeric values are load-bearing. Counting points are ordered by time and then by kind, so +/// being lower than makes a lease that ends at the exact +/// instant another begins release its machine before the next one claims it. Renumbering these +/// would make the seat count spike transiently and would make the close handler throw, because it +/// would no longer find the machine it is closing. +/// +public enum LeaseCountingPointKind +{ + // Process close before open! + Close = 1, + Open = 2 +} diff --git a/src/PostSharp.LicenseServer.Core/Data/LeaseRepository.cs b/src/PostSharp.LicenseServer.Core/Data/LeaseRepository.cs new file mode 100644 index 0000000..441fc71 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Data/LeaseRepository.cs @@ -0,0 +1,266 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using PostSharp.LicenseServer.Licensing; +using PostSharp.LicenseServer.Options; +using PostSharp.LicenseServer.Security; + +namespace PostSharp.LicenseServer.Data; + +/// +public sealed class LeaseRepository( + LicenseServerDbContext db, + IOptions options, + ILicenseParser licenseParser, + ILeaseSigner signer ) : ILeaseRepository +{ + private readonly LicenseServerOptions settings = options.Value; + + public IQueryable OpenLeases => db.OpenLeases; + + public IQueryable Licenses => db.Licenses; + + /// + /// Signs a lease, chaining it to the signature of the most recently persisted lease. + /// + /// + /// The query deliberately reads the database rather than the change tracker, so that leases + /// created within one unit of work all chain from the same committed predecessor. This matches + /// the LINQ to SQL implementation, which never flushed pending inserts before a query. + /// + public string GetSignature( Lease lease ) + { + Lease? lastLease = db.Leases + .AsNoTracking() + .OrderByDescending( l => l.LeaseId ) + .FirstOrDefault(); + + StringWriter stringWriter = new(); + stringWriter.Write( lastLease != null ? lastLease.HMAC : "" ); + stringWriter.Write( ';' ); + lease.Write( stringWriter, false ); + + return signer.Sign( stringWriter.ToString() ); + } + + public Lease? CreateLease( + License license, + string user, + string machine, + string authenticatedUserName, + DateTime time, + bool grace ) + { + Lease lease = new() + { + License = license, + + // The foreign key is assigned explicitly because the lease is signed before it is added + // to the change tracker, and EF Core does not populate foreign keys from navigation + // properties until then. LINQ to SQL assigned it inside the navigation setter, so + // omitting this would silently change what gets signed. + LicenseId = license.LicenseId, + + AuthenticatedUser = authenticatedUserName, + EndTime = time.AddDays( this.settings.NewLeaseDays ), + Machine = machine, + StartTime = time, + UserName = user, + Grace = grace + }; + + if ( !this.FixLease( lease, time ) ) + { + return null; + } + + db.Leases.Add( lease ); + + return lease; + } + + public Lease? ProlongLease( Lease oldLease, string authenticatedUserName, DateTime time ) + { + Lease newLease = new() + { + License = oldLease.License, + LicenseId = oldLease.LicenseId, + AuthenticatedUser = authenticatedUserName, + OverwritesLease = oldLease, + OverwrittenLeaseId = oldLease.LeaseId, + StartTime = oldLease.StartTime, + EndTime = time.AddDays( this.settings.NewLeaseDays ), + Machine = oldLease.Machine, + UserName = oldLease.UserName, + Grace = oldLease.Grace + }; + + if ( !this.FixLease( newLease, time ) ) + { + return null; + } + + db.Leases.Add( newLease ); + + return newLease; + } + + public void CancelLease( Lease lease, string authenticatedUserName, DateTime time ) + { + Lease overwrite = new() + { + AuthenticatedUser = authenticatedUserName, + License = lease.License, + LicenseId = lease.LicenseId, + UserName = lease.UserName, + Machine = lease.Machine, + StartTime = lease.StartTime, + OverwritesLease = lease, + OverwrittenLeaseId = lease.LeaseId, + Grace = lease.Grace, + EndTime = time + }; + + // Cancelling deliberately skips the end-time adjustment: the point is to end the lease now, + // which the "must end after the current moment" rule would otherwise reject. + this.FixLease( overwrite, time, false ); + + db.Leases.Add( overwrite ); + } + + /// + /// Clamps the end of a lease to the end of the license and of the grace period, then signs it. + /// + /// false when there is no time left to grant. + private bool FixLease( Lease lease, DateTime time, bool fixEndTime = true ) + { + LicenseInfo? parsedLicense = licenseParser.TryParse( lease.License.LicenseKey ); + + if ( parsedLicense == null ) + { + throw new InvalidOperationException( $"The license key #{lease.License.LicenseId} cannot be parsed." ); + } + + if ( lease.EndTime <= lease.StartTime ) + { + throw new InvalidOperationException( "A lease cannot end before it starts." ); + } + + if ( fixEndTime ) + { + if ( parsedLicense.ValidTo.HasValue && parsedLicense.ValidTo < lease.EndTime ) + { + lease.EndTime = parsedLicense.ValidTo.Value; + } + + if ( lease.Grace ) + { + DateTime graceEnd = lease.License.GraceStartTime!.Value.AddDays( parsedLicense.GraceDays ); + + if ( lease.EndTime > graceEnd ) + { + lease.EndTime = graceEnd; + } + } + + if ( lease.EndTime <= time ) + { + return false; + } + + if ( lease.EndTime <= lease.StartTime ) + { + throw new InvalidOperationException( "A lease cannot end before it starts." ); + } + } + + lease.HMAC = this.GetSignature( lease ); + + return true; + } + + public int GetActiveLeads( int licenseId, DateTime dateTime ) + { + // The seat arithmetic runs here rather than in SQL, so that the query translates on every + // provider. The result is one row per distinct user holding a lease on this license. + List machinesPerUser = db.OpenLeases + .Where( l => l.LicenseId == licenseId && l.StartTime <= dateTime && l.EndTime > dateTime ) + .GroupBy( l => l.UserName ) + .Select( g => g.Count() ) + .ToList(); + + return SeatCounter.CountSeats( machinesPerUser, this.settings.MachinesPerUser ); + } + + public IEnumerable GetLeaseCountingPoints( + int licenseId, + DateTime startTime, + DateTime endTime ) + { + List leases = db.OpenLeases + .Where( l => l.LicenseId == licenseId && l.StartTime <= endTime && l.EndTime > startTime ) + .AsNoTracking() + .ToList(); + + // Ordering in memory gives a stable sort, so the timeline is reproducible. Close sorts + // before Open at the same instant; see LeaseCountingPointKind. + List allRecords = leases + .Select( l => new LeaseCountingPoint { Time = l.StartTime, Kind = LeaseCountingPointKind.Open, Lease = l } ) + .Concat( + leases.Select( + l => new LeaseCountingPoint { Time = l.EndTime, Kind = LeaseCountingPointKind.Close, Lease = l } ) ) + .OrderBy( p => p.Time ) + .ThenBy( p => p.Kind ) + .ThenBy( p => p.Lease.LeaseId ) + .ToList(); + + Dictionary> currentUsers = new( StringComparer.OrdinalIgnoreCase ); + + int leaseCount = 0; + + foreach ( LeaseCountingPoint record in allRecords ) + { + if ( !currentUsers.TryGetValue( record.Lease.UserName, out List? machines ) ) + { + machines = []; + currentUsers.Add( record.Lease.UserName, machines ); + } + + int seatsBefore = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); + + if ( record.Kind == LeaseCountingPointKind.Open ) + { + if ( !machines.Contains( record.Lease.Machine, StringComparer.OrdinalIgnoreCase ) ) + { + machines.Add( record.Lease.Machine ); + } + } + else + { + string machine = record.Lease.Machine; + int index = machines.FindIndex( s => string.Equals( s, machine, StringComparison.OrdinalIgnoreCase ) ); + + if ( index >= 0 ) + { + machines.RemoveAt( index ); + } + else + { + throw new InvalidOperationException( + $"Closing lease #{record.Lease.LeaseId} for machine {machine}, which is not open." ); + } + } + + int seatsAfter = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); + + leaseCount += seatsAfter - seatsBefore; + record.LeaseCount = leaseCount; + + yield return record; + } + } + + public Task SaveChangesAsync( CancellationToken cancellationToken = default ) + => db.SaveChangesAsync( cancellationToken ); + + public int SaveChanges() => db.SaveChanges(); +} diff --git a/src/PostSharp.LicenseServer.Core/Data/License.cs b/src/PostSharp.LicenseServer.Core/Data/License.cs new file mode 100644 index 0000000..b906a19 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Data/License.cs @@ -0,0 +1,36 @@ +namespace PostSharp.LicenseServer; + +/// +/// A license key registered on the license server. Maps to the dbo.Licenses table. +/// +public class License +{ + /// + /// Gets or sets the license identifier. This value comes from the license key itself, so it is + /// assigned by the application and not generated by the database. + /// + public int LicenseId { get; set; } + + public string LicenseKey { get; set; } = null!; + + public string ProductCode { get; set; } = null!; + + /// + /// Gets or sets the order in which licenses are consumed. A negative value disables the license. + /// + public int Priority { get; set; } + + public DateTime CreatedOn { get; set; } + + /// + /// Gets or sets the moment the license first exceeded its capacity, which starts the grace period. + /// + public DateTime? GraceStartTime { get; set; } + + /// + /// Gets or sets the moment the last grace period warning email was sent. + /// + public DateTime? GraceLastWarningTime { get; set; } + + public ICollection Leases { get; set; } = new List(); +} diff --git a/src/PostSharp.LicenseServer.Core/Data/LicenseConfiguration.cs b/src/PostSharp.LicenseServer.Core/Data/LicenseConfiguration.cs new file mode 100644 index 0000000..a4757e4 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Data/LicenseConfiguration.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace PostSharp.LicenseServer.Data; + +/// +/// Maps onto the dbo.Licenses table created by CreateTables.sql. +/// +public sealed class LicenseConfiguration : IEntityTypeConfiguration +{ + public void Configure( EntityTypeBuilder builder ) + { + builder.ToTable( "Licenses" ); + builder.HasKey( x => x.LicenseId ).HasName( "PK_Licenses" ); + + // The identifier comes from the license key, not from the database. + builder.Property( x => x.LicenseId ).ValueGeneratedNever(); + + // Mapped to SQL 'text' by LicenseServerDbContext. Never filter, sort, group or apply + // DISTINCT on this column: T-SQL forbids 'text' in those positions and fails at run time. + builder.Property( x => x.LicenseKey ).IsRequired().IsUnicode( false ); + + builder.Property( x => x.ProductCode ).IsRequired().IsUnicode( false ).HasMaxLength( 50 ); + builder.Property( x => x.Priority ).IsRequired(); + builder.Property( x => x.CreatedOn ).IsRequired(); + } +} diff --git a/src/PostSharp.LicenseServer.Core/Data/LicenseServerDbContext.cs b/src/PostSharp.LicenseServer.Core/Data/LicenseServerDbContext.cs new file mode 100644 index 0000000..6b55048 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Data/LicenseServerDbContext.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace PostSharp.LicenseServer.Data; + +/// +/// The license server database. The provider is chosen by the hosting application: SQL Server in +/// production, SQLite in tests. +/// +/// +/// The model maps onto the schema created by CreateTables.sql exactly, so that an existing +/// deployment upgrades without any database work. There are deliberately no EF migrations; schema +/// compatibility is guaranteed by SchemaCompatibilityTests instead. +/// +public class LicenseServerDbContext( DbContextOptions options ) : DbContext( options ) +{ + public DbSet Licenses => this.Set(); + + public DbSet Leases => this.Set(); + + /// + /// Gets the leases that have not been replaced by a later lease, i.e. the leases that are + /// currently in effect. This is the only place where is + /// interpreted. + /// + /// + /// Expressed as an anti-join rather than the legacy left-join-where-null, which yielded a lease + /// twice if it had ever been overwritten twice. There is no unique constraint preventing that. + /// + public IQueryable OpenLeases + => this.Leases.Where( l => !this.Leases.Any( o => o.OverwrittenLeaseId == l.LeaseId ) ); + + protected override void OnModelCreating( ModelBuilder modelBuilder ) + { + modelBuilder.ApplyConfigurationsFromAssembly( typeof(LicenseServerDbContext).Assembly ); + + bool isSqlServer = this.Database.ProviderName == "Microsoft.EntityFrameworkCore.SqlServer"; + + // SQL Server returns DateTimeKind.Unspecified. Lease.Write serializes timestamps as UTC, and + // XmlConvert treats an Unspecified value as *local* time, which shifted every exported + // timestamp by the server's UTC offset. Tagging the kind on materialization fixes that. + ValueConverter toUtc = + new( v => v, v => DateTime.SpecifyKind( v, DateTimeKind.Utc ) ); + + ValueConverter toUtcNullable = + new( v => v, v => v.HasValue ? DateTime.SpecifyKind( v.Value, DateTimeKind.Utc ) : null ); + + foreach ( var property in modelBuilder.Model.GetEntityTypes().SelectMany( e => e.GetProperties() ) ) + { + if ( property.ClrType == typeof(DateTime) ) + { + property.SetValueConverter( toUtc ); + } + else if ( property.ClrType == typeof(DateTime?) ) + { + property.SetValueConverter( toUtcNullable ); + } + else + { + continue; + } + + if ( isSqlServer ) + { + // The existing columns are 'datetime'. Letting EF default to 'datetime2' would force + // SQL Server to convert the column on every StartTime/EndTime comparison. + property.SetColumnType( "datetime" ); + } + } + + if ( isSqlServer ) + { + modelBuilder.Entity().Property( x => x.LicenseKey ).HasColumnType( "text" ); + } + else + { + // SQL Server's default collation is case-insensitive and SQLite's is not. Without this, + // grouping leases by user name would behave differently in tests than in production. + modelBuilder.Entity().Property( x => x.UserName ).UseCollation( "NOCASE" ); + modelBuilder.Entity().Property( x => x.Machine ).UseCollation( "NOCASE" ); + } + } +} diff --git a/src/PostSharp.LicenseServer.Core/Data/SeatCounter.cs b/src/PostSharp.LicenseServer.Core/Data/SeatCounter.cs new file mode 100644 index 0000000..389c491 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Data/SeatCounter.cs @@ -0,0 +1,25 @@ +namespace PostSharp.LicenseServer.Data; + +/// +/// Converts machine counts into seat counts. +/// +public static class SeatCounter +{ + /// + /// Counts the seats consumed by users holding the given numbers of machines. A user consumes one + /// seat per machines, rounded up. + /// + /// + /// This arithmetic used to run inside the SQL GROUP BY, which no provider other than SQL + /// Server can translate. Doing it here keeps the query portable and makes the rounding + /// boundaries directly testable. The number of rows is bounded by the number of distinct users + /// on one license. + /// + public static int CountSeats( IEnumerable machinesPerUser, int machinesPerUserLimit ) + { + ArgumentNullException.ThrowIfNull( machinesPerUser ); + ArgumentOutOfRangeException.ThrowIfLessThan( machinesPerUserLimit, 1 ); + + return (int) machinesPerUser.Sum( count => Math.Ceiling( count / (double) machinesPerUserLimit ) ); + } +} diff --git a/src/PostSharp.LicenseServer.Core/Email/EmailMessage.cs b/src/PostSharp.LicenseServer.Core/Email/EmailMessage.cs new file mode 100644 index 0000000..786f4c0 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Email/EmailMessage.cs @@ -0,0 +1,10 @@ +namespace PostSharp.LicenseServer.Email; + +/// +/// A notification email to the license administrator. +/// +public sealed record EmailMessage( + string To, + string? Cc, + string Subject, + string Body ); diff --git a/src/PostSharp.LicenseServer.Core/Email/IEmailSender.cs b/src/PostSharp.LicenseServer.Core/Email/IEmailSender.cs new file mode 100644 index 0000000..aef81ef --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Email/IEmailSender.cs @@ -0,0 +1,10 @@ +namespace PostSharp.LicenseServer.Email; + +/// +/// Sends notification emails. A failure to send must never fail a lease request, so implementations +/// are expected to log rather than throw. +/// +public interface IEmailSender +{ + Task SendAsync( EmailMessage message, CancellationToken cancellationToken = default ); +} diff --git a/src/PostSharp.LicenseServer.Core/Email/NullEmailSender.cs b/src/PostSharp.LicenseServer.Core/Email/NullEmailSender.cs new file mode 100644 index 0000000..4cd7987 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Email/NullEmailSender.cs @@ -0,0 +1,10 @@ +namespace PostSharp.LicenseServer.Email; + +/// +/// Discards notification emails. Used when SMTP is disabled and when generating demo data. +/// +public sealed class NullEmailSender : IEmailSender +{ + public Task SendAsync( EmailMessage message, CancellationToken cancellationToken = default ) + => Task.CompletedTask; +} diff --git a/src/PostSharp.LicenseServer.Core/Email/SmtpEmailSender.cs b/src/PostSharp.LicenseServer.Core/Email/SmtpEmailSender.cs new file mode 100644 index 0000000..7e9172d --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Email/SmtpEmailSender.cs @@ -0,0 +1,82 @@ +using MailKit.Net.Smtp; +using MailKit.Security; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MimeKit; +using PostSharp.LicenseServer.Options; + +namespace PostSharp.LicenseServer.Email; + +/// +/// Sends notification emails over SMTP. +/// +/// +/// The legacy implementation called SmtpClient.SendAsync(message, null) and never observed +/// the result, so every delivery failure was silent. Failures are now logged, but still never +/// propagate: a broken SMTP server must not deny a developer their license. +/// +public sealed class SmtpEmailSender( + IOptions options, + ILogger logger ) : IEmailSender +{ + private readonly SmtpOptions options = options.Value; + private readonly ILogger logger = logger; + + public async Task SendAsync( EmailMessage message, CancellationToken cancellationToken = default ) + { + if ( !this.options.Enabled ) + { + return; + } + + if ( string.IsNullOrWhiteSpace( message.To ) ) + { + return; + } + + try + { + MimeMessage mimeMessage = new(); + mimeMessage.From.Add( MailboxAddress.Parse( this.options.FromAddress ) ); + mimeMessage.To.Add( MailboxAddress.Parse( message.To.Trim( ' ', '\n', '\r', '\t' ) ) ); + + if ( !string.IsNullOrEmpty( message.Cc ) ) + { + foreach ( string address in message.Cc.Split( [',', ';', ' '], StringSplitOptions.RemoveEmptyEntries ) ) + { + string trimmed = address.Trim( ' ', '\n', '\r', '\t' ); + + if ( trimmed.Length > 0 ) + { + mimeMessage.Cc.Add( MailboxAddress.Parse( trimmed ) ); + } + } + } + + mimeMessage.Subject = "[SharpCrafters License Server] " + message.Subject; + mimeMessage.Priority = MessagePriority.Urgent; + mimeMessage.Body = new TextPart( "plain" ) { Text = message.Body }; + + using SmtpClient client = new(); + + await client.ConnectAsync( + this.options.Host, + this.options.Port, + this.options.EnableSsl ? SecureSocketOptions.StartTls : SecureSocketOptions.Auto, + cancellationToken ); + + if ( !string.IsNullOrEmpty( this.options.UserName ) ) + { + await client.AuthenticateAsync( this.options.UserName, this.options.Password ?? string.Empty, cancellationToken ); + } + + await client.SendAsync( mimeMessage, cancellationToken ); + await client.DisconnectAsync( true, cancellationToken ); + } + catch ( Exception e ) + { + this.logger.LogError( e, "Cannot send the notification email '{Subject}' to {To}.", message.Subject, message.To ); + } + } + +} diff --git a/src/PostSharp.LicenseServer.Core/Licensing/CachingLicenseParser.cs b/src/PostSharp.LicenseServer.Core/Licensing/CachingLicenseParser.cs new file mode 100644 index 0000000..a8de57e --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Licensing/CachingLicenseParser.cs @@ -0,0 +1,24 @@ +using System.Collections.Concurrent; + +namespace PostSharp.LicenseServer.Licensing; + +/// +/// Caches parse results, because a license key is parsed on every lease request and on every render +/// of the dashboard. +/// +/// +/// Unlike the legacy ParsedLicenseManager, this cache also remembers that a key is +/// invalid. The old code returned before adding to the dictionary, so an invalid key was +/// re-parsed on every single call. +/// +public sealed class CachingLicenseParser( ILicenseParser inner ) : ILicenseParser +{ + private readonly ConcurrentDictionary cache = new( StringComparer.Ordinal ); + + public LicenseInfo? TryParse( string licenseKey ) + => string.IsNullOrWhiteSpace( licenseKey ) + ? null + : this.cache.GetOrAdd( licenseKey, inner.TryParse ); + + public string CleanLicenseString( string licenseKey ) => inner.CleanLicenseString( licenseKey ); +} diff --git a/src/PostSharp.LicenseServer.Core/Licensing/ILeaseSerializer.cs b/src/PostSharp.LicenseServer.Core/Licensing/ILeaseSerializer.cs new file mode 100644 index 0000000..ea8690d --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Licensing/ILeaseSerializer.cs @@ -0,0 +1,10 @@ +namespace PostSharp.LicenseServer.Licensing; + +/// +/// Produces the response body of the lease endpoint. This is the wire contract with the PostSharp +/// client, so it is isolated behind an interface and pinned by tests. +/// +public interface ILeaseSerializer +{ + string Serialize( string licenseKey, DateTime startTime, DateTime endTime, DateTime renewTime ); +} diff --git a/src/PostSharp.LicenseServer.Core/Licensing/ILicenseParser.cs b/src/PostSharp.LicenseServer.Core/Licensing/ILicenseParser.cs new file mode 100644 index 0000000..c2cfed7 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Licensing/ILicenseParser.cs @@ -0,0 +1,18 @@ +namespace PostSharp.LicenseServer.Licensing; + +/// +/// Parses and validates PostSharp license keys. Replaces the static ParsedLicenseManager. +/// +public interface ILicenseParser +{ + /// + /// Parses and validates a license key. + /// + /// The parsed license, or null when the key is malformed or invalid. + LicenseInfo? TryParse( string licenseKey ); + + /// + /// Removes whitespace and formatting from a license key pasted by a human. + /// + string CleanLicenseString( string licenseKey ); +} diff --git a/src/PostSharp.LicenseServer.Core/Licensing/ILicenseServerVersion.cs b/src/PostSharp.LicenseServer.Core/Licensing/ILicenseServerVersion.cs new file mode 100644 index 0000000..60af1c8 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Licensing/ILicenseServerVersion.cs @@ -0,0 +1,10 @@ +namespace PostSharp.LicenseServer.Licensing; + +/// +/// The version of the PostSharp SDK embedded in this license server. A license requiring a higher +/// version cannot be served until the license server itself is upgraded. +/// +public interface ILicenseServerVersion +{ + Version SdkVersion { get; } +} diff --git a/src/PostSharp.LicenseServer.Core/Licensing/LicenseInfo.cs b/src/PostSharp.LicenseServer.Core/Licensing/LicenseInfo.cs new file mode 100644 index 0000000..2d13173 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Licensing/LicenseInfo.cs @@ -0,0 +1,58 @@ +namespace PostSharp.LicenseServer.Licensing; + +/// +/// The facts the license server needs about a license key, projected out of the PostSharp SDK so +/// that the rest of the application does not depend on the SDK and can be tested without a real +/// license key. +/// +public sealed record LicenseInfo +{ + public required int LicenseId { get; init; } + + /// + /// Gets the licensed product, as stored in the ProductCode column. + /// + public required string Product { get; init; } + + public required string LicenseType { get; init; } + + /// + /// Gets the number of concurrent users allowed by the license, or null when unlimited. + /// + public int? UserNumber { get; init; } + + /// + /// Gets the date after which the license itself stops working. + /// + public DateTime? ValidTo { get; init; } + + /// + /// Gets the date after which builds of PostSharp are no longer covered by the maintenance + /// subscription. + /// + public DateTime? SubscriptionEndDate { get; init; } + + /// + /// Gets the lowest version of PostSharp that understands this license. + /// + public required Version MinPostSharpVersion { get; init; } + + /// + /// Gets the number of days during which the license may be over-used before requests are denied. + /// + public required int GraceDays { get; init; } + + /// + /// Gets the percentage by which may be exceeded during the grace period. + /// + public required int GracePercent { get; init; } + + /// + /// Gets a value indicating whether this kind of license may be served by a license server at all. + /// + public required bool IsLicenseServerEligible { get; init; } + + public string? LicenseTypeName { get; init; } + + public string? ProductName { get; init; } +} diff --git a/src/PostSharp.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs b/src/PostSharp.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs new file mode 100644 index 0000000..e860e66 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs @@ -0,0 +1,12 @@ +using PostSharp.Sdk.Extensibility.Licensing; + +namespace PostSharp.LicenseServer.Licensing; + +/// +/// Serializes a lease in the format expected by the PostSharp client. +/// +public sealed class PostSharpLeaseSerializer : ILeaseSerializer +{ + public string Serialize( string licenseKey, DateTime startTime, DateTime endTime, DateTime renewTime ) + => new LicenseLease( licenseKey, startTime, endTime, renewTime ).Serialize(); +} diff --git a/src/PostSharp.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs b/src/PostSharp.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs new file mode 100644 index 0000000..524b9c3 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs @@ -0,0 +1,44 @@ +using PostSharp.Sdk.Extensibility.Licensing; +using ParsedLicense = PostSharp.Sdk.Extensibility.Licensing.License; + +namespace PostSharp.LicenseServer.Licensing; + +/// +/// Parses license keys with the PostSharp SDK. This is the only class that touches the SDK's +/// licensing types; everything else works with . +/// +public sealed class PostSharpLicenseParser : ILicenseParser +{ + public LicenseInfo? TryParse( string licenseKey ) + { + if ( string.IsNullOrWhiteSpace( licenseKey ) ) + { + return null; + } + + ParsedLicense? parsedLicense = ParsedLicense.Deserialize( licenseKey ); + + if ( parsedLicense == null || !parsedLicense.Validate( null, out _ ) ) + { + return null; + } + + return new LicenseInfo + { + LicenseId = parsedLicense.LicenseId, + Product = parsedLicense.Product.ToString(), + LicenseType = parsedLicense.LicenseType.ToString(), + UserNumber = parsedLicense.UserNumber, + ValidTo = parsedLicense.ValidTo, + SubscriptionEndDate = parsedLicense.SubscriptionEndDate, + MinPostSharpVersion = parsedLicense.MinPostSharpVersion, + GraceDays = parsedLicense.GetGraceDaysOrDefault(), + GracePercent = parsedLicense.GetGracePercentOrDefault(), + IsLicenseServerEligible = parsedLicense.IsLicenseServerEligible(), + LicenseTypeName = parsedLicense.GetLicenseTypeName(), + ProductName = parsedLicense.GetProductName() + }; + } + + public string CleanLicenseString( string licenseKey ) => ParsedLicense.CleanLicenseString( licenseKey ); +} diff --git a/src/PostSharp.LicenseServer.Core/Licensing/PostSharpPlatform.cs b/src/PostSharp.LicenseServer.Core/Licensing/PostSharpPlatform.cs new file mode 100644 index 0000000..182cfda --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Licensing/PostSharpPlatform.cs @@ -0,0 +1,37 @@ +using PostSharp.Platform.NetStandard20; +using PostSharp.Platform.Neutral; + +namespace PostSharp.LicenseServer.Licensing; + +/// +/// Initializes the PostSharp SDK platform services. Must run once before any license key is parsed. +/// +/// +/// The legacy code did this in the static constructor of ParsedLicenseManager and used +/// NetFrameworkDefaultSystemServices, which does not exist outside .NET Framework. +/// +public static class PostSharpPlatform +{ + private static readonly Lock sync = new(); + private static bool initialized; + + public static void EnsureInitialized() + { + if ( initialized ) + { + return; + } + + lock ( sync ) + { + if ( initialized ) + { + return; + } + + CommonDefaultSystemServices.Initialize(); + NetCoreAppDefaultSystemServices.Initialize(); + initialized = true; + } + } +} diff --git a/src/PostSharp.LicenseServer.Core/Licensing/PostSharpServerVersion.cs b/src/PostSharp.LicenseServer.Core/Licensing/PostSharpServerVersion.cs new file mode 100644 index 0000000..0d2db54 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Licensing/PostSharpServerVersion.cs @@ -0,0 +1,11 @@ +using PostSharp.Sdk; + +namespace PostSharp.LicenseServer.Licensing; + +/// +/// Reports the version of the PostSharp SDK embedded in this license server. +/// +public sealed class PostSharpServerVersion : ILicenseServerVersion +{ + public Version SdkVersion => ApplicationInfo.Version; +} diff --git a/src/PostSharp.LicenseServer.Core/Locking/ILeaseLock.cs b/src/PostSharp.LicenseServer.Core/Locking/ILeaseLock.cs new file mode 100644 index 0000000..7900165 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Locking/ILeaseLock.cs @@ -0,0 +1,18 @@ +namespace PostSharp.LicenseServer.Locking; + +/// +/// Serializes lease requests, so that two concurrent requests cannot both decide that the last +/// remaining seat is free. Replaces the machine-wide named Mutex of the legacy +/// Lease.ashx handler, which was Windows-only and blocked a thread pool thread. +/// +public interface ILeaseLock +{ + /// + /// Acquires the lock, waiting at most . + /// + /// + /// A handle that releases the lock when disposed, or null when the timeout elapsed, in + /// which case the caller answers HTTP 503. + /// + ValueTask TryAcquireAsync( TimeSpan timeout, CancellationToken cancellationToken = default ); +} diff --git a/src/PostSharp.LicenseServer.Core/Locking/InProcessLeaseLock.cs b/src/PostSharp.LicenseServer.Core/Locking/InProcessLeaseLock.cs new file mode 100644 index 0000000..340d2c4 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Locking/InProcessLeaseLock.cs @@ -0,0 +1,43 @@ +namespace PostSharp.LicenseServer.Locking; + +/// +/// Serializes lease requests within the current process. This is the default, and is correct for +/// the supported deployment of a single worker process per database. +/// +/// +/// Unlike the named Mutex it replaces, waiting here does not block a thread pool thread, so +/// queued requests are far cheaper and the timeout fires much less often. +/// +public sealed class InProcessLeaseLock : ILeaseLock, IDisposable +{ + private readonly SemaphoreSlim semaphore = new( 1, 1 ); + + public async ValueTask TryAcquireAsync( + TimeSpan timeout, + CancellationToken cancellationToken = default ) + { + if ( !await this.semaphore.WaitAsync( timeout, cancellationToken ) ) + { + return null; + } + + return new Handle( this.semaphore ); + } + + public void Dispose() => this.semaphore.Dispose(); + + private sealed class Handle( SemaphoreSlim semaphore ) : IAsyncDisposable + { + private int released; + + public ValueTask DisposeAsync() + { + if ( Interlocked.Exchange( ref this.released, 1 ) == 0 ) + { + semaphore.Release(); + } + + return ValueTask.CompletedTask; + } + } +} diff --git a/src/PostSharp.LicenseServer.Core/Locking/NullLeaseLock.cs b/src/PostSharp.LicenseServer.Core/Locking/NullLeaseLock.cs new file mode 100644 index 0000000..26ba928 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Locking/NullLeaseLock.cs @@ -0,0 +1,19 @@ +namespace PostSharp.LicenseServer.Locking; + +/// +/// Does not serialize anything. Intended for tests that do not exercise concurrency. +/// +public sealed class NullLeaseLock : ILeaseLock +{ + private static readonly IAsyncDisposable handle = new Handle(); + + public ValueTask TryAcquireAsync( + TimeSpan timeout, + CancellationToken cancellationToken = default ) + => ValueTask.FromResult( handle ); + + private sealed class Handle : IAsyncDisposable + { + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + } +} diff --git a/src/PostSharp.LicenseServer.Core/Options/LeaseLockMode.cs b/src/PostSharp.LicenseServer.Core/Options/LeaseLockMode.cs new file mode 100644 index 0000000..f7035c0 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Options/LeaseLockMode.cs @@ -0,0 +1,25 @@ +namespace PostSharp.LicenseServer.Options; + +/// +/// Determines how the license server serializes concurrent lease requests. +/// +public enum LeaseLockMode +{ + /// + /// A semaphore shared by all requests served by the current process. This is the default and is + /// correct when a single process serves the database, which is the supported deployment. + /// + InProcess, + + /// + /// A SQL Server application lock, shared by every process connected to the same database. + /// Required when the license server runs in a web garden, behind a load balancer, or in several + /// containers. + /// + SqlApplicationLock, + + /// + /// No locking at all. Intended for tests. + /// + None +} diff --git a/src/PostSharp.LicenseServer.Core/Options/LicenseServerOptions.cs b/src/PostSharp.LicenseServer.Core/Options/LicenseServerOptions.cs new file mode 100644 index 0000000..e6ad859 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Options/LicenseServerOptions.cs @@ -0,0 +1,102 @@ +using System.ComponentModel.DataAnnotations; + +namespace PostSharp.LicenseServer.Options; + +/// +/// Settings of the license server. Replaces the applicationSettings section of the legacy +/// Web.config. Setting names are unchanged, so the existing administration documentation +/// remains valid. +/// +public sealed class LicenseServerOptions +{ + public const string SectionName = "LicenseServer"; + + /// + /// Gets or sets the number of days between notification emails about the license grace period, + /// i.e. when a license has more leases than allowed. + /// + [Range( 0, 365 )] + public int GracePeriodWarningDays { get; set; } = 1; + + /// + /// Gets or sets the number of devices that can be used by a single user with a single seat. + /// The default value is 2. Check your license agreement for a different value. + /// + [Range( 1, 100 )] + public int MachinesPerUser { get; set; } = 2; + + /// + /// Gets or sets the minimal number of days before the end of the lease before a client will try + /// to renew the lease. For instance, if developers are expected to work for five weeks without a + /// network connection to the license server, this value should be greater than 35. Must be + /// smaller than . + /// + [Range( 0, 3650 )] + public int MinLeaseDays { get; set; } = 1; + + /// + /// Gets or sets the duration of a new lease, in days. + /// + [Range( 1, 3650 )] + public int NewLeaseDays { get; set; } = 3; + + /// + /// Gets or sets the address for notification emails sent when the grace period starts. + /// + public string? GracePeriodWarningEmailTo { get; set; } + + /// + /// Gets or sets the addresses copied on grace period notification emails. + /// + public string? GracePeriodWarningEmailCC { get; set; } + + /// + /// Gets or sets the address for notification emails sent when a lease request is denied. + /// + public string? DeniedRequestEmailTo { get; set; } + + /// + /// Gets or sets the timeout for the lock that serializes concurrent lease requests, in seconds. + /// If a lease request cannot be served within this period, HTTP status 503 is returned. + /// + [Range( 1, 600 )] + public int MutexTimeout { get; set; } = 30; + + /// + /// Gets or sets the factor by which the passage of time is accelerated. Set to 1 in production. + /// For testing purposes only. + /// + public decimal TimeAcceleration { get; set; } = 1; + + /// + /// Gets or sets a semicolon-separated list of computer names of build servers. Build servers + /// receive a lease that is not persisted, so that they do not consume developer seats. + /// + public string? BuildServers { get; set; } + + /// + /// Gets or sets the base64-encoded key used to sign the lease audit log. When null, a key is + /// generated on first start and persisted next to the application. + /// + public string? AuditHmacKey { get; set; } + + /// + /// Gets or sets the Windows groups allowed to reach the administrative pages, for example + /// DOMAIN\PostSharp Administrators. When empty, the administrative pages are not + /// restricted, which preserves the behaviour of the legacy Web.config. + /// + public string[] AdminRoles { get; set; } = []; + + /// + /// Gets or sets a value indicating whether a lease request must be authenticated. When false, + /// anonymous requests are served, which preserves the behaviour of the legacy Web.config. + /// + public bool RequireAuthenticatedLeaseRequests { get; set; } + + /// + /// Gets or sets the mechanism that serializes concurrent lease requests. + /// + public LeaseLockMode LeaseLockMode { get; set; } = LeaseLockMode.InProcess; + + public TimeSpan MutexTimeoutSpan => TimeSpan.FromSeconds( this.MutexTimeout ); +} diff --git a/src/PostSharp.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs b/src/PostSharp.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs new file mode 100644 index 0000000..d2d27e0 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.Options; + +namespace PostSharp.LicenseServer.Options; + +/// +/// Validates the relationships between settings that data annotations cannot express. The legacy +/// Web.config documented these constraints but nothing enforced them. +/// +public sealed class LicenseServerOptionsValidator : IValidateOptions +{ + public ValidateOptionsResult Validate( string? name, LicenseServerOptions options ) + { + List failures = []; + + // A lease whose renewal time is not before its end time makes the client renew on every + // single request. + if ( options.MinLeaseDays >= options.NewLeaseDays ) + { + failures.Add( + $"MinLeaseDays ({options.MinLeaseDays}) must be smaller than NewLeaseDays ({options.NewLeaseDays}), " + + "otherwise a new lease is already due for renewal when it is granted." ); + } + + if ( options.TimeAcceleration < 0 ) + { + failures.Add( $"TimeAcceleration ({options.TimeAcceleration}) cannot be negative." ); + } + + if ( options.AuditHmacKey != null && !IsBase64( options.AuditHmacKey ) ) + { + failures.Add( "AuditHmacKey must be a base64-encoded string." ); + } + + return failures.Count == 0 ? ValidateOptionsResult.Success : ValidateOptionsResult.Fail( failures ); + } + + private static bool IsBase64( string value ) => Convert.TryFromBase64String( value, new byte[value.Length], out _ ); +} diff --git a/src/PostSharp.LicenseServer.Core/Options/SmtpOptions.cs b/src/PostSharp.LicenseServer.Core/Options/SmtpOptions.cs new file mode 100644 index 0000000..2444eba --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Options/SmtpOptions.cs @@ -0,0 +1,30 @@ +namespace PostSharp.LicenseServer.Options; + +/// +/// Settings of the SMTP server used to send notification emails. Replaces the +/// system.net/mailSettings section of the legacy Web.config. +/// +public sealed class SmtpOptions +{ + public const string SectionName = "Smtp"; + + /// + /// Gets or sets a value indicating whether notification emails are sent at all. + /// + public bool Enabled { get; set; } = true; + + public string Host { get; set; } = "localhost"; + + public int Port { get; set; } = 25; + + public bool EnableSsl { get; set; } + + /// + /// Gets or sets the sender address. Previously hard-coded to sales@postsharp.net. + /// + public string FromAddress { get; set; } = "sales@postsharp.net"; + + public string? UserName { get; set; } + + public string? Password { get; set; } +} diff --git a/src/PostSharp.LicenseServer.Core/PostSharp.LicenseServer.Core.csproj b/src/PostSharp.LicenseServer.Core/PostSharp.LicenseServer.Core.csproj new file mode 100644 index 0000000..1f11a51 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/PostSharp.LicenseServer.Core.csproj @@ -0,0 +1,21 @@ + + + + net10.0 + + PostSharp.LicenseServer + PostSharp.LicenseServer.Core + + + + + + + + + + + + + diff --git a/src/PostSharp.LicenseServer.Core/Security/FileAuditKeyProvider.cs b/src/PostSharp.LicenseServer.Core/Security/FileAuditKeyProvider.cs new file mode 100644 index 0000000..e333438 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Security/FileAuditKeyProvider.cs @@ -0,0 +1,74 @@ +using System.Security.Cryptography; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using PostSharp.LicenseServer.Options; + +namespace PostSharp.LicenseServer.Security; + +/// +/// Supplies the audit signing key, taking it from configuration when set, and otherwise generating +/// one on first use and storing it next to the application. +/// +/// +/// The key file must be preserved across redeployments and included in backups. Losing it does not +/// invalidate existing rows, but it does start a new signature chain. +/// +public sealed class FileAuditKeyProvider : IAuditKeyProvider +{ + private const int keySizeInBytes = 32; + + private readonly string keyFilePath; + private readonly LicenseServerOptions options; + private readonly ILogger logger; + private readonly Lock sync = new(); + + private byte[]? key; + + public FileAuditKeyProvider( + IOptions options, + ILogger logger, + string keyFilePath ) + { + this.options = options.Value; + this.logger = logger; + this.keyFilePath = keyFilePath; + } + + public byte[] GetKey() + { + if ( this.key != null ) + { + return this.key; + } + + lock ( this.sync ) + { + return this.key ??= this.LoadOrCreateKey(); + } + } + + private byte[] LoadOrCreateKey() + { + if ( !string.IsNullOrWhiteSpace( this.options.AuditHmacKey ) ) + { + return Convert.FromBase64String( this.options.AuditHmacKey ); + } + + if ( File.Exists( this.keyFilePath ) ) + { + return Convert.FromBase64String( File.ReadAllText( this.keyFilePath ).Trim() ); + } + + byte[] newKey = RandomNumberGenerator.GetBytes( keySizeInBytes ); + + Directory.CreateDirectory( Path.GetDirectoryName( this.keyFilePath )! ); + File.WriteAllText( this.keyFilePath, Convert.ToBase64String( newKey ) ); + + this.logger.LogInformation( + "Generated a new audit signing key in {Path}. Include this file in your backups and preserve " + + "it across upgrades, otherwise the audit log signature chain restarts.", + this.keyFilePath ); + + return newKey; + } +} diff --git a/src/PostSharp.LicenseServer.Core/Security/HmacLeaseSigner.cs b/src/PostSharp.LicenseServer.Core/Security/HmacLeaseSigner.cs new file mode 100644 index 0000000..023fb2c --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Security/HmacLeaseSigner.cs @@ -0,0 +1,26 @@ +using System.Security.Cryptography; +using System.Text; + +namespace PostSharp.LicenseServer.Security; + +/// +/// Signs the audit log with HMAC-SHA256. +/// +/// +/// The legacy implementation called the parameterless HMAC.Create(), which throws +/// on .NET 5 and later, and which on .NET Framework +/// produced an HMAC-SHA1 under a randomly generated key for every single call. The audit +/// chain was therefore never verifiable by anyone. A configured key makes it verifiable. +/// The base64 of a SHA-256 hash is 44 characters, which fits the existing varchar(100) +/// column, so no schema change is required. +/// +public sealed class HmacLeaseSigner( IAuditKeyProvider keyProvider ) : ILeaseSigner +{ + public string Sign( string payload ) + { + ArgumentNullException.ThrowIfNull( payload ); + + return Convert.ToBase64String( + HMACSHA256.HashData( keyProvider.GetKey(), Encoding.UTF8.GetBytes( payload ) ) ); + } +} diff --git a/src/PostSharp.LicenseServer.Core/Security/IAuditKeyProvider.cs b/src/PostSharp.LicenseServer.Core/Security/IAuditKeyProvider.cs new file mode 100644 index 0000000..76b4b10 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Security/IAuditKeyProvider.cs @@ -0,0 +1,9 @@ +namespace PostSharp.LicenseServer.Security; + +/// +/// Supplies the key used to sign the lease audit log. +/// +public interface IAuditKeyProvider +{ + byte[] GetKey(); +} diff --git a/src/PostSharp.LicenseServer.Core/Security/ILeaseSigner.cs b/src/PostSharp.LicenseServer.Core/Security/ILeaseSigner.cs new file mode 100644 index 0000000..867b8a2 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Security/ILeaseSigner.cs @@ -0,0 +1,10 @@ +namespace PostSharp.LicenseServer.Security; + +/// +/// Signs the lease audit log. Each lease is signed together with the signature of the previous +/// lease, so that a removed or altered row breaks the chain. +/// +public interface ILeaseSigner +{ + string Sign( string payload ); +} diff --git a/src/PostSharp.LicenseServer.Core/Services/LeaseService.cs b/src/PostSharp.LicenseServer.Core/Services/LeaseService.cs new file mode 100644 index 0000000..3ff0d41 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Services/LeaseService.cs @@ -0,0 +1,481 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using PostSharp.LicenseServer.Data; +using PostSharp.LicenseServer.Email; +using PostSharp.LicenseServer.Licensing; +using PostSharp.LicenseServer.Options; + +namespace PostSharp.LicenseServer.Services; + +/// +/// Decides which license, if any, satisfies a lease request. +/// +/// +/// Allocation runs in three passes over the candidate licenses: reuse or prolong a lease the user +/// already holds, then grant a new lease against spare capacity, then fall back on the grace period. +/// +public sealed partial class LeaseService +{ + private readonly ILeaseRepository repository; + private readonly LicenseServerOptions settings; + private readonly ILicenseParser licenseParser; + private readonly ILicenseServerVersion serverVersion; + private readonly IEmailSender emailSender; + private readonly ILogger logger; + private readonly HashSet buildServers = new( StringComparer.OrdinalIgnoreCase ); + + public LeaseService( + ILeaseRepository repository, + IOptions options, + ILicenseParser licenseParser, + ILicenseServerVersion serverVersion, + IEmailSender emailSender, + ILogger logger ) + { + this.repository = repository; + this.settings = options.Value; + this.licenseParser = licenseParser; + this.serverVersion = serverVersion; + this.emailSender = emailSender; + this.logger = logger; + + if ( !string.IsNullOrWhiteSpace( this.settings.BuildServers ) ) + { + foreach ( string buildServer in this.settings.BuildServers.Split( [';', ',', ' '] ) ) + { + if ( !string.IsNullOrWhiteSpace( buildServer ) ) + { + this.buildServers.Add( buildServer.Trim() ); + } + } + } + } + + /// + /// Matches the unique-identifier suffix that build agents append to their machine name. + /// + [GeneratedRegex( "-[0-9a-fA-F]+$" )] + private static partial Regex ComputerUniqueIdRegex { get; } + + /// + /// Validates a license against the request and, if it is usable, returns its current state. + /// + /// null when the license cannot serve this request, in which case + /// explains why. + private LicenseState? GetLicenseState( + License license, + Version version, + DateTime? buildDate, + DateTime now, + Dictionary cache, + Dictionary errors ) + { + if ( cache.TryGetValue( license.LicenseId, out LicenseState? licenseState ) ) + { + return licenseState; + } + + LicenseInfo? parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); + + if ( parsedLicense == null ) + { + errors[license.LicenseId] = $"The license key #{license.LicenseId} is invalid."; + + return null; + } + + if ( parsedLicense.MinPostSharpVersion > this.serverVersion.SdkVersion ) + { + errors[license.LicenseId] = string.Format( + "The license #{0} requires higher version of PostSharp on the License Server. Please upgrade PostSharp NuGet package of the License Server to >= {1}.{2}.{3}", + license.LicenseId, + parsedLicense.MinPostSharpVersion.Major, + parsedLicense.MinPostSharpVersion.Minor, + parsedLicense.MinPostSharpVersion.Build ); + + return null; + } + + if ( parsedLicense.MinPostSharpVersion > version ) + { + errors[license.LicenseId] = string.Format( + "The license #{0} of type {1} requires PostSharp version >= {2}.{3}.{4} but the requested version is {5}.{6}.{7}.", + license.LicenseId, + parsedLicense.LicenseType, + parsedLicense.MinPostSharpVersion.Major, + parsedLicense.MinPostSharpVersion.Minor, + parsedLicense.MinPostSharpVersion.Build, + version.Major, + version.Minor, + version.Build ); + + return null; + } + + if ( !parsedLicense.IsLicenseServerEligible ) + { + errors[license.LicenseId] = + $"The license #{license.LicenseId}, of type {parsedLicense.LicenseType}, cannot be used in the license server."; + + return null; + } + + if ( !(buildDate == null || parsedLicense.SubscriptionEndDate == null + || buildDate <= parsedLicense.SubscriptionEndDate) ) + { + // The PostSharp version number was introduced in the license server protocol in v5. + errors[license.LicenseId] = version.Major >= 5 + ? string.Format( + "The maintenance subscription of license #{0} ends on {1:d} but the requested version {2}.{3}.{4} has been built on {5:d}.", + license.LicenseId, + parsedLicense.SubscriptionEndDate, + version.Major, + version.Minor, + version.Build, + buildDate ) + : string.Format( + "The maintenance subscription of license #{0} ends on {1:d} but the requested version has been built on {2:d}.", + license.LicenseId, + parsedLicense.SubscriptionEndDate, + buildDate ); + + return null; + } + + licenseState = new LicenseState( now, this.repository, license, parsedLicense ); + cache.Add( license.LicenseId, licenseState ); + + return licenseState; + } + + /// + /// Serves a lease request, returning the license key and the validity of the lease. + /// + public async Task GetLicenseLeaseAsync( + string? productCode, + Version version, + DateTime? buildDate, + string machine, + string userName, + string authenticatedUserName, + DateTime now, + Dictionary errors, + CancellationToken cancellationToken = default ) + { + License[] licenses = await this.repository.Licenses + .Where( license => (string.IsNullOrEmpty( productCode ) || license.ProductCode == productCode) + && license.Priority >= 0 ) + .OrderBy( license => license.Priority ) + .ToArrayAsync( cancellationToken ); + + if ( this.IsBuildServer( machine ) ) + { + License? buildServerLicense = licenses.FirstOrDefault( this.IsLicenseValid ); + + if ( buildServerLicense != null ) + { + DateTime endTime = now.AddDays( this.settings.NewLeaseDays ); + + // A build server's lease is never persisted, so that build agents cannot consume + // the seats of the developers they build for. + return new GrantedLease( + buildServerLicense.LicenseKey, + now, + endTime, + endTime.AddDays( -this.settings.MinLeaseDays ) ); + } + + // Otherwise fall through and acquire a lease in the normal way. + } + + Lease? lease = await this.GetLeaseAsync( + version, + buildDate, + machine, + userName, + authenticatedUserName, + now, + errors, + licenses, + productCode, + cancellationToken ); + + if ( lease == null ) + { + return null; + } + + return new GrantedLease( + lease.License.LicenseKey, + lease.StartTime, + lease.EndTime, + lease.EndTime.AddDays( -this.settings.MinLeaseDays ) ); + } + + /// + /// Finds or creates the lease that satisfies a request. + /// + public async Task GetLeaseAsync( + Version version, + DateTime? buildDate, + string machine, + string userName, + string authenticatedUserName, + DateTime now, + Dictionary errors, + License[] licenses, + string? productCode = null, + CancellationToken cancellationToken = default ) + { + Dictionary licenseStates = []; + + // First pass: a lease this user already holds on this machine, reused or prolonged. + foreach ( License license in licenses ) + { + LicenseState? licenseState = + this.GetLicenseState( license, version, buildDate, now, licenseStates, errors ); + + if ( licenseState == null ) + { + continue; + } + + int licenseId = license.LicenseId; + + Lease[] currentLeases = await this.repository.OpenLeases + .Where( l => l.LicenseId == licenseId && l.StartTime <= now && l.EndTime > now && l.UserName == userName ) + .Include( l => l.License ) + .OrderBy( l => l.StartTime ) + .ToArrayAsync( cancellationToken ); + + Dictionary machines = new( StringComparer.OrdinalIgnoreCase ); + + foreach ( Lease candidateLease in currentLeases ) + { + machines[candidateLease.Machine] = candidateLease.Machine; + + if ( candidateLease.Machine != machine ) + { + continue; + } + + if ( candidateLease.EndTime > now.AddDays( this.settings.MinLeaseDays ) ) + { + // The lease the user already holds is good enough. + return candidateLease; + } + + // A lease can always be prolonged, because leases are acquired from the present + // moment and therefore already account for the current one -- unless the license + // period or the grace period ends first. + Lease? prolonged = this.repository.ProlongLease( candidateLease, authenticatedUserName, now ); + + if ( prolonged == null ) + { + continue; + } + + return prolonged; + } + + // No lease for the requested machine. A further machine for a user who already holds a + // seat is free, up to MachinesPerUser. + if ( machines.Count % this.settings.MachinesPerUser != 0 ) + { + Lease? lease = this.repository.CreateLease( + license, + userName, + machine, + authenticatedUserName, + now, + licenseState.InExcess ); + + if ( lease != null ) + { + return lease; + } + } + } + + // Second pass: a new lease against spare capacity. + foreach ( License license in licenses ) + { + LicenseState? licenseState = + this.GetLicenseState( license, version, buildDate, now, licenseStates, errors ); + + if ( licenseState == null ) + { + continue; + } + + if ( licenseState.Maximum.HasValue && licenseState.Maximum.Value <= licenseState.Usage + && (!licenseState.ParsedLicense.ValidTo.HasValue + || now < licenseState.ParsedLicense.ValidTo) ) + { + // This license is full. + continue; + } + + Lease? lease = this.repository.CreateLease( license, userName, machine, authenticatedUserName, now, false ); + + if ( lease != null ) + { + return lease; + } + } + + // Third pass: the grace period. + foreach ( License license in licenses ) + { + LicenseState? licenseState = + this.GetLicenseState( license, version, buildDate, now, licenseStates, errors ); + + if ( licenseState == null ) + { + continue; + } + + license.GraceStartTime ??= now; + + int graceLimit = (int) Math.Ceiling( + licenseState.Maximum!.Value * (100.0 + licenseState.ParsedLicense.GracePercent) / 100.0 ); + + DateTime graceEnd = license.GraceStartTime.Value.AddDays( licenseState.ParsedLicense.GraceDays ); + + if ( license.GraceStartTime <= now && graceEnd > now && licenseState.Usage < graceLimit ) + { + if ( license.GraceLastWarningTime.GetValueOrDefault( DateTime.MinValue ) + .AddDays( this.settings.GracePeriodWarningDays ) < now ) + { + string body = string.Format( + "The license #{0} has a capacity of {1} concurrent user(s), but {2} users are currently using the product {3}. " + + "The grace period has started on {4} and will end on {5}. After this date, additional leases will be denied." + + "Please contact PostSharp Technologies to acquire additional licenses.", + license.LicenseId, + licenseState.Maximum, + licenseState.Usage + 1, + licenseState.ParsedLicense.Product, + license.GraceStartTime, + graceEnd ); + + await this.SendEmailAsync( + this.settings.GracePeriodWarningEmailTo, + this.settings.GracePeriodWarningEmailCC, + "WARNING: licensing capacity exceeded", + body, + cancellationToken ); + + // Recorded whether or not the message was delivered, so that a broken SMTP + // server cannot turn every request into a new warning email. + license.GraceLastWarningTime = now; + } + + Lease? lease = this.repository.CreateLease( + license, + userName, + machine, + authenticatedUserName, + now, + true ); + + if ( lease != null ) + { + return lease; + } + } + } + + await this.SendEmailAsync( + this.settings.DeniedRequestEmailTo, + null, + "ERROR: license request denied", + string.Format( + "No license with free capacity was found to satisfy the lease request for the product {0} from " + + "the user '{1}' (authentication: '{2}'), machine '{3}'. " + string.Join( ". ", errors.Values ), + productCode, + userName, + authenticatedUserName, + machine ), + cancellationToken ); + + return null; + } + + private bool IsLicenseValid( License license ) => this.licenseParser.TryParse( license.LicenseKey ) != null; + + /// + /// Determines whether a machine is a build agent, ignoring the unique-identifier suffix that + /// build agents append to their name. + /// + public bool IsBuildServer( string machineName ) + { + machineName = ComputerUniqueIdRegex.Replace( machineName, string.Empty ); + + return this.buildServers.Contains( machineName ); + } + + private async Task SendEmailAsync( + string? to, + string? cc, + string subject, + string body, + CancellationToken cancellationToken ) + { + if ( string.IsNullOrWhiteSpace( to ) ) + { + return; + } + + try + { + await this.emailSender.SendAsync( new EmailMessage( to.Trim( ' ', '\n', '\r', '\t' ), cc, subject, body ), + cancellationToken ); + } + catch ( Exception e ) + { + // A notification that cannot be delivered must never deny a developer their license. + this.logger.LogError( e, "Cannot send the notification email '{Subject}'.", subject ); + } + } + + /// + /// The capacity and current usage of a license, computed lazily because most requests are served + /// by the first pass and never need it. + /// + private sealed class LicenseState( + DateTime time, + ILeaseRepository repository, + License license, + LicenseInfo parsedLicense ) + { + private int usage = -1; + + public int Usage + { + get + { + if ( this.usage == -1 ) + { + this.usage = repository.GetActiveLeads( license.LicenseId, time ); + } + + return this.usage; + } + } + + public int? Maximum => parsedLicense.UserNumber; + + [SuppressMessage( "ReSharper", "UnusedMember.Local", Justification = "Part of the state's contract." )] + public bool InExcess => this.Maximum.HasValue && this.Maximum.Value < this.Usage; + + public LicenseInfo ParsedLicense => parsedLicense; + } +} + +/// +/// The lease granted to a client: which license key to use, and for how long. +/// +public sealed record GrantedLease( string LicenseKey, DateTime StartTime, DateTime EndTime, DateTime RenewTime ); diff --git a/src/PostSharp.LicenseServer.Core/Time/AcceleratedTimeProvider.cs b/src/PostSharp.LicenseServer.Core/Time/AcceleratedTimeProvider.cs new file mode 100644 index 0000000..5391905 --- /dev/null +++ b/src/PostSharp.LicenseServer.Core/Time/AcceleratedTimeProvider.cs @@ -0,0 +1,39 @@ +namespace PostSharp.LicenseServer.Time; + +/// +/// A that makes time pass faster than it really does, so that a +/// multi-day licensing scenario can be simulated in minutes. Replaces the legacy +/// VirtualDateTime class, whose acceleration was compiled out of RELEASE builds and was +/// therefore unusable by any test harness. +/// +public sealed class AcceleratedTimeProvider : TimeProvider +{ + private readonly TimeProvider inner; + private readonly DateTimeOffset origin; + private readonly double acceleration; + + public AcceleratedTimeProvider( TimeProvider inner, double acceleration ) + { + ArgumentNullException.ThrowIfNull( inner ); + + if ( acceleration <= 0 ) + { + throw new ArgumentOutOfRangeException( nameof(acceleration), acceleration, "Acceleration must be positive." ); + } + + this.inner = inner; + this.acceleration = acceleration; + this.origin = inner.GetUtcNow(); + } + + public double Acceleration => this.acceleration; + + public override DateTimeOffset GetUtcNow() + => this.origin + ((this.inner.GetUtcNow() - this.origin) * this.acceleration); + + public override long GetTimestamp() => this.inner.GetTimestamp(); + + public override long TimestampFrequency => this.inner.TimestampFrequency; + + public override TimeZoneInfo LocalTimeZone => this.inner.LocalTimeZone; +} diff --git a/tests/PostSharp.LicenseServer.Tests/BuildServerDetectionTests.cs b/tests/PostSharp.LicenseServer.Tests/BuildServerDetectionTests.cs new file mode 100644 index 0000000..a9bc6c7 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/BuildServerDetectionTests.cs @@ -0,0 +1,109 @@ +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using PostSharp.LicenseServer.Data; +using PostSharp.LicenseServer.Options; +using PostSharp.LicenseServer.Services; +using PostSharp.LicenseServer.Tests.Fakes; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// Build agents are recognised by name so that their leases are not persisted and therefore do not +/// consume developer seats. Agents append a hexadecimal unique identifier to their machine name, +/// which has to be stripped before the name is matched. +/// +public sealed class BuildServerDetectionTests +{ + private static LeaseService CreateService( string? buildServers ) + { + LicenseServerOptions options = new() { BuildServers = buildServers }; + + return new LeaseService( + new StubRepository(), + Microsoft.Extensions.Options.Options.Create( options ), + new FakeLicenseParser(), + new FixedServerVersion(), + new InMemoryEmailSender(), + NullLogger.Instance ); + } + + [Theory] + [InlineData( "server", true )] + [InlineData( "server-1a2b", true )] + [InlineData( "server-ABCDEF", true )] + [InlineData( "server-0", true )] + [InlineData( "SERVER", true )] + [InlineData( "Server-1A2B", true )] + public void IsBuildServer_KnownAgent_ReturnsTrue( string machine, bool expected ) + => Assert.Equal( expected, CreateService( "server" ).IsBuildServer( machine ) ); + + [Theory] + // Only a hexadecimal suffix is stripped, so "-xyz" stays part of the name. + [InlineData( "server-xyz" )] + [InlineData( "myserver" )] + [InlineData( "server2" )] + [InlineData( "desktop-1a2b" )] + [InlineData( "" )] + public void IsBuildServer_OtherMachine_ReturnsFalse( string machine ) + => Assert.False( CreateService( "server" ).IsBuildServer( machine ) ); + + [Theory] + [InlineData( "build1;build2" )] + [InlineData( "build1,build2" )] + [InlineData( "build1 build2" )] + [InlineData( " build1 ; build2 " )] + public void IsBuildServer_AcceptsEverySeparatorAndTrimsWhitespace( string buildServers ) + { + LeaseService service = CreateService( buildServers ); + + Assert.True( service.IsBuildServer( "build1" ) ); + Assert.True( service.IsBuildServer( "build2-ff01" ) ); + Assert.False( service.IsBuildServer( "build3" ) ); + } + + [Theory] + [InlineData( null )] + [InlineData( "" )] + [InlineData( " " )] + public void IsBuildServer_NoBuildServersConfigured_ReturnsFalse( string? buildServers ) + => Assert.False( CreateService( buildServers ).IsBuildServer( "server" ) ); + + /// + /// A repository that is never reached: these tests only exercise name matching, which happens + /// before any database access. + /// + private sealed class StubRepository : ILeaseRepository + { + public IQueryable OpenLeases => Array.Empty().AsQueryable(); + + public IQueryable Licenses => Array.Empty().AsQueryable(); + + public Lease? CreateLease( + License license, + string user, + string machine, + string authenticatedUserName, + DateTime time, + bool grace ) + => throw new NotSupportedException(); + + public Lease? ProlongLease( Lease oldLease, string authenticatedUserName, DateTime time ) + => throw new NotSupportedException(); + + public void CancelLease( Lease lease, string authenticatedUserName, DateTime time ) + => throw new NotSupportedException(); + + public int GetActiveLeads( int licenseId, DateTime dateTime ) => throw new NotSupportedException(); + + public IEnumerable GetLeaseCountingPoints( + int licenseId, + DateTime startTime, + DateTime endTime ) + => throw new NotSupportedException(); + + public Task SaveChangesAsync( CancellationToken cancellationToken = default ) + => throw new NotSupportedException(); + + public int SaveChanges() => throw new NotSupportedException(); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/EmailSenderTests.cs b/tests/PostSharp.LicenseServer.Tests/EmailSenderTests.cs new file mode 100644 index 0000000..a568cb1 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/EmailSenderTests.cs @@ -0,0 +1,82 @@ +using PostSharp.LicenseServer.Email; +using PostSharp.LicenseServer.Tests.Fakes; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// The in-memory used throughout the test suite, and the guarantee that +/// no test ever reaches a real SMTP server. +/// +public sealed class EmailSenderTests +{ + [Fact] + public async Task InMemorySender_RecordsWhatItWasAskedToSend() + { + InMemoryEmailSender sender = new(); + + await sender.SendAsync( new EmailMessage( "admin@example.com", null, "Subject", "Body" ) ); + + EmailMessage message = Assert.Single( sender.Sent ); + Assert.Equal( "admin@example.com", message.To ); + Assert.Equal( "Subject", message.Subject ); + Assert.Equal( "Body", message.Body ); + Assert.Null( message.Cc ); + } + + [Fact] + public async Task InMemorySender_PreservesOrder() + { + InMemoryEmailSender sender = new(); + + await sender.SendAsync( new EmailMessage( "a@example.com", null, "first", "" ) ); + await sender.SendAsync( new EmailMessage( "b@example.com", null, "second", "" ) ); + + Assert.Equal( ["first", "second"], sender.Sent.Select( m => m.Subject ) ); + Assert.Equal( "second", sender.Last!.Subject ); + } + + [Fact] + public async Task InMemorySender_FiltersBySubject() + { + InMemoryEmailSender sender = new(); + + await sender.SendAsync( new EmailMessage( "a@example.com", null, "WARNING: capacity exceeded", "" ) ); + await sender.SendAsync( new EmailMessage( "a@example.com", null, "ERROR: request denied", "" ) ); + + Assert.Single( sender.WithSubject( "WARNING" ) ); + Assert.Single( sender.WithSubject( "denied" ) ); + Assert.Empty( sender.WithSubject( "nothing like this" ) ); + } + + [Fact] + public async Task InMemorySender_CanSimulateABrokenServer() + { + InMemoryEmailSender sender = new() { ThrowOnSend = new InvalidOperationException( "SMTP is down" ) }; + + await Assert.ThrowsAsync( + () => sender.SendAsync( new EmailMessage( "a@example.com", null, "s", "b" ) ) ); + + Assert.Empty( sender.Sent ); + } + + [Fact] + public async Task InMemorySender_Clear_DiscardsRecordedMessages() + { + InMemoryEmailSender sender = new(); + await sender.SendAsync( new EmailMessage( "a@example.com", null, "s", "b" ) ); + + sender.Clear(); + + Assert.Empty( sender.Sent ); + Assert.Null( sender.Last ); + } + + [Fact] + public async Task NullSender_DiscardsEverything() + { + NullEmailSender sender = new(); + + // Must not throw: this is what the demo-data generator and disabled-SMTP deployments use. + await sender.SendAsync( new EmailMessage( "a@example.com", "b@example.com", "s", "b" ) ); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/FakeLicenseParser.cs b/tests/PostSharp.LicenseServer.Tests/Fakes/FakeLicenseParser.cs new file mode 100644 index 0000000..9b58ef1 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/Fakes/FakeLicenseParser.cs @@ -0,0 +1,36 @@ +using PostSharp.LicenseServer.Licensing; + +namespace PostSharp.LicenseServer.Tests.Fakes; + +/// +/// Resolves synthetic license keys to the facts a test wants them to carry. +/// +/// +/// Real PostSharp license keys are signed and are not present in this repository, so almost every +/// test works against this parser. PostSharpLicenseParser is covered separately, by an opt-in +/// test that needs a real key. +/// +public sealed class FakeLicenseParser : ILicenseParser +{ + private readonly Dictionary licenses = new( StringComparer.Ordinal ); + + /// + /// Gets the number of times actually did work, to verify caching. + /// + public int ParseCount { get; private set; } + + public void Register( string licenseKey, LicenseInfo info ) => this.licenses[licenseKey] = info; + + public LicenseInfo? TryParse( string licenseKey ) + { + this.ParseCount++; + + return this.licenses.GetValueOrDefault( licenseKey ); + } + + /// + /// Mimics the real implementation, which strips whitespace from a pasted key. + /// + public string CleanLicenseString( string licenseKey ) + => new( licenseKey.Where( c => !char.IsWhiteSpace( c ) ).ToArray() ); +} diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/FixedServerVersion.cs b/tests/PostSharp.LicenseServer.Tests/Fakes/FixedServerVersion.cs new file mode 100644 index 0000000..eaf2554 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/Fakes/FixedServerVersion.cs @@ -0,0 +1,14 @@ +using PostSharp.LicenseServer.Licensing; + +namespace PostSharp.LicenseServer.Tests.Fakes; + +/// +/// Reports a fixed PostSharp SDK version, so that the "the license server itself is too old" branch +/// can be reached from a test. +/// +public sealed class FixedServerVersion( Version version ) : ILicenseServerVersion +{ + public FixedServerVersion() : this( new Version( 2025, 1, 5 ) ) { } + + public Version SdkVersion { get; } = version; +} diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs b/tests/PostSharp.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs new file mode 100644 index 0000000..fbc938b --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs @@ -0,0 +1,46 @@ +using System.Collections.Concurrent; +using PostSharp.LicenseServer.Email; + +namespace PostSharp.LicenseServer.Tests.Fakes; + +/// +/// An that keeps the messages in memory instead of sending them, so that +/// tests can assert on what the license server would have notified the administrator about. +/// +public sealed class InMemoryEmailSender : IEmailSender +{ + private readonly ConcurrentQueue sent = new(); + + /// + /// Gets or sets an exception to throw instead of recording the message, to verify that a broken + /// SMTP server never denies a developer their license. + /// + public Exception? ThrowOnSend { get; set; } + + /// + /// Gets the messages recorded so far, in the order they were sent. + /// + public IReadOnlyList Sent => this.sent.ToArray(); + + public EmailMessage? Last => this.Sent.LastOrDefault(); + + public Task SendAsync( EmailMessage message, CancellationToken cancellationToken = default ) + { + if ( this.ThrowOnSend != null ) + { + throw this.ThrowOnSend; + } + + this.sent.Enqueue( message ); + + return Task.CompletedTask; + } + + public void Clear() => this.sent.Clear(); + + /// + /// Returns the messages whose subject contains the given text. + /// + public IReadOnlyList WithSubject( string substring ) + => this.Sent.Where( m => m.Subject.Contains( substring, StringComparison.OrdinalIgnoreCase ) ).ToArray(); +} diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs b/tests/PostSharp.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs new file mode 100644 index 0000000..b80972f --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs @@ -0,0 +1,14 @@ +using PostSharp.LicenseServer.Locking; + +namespace PostSharp.LicenseServer.Tests.Fakes; + +/// +/// Never grants the lock, so that the "service overloaded" path can be exercised. +/// +public sealed class NeverAcquiringLeaseLock : ILeaseLock +{ + public ValueTask TryAcquireAsync( + TimeSpan timeout, + CancellationToken cancellationToken = default ) + => ValueTask.FromResult( null ); +} diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs b/tests/PostSharp.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs new file mode 100644 index 0000000..25163f6 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs @@ -0,0 +1,32 @@ +using System.Security.Cryptography; +using System.Text; +using PostSharp.LicenseServer.Security; + +namespace PostSharp.LicenseServer.Tests.Fakes; + +/// +/// Signs deterministically and keeps every payload it was asked to sign, so that tests can assert on +/// what exactly goes into the audit signature chain. +/// +public sealed class RecordingLeaseSigner : ILeaseSigner +{ + private static readonly byte[] key = Encoding.UTF8.GetBytes( "test-audit-signing-key" ); + + private readonly List payloads = []; + + /// + /// Gets the payloads signed so far, in order. + /// + public IReadOnlyList Payloads => this.payloads; + + public string? LastPayload => this.payloads.Count == 0 ? null : this.payloads[^1]; + + public string Sign( string payload ) + { + this.payloads.Add( payload ); + + return Convert.ToBase64String( HMACSHA256.HashData( key, Encoding.UTF8.GetBytes( payload ) ) ); + } + + public void Clear() => this.payloads.Clear(); +} diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs b/tests/PostSharp.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs new file mode 100644 index 0000000..523c53f --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs @@ -0,0 +1,14 @@ +using System.Text; +using PostSharp.LicenseServer.Security; + +namespace PostSharp.LicenseServer.Tests.Fakes; + +/// +/// Supplies a constant audit signing key. +/// +public sealed class StaticAuditKeyProvider : IAuditKeyProvider +{ + private readonly byte[] key = Encoding.UTF8.GetBytes( "constant-test-key-for-audit-hmac" ); + + public byte[] GetKey() => this.key; +} diff --git a/tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs b/tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs new file mode 100644 index 0000000..f73da15 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs @@ -0,0 +1,107 @@ +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using PostSharp.LicenseServer.Data; +using PostSharp.LicenseServer.Licensing; +using PostSharp.LicenseServer.Options; +using PostSharp.LicenseServer.Services; +using PostSharp.LicenseServer.Tests.Fakes; + +namespace PostSharp.LicenseServer.Tests.Infrastructure; + +/// +/// A license server wired up for a test: a real and +/// over an in-memory database, with the license parser, the +/// clock, the signer and the email sender replaced by test doubles. +/// +public sealed class LicenseServerTestContext : IAsyncDisposable +{ + private readonly SqliteDatabaseFixture fixture; + private readonly LicenseServerDbContext db; + + private LicenseServerTestContext( + SqliteDatabaseFixture fixture, + LicenseServerDbContext db, + LicenseServerOptions options ) + { + this.fixture = fixture; + this.db = db; + this.Options = options; + this.LicenseParser = new FakeLicenseParser(); + this.EmailSender = new InMemoryEmailSender(); + this.Signer = new RecordingLeaseSigner(); + this.ServerVersion = new FixedServerVersion(); + + this.Repository = new LeaseRepository( + db, + Microsoft.Extensions.Options.Options.Create( options ), + this.LicenseParser, + this.Signer ); + + this.LeaseService = new LeaseService( + this.Repository, + Microsoft.Extensions.Options.Options.Create( options ), + this.LicenseParser, + this.ServerVersion, + this.EmailSender, + NullLogger.Instance ); + } + + public LicenseServerOptions Options { get; } + + public FakeLicenseParser LicenseParser { get; } + + public InMemoryEmailSender EmailSender { get; } + + public RecordingLeaseSigner Signer { get; } + + public FixedServerVersion ServerVersion { get; } + + public LeaseRepository Repository { get; } + + public LeaseService LeaseService { get; } + + public LicenseServerDbContext Db => this.db; + + /// + /// Creates a context over a database that is private to the calling test. Creating an in-memory + /// SQLite database takes well under a millisecond, so there is no reason to share one between + /// tests and then have to reset it. + /// + public static async Task CreateAsync( Action? configure = null ) + { + LicenseServerOptions options = new() + { + MachinesPerUser = 2, + NewLeaseDays = 3, + MinLeaseDays = 1, + GracePeriodWarningDays = 1, + GracePeriodWarningEmailTo = "admin@example.com", + DeniedRequestEmailTo = "admin@example.com" + }; + + configure?.Invoke( options ); + + SqliteDatabaseFixture fixture = await SqliteDatabaseFixture.CreateAsync(); + + return new LicenseServerTestContext( fixture, fixture.CreateContext(), options ); + } + + /// + /// Returns a repository over a fresh unit of work, sharing the same database. Use this to assert + /// on what was actually persisted, rather than on what the change tracker remembers. + /// + public LeaseRepository CreateFreshRepository() + => new( + this.fixture.CreateContext(), + Microsoft.Extensions.Options.Options.Create( this.Options ), + this.LicenseParser, + this.Signer ); + + public LicenseServerDbContext CreateFreshContext() => this.fixture.CreateContext(); + + public async ValueTask DisposeAsync() + { + await this.db.DisposeAsync(); + await this.fixture.DisposeAsync(); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs b/tests/PostSharp.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs new file mode 100644 index 0000000..8621560 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs @@ -0,0 +1,49 @@ +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using PostSharp.LicenseServer.Data; + +namespace PostSharp.LicenseServer.Tests.Infrastructure; + +/// +/// An in-memory SQLite database, created from the EF Core model, that behaves like a real relational +/// database: foreign keys, transactions, and server-side query translation all apply. +/// +/// +/// A SQLite in-memory database lives exactly as long as a connection to it is open, so this fixture +/// holds one connection for its whole lifetime and hands out contexts that share it. Each context is +/// a separate unit of work with its own change tracker, which is what lets a test tell a pending +/// lease apart from a committed one. +/// +public sealed class SqliteDatabaseFixture : IAsyncDisposable +{ + private readonly SqliteConnection connection; + + private SqliteDatabaseFixture( SqliteConnection connection ) + { + this.connection = connection; + } + + public SqliteConnection Connection => this.connection; + + public static async Task CreateAsync() + { + SqliteConnection connection = new( "DataSource=:memory:" ); + await connection.OpenAsync(); + + SqliteDatabaseFixture fixture = new( connection ); + + await using LicenseServerDbContext context = fixture.CreateContext(); + await context.Database.EnsureCreatedAsync(); + + return fixture; + } + + public LicenseServerDbContext CreateContext() + => new( + new DbContextOptionsBuilder() + .UseSqlite( this.connection ) + .EnableSensitiveDataLogging() + .Options ); + + public async ValueTask DisposeAsync() => await this.connection.DisposeAsync(); +} diff --git a/tests/PostSharp.LicenseServer.Tests/Infrastructure/TestData.cs b/tests/PostSharp.LicenseServer.Tests/Infrastructure/TestData.cs new file mode 100644 index 0000000..fa34219 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/Infrastructure/TestData.cs @@ -0,0 +1,255 @@ +using PostSharp.LicenseServer.Licensing; + +namespace PostSharp.LicenseServer.Tests.Infrastructure; + +/// +/// Fixed points in time used by the tests. +/// +public static class TestClock +{ + /// + /// A Monday, at a whole second. + /// + /// + /// Monday exercises the weekday-label branch of the usage graph. Whole seconds keep the tests + /// independent of the 1/300-second rounding of the SQL datetime type. + /// + public static readonly DateTime Origin = new( 2026, 1, 5, 9, 0, 0, DateTimeKind.Utc ); + + public static DateTime Days( double days ) => Origin.AddDays( days ); + + public static DateTime Hours( double hours ) => Origin.AddHours( hours ); +} + +/// +/// Builds a license together with the facts the fake parser will report for its key. +/// +public sealed class LicenseBuilder +{ + private int licenseId = 1; + private int? userNumber = 5; + private int priority; + private string product = "Ultimate"; + private string licenseType = "PerUser"; + private DateTime? validTo; + private DateTime? subscriptionEndDate; + private Version minPostSharpVersion = new( 1, 0, 0 ); + private int graceDays = 30; + private int gracePercent = 20; + private bool isLicenseServerEligible = true; + private DateTime? graceStartTime; + + public static LicenseBuilder Default() => new(); + + public LicenseBuilder WithLicenseId( int value ) + { + this.licenseId = value; + + return this; + } + + /// + /// Sets the number of concurrent users, or null for an unlimited license. + /// + public LicenseBuilder WithUsers( int? value ) + { + this.userNumber = value; + + return this; + } + + public LicenseBuilder WithPriority( int value ) + { + this.priority = value; + + return this; + } + + public LicenseBuilder WithProduct( string value ) + { + this.product = value; + + return this; + } + + public LicenseBuilder WithLicenseType( string value ) + { + this.licenseType = value; + + return this; + } + + public LicenseBuilder WithValidTo( DateTime? value ) + { + this.validTo = value; + + return this; + } + + public LicenseBuilder WithSubscriptionEndDate( DateTime? value ) + { + this.subscriptionEndDate = value; + + return this; + } + + public LicenseBuilder WithMinPostSharpVersion( Version value ) + { + this.minPostSharpVersion = value; + + return this; + } + + public LicenseBuilder WithGraceDays( int value ) + { + this.graceDays = value; + + return this; + } + + public LicenseBuilder WithGracePercent( int value ) + { + this.gracePercent = value; + + return this; + } + + public LicenseBuilder NotLicenseServerEligible() + { + this.isLicenseServerEligible = false; + + return this; + } + + public LicenseBuilder WithGraceStartTime( DateTime? value ) + { + this.graceStartTime = value; + + return this; + } + + public LicenseInfo BuildInfo() + => new() + { + LicenseId = this.licenseId, + Product = this.product, + LicenseType = this.licenseType, + UserNumber = this.userNumber, + ValidTo = this.validTo, + SubscriptionEndDate = this.subscriptionEndDate, + MinPostSharpVersion = this.minPostSharpVersion, + GraceDays = this.graceDays, + GracePercent = this.gracePercent, + IsLicenseServerEligible = this.isLicenseServerEligible, + LicenseTypeName = this.licenseType, + ProductName = this.product + }; + + /// + /// Adds the license to the database and registers its key with the fake parser. + /// + public License AddTo( LicenseServerTestContext context ) + { + string key = $"FAKE-KEY-{this.licenseId}"; + + License license = new() + { + LicenseId = this.licenseId, + LicenseKey = key, + ProductCode = this.product, + Priority = this.priority, + CreatedOn = TestClock.Origin, + GraceStartTime = this.graceStartTime + }; + + context.LicenseParser.Register( key, this.BuildInfo() ); + context.Db.Licenses.Add( license ); + context.Db.SaveChanges(); + + return license; + } +} + +/// +/// Builds a lease directly, bypassing the allocation rules, to set up a starting state. +/// +public sealed class LeaseBuilder +{ + private readonly License license; + private string userName = "alice"; + private string machine = "desktop-1"; + private DateTime startTime = TestClock.Origin; + private DateTime endTime = TestClock.Origin.AddDays( 3 ); + private bool grace; + + private LeaseBuilder( License license ) + { + this.license = license; + } + + public static LeaseBuilder For( License license ) => new( license ); + + public LeaseBuilder User( string value ) + { + this.userName = value; + + return this; + } + + public LeaseBuilder Machine( string value ) + { + this.machine = value; + + return this; + } + + public LeaseBuilder From( DateTime value ) + { + this.startTime = value; + + return this; + } + + public LeaseBuilder To( DateTime value ) + { + this.endTime = value; + + return this; + } + + public LeaseBuilder Lasting( double days ) + { + this.endTime = this.startTime.AddDays( days ); + + return this; + } + + public LeaseBuilder InGrace() + { + this.grace = true; + + return this; + } + + public Lease AddTo( LicenseServerTestContext context ) + { + Lease lease = new() + { + License = this.license, + LicenseId = this.license.LicenseId, + UserName = this.userName, + Machine = this.machine, + AuthenticatedUser = this.userName, + StartTime = this.startTime, + EndTime = this.endTime, + Grace = this.grace + }; + + lease.HMAC = context.Repository.GetSignature( lease ); + + context.Db.Leases.Add( lease ); + context.Db.SaveChanges(); + + return lease; + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseAuditLineTests.cs b/tests/PostSharp.LicenseServer.Tests/LeaseAuditLineTests.cs new file mode 100644 index 0000000..749bab4 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/LeaseAuditLineTests.cs @@ -0,0 +1,126 @@ +using System.Globalization; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// The audit-log line is a serialization contract: exported files are archived by customers and +/// compared across years, so the format is pinned here against literal expected strings rather than +/// against a re-implementation of the same logic. +/// +public sealed class LeaseAuditLineTests +{ + // Hashes produced by CryptoUtilities.ComputeStringHash64, which is what anonymises the names. + private const string aliceHash = "f5cb4b18b2e28463"; + private const string desktop1Hash = "da7251349d0ffa49"; + + private static Lease CreateLease() + => new() + { + LeaseId = 42, + OverwrittenLeaseId = 41, + LicenseId = 7, + StartTime = new DateTime( 2026, 1, 5, 9, 0, 0, DateTimeKind.Utc ), + EndTime = new DateTime( 2026, 1, 8, 9, 0, 0, DateTimeKind.Utc ), + UserName = "alice", + Machine = "desktop-1", + AuthenticatedUser = "DOMAIN\\alice", + HMAC = "SIGNATURE==" + }; + + [Fact] + public void Write_WithoutHmac_ProducesTheExpectedLine() + { + Assert.Equal( + $"42;41;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;{desktop1Hash};{aliceHash}", + CreateLease().ToAuditLine( false ) ); + } + + [Fact] + public void Write_WithHmac_AppendsTheSignature() + { + Assert.Equal( + $"42;41;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;{desktop1Hash};{aliceHash};SIGNATURE==", + CreateLease().ToAuditLine( true ) ); + } + + [Fact] + public void Write_NoOverwrittenLease_LeavesTheFieldEmpty() + { + Lease lease = CreateLease(); + lease.OverwrittenLeaseId = null; + + Assert.Equal( + $"42;;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;{desktop1Hash};{aliceHash}", + lease.ToAuditLine( false ) ); + } + + [Fact] + public void Write_NeverDisclosesTheUserOrMachineName() + { + string line = CreateLease().ToAuditLine( true ); + + Assert.DoesNotContain( "alice", line, StringComparison.OrdinalIgnoreCase ); + Assert.DoesNotContain( "desktop", line, StringComparison.OrdinalIgnoreCase ); + } + + /// + /// Timestamps must be absolute. The legacy implementation serialized values whose + /// was -- which is what SQL + /// Server returns -- in a mode that treated them as local time and shifted them, so the exported + /// file depended on the time zone of the machine that produced it. + /// + [Fact] + public void Write_IsIndependentOfTheServerTimeZone() + { + string expected = CreateLease().ToAuditLine( false ); + + foreach ( string timeZoneId in new[] { "UTC", "Pacific Standard Time", "Tokyo Standard Time" } ) + { + if ( !TryFindTimeZone( timeZoneId, out _ ) ) + { + continue; + } + + // The value carries its own UTC offset, so no ambient time zone can change it. + Assert.Equal( expected, CreateLease().ToAuditLine( false ) ); + } + + Assert.EndsWith( "Z", expected.Split( ';' )[3], StringComparison.Ordinal ); + Assert.EndsWith( "Z", expected.Split( ';' )[4], StringComparison.Ordinal ); + } + + [Fact] + public void Write_UsesInvariantFormatting() + { + CultureInfo original = CultureInfo.CurrentCulture; + + try + { + CultureInfo.CurrentCulture = new CultureInfo( "de-DE" ); + + Assert.Equal( + $"42;41;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;{desktop1Hash};{aliceHash}", + CreateLease().ToAuditLine( false ) ); + } + finally + { + CultureInfo.CurrentCulture = original; + } + } + + private static bool TryFindTimeZone( string id, out TimeZoneInfo? timeZone ) + { + try + { + timeZone = TimeZoneInfo.FindSystemTimeZoneById( id ); + + return true; + } + catch ( TimeZoneNotFoundException ) + { + timeZone = null; + + return false; + } + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/OpenLeasesTests.cs b/tests/PostSharp.LicenseServer.Tests/OpenLeasesTests.cs new file mode 100644 index 0000000..9ca3b80 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/OpenLeasesTests.cs @@ -0,0 +1,102 @@ +using Microsoft.EntityFrameworkCore; +using PostSharp.LicenseServer.Tests.Infrastructure; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// Leases are never updated in place: prolonging or cancelling one inserts a replacement that points +/// back at it. "Open" leases are those nothing points back at. +/// +public sealed class OpenLeasesTests +{ + [Fact] + public async Task OpenLeases_LeaseNeverReplaced_IsOpen() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease lease = LeaseBuilder.For( license ).AddTo( context ); + + Assert.Equal( [lease.LeaseId], await context.Db.OpenLeases.Select( l => l.LeaseId ).ToListAsync() ); + } + + [Fact] + public async Task OpenLeases_ReplacedLease_IsNotOpen() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease original = LeaseBuilder.For( license ).AddTo( context ); + + Lease? replacement = context.Repository.ProlongLease( original, "alice", TestClock.Days( 2.5 ) ); + await context.Repository.SaveChangesAsync(); + + Assert.NotNull( replacement ); + Assert.Equal( [replacement.LeaseId], await context.Db.OpenLeases.Select( l => l.LeaseId ).ToListAsync() ); + } + + [Fact] + public async Task OpenLeases_ChainOfReplacements_LeavesOnlyTheLast() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + Lease current = LeaseBuilder.For( license ).AddTo( context ); + + for ( int i = 1; i <= 3; i++ ) + { + current = context.Repository.ProlongLease( current, "alice", TestClock.Days( i * 2.5 ) )!; + await context.Repository.SaveChangesAsync(); + } + + Assert.Equal( 4, await context.Db.Leases.CountAsync() ); + Assert.Equal( [current.LeaseId], await context.Db.OpenLeases.Select( l => l.LeaseId ).ToListAsync() ); + } + + /// + /// Nothing in the schema prevents two leases from replacing the same lease. The legacy + /// left-join query returned such a lease once per replacement; an anti-join returns it once. + /// + [Fact] + public async Task OpenLeases_LeaseReplacedTwice_IsStillListedOnlyOnce() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease original = LeaseBuilder.For( license ).AddTo( context ); + + Lease survivor = LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); + + foreach ( int _ in Enumerable.Range( 0, 2 ) ) + { + context.Db.Leases.Add( + new Lease + { + LicenseId = license.LicenseId, + OverwrittenLeaseId = original.LeaseId, + UserName = original.UserName, + Machine = original.Machine, + AuthenticatedUser = original.AuthenticatedUser, + StartTime = original.StartTime, + EndTime = TestClock.Days( 4 ), + HMAC = "x" + } ); + } + + await context.Db.SaveChangesAsync(); + + List open = await context.Db.OpenLeases.Select( l => l.LeaseId ).ToListAsync(); + + Assert.DoesNotContain( original.LeaseId, open ); + Assert.Equal( open.Count, open.Distinct().Count() ); + Assert.Contains( survivor.LeaseId, open ); + } + + [Fact] + public async Task OpenLeases_TranslatesToSqlAsAnAntiJoin() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + string sql = context.Db.OpenLeases.ToQueryString(); + + // Proves the filter runs in the database rather than after loading every lease. + Assert.Contains( "NOT EXISTS", sql, StringComparison.OrdinalIgnoreCase ); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj b/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj new file mode 100644 index 0000000..5694078 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj @@ -0,0 +1,29 @@ + + + + net10.0 + PostSharp.LicenseServer.Tests + false + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/PostSharp.LicenseServer.Tests/SeatCountingTests.cs b/tests/PostSharp.LicenseServer.Tests/SeatCountingTests.cs new file mode 100644 index 0000000..edf73c1 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/SeatCountingTests.cs @@ -0,0 +1,51 @@ +using PostSharp.LicenseServer.Data; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// The rounding rule that decides how many seats a set of users consumes. This arithmetic used to +/// live inside a SQL GROUP BY, where it could not be tested at all. +/// +public sealed class SeatCountingTests +{ + [Fact] + public void CountSeats_NoUsers_ReturnsZero() + => Assert.Equal( 0, SeatCounter.CountSeats( [], 2 ) ); + + [Theory] + // One user, N machines, two machines per seat. + [InlineData( 1, 1 )] + [InlineData( 2, 1 )] + [InlineData( 3, 2 )] + [InlineData( 4, 2 )] + [InlineData( 5, 3 )] + public void CountSeats_SingleUser_RoundsMachinesUp( int machines, int expectedSeats ) + => Assert.Equal( expectedSeats, SeatCounter.CountSeats( [machines], 2 ) ); + + [Fact] + public void CountSeats_TwoUsersOneMachineEach_ConsumesTwoSeats() + => Assert.Equal( 2, SeatCounter.CountSeats( [1, 1], 2 ) ); + + [Fact] + public void CountSeats_TwoUsersTwoMachinesEach_ConsumesTwoSeats() + => Assert.Equal( 2, SeatCounter.CountSeats( [2, 2], 2 ) ); + + [Fact] + public void CountSeats_RoundsUpPerUserNotInTotal() + { + // Three machines for one user and one for another is 3 seats, not ceil(4/2) == 2. + Assert.Equal( 3, SeatCounter.CountSeats( [3, 1], 2 ) ); + } + + [Theory] + [InlineData( 1, 3, 3 )] + [InlineData( 3, 3, 1 )] + [InlineData( 3, 4, 2 )] + [InlineData( 3, 7, 3 )] + public void CountSeats_HonoursMachinesPerUser( int machinesPerUser, int machines, int expectedSeats ) + => Assert.Equal( expectedSeats, SeatCounter.CountSeats( [machines], machinesPerUser ) ); + + [Fact] + public void CountSeats_MachinesPerUserBelowOne_Throws() + => Assert.Throws( () => SeatCounter.CountSeats( [1], 0 ) ); +} From 864b26f7e72a0cef58b54924a0393d9cc453ed28 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 09:16:40 +0200 Subject: [PATCH 04/44] Cover the licensing rules with tests Add 130 tests over the lease allocation rules, the audit log and the configuration, running against an in-memory SQLite database so the suite needs no SQL Server. Two invariants that a reader of the diff could not see are pinned deliberately, and both were checked by reintroducing the defect and confirming that only the intended tests fail: - The foreign keys of a lease must be assigned before it is signed, because EF Core populates them later than LINQ to SQL did. - Close must sort before Open at the same instant, which is what the values of LeaseCountingPointKind encode, otherwise a machine handed from one lease to the next is briefly counted twice. Co-Authored-By: Claude Opus 5 --- .../CancelLeaseTests.cs | 119 ++++++++ .../ConfigurationTests.cs | 141 +++++++++ .../GetActiveLeadsTests.cs | 139 +++++++++ .../LeaseAllocationTests.cs | 288 ++++++++++++++++++ .../LeaseCountingPointsTests.cs | 181 +++++++++++ .../LeaseSignatureTests.cs | 199 ++++++++++++ .../LicenseValidationTests.cs | 172 +++++++++++ 7 files changed, 1239 insertions(+) create mode 100644 tests/PostSharp.LicenseServer.Tests/CancelLeaseTests.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/ConfigurationTests.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/GetActiveLeadsTests.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/LeaseAllocationTests.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/LeaseCountingPointsTests.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/LeaseSignatureTests.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/LicenseValidationTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/CancelLeaseTests.cs b/tests/PostSharp.LicenseServer.Tests/CancelLeaseTests.cs new file mode 100644 index 0000000..17ca36c --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/CancelLeaseTests.cs @@ -0,0 +1,119 @@ +using Microsoft.EntityFrameworkCore; +using PostSharp.LicenseServer.Tests.Infrastructure; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// An administrator can end a lease early, which inserts a replacement ending now rather than +/// deleting anything. +/// +public sealed class CancelLeaseTests +{ + [Fact] + public async Task CancelLease_InsertsAReplacementEndingNow() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease original = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + + context.Repository.CancelLease( original, "DOMAIN\\admin", TestClock.Days( 1 ) ); + await context.Repository.SaveChangesAsync(); + + Lease replacement = await context.Db.Leases.SingleAsync( l => l.LeaseId != original.LeaseId ); + + Assert.Equal( original.LeaseId, replacement.OverwrittenLeaseId ); + Assert.Equal( TestClock.Days( 1 ), replacement.EndTime ); + Assert.Equal( original.StartTime, replacement.StartTime ); + Assert.Equal( original.UserName, replacement.UserName ); + Assert.Equal( original.Machine, replacement.Machine ); + Assert.Equal( "DOMAIN\\admin", replacement.AuthenticatedUser ); + } + + [Fact] + public async Task CancelLease_KeepsTheOriginalRow() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease original = LeaseBuilder.For( license ).AddTo( context ); + + context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); + await context.Repository.SaveChangesAsync(); + + // The audit log is append-only. + Assert.Equal( 2, await context.Db.Leases.CountAsync() ); + Assert.NotNull( await context.Db.Leases.FindAsync( original.LeaseId ) ); + } + + [Fact] + public async Task CancelLease_ReleasesTheSeat() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease original = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + + Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 2 ) ) ); + + context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); + await context.Repository.SaveChangesAsync(); + + Assert.Equal( 0, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 2 ) ) ); + } + + /// + /// Cancelling skips the end-time adjustment applied to new leases. Without that, a lease ending + /// "now" would be rejected for not extending beyond the current moment, and cancelling would + /// silently do nothing. + /// + [Fact] + public async Task CancelLease_IsNotRejectedForEndingImmediately() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithValidTo( TestClock.Days( 2 ) ).AddTo( context ); + Lease original = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 2 ).AddTo( context ); + + context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); + await context.Repository.SaveChangesAsync(); + + Lease replacement = await context.Db.Leases.SingleAsync( l => l.LeaseId != original.LeaseId ); + Assert.Equal( TestClock.Days( 1 ), replacement.EndTime ); + } + + [Fact] + public async Task CancelLease_SignsTheReplacement() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease original = LeaseBuilder.For( license ).AddTo( context ); + + context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); + await context.Repository.SaveChangesAsync(); + + Lease replacement = await context.Db.Leases.SingleAsync( l => l.LeaseId != original.LeaseId ); + Assert.False( string.IsNullOrEmpty( replacement.HMAC ) ); + } + + [Fact] + public async Task CancelLease_ThenRequestAgain_GrantsAFreshLease() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease original = LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); + + context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); + await context.Repository.SaveChangesAsync(); + + Lease? lease = await context.LeaseService.GetLeaseAsync( + new Version( 2025, 1, 0 ), + null, + "desktop-1", + "alice", + "alice", + TestClock.Days( 2 ), + [], + [license] ); + + Assert.NotNull( lease ); + Assert.NotEqual( original.LeaseId, lease.LeaseId ); + Assert.Equal( TestClock.Days( 2 ), lease.StartTime ); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/ConfigurationTests.cs b/tests/PostSharp.LicenseServer.Tests/ConfigurationTests.cs new file mode 100644 index 0000000..0a0df7b --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/ConfigurationTests.cs @@ -0,0 +1,141 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Time.Testing; +using PostSharp.LicenseServer.Licensing; +using PostSharp.LicenseServer.Options; +using PostSharp.LicenseServer.Time; +using PostSharp.LicenseServer.Tests.Fakes; +using PostSharp.LicenseServer.Tests.Infrastructure; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// Settings validation, the accelerated clock and the license parse cache. +/// +public sealed class ConfigurationTests +{ + private static ValidateOptionsResult Validate( Action configure ) + { + LicenseServerOptions options = new(); + configure( options ); + + return new LicenseServerOptionsValidator().Validate( null, options ); + } + + [Fact] + public void Defaults_AreValid() => Assert.True( Validate( _ => { } ).Succeeded ); + + /// + /// A renewal time that is not before the end of the lease makes the client renew on every single + /// request. The legacy configuration documented this constraint but never enforced it. + /// + [Theory] + [InlineData( 3, 3 )] + [InlineData( 5, 3 )] + public void MinLeaseDaysNotBelowNewLeaseDays_IsRejected( int minLeaseDays, int newLeaseDays ) + { + ValidateOptionsResult result = Validate( + o => + { + o.MinLeaseDays = minLeaseDays; + o.NewLeaseDays = newLeaseDays; + } ); + + Assert.True( result.Failed ); + Assert.Contains( result.Failures!, f => f.Contains( "MinLeaseDays", StringComparison.Ordinal ) ); + } + + [Fact] + public void NegativeTimeAcceleration_IsRejected() + => Assert.True( Validate( o => o.TimeAcceleration = -1 ).Failed ); + + [Fact] + public void NonBase64AuditKey_IsRejected() + => Assert.True( Validate( o => o.AuditHmacKey = "not base64 !!!" ).Failed ); + + [Fact] + public void Base64AuditKey_IsAccepted() + => Assert.True( Validate( o => o.AuditHmacKey = Convert.ToBase64String( new byte[32] ) ).Succeeded ); + + [Theory] + [InlineData( 0 )] + [InlineData( -1 )] + public void MachinesPerUserBelowOne_FailsDataAnnotations( int value ) + { + LicenseServerOptions options = new() { MachinesPerUser = value }; + List results = []; + + Assert.False( + Validator.TryValidateObject( options, new ValidationContext( options ), results, true ) ); + } + + [Fact] + public void MutexTimeout_IsExposedAsATimeSpan() + => Assert.Equal( TimeSpan.FromSeconds( 45 ), new LicenseServerOptions { MutexTimeout = 45 }.MutexTimeoutSpan ); + + [Fact] + public void AcceleratedTimeProvider_MultipliesElapsedTime() + { + FakeTimeProvider inner = new( TestClock.Origin ); + AcceleratedTimeProvider accelerated = new( inner, 1440 ); + + inner.Advance( TimeSpan.FromSeconds( 1 ) ); + + // A second of real time becomes a day of simulated time. + Assert.Equal( TestClock.Origin.AddMinutes( 24 ), accelerated.GetUtcNow().UtcDateTime ); + } + + [Fact] + public void AcceleratedTimeProvider_StartsAtTheCurrentTime() + { + FakeTimeProvider inner = new( TestClock.Origin ); + + Assert.Equal( TestClock.Origin, new AcceleratedTimeProvider( inner, 100 ).GetUtcNow().UtcDateTime ); + } + + [Theory] + [InlineData( 0 )] + [InlineData( -2 )] + public void AcceleratedTimeProvider_RejectsNonPositiveAcceleration( double acceleration ) + => Assert.Throws( + () => new AcceleratedTimeProvider( TimeProvider.System, acceleration ) ); + + [Fact] + public void CachingLicenseParser_ParsesEachKeyOnlyOnce() + { + FakeLicenseParser inner = new(); + inner.Register( "KEY", LicenseBuilder.Default().BuildInfo() ); + + CachingLicenseParser cache = new( inner ); + + Assert.NotNull( cache.TryParse( "KEY" ) ); + Assert.NotNull( cache.TryParse( "KEY" ) ); + Assert.Equal( 1, inner.ParseCount ); + } + + /// + /// The legacy cache returned before storing a failure, so an invalid key was re-parsed on every + /// request and on every render of the dashboard. + /// + [Fact] + public void CachingLicenseParser_RemembersThatAKeyIsInvalid() + { + FakeLicenseParser inner = new(); + CachingLicenseParser cache = new( inner ); + + Assert.Null( cache.TryParse( "BAD" ) ); + Assert.Null( cache.TryParse( "BAD" ) ); + Assert.Equal( 1, inner.ParseCount ); + } + + [Theory] + [InlineData( "" )] + [InlineData( " " )] + public void CachingLicenseParser_BlankKey_IsRejectedWithoutParsing( string key ) + { + FakeLicenseParser inner = new(); + + Assert.Null( new CachingLicenseParser( inner ).TryParse( key ) ); + Assert.Equal( 0, inner.ParseCount ); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/GetActiveLeadsTests.cs b/tests/PostSharp.LicenseServer.Tests/GetActiveLeadsTests.cs new file mode 100644 index 0000000..dc94a21 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/GetActiveLeadsTests.cs @@ -0,0 +1,139 @@ +using PostSharp.LicenseServer.Tests.Infrastructure; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// How many seats of a license are in use at a given moment. +/// +public sealed class GetActiveLeadsTests +{ + [Fact] + public async Task GetActiveLeads_NoLeases_ReturnsZero() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + Assert.Equal( 0, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + } + + [Fact] + public async Task GetActiveLeads_OneUserOneMachine_ReturnsOne() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); + + Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + } + + [Fact] + public async Task GetActiveLeads_OneUserTwoMachines_StillReturnsOne() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).AddTo( context ); + + Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + } + + [Fact] + public async Task GetActiveLeads_OneUserThreeMachines_ReturnsTwo() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + foreach ( string machine in new[] { "desktop-1", "laptop-1", "desktop-2" } ) + { + LeaseBuilder.For( license ).User( "alice" ).Machine( machine ).AddTo( context ); + } + + Assert.Equal( 2, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + } + + [Fact] + public async Task GetActiveLeads_TwoUsers_ReturnsTwo() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).AddTo( context ); + LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); + + Assert.Equal( 2, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + } + + [Fact] + public async Task GetActiveLeads_LeaseStartingExactlyNow_IsCounted() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + + Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Origin ) ); + } + + [Fact] + public async Task GetActiveLeads_LeaseEndingExactlyNow_IsNotCounted() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + + Assert.Equal( 0, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 3 ) ) ); + } + + [Fact] + public async Task GetActiveLeads_OtherLicense_IsNotCounted() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License first = LicenseBuilder.Default().WithLicenseId( 1 ).AddTo( context ); + License second = LicenseBuilder.Default().WithLicenseId( 2 ).AddTo( context ); + + LeaseBuilder.For( second ).AddTo( context ); + + Assert.Equal( 0, context.Repository.GetActiveLeads( first.LicenseId, TestClock.Days( 1 ) ) ); + Assert.Equal( 1, context.Repository.GetActiveLeads( second.LicenseId, TestClock.Days( 1 ) ) ); + } + + [Fact] + public async Task GetActiveLeads_ReplacedLease_IsNotCounted() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease original = LeaseBuilder.For( license ).AddTo( context ); + + context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); + await context.Repository.SaveChangesAsync(); + + Assert.Equal( 0, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 2 ) ) ); + } + + /// + /// SQL Server's default collation is case-insensitive. The in-memory database is configured to + /// match, so that a test cannot pass here and fail in production. + /// + [Fact] + public async Task GetActiveLeads_UserNameCasingDiffers_CountsAsOneUser() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); + LeaseBuilder.For( license ).User( "ALICE" ).Machine( "laptop-1" ).AddTo( context ); + + Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + } + + [Fact] + public async Task GetActiveLeads_HonoursMachinesPerUser() + { + await using LicenseServerTestContext context = + await LicenseServerTestContext.CreateAsync( o => o.MachinesPerUser = 1 ); + + License license = LicenseBuilder.Default().AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).AddTo( context ); + + // With one machine per seat, the same user on two machines consumes two seats. + Assert.Equal( 2, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseAllocationTests.cs b/tests/PostSharp.LicenseServer.Tests/LeaseAllocationTests.cs new file mode 100644 index 0000000..eb75cce --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/LeaseAllocationTests.cs @@ -0,0 +1,288 @@ +using Microsoft.EntityFrameworkCore; +using PostSharp.LicenseServer.Tests.Infrastructure; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// The rules that decide whether a developer gets a license: reuse what they hold, grant spare +/// capacity, or fall back on the grace period before denying the request. +/// +public sealed class LeaseAllocationTests +{ + private static readonly Version currentVersion = new( 2025, 1, 0 ); + + private static Task RequestAsync( + LicenseServerTestContext context, + License[] licenses, + string user = "alice", + string machine = "desktop-1", + DateTime? now = null, + Dictionary? errors = null ) + => context.LeaseService.GetLeaseAsync( + currentVersion, + null, + machine, + user, + user, + now ?? TestClock.Origin, + errors ?? [], + licenses ); + + [Fact] + public async Task GetLease_NoExistingLease_GrantsANewOne() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 5 ).AddTo( context ); + + Lease? lease = await RequestAsync( context, [license] ); + + Assert.NotNull( lease ); + Assert.Equal( "alice", lease.UserName ); + Assert.Equal( "desktop-1", lease.Machine ); + Assert.Equal( TestClock.Origin, lease.StartTime ); + Assert.Equal( TestClock.Days( 3 ), lease.EndTime ); + Assert.False( lease.Grace ); + } + + [Fact] + public async Task GetLease_GoodExistingLease_IsReusedWithoutInsertingARow() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease existing = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + + Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); + + Assert.Equal( existing.LeaseId, lease!.LeaseId ); + Assert.Empty( context.Db.ChangeTracker.Entries().Where( e => e.State == EntityState.Added ) ); + } + + [Fact] + public async Task GetLease_LeaseNearingExpiry_IsProlonged() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease existing = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + + // Within MinLeaseDays of the end, so the client is told to renew. + Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 2.5 ) ); + + Assert.NotNull( lease ); + Assert.NotEqual( existing.LeaseId, lease.LeaseId ); + Assert.Equal( existing.LeaseId, lease.OverwrittenLeaseId ); + Assert.Equal( existing.StartTime, lease.StartTime ); + Assert.Equal( TestClock.Days( 5.5 ), lease.EndTime ); + } + + [Fact] + public async Task GetLease_SecondMachineForTheSameUser_DoesNotConsumeASeat() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); + + Lease? lease = await RequestAsync( context, [license], machine: "laptop-1", now: TestClock.Days( 1 ) ); + await context.Repository.SaveChangesAsync(); + + Assert.NotNull( lease ); + Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + } + + [Fact] + public async Task GetLease_ThirdMachineOnAFullLicense_FallsBackToGrace() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).AddTo( context ); + + // A third machine rounds up to a second seat, which this one-seat license does not have. + Lease? lease = await RequestAsync( context, [license], machine: "desktop-2", now: TestClock.Days( 1 ) ); + + Assert.NotNull( lease ); + Assert.True( lease.Grace ); + } + + [Fact] + public async Task GetLease_CapacityAvailable_DoesNotUseGrace() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 5 ).AddTo( context ); + LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); + + Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); + + Assert.False( lease!.Grace ); + Assert.Null( license.GraceStartTime ); + } + + [Fact] + public async Task GetLease_CapacityExhausted_StartsTheGracePeriodAndWarns() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); + LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); + + Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); + + Assert.NotNull( lease ); + Assert.True( lease.Grace ); + Assert.Equal( TestClock.Days( 1 ), license.GraceStartTime ); + + var warning = Assert.Single( context.EmailSender.WithSubject( "WARNING" ) ); + Assert.Equal( "admin@example.com", warning.To ); + Assert.Contains( "capacity of 1 concurrent user", warning.Body, StringComparison.Ordinal ); + } + + [Fact] + public async Task GetLease_WithinGraceLimit_IsGranted() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + // 10 seats, 20 percent grace, so up to 12 are tolerated. + License license = LicenseBuilder.Default().WithUsers( 10 ).WithGracePercent( 20 ) + .WithGraceStartTime( TestClock.Origin ).AddTo( context ); + + for ( int i = 0; i < 11; i++ ) + { + LeaseBuilder.For( license ).User( $"user{i}" ).AddTo( context ); + } + + Lease? lease = await RequestAsync( context, [license], user: "newcomer", now: TestClock.Days( 1 ) ); + + Assert.NotNull( lease ); + Assert.True( lease.Grace ); + } + + [Fact] + public async Task GetLease_AtTheGraceLimit_IsDenied() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 10 ).WithGracePercent( 20 ) + .WithGraceStartTime( TestClock.Origin ).AddTo( context ); + + for ( int i = 0; i < 12; i++ ) + { + LeaseBuilder.For( license ).User( $"user{i}" ).AddTo( context ); + } + + Lease? lease = await RequestAsync( context, [license], user: "newcomer", now: TestClock.Days( 1 ) ); + + Assert.Null( lease ); + } + + [Fact] + public async Task GetLease_GracePeriodOver_IsDenied() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 1 ).WithGraceDays( 30 ) + .WithGraceStartTime( TestClock.Origin ).AddTo( context ); + + LeaseBuilder.For( license ).User( "bob" ).From( TestClock.Days( 40 ) ).Lasting( 3 ).AddTo( context ); + + Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 41 ) ); + + Assert.Null( lease ); + } + + [Fact] + public async Task GetLease_GraceLease_EndsWhenTheGracePeriodEnds() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 1 ).WithGraceDays( 30 ) + .WithGraceStartTime( TestClock.Origin ).AddTo( context ); + + LeaseBuilder.For( license ).User( "bob" ).From( TestClock.Days( 28 ) ).Lasting( 5 ).AddTo( context ); + + Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 29 ) ); + + // A three-day lease would run past the end of the grace period, so it is cut short. + Assert.NotNull( lease ); + Assert.Equal( TestClock.Days( 30 ), lease.EndTime ); + } + + [Fact] + public async Task GetLease_DeniedRequest_NotifiesTheAdministrator() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 1 ).WithGracePercent( 0 ) + .WithGraceStartTime( TestClock.Origin.AddDays( -100 ) ).WithGraceDays( 1 ).AddTo( context ); + + LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); + + Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); + + Assert.Null( lease ); + var denial = Assert.Single( context.EmailSender.WithSubject( "denied" ) ); + Assert.Contains( "alice", denial.Body, StringComparison.Ordinal ); + Assert.Contains( "desktop-1", denial.Body, StringComparison.Ordinal ); + } + + [Fact] + public async Task GetLease_LeaseEndClampedByLicenseExpiry() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithValidTo( TestClock.Days( 1 ) ).AddTo( context ); + + Lease? lease = await RequestAsync( context, [license] ); + + Assert.NotNull( lease ); + Assert.Equal( TestClock.Days( 1 ), lease.EndTime ); + } + + [Fact] + public async Task GetLease_LicenseAlreadyExpired_IsDenied() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithValidTo( TestClock.Days( -1 ) ).AddTo( context ); + + Assert.Null( await RequestAsync( context, [license] ) ); + } + + [Fact] + public async Task GetLease_TriesLicensesInPriorityOrder() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License low = LicenseBuilder.Default().WithLicenseId( 1 ).WithPriority( 10 ).AddTo( context ); + License high = LicenseBuilder.Default().WithLicenseId( 2 ).WithPriority( 0 ).AddTo( context ); + + Lease? lease = await RequestAsync( context, [high, low] ); + + Assert.Equal( high.LicenseId, lease!.LicenseId ); + } + + /// + /// A warning is recorded even when it could not be delivered, so a broken SMTP server cannot + /// turn every subsequent request into another attempt. + /// + [Fact] + public async Task GetLease_WarningEmailFails_StillRecordsThatItWasAttempted() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + context.EmailSender.ThrowOnSend = new InvalidOperationException( "SMTP is down" ); + + License license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); + LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); + + Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); + + Assert.NotNull( lease ); + Assert.Equal( TestClock.Days( 1 ), license.GraceLastWarningTime ); + } + + [Fact] + public async Task GetLease_WarningIsNotRepeatedWithinTheConfiguredInterval() + { + await using LicenseServerTestContext context = + await LicenseServerTestContext.CreateAsync( o => o.GracePeriodWarningDays = 7 ); + + License license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); + LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); + + await RequestAsync( context, [license], user: "alice", now: TestClock.Days( 1 ) ); + await context.Repository.SaveChangesAsync(); + await RequestAsync( context, [license], user: "carol", now: TestClock.Days( 2 ) ); + + Assert.Single( context.EmailSender.WithSubject( "WARNING" ) ); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseCountingPointsTests.cs b/tests/PostSharp.LicenseServer.Tests/LeaseCountingPointsTests.cs new file mode 100644 index 0000000..573f316 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/LeaseCountingPointsTests.cs @@ -0,0 +1,181 @@ +using PostSharp.LicenseServer.Tests.Infrastructure; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// The usage timeline behind the graph: a sequence of lease open and close events carrying the +/// running seat count. +/// +public sealed class LeaseCountingPointsTests +{ + private static List Timeline( LicenseServerTestContext context, License license ) + => context.Repository + .GetLeaseCountingPoints( license.LicenseId, TestClock.Days( -10 ), TestClock.Days( 100 ) ) + .ToList(); + + [Fact] + public async Task GetLeaseCountingPoints_OneLease_OpensThenCloses() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + + List points = Timeline( context, license ); + + Assert.Equal( 2, points.Count ); + Assert.Equal( LeaseCountingPointKind.Open, points[0].Kind ); + Assert.Equal( 1, points[0].LeaseCount ); + Assert.Equal( LeaseCountingPointKind.Close, points[1].Kind ); + Assert.Equal( 0, points[1].LeaseCount ); + } + + [Fact] + public async Task GetLeaseCountingPoints_OneUserTwoMachines_NeverExceedsOneSeat() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).From( TestClock.Origin ).Lasting( 3 ) + .AddTo( context ); + + LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).From( TestClock.Days( 1 ) ).Lasting( 3 ) + .AddTo( context ); + + Assert.Equal( 1, Timeline( context, license ).Max( p => p.LeaseCount ) ); + } + + [Fact] + public async Task GetLeaseCountingPoints_OneUserThreeMachines_ReachesTwoSeats() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + foreach ( string machine in new[] { "desktop-1", "laptop-1", "desktop-2" } ) + { + LeaseBuilder.For( license ).User( "alice" ).Machine( machine ).From( TestClock.Origin ).Lasting( 3 ) + .AddTo( context ); + } + + Assert.Equal( 2, Timeline( context, license ).Max( p => p.LeaseCount ) ); + } + + /// + /// When one lease ends at the exact instant another begins, the machine must be released before + /// it is claimed again. This is what the numeric values of + /// encode, and it is the reason they must not be renumbered. + /// + [Fact] + public async Task GetLeaseCountingPoints_CloseIsProcessedBeforeOpenAtTheSameInstant() + { + await using LicenseServerTestContext context = + await LicenseServerTestContext.CreateAsync( o => o.MachinesPerUser = 1 ); + + License license = LicenseBuilder.Default().AddTo( context ); + + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) + .From( TestClock.Origin ).To( TestClock.Days( 1 ) ).AddTo( context ); + + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) + .From( TestClock.Days( 1 ) ).To( TestClock.Days( 2 ) ).AddTo( context ); + + List points = Timeline( context, license ); + + // With one machine per seat, a transient double-count would show up as 2. + Assert.Equal( 1, points.Max( p => p.LeaseCount ) ); + + LeaseCountingPoint[] atHandover = points.Where( p => p.Time == TestClock.Days( 1 ) ).ToArray(); + Assert.Equal( 2, atHandover.Length ); + Assert.Equal( LeaseCountingPointKind.Close, atHandover[0].Kind ); + Assert.Equal( LeaseCountingPointKind.Open, atHandover[1].Kind ); + } + + [Fact] + public void LeaseCountingPointKind_OrdersCloseBeforeOpen() + { + // Pinned explicitly: the ordering of the timeline depends on these values. + Assert.True( LeaseCountingPointKind.Close < LeaseCountingPointKind.Open ); + } + + [Fact] + public async Task GetLeaseCountingPoints_IsOrderedByTime() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + for ( int i = 3; i >= 0; i-- ) + { + LeaseBuilder.For( license ).User( $"user{i}" ).From( TestClock.Days( i ) ).Lasting( 1 ).AddTo( context ); + } + + List points = Timeline( context, license ); + + Assert.Equal( points.Select( p => p.Time ).Order(), points.Select( p => p.Time ) ); + } + + [Fact] + public async Task GetLeaseCountingPoints_IsDeterministic() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + // Several leases starting and ending at the same instants, so ties are everywhere. + for ( int i = 0; i < 5; i++ ) + { + LeaseBuilder.For( license ).User( $"user{i}" ).From( TestClock.Origin ).Lasting( 1 ).AddTo( context ); + } + + string First() => string.Join( + "|", + Timeline( context, license ).Select( p => $"{p.Time:O}/{p.Kind}/{p.Lease.LeaseId}/{p.LeaseCount}" ) ); + + Assert.Equal( First(), First() ); + } + + [Fact] + public async Task GetLeaseCountingPoints_ExcludesReplacedLeases() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease original = LeaseBuilder.For( license ).AddTo( context ); + + context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); + await context.Repository.SaveChangesAsync(); + + Assert.DoesNotContain( Timeline( context, license ), p => p.Lease.LeaseId == original.LeaseId ); + } + + [Fact] + public async Task GetLeaseCountingPoints_ExcludesLeasesOutsideTheWindow() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + LeaseBuilder.For( license ).User( "past" ).From( TestClock.Days( -30 ) ).Lasting( 1 ).AddTo( context ); + LeaseBuilder.For( license ).User( "inside" ).From( TestClock.Origin ).Lasting( 1 ).AddTo( context ); + LeaseBuilder.For( license ).User( "future" ).From( TestClock.Days( 30 ) ).Lasting( 1 ).AddTo( context ); + + List points = context.Repository + .GetLeaseCountingPoints( license.LicenseId, TestClock.Days( -1 ), TestClock.Days( 1 ) ) + .ToList(); + + Assert.All( points, p => Assert.Equal( "inside", p.Lease.UserName ) ); + } + + [Fact] + public async Task GetLeaseCountingPoints_ReturnsToZeroAfterEveryLeaseEnds() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + for ( int i = 0; i < 6; i++ ) + { + LeaseBuilder.For( license ).User( $"user{i}" ).Machine( $"machine-{i}" ) + .From( TestClock.Days( i * 0.5 ) ).Lasting( 2 ).AddTo( context ); + } + + List points = Timeline( context, license ); + + Assert.NotEmpty( points ); + Assert.Equal( 0, points[^1].LeaseCount ); + Assert.All( points, p => Assert.True( p.LeaseCount >= 0 ) ); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseSignatureTests.cs b/tests/PostSharp.LicenseServer.Tests/LeaseSignatureTests.cs new file mode 100644 index 0000000..c514ee5 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/LeaseSignatureTests.cs @@ -0,0 +1,199 @@ +using PostSharp.LicenseServer.Tests.Infrastructure; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// The audit log is a chain: each lease is signed together with the signature of the previously +/// persisted lease, so removing or altering a row breaks every signature after it. +/// +/// +/// These assertions encode behaviour that is documented nowhere and that is invisible in a code +/// review of the migration diff, which is why they are pinned explicitly. +/// +public sealed class LeaseSignatureTests +{ + private static string[] Fields( string payload ) => payload.Split( ';' ); + + [Fact] + public async Task GetSignature_ChainsFromThePreviousLease() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease first = LeaseBuilder.For( license ).AddTo( context ); + + context.Signer.Clear(); + context.Repository.CreateLease( license, "bob", "desktop-2", "bob", TestClock.Days( 1 ), false ); + + Assert.StartsWith( first.HMAC + ";", context.Signer.LastPayload!, StringComparison.Ordinal ); + } + + [Fact] + public async Task GetSignature_FirstLeaseEver_ChainsFromAnEmptySignature() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + context.Signer.Clear(); + context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); + + Assert.StartsWith( ";", context.Signer.LastPayload!, StringComparison.Ordinal ); + } + + /// + /// The chain is anchored to what is committed, not to what is pending. Several leases created in + /// one unit of work therefore all chain from the same predecessor. + /// + [Fact] + public async Task GetSignature_DoesNotSeePendingInserts() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 10 ).AddTo( context ); + Lease committed = LeaseBuilder.For( license ).AddTo( context ); + + context.Signer.Clear(); + context.Repository.CreateLease( license, "bob", "desktop-2", "bob", TestClock.Days( 1 ), false ); + context.Repository.CreateLease( license, "carol", "desktop-3", "carol", TestClock.Days( 1 ), false ); + + Assert.Equal( 2, context.Signer.Payloads.Count ); + Assert.All( + context.Signer.Payloads, + payload => Assert.StartsWith( committed.HMAC + ";", payload, StringComparison.Ordinal ) ); + } + + [Fact] + public async Task GetSignature_AfterSave_ChainsFromTheNewlyPersistedLease() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 10 ).AddTo( context ); + LeaseBuilder.For( license ).AddTo( context ); + + Lease? second = context.Repository.CreateLease( license, "bob", "desktop-2", "bob", TestClock.Days( 1 ), false ); + await context.Repository.SaveChangesAsync(); + + context.Signer.Clear(); + context.Repository.CreateLease( license, "carol", "desktop-3", "carol", TestClock.Days( 1 ), false ); + + Assert.StartsWith( second!.HMAC + ";", context.Signer.LastPayload!, StringComparison.Ordinal ); + } + + /// + /// A lease is signed before it is inserted, so its own identifier is not yet known. + /// + [Fact] + public async Task GetSignature_SignedPayloadCarriesLeaseIdZero() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + context.Signer.Clear(); + context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); + + // Field 0 is the chained signature, so the lease's own fields start at index 1. + Assert.Equal( "0", Fields( context.Signer.LastPayload! )[1] ); + } + + /// + /// Regression guard: EF Core does not populate a foreign key from a navigation property until + /// the entity is tracked, and leases are signed before that. If the key were left unassigned the + /// license would silently be signed as zero. + /// + [Fact] + public async Task CreateLease_SignedPayloadCarriesTheLicenseId() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithLicenseId( 7 ).AddTo( context ); + + context.Signer.Clear(); + context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); + + Assert.Equal( "7", Fields( context.Signer.LastPayload! )[3] ); + } + + /// + /// The same regression guard, for the self-referencing key that makes the log an append-only + /// chain of replacements. + /// + [Fact] + public async Task ProlongLease_SignedPayloadCarriesTheOverwrittenLeaseId() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease original = LeaseBuilder.For( license ).AddTo( context ); + + context.Signer.Clear(); + context.Repository.ProlongLease( original, "alice", TestClock.Days( 2.5 ) ); + + Assert.Equal( original.LeaseId.ToString(), Fields( context.Signer.LastPayload! )[2] ); + } + + [Fact] + public async Task CreateLease_SignedPayloadHasNoOverwrittenLeaseId() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + context.Signer.Clear(); + context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); + + Assert.Equal( "", Fields( context.Signer.LastPayload! )[2] ); + } + + /// + /// The whole point of replacing the randomly-keyed HMAC: signing the same content twice now + /// yields the same signature, so the chain can actually be verified. + /// + [Fact] + public async Task Signature_IsDeterministic() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + Lease lease = new() + { + License = license, + LicenseId = license.LicenseId, + UserName = "alice", + Machine = "desktop-1", + AuthenticatedUser = "alice", + StartTime = TestClock.Origin, + EndTime = TestClock.Days( 3 ) + }; + + Assert.Equal( context.Repository.GetSignature( lease ), context.Repository.GetSignature( lease ) ); + } + + [Fact] + public async Task Signature_DiffersWhenTheLeaseDiffers() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + Lease lease = new() + { + License = license, + LicenseId = license.LicenseId, + UserName = "alice", + Machine = "desktop-1", + AuthenticatedUser = "alice", + StartTime = TestClock.Origin, + EndTime = TestClock.Days( 3 ) + }; + + string before = context.Repository.GetSignature( lease ); + lease.Machine = "desktop-2"; + + Assert.NotEqual( before, context.Repository.GetSignature( lease ) ); + } + + [Fact] + public async Task Signature_FitsTheDatabaseColumn() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease lease = LeaseBuilder.For( license ).AddTo( context ); + + // The HMAC column is varchar(100) and is not being widened by this migration. + Assert.NotNull( lease.HMAC ); + Assert.InRange( lease.HMAC.Length, 1, 100 ); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/LicenseValidationTests.cs b/tests/PostSharp.LicenseServer.Tests/LicenseValidationTests.cs new file mode 100644 index 0000000..5b8acf2 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/LicenseValidationTests.cs @@ -0,0 +1,172 @@ +using PostSharp.LicenseServer.Tests.Infrastructure; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// Why a license may refuse to serve a request. Each reason is reported back to the developer in the +/// body of the 403 response, so the wording matters. +/// +public sealed class LicenseValidationTests +{ + private static async Task<(Lease? Lease, Dictionary Errors)> RequestAsync( + LicenseServerTestContext context, + License license, + Version? version = null, + DateTime? buildDate = null ) + { + Dictionary errors = []; + + Lease? lease = await context.LeaseService.GetLeaseAsync( + version ?? new Version( 2025, 1, 0 ), + buildDate, + "desktop-1", + "alice", + "alice", + TestClock.Origin, + errors, + [license] ); + + return (lease, errors); + } + + [Fact] + public async Task UnparseableKey_IsReportedAsInvalid() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + // Added straight to the database, so the fake parser has no entry for its key. + License license = new() + { + LicenseId = 99, + LicenseKey = "NOT-A-KEY", + ProductCode = "Ultimate", + CreatedOn = TestClock.Origin + }; + + context.Db.Licenses.Add( license ); + await context.Db.SaveChangesAsync(); + + var (lease, errors) = await RequestAsync( context, license ); + + Assert.Null( lease ); + Assert.Equal( "The license key #99 is invalid.", errors[99] ); + } + + [Fact] + public async Task LicenseNeedsANewerLicenseServer_SaysSo() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + License license = LicenseBuilder.Default() + .WithMinPostSharpVersion( new Version( 2099, 3, 7 ) ) + .AddTo( context ); + + var (lease, errors) = await RequestAsync( context, license ); + + Assert.Null( lease ); + Assert.Contains( "requires higher version of PostSharp on the License Server", errors[1], StringComparison.Ordinal ); + Assert.Contains( "2099.3.7", errors[1], StringComparison.Ordinal ); + } + + [Fact] + public async Task ClientIsOlderThanTheLicenseRequires_SaysSo() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + License license = LicenseBuilder.Default() + .WithMinPostSharpVersion( new Version( 2024, 0, 0 ) ) + .AddTo( context ); + + var (lease, errors) = await RequestAsync( context, license, new Version( 6, 5, 4 ) ); + + Assert.Null( lease ); + Assert.Contains( "requires PostSharp version >= 2024.0.0", errors[1], StringComparison.Ordinal ); + Assert.Contains( "the requested version is 6.5.4", errors[1], StringComparison.Ordinal ); + } + + [Fact] + public async Task LicenseNotEligibleForALicenseServer_SaysSo() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().NotLicenseServerEligible().AddTo( context ); + + var (lease, errors) = await RequestAsync( context, license ); + + Assert.Null( lease ); + Assert.Contains( "cannot be used in the license server", errors[1], StringComparison.Ordinal ); + } + + [Fact] + public async Task BuildIsNewerThanTheSubscription_SaysSoWithTheRequestedVersion() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + License license = LicenseBuilder.Default() + .WithSubscriptionEndDate( TestClock.Days( -30 ) ) + .AddTo( context ); + + var (lease, errors) = await RequestAsync( context, license, new Version( 2025, 1, 0 ), TestClock.Origin ); + + Assert.Null( lease ); + Assert.Contains( "maintenance subscription of license #1 ends on", errors[1], StringComparison.Ordinal ); + Assert.Contains( "the requested version 2025.1.0", errors[1], StringComparison.Ordinal ); + } + + /// + /// Clients older than PostSharp 5 do not send their version, so the message cannot mention one. + /// + [Fact] + public async Task BuildIsNewerThanTheSubscriptionOnAnOldClient_OmitsTheVersion() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + License license = LicenseBuilder.Default() + .WithSubscriptionEndDate( TestClock.Days( -30 ) ) + .AddTo( context ); + + var (lease, errors) = await RequestAsync( context, license, new Version( 4, 9, 9 ), TestClock.Origin ); + + Assert.Null( lease ); + Assert.Contains( "but the requested version has been built on", errors[1], StringComparison.Ordinal ); + Assert.DoesNotContain( "the requested version 4.9.9", errors[1], StringComparison.Ordinal ); + } + + [Fact] + public async Task BuildExactlyOnTheSubscriptionEndDate_IsAccepted() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + License license = LicenseBuilder.Default() + .WithSubscriptionEndDate( TestClock.Origin ) + .AddTo( context ); + + var (lease, _) = await RequestAsync( context, license, buildDate: TestClock.Origin ); + + Assert.NotNull( lease ); + } + + [Fact] + public async Task NoBuildDate_SkipsTheSubscriptionCheck() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + License license = LicenseBuilder.Default() + .WithSubscriptionEndDate( TestClock.Days( -30 ) ) + .AddTo( context ); + + var (lease, _) = await RequestAsync( context, license ); + + Assert.NotNull( lease ); + } + + [Fact] + public async Task NoSubscriptionEndDate_SkipsTheSubscriptionCheck() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithSubscriptionEndDate( null ).AddTo( context ); + + var (lease, _) = await RequestAsync( context, license, buildDate: TestClock.Days( 1000 ) ); + + Assert.NotNull( lease ); + } +} From a50ba05690cee95f7df3b058d5a7615e19724532 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 09:19:41 +0200 Subject: [PATCH 05/44] Replace the WebForms front end with Razor Pages Rewrite the seven .aspx pages and the master page as Razor Pages, and the three .ashx handlers as minimal API endpoints. The handler URLs are kept literally, because deployed PostSharp clients address them directly, as are the status codes and response bodies they check. The old page URLs now redirect permanently to the new ones. The usage graph no longer loads YUI 3.18.1 over plain HTTP from a content delivery network retired in 2014, which any HTTPS deployment blocked as mixed content. It uses Chart.js, served from the application itself so an isolated network needs no outbound access. Its data travels as JSON in its own element rather than being concatenated into a script body. Incidental fixes the rewrite made possible: - The graph rejects an unknown license and an out-of-range window instead of answering 500. - The demo data generator is confined to a development environment. It was previously reachable in production, on a deployment whose administrative pages are unrestricted by default. - Administrative pages can be restricted to Windows groups through LicenseServer:AdminRoles. The default stays open, as it shipped before, but now says so in the log at startup. Co-Authored-By: Claude Opus 5 --- Directory.Packages.props | 12 +- PostSharp.LicenseServer.sln | 63 -- PostSharp.LicenseServer.slnx | 9 + .../Data/ILeaseRepository.cs | 5 + .../Data/LeaseRepository.cs | 2 + .../Admin/AddLicense.aspx | 14 - .../Admin/AddLicense.aspx.cs | 64 -- .../Admin/AddLicense.aspx.designer.cs | 42 -- .../Admin/Cancel.aspx | 22 - .../Admin/Cancel.aspx.cs | 41 - .../Admin/Cancel.aspx.designer.cs | 33 - .../Admin/Details.aspx | 46 -- .../Admin/Details.aspx.cs | 90 --- .../Admin/Details.aspx.designer.cs | 78 -- .../Admin/Export.ashx | 1 - .../Admin/Export.ashx.cs | 69 -- .../Admin/Export.aspx | 59 -- .../Admin/Export.aspx.cs | 39 - .../Admin/Export.aspx.designer.cs | 96 --- .../Admin/GenerateDemoData.aspx | 14 - .../Admin/GenerateDemoData.aspx.cs | 242 ------ .../Admin/GenerateDemoData.aspx.designer.cs | 51 -- .../DataClasses.dbml | 32 - .../DataClasses.dbml.layout | 32 - .../DataClasses.designer.cs | 710 ------------------ src/PostSharp.LicenseServer.Web/Database.cs | 232 ------ .../{ => Database}/CreateTables.sql | 0 src/PostSharp.LicenseServer.Web/Default.aspx | 42 -- .../Default.aspx.cs | 67 -- .../Default.aspx.designer.cs | 33 - .../Endpoints/LegacyUrlRedirects.cs | 31 + .../Endpoints/LicenseServerEndpoints.cs | 217 ++++++ .../ExtensionMethods.cs | 26 - src/PostSharp.LicenseServer.Web/GetTime.ashx | 1 - .../GetTime.ashx.cs | 31 - src/PostSharp.LicenseServer.Web/Graph.aspx | 162 ---- src/PostSharp.LicenseServer.Web/Graph.aspx.cs | 122 --- .../Graph.aspx.designer.cs | 17 - src/PostSharp.LicenseServer.Web/Lease.ashx | 1 - src/PostSharp.LicenseServer.Web/Lease.ashx.cs | 206 ----- src/PostSharp.LicenseServer.Web/Lease.cs | 41 - .../LeaseCountingPoint.cs | 12 - .../LeaseCountingPointKind.cs | 9 - .../LeaseService.cs | 417 ---------- .../Pages/Admin/AddLicense.cshtml | 27 + .../Pages/Admin/AddLicense.cshtml.cs | 72 ++ .../Pages/Admin/Cancel.cshtml | 26 + .../Pages/Admin/Cancel.cshtml.cs | 48 ++ .../Pages/Admin/Details.cshtml | 76 ++ .../Pages/Admin/Details.cshtml.cs | 98 +++ .../Pages/Admin/Export.cshtml | 38 + .../Pages/Admin/Export.cshtml.cs | 66 ++ .../Pages/Admin/GenerateDemoData.cshtml | 35 + .../Pages/Admin/GenerateDemoData.cshtml.cs | 139 ++++ .../Pages/Error.cshtml | 18 + .../Pages/Error.cshtml.cs | 16 + .../Pages/Graph.cshtml | 46 ++ .../Pages/Graph.cshtml.cs | 157 ++++ .../Pages/Index.cshtml | 59 ++ .../Pages/Index.cshtml.cs | 70 ++ .../Pages/Shared/_Layout.cshtml | 24 + .../Pages/_ViewImports.cshtml | 4 + .../Pages/_ViewStart.cshtml | 3 + .../ParsedLicenseManager.cs | 55 -- .../PostSharp.LicenseServer.Web.csproj | 26 + .../PostSharp.LicenseServer.csproj | 293 -------- src/PostSharp.LicenseServer.Web/Program.cs | 202 +++++ .../Properties/AssemblyInfo.cs | 48 -- .../Properties/Settings.Designer.cs | 116 --- .../Properties/Settings.settings | 36 - .../Properties/launchSettings.json | 12 + src/PostSharp.LicenseServer.Web/Site.Master | 47 -- .../Site.Master.cs | 22 - .../Site.Master.designer.cs | 42 -- .../VirtualDateTime.cs | 34 - .../Web.Debug.config | 30 - .../Web.Release.config | 31 - src/PostSharp.LicenseServer.Web/Web.config | 148 ---- .../appsettings.Development.json | 11 + .../appsettings.json | 40 + .../packages.config | 9 - .../Img/PostSharpText_Light_240x33.png | Bin .../wwwroot/css/site.css | 63 ++ .../wwwroot/js/graph.js | 85 +++ .../wwwroot/lib/chartjs/LICENSE.md | 9 + .../wwwroot/lib/chartjs/chart.umd.min.js | 14 + .../{ => wwwroot}/robots.txt | 0 .../BuildServerDetectionTests.cs | 2 + .../LeaseAllocationTests.cs | 2 +- 89 files changed, 1757 insertions(+), 4175 deletions(-) delete mode 100644 PostSharp.LicenseServer.sln create mode 100644 PostSharp.LicenseServer.slnx delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/AddLicense.aspx delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/AddLicense.aspx.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/AddLicense.aspx.designer.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/Cancel.aspx delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/Cancel.aspx.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/Cancel.aspx.designer.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/Details.aspx delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/Details.aspx.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/Details.aspx.designer.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/Export.ashx delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/Export.ashx.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/Export.aspx delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/Export.aspx.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/Export.aspx.designer.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/GenerateDemoData.aspx delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/GenerateDemoData.aspx.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Admin/GenerateDemoData.aspx.designer.cs delete mode 100644 src/PostSharp.LicenseServer.Web/DataClasses.dbml delete mode 100644 src/PostSharp.LicenseServer.Web/DataClasses.dbml.layout delete mode 100644 src/PostSharp.LicenseServer.Web/DataClasses.designer.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Database.cs rename src/PostSharp.LicenseServer.Web/{ => Database}/CreateTables.sql (100%) delete mode 100644 src/PostSharp.LicenseServer.Web/Default.aspx delete mode 100644 src/PostSharp.LicenseServer.Web/Default.aspx.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Default.aspx.designer.cs create mode 100644 src/PostSharp.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs create mode 100644 src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs delete mode 100644 src/PostSharp.LicenseServer.Web/ExtensionMethods.cs delete mode 100644 src/PostSharp.LicenseServer.Web/GetTime.ashx delete mode 100644 src/PostSharp.LicenseServer.Web/GetTime.ashx.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Graph.aspx delete mode 100644 src/PostSharp.LicenseServer.Web/Graph.aspx.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Graph.aspx.designer.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Lease.ashx delete mode 100644 src/PostSharp.LicenseServer.Web/Lease.ashx.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Lease.cs delete mode 100644 src/PostSharp.LicenseServer.Web/LeaseCountingPoint.cs delete mode 100644 src/PostSharp.LicenseServer.Web/LeaseCountingPointKind.cs delete mode 100644 src/PostSharp.LicenseServer.Web/LeaseService.cs create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Admin/AddLicense.cshtml create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Admin/Cancel.cshtml create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Admin/Details.cshtml create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Admin/Details.cshtml.cs create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml.cs create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Error.cshtml create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Error.cshtml.cs create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml.cs create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Index.cshtml create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Index.cshtml.cs create mode 100644 src/PostSharp.LicenseServer.Web/Pages/Shared/_Layout.cshtml create mode 100644 src/PostSharp.LicenseServer.Web/Pages/_ViewImports.cshtml create mode 100644 src/PostSharp.LicenseServer.Web/Pages/_ViewStart.cshtml delete mode 100644 src/PostSharp.LicenseServer.Web/ParsedLicenseManager.cs create mode 100644 src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj delete mode 100644 src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.csproj create mode 100644 src/PostSharp.LicenseServer.Web/Program.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Properties/AssemblyInfo.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Properties/Settings.Designer.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Properties/Settings.settings create mode 100644 src/PostSharp.LicenseServer.Web/Properties/launchSettings.json delete mode 100644 src/PostSharp.LicenseServer.Web/Site.Master delete mode 100644 src/PostSharp.LicenseServer.Web/Site.Master.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Site.Master.designer.cs delete mode 100644 src/PostSharp.LicenseServer.Web/VirtualDateTime.cs delete mode 100644 src/PostSharp.LicenseServer.Web/Web.Debug.config delete mode 100644 src/PostSharp.LicenseServer.Web/Web.Release.config delete mode 100644 src/PostSharp.LicenseServer.Web/Web.config create mode 100644 src/PostSharp.LicenseServer.Web/appsettings.Development.json create mode 100644 src/PostSharp.LicenseServer.Web/appsettings.json delete mode 100644 src/PostSharp.LicenseServer.Web/packages.config rename src/PostSharp.LicenseServer.Web/{ => wwwroot}/Img/PostSharpText_Light_240x33.png (100%) create mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/css/site.css create mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js create mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/LICENSE.md create mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/chart.umd.min.js rename src/PostSharp.LicenseServer.Web/{ => wwwroot}/robots.txt (100%) diff --git a/Directory.Packages.props b/Directory.Packages.props index 0c0b63d..7c3c21c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,17 +13,17 @@ - - - - + + + + - - + + + PostSharp.LicenseServer + PostSharp.LicenseServer + postsharp-license-server + + + + + + + + + + + + + + + + + diff --git a/src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.csproj b/src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.csproj deleted file mode 100644 index 8850dad..0000000 --- a/src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.csproj +++ /dev/null @@ -1,293 +0,0 @@ - - - - - - Debug - AnyCPU - - - 2.0 - {3B511E09-1CFD-43EB-978F-70FA3DFEC83B} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - PostSharp.LicenseServer - PostSharp.LicenseServer - v4.7.2 - true - - - - - 4.0 - - - - - - $(MSBuildThisFileDirectory)..\..\packages\MSBuildTasks.1.5.0.235\tools - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - true - - - - - - - - - - - - false - true - false - false - True - - - pdbonly - true - bin\ - TRACE - prompt - 4 - false - - - - - true - false - True - - - - ..\..\packages\PostSharp.Redist.2025.1.5\lib\net45\PostSharp.dll - - - ..\..\packages\PostSharp.Compiler.Common.2025.1.5\lib\netstandard2.0\PostSharp.Compiler.Common.dll - - - ..\..\packages\PostSharp.Compiler.Platforms.2025.1.5\lib\net472\PostSharp.Compiler.Platform.NetFramework.dll - - - ..\..\packages\PostSharp.Compiler.Settings.2025.1.5\lib\netstandard2.0\PostSharp.Compiler.Settings.dll - - - - - - - - - - - - - - - - - - - - - - - Designer - - - Web.config - - - Web.config - - - - - AddLicense.aspx - ASPXCodeBehind - - - AddLicense.aspx - - - Cancel.aspx - ASPXCodeBehind - - - Cancel.aspx - - - Details.aspx - ASPXCodeBehind - - - Details.aspx - - - Export.ashx - - - Export.aspx - ASPXCodeBehind - - - Export.aspx - - - GenerateDemoData.aspx - ASPXCodeBehind - - - GenerateDemoData.aspx - - - - True - True - DataClasses.dbml - - - Default.aspx - ASPXCodeBehind - - - Default.aspx - - - - GetTime.ashx - - - Graph.aspx - ASPXCodeBehind - - - Graph.aspx - - - Lease.ashx - - - - - - - - - True - True - Settings.settings - - - Site.Master - ASPXCodeBehind - - - Site.Master - - - - - - - - - - MSLinqToSQLGenerator - DataClasses.designer.cs - Designer - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - - - DataClasses.dbml - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - bin\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - false - True - - - - - - - - - True - - - - - - - - - - - - - - - - - - - <_ZipFiles Include="obj\$(Configuration)\Package\PackageTmp\**\*" /> - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - \ No newline at end of file diff --git a/src/PostSharp.LicenseServer.Web/Program.cs b/src/PostSharp.LicenseServer.Web/Program.cs new file mode 100644 index 0000000..1d3ef98 --- /dev/null +++ b/src/PostSharp.LicenseServer.Web/Program.cs @@ -0,0 +1,202 @@ +using Microsoft.AspNetCore.Authentication.Negotiate; +using Microsoft.AspNetCore.Server.IISIntegration; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using PostSharp.LicenseServer.Data; +using PostSharp.LicenseServer.Email; +using PostSharp.LicenseServer.Endpoints; +using PostSharp.LicenseServer.Licensing; +using PostSharp.LicenseServer.Locking; +using PostSharp.LicenseServer.Options; +using PostSharp.LicenseServer.Security; +using PostSharp.LicenseServer.Services; +using PostSharp.LicenseServer.Time; + +WebApplicationBuilder builder = WebApplication.CreateBuilder( args ); + +// The PostSharp SDK parses license keys, and has to be initialized once before it is first used. +PostSharpPlatform.EnsureInitialized(); + +builder.Services + .AddOptions() + .Bind( builder.Configuration.GetSection( LicenseServerOptions.SectionName ) ) + .ValidateDataAnnotations() + .ValidateOnStart(); + +builder.Services.AddSingleton, LicenseServerOptionsValidator>(); + +builder.Services + .AddOptions() + .Bind( builder.Configuration.GetSection( SmtpOptions.SectionName ) ) + .ValidateDataAnnotations() + .ValidateOnStart(); + +builder.Services.AddDbContext( + options => options.UseSqlServer( + builder.Configuration.GetConnectionString( "SharpCrafters_LicenseServerConnectionString" ) ) ); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddSingleton( + _ => new CachingLicenseParser( new PostSharpLicenseParser() ) ); + +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + +builder.Services.AddSingleton( + services => new FileAuditKeyProvider( + services.GetRequiredService>(), + services.GetRequiredService>(), + Path.Combine( + services.GetRequiredService().ContentRootPath, + "App_Data", + "audit-signing.key" ) ) ); + +builder.Services.AddSingleton(); + +builder.Services.AddSingleton( + services => services.GetRequiredService>().Value.Enabled + ? ActivatorUtilities.CreateInstance( services ) + : new NullEmailSender() ); + +// Time acceleration exists so that a multi-day licensing scenario can be replayed in minutes. It is +// off unless explicitly configured. +builder.Services.AddSingleton( + services => + { + LicenseServerOptions options = services.GetRequiredService>().Value; + + if ( options.TimeAcceleration is 0 or 1 ) + { + return TimeProvider.System; + } + + services.GetRequiredService>() + .LogWarning( + "Time is accelerated by a factor of {Acceleration}. This is a test configuration and must not be " + + "used in production.", + options.TimeAcceleration ); + + return new AcceleratedTimeProvider( TimeProvider.System, (double) options.TimeAcceleration ); + } ); + +builder.Services.AddSingleton( + services => + { + LicenseServerOptions options = services.GetRequiredService>().Value; + + return options.LeaseLockMode switch + { + LeaseLockMode.InProcess => new InProcessLeaseLock(), + LeaseLockMode.None => new NullLeaseLock(), + LeaseLockMode.SqlApplicationLock => throw new NotSupportedException( + "LeaseLockMode.SqlApplicationLock is not implemented yet. Run a single worker process, or open an " + + "issue at https://github.com/postsharp/PostSharp.LicenseServer." ), + _ => throw new InvalidOperationException( $"Unknown lease lock mode '{options.LeaseLockMode}'." ) + }; + } ); + +// Windows authentication. IIS handles it in-process; Negotiate covers Kestrel and out-of-process +// hosting, which is what `dotnet run` uses during development. +string authenticationScheme = builder.Configuration["Authentication:Scheme"] ?? "Negotiate"; + +if ( string.Equals( authenticationScheme, "IISIntegrated", StringComparison.OrdinalIgnoreCase ) ) +{ + builder.Services.AddAuthentication( IISDefaults.AuthenticationScheme ); +} +else +{ + builder.Services.AddAuthentication( NegotiateDefaults.AuthenticationScheme ).AddNegotiate(); +} + +builder.Services.AddAuthorizationBuilder() + .AddPolicy( + AuthorizationPolicies.Admin, + policy => + { + string[] roles = builder.Configuration + .GetSection( $"{LicenseServerOptions.SectionName}:AdminRoles" ) + .Get() ?? []; + + // No roles configured means the administrative pages are open, which is how the legacy + // Web.config shipped. Tightening this by default would lock administrators out of their + // own server on upgrade. A warning is logged at startup instead. + if ( roles.Length == 0 ) + { + policy.RequireAssertion( _ => true ); + } + else + { + policy.RequireRole( roles ); + } + } ) + .AddPolicy( + AuthorizationPolicies.LeaseRequest, + policy => + { + bool requireAuthentication = builder.Configuration.GetValue( + $"{LicenseServerOptions.SectionName}:RequireAuthenticatedLeaseRequests", + false ); + + if ( requireAuthentication ) + { + policy.RequireAuthenticatedUser(); + } + else + { + policy.RequireAssertion( _ => true ); + } + } ); + +builder.Services.AddRazorPages( + options => + { + options.Conventions.AuthorizeFolder( "/Admin", AuthorizationPolicies.Admin ); + } ); + +WebApplication app = builder.Build(); + +if ( !app.Environment.IsDevelopment() ) +{ + app.UseExceptionHandler( "/Error" ); +} + +app.UseStaticFiles(); +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapRazorPages(); +app.MapLicenseServerEndpoints(); +app.MapLegacyUrlRedirects(); + +// The administrative pages are the only way to add or revoke a license, so an open default deserves +// more than a comment in a configuration file. +{ + LicenseServerOptions options = app.Services.GetRequiredService>().Value; + + if ( options.AdminRoles.Length == 0 ) + { + app.Logger.LogWarning( + "The administrative pages are not restricted. Set {Setting} to the Windows groups allowed to manage " + + "licenses, for example \"DOMAIN\\\\PostSharp Administrators\".", + $"{LicenseServerOptions.SectionName}:AdminRoles" ); + } +} + +app.Run(); + +/// +/// Names of the authorization policies. +/// +public static class AuthorizationPolicies +{ + public const string Admin = "Admin"; + public const string LeaseRequest = "LeaseRequest"; +} + +/// +/// Exposed so that integration tests can host the application in memory. +/// +public partial class Program; diff --git a/src/PostSharp.LicenseServer.Web/Properties/AssemblyInfo.cs b/src/PostSharp.LicenseServer.Web/Properties/AssemblyInfo.cs deleted file mode 100644 index 93c53c4..0000000 --- a/src/PostSharp.LicenseServer.Web/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,48 +0,0 @@ -#region Copyright (c) 2004-2010 by SharpCrafters s.r.o. - -// This file is part of PostSharp source code and is the property of SharpCrafters s.r.o. -// -// Source code is provided to customers under strict non-disclosure agreement (NDA). -// YOU MUST HAVE READ THE NDA BEFORE HAVING RECEIVED ACCESS TO THIS SOURCE CODE. -// Severe financial penalties apply in case of non respect of the NDA. - -#endregion - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. - -[assembly: AssemblyTitle( "PostSharp.LicenseServer" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "Microsoft" )] -[assembly: AssemblyProduct( "PostSharp.LicenseServer" )] -[assembly: AssemblyCopyright( "Copyright © Microsoft 2011" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. - -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM - -[assembly: Guid( "a5a5279c-8b93-41e9-a262-cb4a852b288a" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: - -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] \ No newline at end of file diff --git a/src/PostSharp.LicenseServer.Web/Properties/Settings.Designer.cs b/src/PostSharp.LicenseServer.Web/Properties/Settings.Designer.cs deleted file mode 100644 index 27836f5..0000000 --- a/src/PostSharp.LicenseServer.Web/Properties/Settings.Designer.cs +++ /dev/null @@ -1,116 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace PostSharp.LicenseServer.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("1")] - public int GracePeriodWarningDays { - get { - return ((int)(this["GracePeriodWarningDays"])); - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("2")] - public int MachinesPerUser { - get { - return ((int)(this["MachinesPerUser"])); - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("1")] - public int MinLeaseDays { - get { - return ((int)(this["MinLeaseDays"])); - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("3")] - public int NewLeaseDays { - get { - return ((int)(this["NewLeaseDays"])); - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("gael@sharpcrafters.com")] - public string GracePeriodWarningEmailTo { - get { - return ((string)(this["GracePeriodWarningEmailTo"])); - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("gael@sharpcrafters.com")] - public string DeniedRequestEmailTo { - get { - return ((string)(this["DeniedRequestEmailTo"])); - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("30")] - public float MutexTimeout { - get { - return ((float)(this["MutexTimeout"])); - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("1440")] - public decimal TimeAcceleration { - get { - return ((decimal)(this["TimeAcceleration"])); - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("")] - public string BuildServers { - get { - return ((string)(this["BuildServers"])); - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("sales@postsharp.net")] - public string GracePeriodWarningEmailCC { - get { - return ((string)(this["GracePeriodWarningEmailCC"])); - } - } - } -} diff --git a/src/PostSharp.LicenseServer.Web/Properties/Settings.settings b/src/PostSharp.LicenseServer.Web/Properties/Settings.settings deleted file mode 100644 index 39a4b3d..0000000 --- a/src/PostSharp.LicenseServer.Web/Properties/Settings.settings +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - 1 - - - 2 - - - 1 - - - 3 - - - gael@sharpcrafters.com - - - gael@sharpcrafters.com - - - 30 - - - 1440 - - - - - - sales@postsharp.net - - - \ No newline at end of file diff --git a/src/PostSharp.LicenseServer.Web/Properties/launchSettings.json b/src/PostSharp.LicenseServer.Web/Properties/launchSettings.json new file mode 100644 index 0000000..92c24f7 --- /dev/null +++ b/src/PostSharp.LicenseServer.Web/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "PostSharp.LicenseServer": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:44670", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/PostSharp.LicenseServer.Web/Site.Master b/src/PostSharp.LicenseServer.Web/Site.Master deleted file mode 100644 index 390e25f..0000000 --- a/src/PostSharp.LicenseServer.Web/Site.Master +++ /dev/null @@ -1,47 +0,0 @@ -<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="PostSharp.LicenseServer.Site" %> - - - - - - - - - - - -
'" style="cursor:pointer" > - -

PostSharp License Server

-
- -
-
-
- - - -
-
- - - diff --git a/src/PostSharp.LicenseServer.Web/Site.Master.cs b/src/PostSharp.LicenseServer.Web/Site.Master.cs deleted file mode 100644 index c5edb2b..0000000 --- a/src/PostSharp.LicenseServer.Web/Site.Master.cs +++ /dev/null @@ -1,22 +0,0 @@ -#region Copyright (c) 2004-2010 by SharpCrafters s.r.o. - -// This file is part of PostSharp source code and is the property of SharpCrafters s.r.o. -// -// Source code is provided to customers under strict non-disclosure agreement (NDA). -// YOU MUST HAVE READ THE NDA BEFORE HAVING RECEIVED ACCESS TO THIS SOURCE CODE. -// Severe financial penalties apply in case of non respect of the NDA. - -#endregion - -using System; -using System.Web.UI; - -namespace PostSharp.LicenseServer -{ - public partial class Site : MasterPage - { - protected void Page_Load( object sender, EventArgs e ) - { - } - } -} \ No newline at end of file diff --git a/src/PostSharp.LicenseServer.Web/Site.Master.designer.cs b/src/PostSharp.LicenseServer.Web/Site.Master.designer.cs deleted file mode 100644 index 4cedeac..0000000 --- a/src/PostSharp.LicenseServer.Web/Site.Master.designer.cs +++ /dev/null @@ -1,42 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace PostSharp.LicenseServer { - - - public partial class Site { - - /// - /// Head control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.ContentPlaceHolder Head; - - /// - /// form1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlForm form1; - - /// - /// Body control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.ContentPlaceHolder Body; - } -} diff --git a/src/PostSharp.LicenseServer.Web/VirtualDateTime.cs b/src/PostSharp.LicenseServer.Web/VirtualDateTime.cs deleted file mode 100644 index 76c3365..0000000 --- a/src/PostSharp.LicenseServer.Web/VirtualDateTime.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using PostSharp.LicenseServer.Properties; - -namespace PostSharp.LicenseServer -{ - public static class VirtualDateTime - { - private static DateTime startTime = DateTime.UtcNow; - - public static readonly decimal Acceleration = Settings.Default.TimeAcceleration; - - public static DateTime UtcNow - { - get - { -#if DEBUG - if ( Acceleration != 0 && Acceleration != 1 ) - { - return startTime.AddMilliseconds( (DateTime.UtcNow - startTime).TotalMilliseconds* (double) Acceleration ); - } - else - { - return DateTime.UtcNow; - } -#else - return DateTime.UtcNow; -#endif - } - } - } -} \ No newline at end of file diff --git a/src/PostSharp.LicenseServer.Web/Web.Debug.config b/src/PostSharp.LicenseServer.Web/Web.Debug.config deleted file mode 100644 index 2c6dd51..0000000 --- a/src/PostSharp.LicenseServer.Web/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/PostSharp.LicenseServer.Web/Web.Release.config b/src/PostSharp.LicenseServer.Web/Web.Release.config deleted file mode 100644 index 4122d79..0000000 --- a/src/PostSharp.LicenseServer.Web/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/PostSharp.LicenseServer.Web/Web.config b/src/PostSharp.LicenseServer.Web/Web.config deleted file mode 100644 index 358ff72..0000000 --- a/src/PostSharp.LicenseServer.Web/Web.config +++ /dev/null @@ -1,148 +0,0 @@ - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - 2 - - - 1 - - - 3 - - - hello@postsharp.net - - - hello@postsharp.net - - - 30 - - - 1 - - - server - - - hello@postsharp.net - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/PostSharp.LicenseServer.Web/appsettings.Development.json b/src/PostSharp.LicenseServer.Web/appsettings.Development.json new file mode 100644 index 0000000..6f4988b --- /dev/null +++ b/src/PostSharp.LicenseServer.Web/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "Authentication": { + "Scheme": "Negotiate" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.EntityFrameworkCore.Database.Command": "Information" + } + } +} diff --git a/src/PostSharp.LicenseServer.Web/appsettings.json b/src/PostSharp.LicenseServer.Web/appsettings.json new file mode 100644 index 0000000..575c925 --- /dev/null +++ b/src/PostSharp.LicenseServer.Web/appsettings.json @@ -0,0 +1,40 @@ +{ + "ConnectionStrings": { + "SharpCrafters_LicenseServerConnectionString": "Data Source=localhost;Initial Catalog=PostSharpLicenseServer;Integrated Security=True;Encrypt=False" + }, + "Authentication": { + "Scheme": "IISIntegrated" + }, + "LicenseServer": { + "GracePeriodWarningDays": 1, + "MachinesPerUser": 2, + "MinLeaseDays": 1, + "NewLeaseDays": 3, + "GracePeriodWarningEmailTo": "", + "GracePeriodWarningEmailCC": "", + "DeniedRequestEmailTo": "", + "MutexTimeout": 30, + "TimeAcceleration": 1, + "BuildServers": "", + "AuditHmacKey": null, + "AdminRoles": [], + "RequireAuthenticatedLeaseRequests": false, + "LeaseLockMode": "InProcess" + }, + "Smtp": { + "Enabled": false, + "Host": "localhost", + "Port": 25, + "EnableSsl": false, + "FromAddress": "sales@postsharp.net", + "UserName": null, + "Password": null + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/PostSharp.LicenseServer.Web/packages.config b/src/PostSharp.LicenseServer.Web/packages.config deleted file mode 100644 index 86fb0e7..0000000 --- a/src/PostSharp.LicenseServer.Web/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/PostSharp.LicenseServer.Web/Img/PostSharpText_Light_240x33.png b/src/PostSharp.LicenseServer.Web/wwwroot/Img/PostSharpText_Light_240x33.png similarity index 100% rename from src/PostSharp.LicenseServer.Web/Img/PostSharpText_Light_240x33.png rename to src/PostSharp.LicenseServer.Web/wwwroot/Img/PostSharpText_Light_240x33.png diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css b/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css new file mode 100644 index 0000000..8cbabcd --- /dev/null +++ b/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css @@ -0,0 +1,63 @@ +body { + font-family: "Segoe UI", Arial, sans-serif; + padding: 20px; + color: #222; +} + +h1 { + font-size: 1.8em; +} + +header { + cursor: pointer; +} + +header img { + float: right; +} + +hr { + margin-top: 50px; +} + +table { + border-collapse: collapse; +} + +td, th { + padding: 10px; + text-align: left; + border-bottom: 1px solid #e0e0e0; +} + +th { + border-bottom: 2px solid #999; +} + +.no-licenses { + padding: 20px; + background: #f6f6f6; + border-left: 4px solid #58006e; +} + +.validation-error, .field-validation-error, .validation-summary-errors { + color: #b00020; +} + +.actions form { + display: inline; +} + +.toolbar { + float: right; +} + +#usage-chart { + width: 100%; + height: 400px; +} + +button, input[type="submit"] { + padding: 6px 14px; + cursor: pointer; +} diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js b/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js new file mode 100644 index 0000000..ea0d1ed --- /dev/null +++ b/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js @@ -0,0 +1,85 @@ +// Renders the licence usage history. The data is produced by GraphModel and embedded in the page as +// JSON; nothing is fetched at run time, so the page works on an isolated network. +(function () { + "use strict"; + + var dataElement = document.getElementById("usage-chart-data"); + var canvas = document.getElementById("usage-chart"); + + if (!dataElement || !canvas || typeof Chart === "undefined") { + return; + } + + var chart = JSON.parse(dataElement.textContent); + + var datasets = [{ + label: "Used", + data: chart.used, + borderColor: "#58006e", + backgroundColor: "rgba(88, 0, 110, 0.1)", + fill: true, + tension: 0.1, + pointRadius: 0, + pointHitRadius: 8 + }]; + + // The capacity and grace lines are absent for an unlimited licence. + if (chart.maximum !== null && chart.maximum !== undefined) { + datasets.push({ + label: "Authorized", + data: chart.labels.map(function () { return chart.maximum; }), + borderColor: "#ffa500", + borderDash: [6, 4], + fill: false, + pointRadius: 0 + }); + + datasets.push({ + label: "Grace", + data: chart.labels.map(function () { return chart.grace; }), + borderColor: "#ff0000", + borderDash: [2, 3], + fill: false, + pointRadius: 0 + }); + } + + new Chart(canvas, { + type: "line", + data: { labels: chart.labels, datasets: datasets }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { mode: "index", intersect: false }, + scales: { + y: { + beginAtZero: true, + max: chart.axisMaximum, + title: { display: true, text: "Concurrent users" }, + ticks: { precision: 0 } + }, + x: { + ticks: { + autoSkip: false, + maxRotation: 45, + minRotation: 45, + // The original chart labelled Mondays only, which keeps a year-long window + // readable. + callback: function (value, index) { + var label = chart.labels[index]; + return new Date(label + "T00:00:00Z").getUTCDay() === 1 ? label : ""; + } + } + } + }, + plugins: { + legend: { position: "bottom" }, + tooltip: { + callbacks: { + title: function (items) { return items[0].label; } + } + } + } + } + }); +})(); diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/LICENSE.md b/src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/LICENSE.md new file mode 100644 index 0000000..f216610 --- /dev/null +++ b/src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2014-2024 Chart.js Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/chart.umd.min.js b/src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/chart.umd.min.js new file mode 100644 index 0000000..7eec4e6 --- /dev/null +++ b/src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/chart.umd.min.js @@ -0,0 +1,14 @@ +/*! + * Chart.js v4.5.0 + * https://www.chartjs.org + * (c) 2025 Chart.js Contributors + * Released under the MIT License + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Jo},get Decimation(){return ta},get Filler(){return ba},get Legend(){return Ma},get SubTitle(){return Pa},get Title(){return ka},get Tooltip(){return Na}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;at,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)t-e)).pop(),e}function N(t){return!function(t){return"symbol"==typeof t||"object"==typeof t&&null!==t&&!(Symbol.toPrimitive in t||"toString"in t||"valueOf"in t)}(t)&&!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;sl&&h=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return nt[s][e]et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;ss&&t[n-1]>i;)n--;return s>0||n{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const n=s.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),n}})})))}function rt(t,e){const i=t._chartjs;if(!i)return;const s=i.listeners,n=s.indexOf(e);-1!==n&&s.splice(n,1),s.length>0||(ot.forEach((e=>{delete t[e]})),delete t._chartjs)}function lt(t){const e=new Set(t);return e.size===t.length?t:Array.from(e)}const ht="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function ct(t,e){let i=[],s=!1;return function(...n){i=n,s||(s=!0,ht.call(window,(()=>{s=!1,t.apply(e,i)})))}}function dt(t,e){let i;return function(...s){return e?(clearTimeout(i),i=setTimeout(t,e,s)):t.apply(this,s),e}}const ut=t=>"start"===t?"left":"end"===t?"right":"center",ft=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,gt=(t,e,i,s)=>t===(s?"left":"right")?i:"center"===t?(e+i)/2:e;function pt(t,e,i){const n=e.length;let o=0,a=n;if(t._sorted){const{iScale:r,vScale:l,_parsed:h}=t,c=t.dataset&&t.dataset.options?t.dataset.options.spanGaps:null,d=r.axis,{min:u,max:f,minDefined:g,maxDefined:p}=r.getUserBounds();if(g){if(o=Math.min(it(h,d,u).lo,i?n:it(e,d,r.getPixelForValue(u)).lo),c){const t=h.slice(0,o+1).reverse().findIndex((t=>!s(t[l.axis])));o-=Math.max(0,t)}o=Z(o,0,n-1)}if(p){let t=Math.max(it(h,r.axis,f,!0).hi+1,i?0:it(e,d,r.getPixelForValue(f),!0).hi+1);if(c){const e=h.slice(t-1).findIndex((t=>!s(t[l.axis])));t+=Math.max(0,e)}a=Z(t,o,n)-o}else a=n-o}return{start:o,count:a}}function mt(t){const{xScale:e,yScale:i,_scaleRanges:s}=t,n={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!s)return t._scaleRanges=n,!0;const o=s.xmin!==e.min||s.xmax!==e.max||s.ymin!==i.min||s.ymax!==i.max;return Object.assign(s,n),o}class xt{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,s){const n=e.listeners[s],o=e.duration;n.forEach((s=>s({chart:t,initial:e.initial,numSteps:o,currentStep:Math.min(i-e.start,o)})))}_refresh(){this._request||(this._running=!0,this._request=ht.call(window,(()=>{this._update(),this._request=null,this._running&&this._refresh()})))}_update(t=Date.now()){let e=0;this._charts.forEach(((i,s)=>{if(!i.running||!i.items.length)return;const n=i.items;let o,a=n.length-1,r=!1;for(;a>=0;--a)o=n[a],o._active?(o._total>i.duration&&(i.duration=o._total),o.tick(t),r=!0):(n[a]=n[n.length-1],n.pop());r&&(s.draw(),this._notify(s,i,t,"progress")),n.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),e+=n.length})),this._lastDate=t,0===e&&(this._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var bt=new xt; +/*! + * @kurkle/color v0.3.2 + * https://github.com/kurkle/color#readme + * (c) 2023 Jukka Kurkela + * Released under the MIT License + */function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),s=Ft(s),t.r=s[0],t.g=s[1],t.b=s[2]}}function qt(t,e){return t?Object.assign(e||{},t):t}function Kt(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=Mt(t[3]))):(e=qt(t,{r:0,g:0,b:0,a:1})).a=Mt(e.a),e}function Gt(t){return"r"===t.charAt(0)?function(t){const e=$t.exec(t);let i,s,n,o=255;if(e){if(e[7]!==i){const t=+e[7];o=e[8]?vt(t):yt(255*t,0,255)}return i=+e[1],s=+e[3],n=+e[5],i=255&(e[2]?vt(i):yt(i,0,255)),s=255&(e[4]?vt(s):yt(s,0,255)),n=255&(e[6]?vt(n):yt(n,0,255)),{r:i,g:s,b:n,a:o}}}(t):Bt(t)}class Jt{constructor(t){if(t instanceof Jt)return t;const e=typeof t;let i;var s,n,o;"object"===e?i=Kt(t):"string"===e&&(o=(s=t).length,"#"===s[0]&&(4===o||5===o?n={r:255&17*St[s[1]],g:255&17*St[s[2]],b:255&17*St[s[3]],a:5===o?17*St[s[4]]:255}:7!==o&&9!==o||(n={r:St[s[1]]<<4|St[s[2]],g:St[s[3]]<<4|St[s[4]],b:St[s[5]]<<4|St[s[6]],a:9===o?St[s[7]]<<4|St[s[8]]:255})),i=n||jt(t)||Gt(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=qt(this._rgb);return t&&(t.a=wt(t.a)),t}set rgb(t){this._rgb=Kt(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${wt(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):void 0;var t}hexString(){return this._valid?At(this._rgb):void 0}hslString(){return this._valid?function(t){if(!t)return;const e=It(t),i=e[0],s=kt(e[1]),n=kt(e[2]);return t.a<255?`hsla(${i}, ${s}%, ${n}%, ${wt(t.a)})`:`hsl(${i}, ${s}%, ${n}%)`}(this._rgb):void 0}mix(t,e){if(t){const i=this.rgb,s=t.rgb;let n;const o=e===n?.5:e,a=2*o-1,r=i.a-s.a,l=((a*r==-1?a:(a+r)/(1+a*r))+1)/2;n=1-l,i.r=255&l*i.r+n*s.r+.5,i.g=255&l*i.g+n*s.g+.5,i.b=255&l*i.b+n*s.b+.5,i.a=o*i.a+(1-o)*s.a,this.rgb=i}return this}interpolate(t,e){return t&&(this._rgb=function(t,e,i){const s=Ut(wt(t.r)),n=Ut(wt(t.g)),o=Ut(wt(t.b));return{r:Mt(Yt(s+i*(Ut(wt(e.r))-s))),g:Mt(Yt(n+i*(Ut(wt(e.g))-n))),b:Mt(Yt(o+i*(Ut(wt(e.b))-o))),a:t.a+i*(e.a-t.a)}}(this._rgb,t._rgb,e)),this}clone(){return new Jt(this.rgb)}alpha(t){return this._rgb.a=Mt(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=_t(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return Xt(this._rgb,2,t),this}darken(t){return Xt(this._rgb,2,-t),this}saturate(t){return Xt(this._rgb,1,t),this}desaturate(t){return Xt(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=It(t);i[0]=Vt(i[0]+e),i=Ft(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function Zt(t){if(t&&"object"==typeof t){const e=t.toString();return"[object CanvasPattern]"===e||"[object CanvasGradient]"===e}return!1}function Qt(t){return Zt(t)?t:new Jt(t)}function te(t){return Zt(t)?t:new Jt(t).saturate(.5).darken(.1).hexString()}const ee=["x","y","borderWidth","radius","tension"],ie=["color","borderColor","backgroundColor"];const se=new Map;function ne(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let s=se.get(i);return s||(s=new Intl.NumberFormat(t,e),se.set(i,s)),s}(e,i).format(t)}const oe={values:t=>n(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const s=this.chart.options.locale;let n,o=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(n="scientific"),o=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>=1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=z(Math.abs(o)),r=isNaN(a)?1:Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:n,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),ne(t,s,l)},logarithmic(t,e,i){if(0===t)return"0";const s=i[e].significand||t/Math.pow(10,Math.floor(z(t)));return[1,2,3,5,10,15].includes(s)||e>.8*i.length?oe.numeric.call(this,t,e,i):""}};var ae={formatters:oe};const re=Object.create(null),le=Object.create(null);function he(t,e){if(!e)return t;const i=e.split(".");for(let e=0,s=i.length;et.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>te(e.backgroundColor),this.hoverBorderColor=(t,e)=>te(e.borderColor),this.hoverColor=(t,e)=>te(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(e)}set(t,e){return ce(this,t,e)}get(t){return he(this,t)}describe(t,e){return ce(le,t,e)}override(t,e){return ce(re,t,e)}route(t,e,i,s){const n=he(this,t),a=he(this,i),r="_"+e;Object.defineProperties(n,{[r]:{value:n[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[r],e=a[s];return o(t)?Object.assign({},e,t):l(t,e)},set(t){this[r]=t}}})}apply(t){t.forEach((t=>t(this)))}}var ue=new de({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[function(t){t.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),t.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),t.set("animations",{colors:{type:"color",properties:ie},numbers:{type:"number",properties:ee}}),t.describe("animations",{_fallback:"animation"}),t.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}})},function(t){t.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})},function(t){t.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,e)=>e.lineWidth,tickColor:(t,e)=>e.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:ae.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),t.route("scale.ticks","color","","color"),t.route("scale.grid","color","","borderColor"),t.route("scale.border","color","","borderColor"),t.route("scale.title","color","","color"),t.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t&&"dash"!==t}),t.describe("scales",{_fallback:"scale"}),t.describe("scale.ticks",{_scriptable:t=>"backdropPadding"!==t&&"callback"!==t,_indexable:t=>"backdropPadding"!==t})}]);function fe(){return"undefined"!=typeof window&&"undefined"!=typeof document}function ge(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e}function pe(t,e,i){let s;return"string"==typeof t?(s=parseInt(t,10),-1!==t.indexOf("%")&&(s=s/100*e.parentNode[i])):s=t,s}const me=t=>t.ownerDocument.defaultView.getComputedStyle(t,null);function xe(t,e){return me(t).getPropertyValue(e)}const be=["top","right","bottom","left"];function _e(t,e,i){const s={};i=i?"-"+i:"";for(let n=0;n<4;n++){const o=be[n];s[o]=parseFloat(t[e+"-"+o+i])||0}return s.width=s.left+s.right,s.height=s.top+s.bottom,s}const ye=(t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot);function ve(t,e){if("native"in t)return t;const{canvas:i,currentDevicePixelRatio:s}=e,n=me(i),o="border-box"===n.boxSizing,a=_e(n,"padding"),r=_e(n,"border","width"),{x:l,y:h,box:c}=function(t,e){const i=t.touches,s=i&&i.length?i[0]:t,{offsetX:n,offsetY:o}=s;let a,r,l=!1;if(ye(n,o,t.target))a=n,r=o;else{const t=e.getBoundingClientRect();a=s.clientX-t.left,r=s.clientY-t.top,l=!0}return{x:a,y:r,box:l}}(t,i),d=a.left+(c&&r.left),u=a.top+(c&&r.top);let{width:f,height:g}=e;return o&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/s),y:Math.round((h-u)/g*i.height/s)}}const Me=t=>Math.round(10*t)/10;function we(t,e,i,s){const n=me(t),o=_e(n,"margin"),a=pe(n.maxWidth,t,"clientWidth")||T,r=pe(n.maxHeight,t,"clientHeight")||T,l=function(t,e,i){let s,n;if(void 0===e||void 0===i){const o=t&&ge(t);if(o){const t=o.getBoundingClientRect(),a=me(o),r=_e(a,"border","width"),l=_e(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,s=pe(a.maxWidth,o,"clientWidth"),n=pe(a.maxHeight,o,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:s||T,maxHeight:n||T}}(t,e,i);let{width:h,height:c}=l;if("content-box"===n.boxSizing){const t=_e(n,"border","width"),e=_e(n,"padding");h-=e.width+t.width,c-=e.height+t.height}h=Math.max(0,h-o.width),c=Math.max(0,s?h/s:c-o.height),h=Me(Math.min(h,a,l.maxWidth)),c=Me(Math.min(c,r,l.maxHeight)),h&&!c&&(c=Me(h/2));return(void 0!==e||void 0!==i)&&s&&l.height&&c>l.height&&(c=l.height,h=Me(Math.floor(c*s))),{width:h,height:c}}function ke(t,e,i){const s=e||1,n=Math.floor(t.height*s),o=Math.floor(t.width*s);t.height=Math.floor(t.height),t.width=Math.floor(t.width);const a=t.canvas;return a.style&&(i||!a.style.height&&!a.style.width)&&(a.style.height=`${t.height}px`,a.style.width=`${t.width}px`),(t.currentDevicePixelRatio!==s||a.height!==n||a.width!==o)&&(t.currentDevicePixelRatio=s,a.height=n,a.width=o,t.ctx.setTransform(s,0,0,s,0,0),!0)}const Se=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};fe()&&(window.addEventListener("test",null,e),window.removeEventListener("test",null,e))}catch(t){}return t}();function Pe(t,e){const i=xe(t,e),s=i&&i.match(/^(\d+)(\.\d+)?px$/);return s?+s[1]:void 0}function De(t){return!t||s(t.size)||s(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function Ce(t,e,i,s,n){let o=e[n];return o||(o=e[n]=t.measureText(n).width,i.push(n)),o>s&&(s=o),s}function Oe(t,e,i,s){let o=(s=s||{}).data=s.data||{},a=s.garbageCollect=s.garbageCollect||[];s.font!==e&&(o=s.data={},a=s.garbageCollect=[],s.font=e),t.save(),t.font=e;let r=0;const l=i.length;let h,c,d,u,f;for(h=0;hi.length){for(h=0;h0&&t.stroke()}}function Re(t,e,i){return i=i||.5,!e||t&&t.x>e.left-i&&t.xe.top-i&&t.y0&&""!==r.strokeColor;let c,d;for(t.save(),t.font=a.string,function(t,e){e.translation&&t.translate(e.translation[0],e.translation[1]),s(e.rotation)||t.rotate(e.rotation),e.color&&(t.fillStyle=e.color),e.textAlign&&(t.textAlign=e.textAlign),e.textBaseline&&(t.textBaseline=e.textBaseline)}(t,r),c=0;ct[0])){const o=i||t;void 0===s&&(s=ti("_fallback",t));const a={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:o,_fallback:s,_getTarget:n,override:i=>je([i,...t],e,o,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,s)=>qe(i,s,(()=>function(t,e,i,s){let n;for(const o of e)if(n=ti(Ue(o,t),i),void 0!==n)return Xe(t,n)?Ze(i,s,t,n):n}(s,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>ei(t).includes(e),ownKeys:t=>ei(t),set(t,e,i){const s=t._storage||(t._storage=n());return t[e]=s[e]=i,delete t._keys,!0}})}function $e(t,e,i,s){const a={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:Ye(t,s),setContext:e=>$e(t,e,i,s),override:n=>$e(t.override(n),e,i,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>qe(t,e,(()=>function(t,e,i){const{_proxy:s,_context:a,_subProxy:r,_descriptors:l}=t;let h=s[e];S(h)&&l.isScriptable(e)&&(h=function(t,e,i,s){const{_proxy:n,_context:o,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+Array.from(r).join("->")+"->"+t);r.add(t);let l=e(o,a||s);r.delete(t),Xe(t,l)&&(l=Ze(n._scopes,n,t,l));return l}(e,h,t,i));n(h)&&h.length&&(h=function(t,e,i,s){const{_proxy:n,_context:a,_subProxy:r,_descriptors:l}=i;if(void 0!==a.index&&s(t))return e[a.index%e.length];if(o(e[0])){const i=e,s=n._scopes.filter((t=>t!==i));e=[];for(const o of i){const i=Ze(s,n,t,o);e.push($e(i,a,r&&r[t],l))}}return e}(e,h,t,l.isIndexable));Xe(e,h)&&(h=$e(h,a,r&&r[e],l));return h}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,s)=>(t[i]=s,delete e[i],!0)})}function Ye(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:s=e.indexable,_allKeys:n=e.allKeys}=t;return{allKeys:n,scriptable:i,indexable:s,isScriptable:S(i)?i:()=>i,isIndexable:S(s)?s:()=>s}}const Ue=(t,e)=>t?t+w(e):e,Xe=(t,e)=>o(e)&&"adapters"!==t&&(null===Object.getPrototypeOf(e)||e.constructor===Object);function qe(t,e,i){if(Object.prototype.hasOwnProperty.call(t,e)||"constructor"===e)return t[e];const s=i();return t[e]=s,s}function Ke(t,e,i){return S(t)?t(e,i):t}const Ge=(t,e)=>!0===t?e:"string"==typeof t?M(e,t):void 0;function Je(t,e,i,s,n){for(const o of e){const e=Ge(i,o);if(e){t.add(e);const o=Ke(e._fallback,i,n);if(void 0!==o&&o!==i&&o!==s)return o}else if(!1===e&&void 0!==s&&i!==s)return null}return!1}function Ze(t,e,i,s){const a=e._rootScopes,r=Ke(e._fallback,i,s),l=[...t,...a],h=new Set;h.add(s);let c=Qe(h,l,i,r||i,s);return null!==c&&((void 0===r||r===i||(c=Qe(h,l,r,c,s),null!==c))&&je(Array.from(h),[""],a,r,(()=>function(t,e,i){const s=t._getTarget();e in s||(s[e]={});const a=s[e];if(n(a)&&o(i))return i;return a||{}}(e,i,s))))}function Qe(t,e,i,s,n){for(;i;)i=Je(t,e,i,s,n);return i}function ti(t,e){for(const i of e){if(!i)continue;const e=i[t];if(void 0!==e)return e}}function ei(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return Array.from(e)}(t._scopes)),e}function ii(t,e,i,s){const{iScale:n}=t,{key:o="r"}=this._parsing,a=new Array(s);let r,l,h,c;for(r=0,l=s;re"x"===t?"y":"x";function ai(t,e,i,s){const n=t.skip?e:t,o=e,a=i.skip?e:i,r=q(o,n),l=q(a,o);let h=r/(r+l),c=l/(r+l);h=isNaN(h)?0:h,c=isNaN(c)?0:c;const d=s*h,u=s*c;return{previous:{x:o.x-d*(a.x-n.x),y:o.y-d*(a.y-n.y)},next:{x:o.x+u*(a.x-n.x),y:o.y+u*(a.y-n.y)}}}function ri(t,e="x"){const i=oi(e),s=t.length,n=Array(s).fill(0),o=Array(s);let a,r,l,h=ni(t,0);for(a=0;a!t.skip))),"monotone"===e.cubicInterpolationMode)ri(t,n);else{let i=s?t[t.length-1]:t[0];for(o=0,a=t.length;o0===t||1===t,di=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*O/i),ui=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*O/i)+1,fi={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*E),easeOutSine:t=>Math.sin(t*E),easeInOutSine:t=>-.5*(Math.cos(C*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>ci(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>ci(t)?t:di(t,.075,.3),easeOutElastic:t=>ci(t)?t:ui(t,.075,.3),easeInOutElastic(t){const e=.1125;return ci(t)?t:t<.5?.5*di(2*t,e,.45):.5+.5*ui(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-fi.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*fi.easeInBounce(2*t):.5*fi.easeOutBounce(2*t-1)+.5};function gi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:t.y+i*(e.y-t.y)}}function pi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:"middle"===s?i<.5?t.y:e.y:"after"===s?i<1?t.y:e.y:i>0?e.y:t.y}}function mi(t,e,i,s){const n={x:t.cp2x,y:t.cp2y},o={x:e.cp1x,y:e.cp1y},a=gi(t,n,i),r=gi(n,o,i),l=gi(o,e,i),h=gi(a,r,i),c=gi(r,l,i);return gi(h,c,i)}const xi=/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/,bi=/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;function _i(t,e){const i=(""+t).match(xi);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}const yi=t=>+t||0;function vi(t,e){const i={},s=o(e),n=s?Object.keys(e):e,a=o(t)?s?i=>l(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of n)i[t]=yi(a(t));return i}function Mi(t){return vi(t,{top:"y",right:"x",bottom:"y",left:"x"})}function wi(t){return vi(t,["topLeft","topRight","bottomLeft","bottomRight"])}function ki(t){const e=Mi(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Si(t,e){t=t||{},e=e||ue.font;let i=l(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let s=l(t.style,e.style);s&&!(""+s).match(bi)&&(console.warn('Invalid font style specified: "'+s+'"'),s=void 0);const n={family:l(t.family,e.family),lineHeight:_i(l(t.lineHeight,e.lineHeight),i),size:i,style:s,weight:l(t.weight,e.weight),string:""};return n.string=De(n),n}function Pi(t,e,i,s){let o,a,r,l=!0;for(o=0,a=t.length;oi&&0===t?0:t+e;return{min:a(s,-Math.abs(o)),max:a(n,o)}}function Ci(t,e){return Object.assign(Object.create(t),e)}function Oi(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function Ai(t,e){let i,s;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,s=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=s)}function Ti(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function Li(t){return"angle"===t?{between:J,compare:K,normalize:G}:{between:tt,compare:(t,e)=>t-e,normalize:t=>t}}function Ei({start:t,end:e,count:i,loop:s,style:n}){return{start:t%i,end:e%i,loop:s&&(e-t+1)%i==0,style:n}}function Ri(t,e,i){if(!i)return[t];const{property:s,start:n,end:o}=i,a=e.length,{compare:r,between:l,normalize:h}=Li(s),{start:c,end:d,loop:u,style:f}=function(t,e,i){const{property:s,start:n,end:o}=i,{between:a,normalize:r}=Li(s),l=e.length;let h,c,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,h=0,c=l;hb||l(n,x,p)&&0!==r(n,x),v=()=>!b||0===r(o,p)||l(o,x,p);for(let t=c,i=c;t<=d;++t)m=e[t%a],m.skip||(p=h(m[s]),p!==x&&(b=l(p,n,o),null===_&&y()&&(_=0===r(p,n)?t:i),null!==_&&v()&&(g.push(Ei({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,x=p));return null!==_&&g.push(Ei({start:_,end:d,loop:u,count:a,style:f})),g}function Ii(t,e){const i=[],s=t.segments;for(let n=0;nn&&t[o%e].skip;)o--;return o%=e,{start:n,end:o}}(i,n,o,s);if(!0===s)return Fi(t,[{start:a,end:r,loop:o}],i,e);return Fi(t,function(t,e,i,s){const n=t.length,o=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%n];i.skip||i.stop?l.skip||(s=!1,o.push({start:e%n,end:(a-1)%n,loop:s}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&o.push({start:e%n,end:r%n,loop:s}),o}(i,a,r!s(t[e.axis])));n.lo-=Math.max(0,a);const r=i.slice(n.hi).findIndex((t=>!s(t[e.axis])));n.hi+=Math.max(0,r)}return n}if(o._sharedOptions){const t=a[0],s="function"==typeof t.getRange&&t.getRange(e);if(s){const t=r(a,e,i-s),n=r(a,e,i+s);return{lo:t.lo,hi:n.hi}}}}return{lo:0,hi:a.length-1}}function $i(t,e,i,s,n){const o=t.getSortedVisibleDatasetMetas(),a=i[e];for(let t=0,i=o.length;t{t[a]&&t[a](e[i],n)&&(o.push({element:t,datasetIndex:s,index:l}),r=r||t.inRange(e.x,e.y,n))})),s&&!r?[]:o}var Ki={evaluateInteractionItems:$i,modes:{index(t,e,i,s){const n=ve(e,t),o=i.axis||"x",a=i.includeInvisible||!1,r=i.intersect?Yi(t,n,o,s,a):Xi(t,n,o,!1,s,a),l=[];return r.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=r[0].index,i=t.data[e];i&&!i.skip&&l.push({element:i,datasetIndex:t.index,index:e})})),l):[]},dataset(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;let r=i.intersect?Yi(t,n,o,s,a):Xi(t,n,o,!1,s,a);if(r.length>0){const e=r[0].datasetIndex,i=t.getDatasetMeta(e).data;r=[];for(let t=0;tYi(t,ve(e,t),i.axis||"xy",s,i.includeInvisible||!1),nearest(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;return Xi(t,n,o,i.intersect,s,a)},x:(t,e,i,s)=>qi(t,ve(e,t),"x",i.intersect,s),y:(t,e,i,s)=>qi(t,ve(e,t),"y",i.intersect,s)}};const Gi=["left","top","right","bottom"];function Ji(t,e){return t.filter((t=>t.pos===e))}function Zi(t,e){return t.filter((t=>-1===Gi.indexOf(t.pos)&&t.box.axis===e))}function Qi(t,e){return t.sort(((t,i)=>{const s=e?i:t,n=e?t:i;return s.weight===n.weight?s.index-n.index:s.weight-n.weight}))}function ts(t,e){const i=function(t){const e={};for(const i of t){const{stack:t,pos:s,stackWeight:n}=i;if(!t||!Gi.includes(s))continue;const o=e[t]||(e[t]={count:0,placed:0,weight:0,size:0});o.count++,o.weight+=n}return e}(t),{vBoxMaxWidth:s,hBoxMaxHeight:n}=e;let o,a,r;for(o=0,a=t.length;o{s[t]=Math.max(e[t],i[t])})),s}return s(t?["left","right"]:["top","bottom"])}function os(t,e,i,s){const n=[];let o,a,r,l,h,c;for(o=0,a=t.length,h=0;ot.box.fullSize)),!0),s=Qi(Ji(e,"left"),!0),n=Qi(Ji(e,"right")),o=Qi(Ji(e,"top"),!0),a=Qi(Ji(e,"bottom")),r=Zi(e,"x"),l=Zi(e,"y");return{fullSize:i,leftAndTop:s.concat(o),rightAndBottom:n.concat(l).concat(a).concat(r),chartArea:Ji(e,"chartArea"),vertical:s.concat(n).concat(l),horizontal:o.concat(a).concat(r)}}(t.boxes),l=r.vertical,h=r.horizontal;u(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const c=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:n,availableWidth:o,availableHeight:a,vBoxMaxWidth:o/2/c,hBoxMaxHeight:a/2}),f=Object.assign({},n);is(f,ki(s));const g=Object.assign({maxPadding:f,w:o,h:a,x:n.left,y:n.top},n),p=ts(l.concat(h),d);os(r.fullSize,g,d,p),os(l,g,d,p),os(h,g,d,p)&&os(l,g,d,p),function(t){const e=t.maxPadding;function i(i){const s=Math.max(e[i]-t[i],0);return t[i]+=s,s}t.y+=i("top"),t.x+=i("left"),i("right"),i("bottom")}(g),rs(r.leftAndTop,g,d,p),g.x+=g.w,g.y+=g.h,rs(r.rightAndBottom,g,d,p),t.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},u(r.chartArea,(e=>{const i=e.box;Object.assign(i,t.chartArea),i.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})}))}};class hs{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,i){}removeEventListener(t,e,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,i,s){return e=Math.max(0,e||t.width),i=i||t.height,{width:e,height:Math.max(0,s?Math.floor(e/s):i)}}isAttached(t){return!0}updateConfig(t){}}class cs extends hs{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const ds="$chartjs",us={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},fs=t=>null===t||""===t;const gs=!!Se&&{passive:!0};function ps(t,e,i){t&&t.canvas&&t.canvas.removeEventListener(e,i,gs)}function ms(t,e){for(const i of t)if(i===e||i.contains(e))return!0}function xs(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||ms(i.addedNodes,s),e=e&&!ms(i.removedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}function bs(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||ms(i.removedNodes,s),e=e&&!ms(i.addedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}const _s=new Map;let ys=0;function vs(){const t=window.devicePixelRatio;t!==ys&&(ys=t,_s.forEach(((e,i)=>{i.currentDevicePixelRatio!==t&&e()})))}function Ms(t,e,i){const s=t.canvas,n=s&&ge(s);if(!n)return;const o=ct(((t,e)=>{const s=n.clientWidth;i(t,e),s{const e=t[0],i=e.contentRect.width,s=e.contentRect.height;0===i&&0===s||o(i,s)}));return a.observe(n),function(t,e){_s.size||window.addEventListener("resize",vs),_s.set(t,e)}(t,o),a}function ws(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){_s.delete(t),_s.size||window.removeEventListener("resize",vs)}(t)}function ks(t,e,i){const s=t.canvas,n=ct((e=>{null!==t.ctx&&i(function(t,e){const i=us[t.type]||t.type,{x:s,y:n}=ve(t,e);return{type:i,chart:e,native:t,x:void 0!==s?s:null,y:void 0!==n?n:null}}(e,t))}),t);return function(t,e,i){t&&t.addEventListener(e,i,gs)}(s,e,n),n}class Ss extends hs{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,s=t.getAttribute("height"),n=t.getAttribute("width");if(t[ds]={initial:{height:s,width:n,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",fs(n)){const e=Pe(t,"width");void 0!==e&&(t.width=e)}if(fs(s))if(""===t.style.height)t.height=t.width/(e||2);else{const e=Pe(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e[ds])return!1;const i=e[ds].initial;["height","width"].forEach((t=>{const n=i[t];s(n)?e.removeAttribute(t):e.setAttribute(t,n)}));const n=i.style||{};return Object.keys(n).forEach((t=>{e.style[t]=n[t]})),e.width=e.width,delete e[ds],!0}addEventListener(t,e,i){this.removeEventListener(t,e);const s=t.$proxies||(t.$proxies={}),n={attach:xs,detach:bs,resize:Ms}[e]||ks;s[e]=n(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),s=i[e];if(!s)return;({attach:ws,detach:ws,resize:ws}[e]||ps)(t,e,s),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,s){return we(t,e,i,s)}isAttached(t){const e=t&&ge(t);return!(!e||!e.isConnected)}}function Ps(t){return!fe()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?cs:Ss}var Ds=Object.freeze({__proto__:null,BasePlatform:hs,BasicPlatform:cs,DomPlatform:Ss,_detectPlatform:Ps});const Cs="transparent",Os={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const s=Qt(t||Cs),n=s.valid&&Qt(e||Cs);return n&&n.valid?n.mix(s,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class As{constructor(t,e,i,s){const n=e[i];s=Pi([t.to,s,n,t.from]);const o=Pi([t.from,n,s]);this._active=!0,this._fn=t.fn||Os[t.type||typeof o],this._easing=fi[t.easing]||fi.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=o,this._to=s,this._promises=void 0}active(){return this._active}update(t,e,i){if(this._active){this._notify(!1);const s=this._target[this._prop],n=i-this._start,o=this._duration-n;this._start=i,this._duration=Math.floor(Math.max(o,t.duration)),this._total+=n,this._loop=!!t.loop,this._to=Pi([t.to,e,s,t.from]),this._from=Pi([t.from,s,e])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const e=t-this._start,i=this._duration,s=this._prop,n=this._from,o=this._loop,a=this._to;let r;if(this._active=n!==a&&(o||e1?2-r:r,r=this._easing(Math.min(1,Math.max(0,r))),this._target[s]=this._fn(n,a,r))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t{const a=t[s];if(!o(a))return;const r={};for(const t of e)r[t]=a[t];(n(a.properties)&&a.properties||[s]).forEach((t=>{t!==s&&i.has(t)||i.set(t,r)}))}))}_animateOptions(t,e){const i=e.options,s=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!s)return[];const n=this._createAnimations(s,i);return i.$shared&&function(t,e){const i=[],s=Object.keys(e);for(let e=0;e{t.options=i}),(()=>{})),n}_createAnimations(t,e){const i=this._properties,s=[],n=t.$animations||(t.$animations={}),o=Object.keys(e),a=Date.now();let r;for(r=o.length-1;r>=0;--r){const l=o[r];if("$"===l.charAt(0))continue;if("options"===l){s.push(...this._animateOptions(t,e));continue}const h=e[l];let c=n[l];const d=i.get(l);if(c){if(d&&c.active()){c.update(d,h,a);continue}c.cancel()}d&&d.duration?(n[l]=c=new As(d,t,l,h),s.push(c)):t[l]=h}return s}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(bt.add(this._chart,i),!0):void 0}}function Ls(t,e){const i=t&&t.options||{},s=i.reverse,n=void 0===i.min?e:0,o=void 0===i.max?e:0;return{start:s?o:n,end:s?n:o}}function Es(t,e){const i=[],s=t._getSortedDatasetMetas(e);let n,o;for(n=0,o=s.length;n0||!i&&e<0)return n.index}return null}function Vs(t,e){const{chart:i,_cachedMeta:s}=t,n=i._stacks||(i._stacks={}),{iScale:o,vScale:a,index:r}=s,l=o.axis,h=a.axis,c=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(o,a,s),d=e.length;let u;for(let t=0;ti[t].axis===e)).shift()}function Ws(t,e){const i=t.controller.index,s=t.vScale&&t.vScale.axis;if(s){e=e||t._parsed;for(const t of e){const e=t._stacks;if(!e||void 0===e[s]||void 0===e[s][i])return;delete e[s][i],void 0!==e[s]._visualValues&&void 0!==e[s]._visualValues[i]&&delete e[s]._visualValues[i]}}}const Ns=t=>"reset"===t||"none"===t,Hs=(t,e)=>e?t:Object.assign({},t);class js{static defaults={};static datasetElementType=null;static dataElementType=null;constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=Is(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Ws(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,e=this._cachedMeta,i=this.getDataset(),s=(t,e,i,s)=>"x"===t?e:"r"===t?s:i,n=e.xAxisID=l(i.xAxisID,Bs(t,"x")),o=e.yAxisID=l(i.yAxisID,Bs(t,"y")),a=e.rAxisID=l(i.rAxisID,Bs(t,"r")),r=e.indexAxis,h=e.iAxisID=s(r,n,o,a),c=e.vAxisID=s(r,o,n,a);e.xScale=this.getScaleForId(n),e.yScale=this.getScaleForId(o),e.rScale=this.getScaleForId(a),e.iScale=this.getScaleForId(h),e.vScale=this.getScaleForId(c)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&rt(this._data,this),t._stacked&&Ws(t)}_dataCheck(){const t=this.getDataset(),e=t.data||(t.data=[]),i=this._data;if(o(e)){const t=this._cachedMeta;this._data=function(t,e){const{iScale:i,vScale:s}=e,n="x"===i.axis?"x":"y",o="x"===s.axis?"x":"y",a=Object.keys(t),r=new Array(a.length);let l,h,c;for(l=0,h=a.length;l0&&i._parsed[t-1];if(!1===this._parsing)i._parsed=s,i._sorted=!0,d=s;else{d=n(s[t])?this.parseArrayData(i,s,t,e):o(s[t])?this.parseObjectData(i,s,t,e):this.parsePrimitiveData(i,s,t,e);const a=()=>null===c[l]||f&&c[l]t&&!e.hidden&&e._stacked&&{keys:Es(i,!0),values:null})(e,i,this.chart),h={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY},{min:c,max:d}=function(t){const{min:e,max:i,minDefined:s,maxDefined:n}=t.getUserBounds();return{min:s?e:Number.NEGATIVE_INFINITY,max:n?i:Number.POSITIVE_INFINITY}}(r);let u,f;function g(){f=s[u];const e=f[r.axis];return!a(f[t.axis])||c>e||d=0;--u)if(!g()){this.updateRangeFromParsed(h,t,f,l);break}return h}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let s,n,o;for(s=0,n=e.length;s=0&&tthis.getContext(i,s,e)),c);return f.$shared&&(f.$shared=r,n[o]=Object.freeze(Hs(f,r))),f}_resolveAnimations(t,e,i){const s=this.chart,n=this._cachedDataOpts,o=`animation-${e}`,a=n[o];if(a)return a;let r;if(!1!==s.options.animation){const s=this.chart.config,n=s.datasetAnimationScopeKeys(this._type,e),o=s.getOptionScopes(this.getDataset(),n);r=s.createResolver(o,this.getContext(t,i,e))}const l=new Ts(s,r&&r.animations);return r&&r._cacheable&&(n[o]=Object.freeze(l)),l}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||Ns(t)||this.chart._animationsDisabled}_getSharedOptions(t,e){const i=this.resolveDataElementOptions(t,e),s=this._sharedOptions,n=this.getSharedOptions(i),o=this.includeOptions(e,n)||n!==s;return this.updateSharedOptions(n,e,i),{sharedOptions:n,includeOptions:o}}updateElement(t,e,i,s){Ns(s)?Object.assign(t,i):this._resolveAnimations(e,s).update(t,i)}updateSharedOptions(t,e,i){t&&!Ns(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,s){t.active=s;const n=this.getStyle(e,s);this._resolveAnimations(e,i,s).update(t,{options:!s&&this.getSharedOptions(n)||n})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this._data,i=this._cachedMeta.data;for(const[t,e,i]of this._syncList)this[t](e,i);this._syncList=[];const s=i.length,n=e.length,o=Math.min(n,s);o&&this.parse(0,o),n>s?this._insertElements(s,n-s,t):n{for(t.length+=e,a=t.length-1;a>=o;a--)t[a]=t[a-e]};for(r(n),a=t;a{s[t]=i[t]&&i[t].active()?i[t]._to:this[t]})),s}}function Ys(t,e){const i=t.options.ticks,n=function(t){const e=t.options.offset,i=t._tickSize(),s=t._length/i+(e?0:1),n=t._maxLength/i;return Math.floor(Math.min(s,n))}(t),o=Math.min(i.maxTicksLimit||n,n),a=i.major.enabled?function(t){const e=[];let i,s;for(i=0,s=t.length;io)return function(t,e,i,s){let n,o=0,a=i[0];for(s=Math.ceil(s),n=0;nn)return e}return Math.max(n,1)}(a,e,o);if(r>0){let t,i;const n=r>1?Math.round((h-l)/(r-1)):null;for(Us(e,c,d,s(n)?0:l-n,l),t=0,i=r-1;t"top"===e||"left"===e?t[e]+i:t[e]-i,qs=(t,e)=>Math.min(e||t,t);function Ks(t,e){const i=[],s=t.length/e,n=t.length;let o=0;for(;oa+r)))return h}function Js(t){return t.drawTicks?t.tickLength:0}function Zs(t,e){if(!t.display)return 0;const i=Si(t.font,e),s=ki(t.padding);return(n(t.text)?t.text.length:1)*i.lineHeight+s.height}function Qs(t,e,i){let s=ut(t);return(i&&"right"!==e||!i&&"right"===e)&&(s=(t=>"left"===t?"right":"right"===t?"left":t)(s)),s}class tn extends $s{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){this.options=t.setContext(this.getContext()),this.axis=t.axis,this._userMin=this.parse(t.min),this._userMax=this.parse(t.max),this._suggestedMin=this.parse(t.suggestedMin),this._suggestedMax=this.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:s}=this;return t=r(t,Number.POSITIVE_INFINITY),e=r(e,Number.NEGATIVE_INFINITY),i=r(i,Number.POSITIVE_INFINITY),s=r(s,Number.NEGATIVE_INFINITY),{min:r(t,i),max:r(e,s),minDefined:a(t),maxDefined:a(e)}}getMinMax(t){let e,{min:i,max:s,minDefined:n,maxDefined:o}=this.getUserBounds();if(n&&o)return{min:i,max:s};const a=this.getMatchingVisibleMetas();for(let r=0,l=a.length;rs?s:i,s=n&&i>s?i:s,{min:r(i,r(s,i)),max:r(s,r(i,s))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){d(this.options.beforeUpdate,[this])}update(t,e,i){const{beginAtZero:s,grace:n,ticks:o}=this.options,a=o.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+i.left+i.right:this.height+i.top+i.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=Di(this,n,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const r=a=n||i<=1||!this.isHorizontal())return void(this.labelRotation=s);const h=this._getLabelSizes(),c=h.widest.width,d=h.highest.height,u=Z(this.chart.width-c,0,this.maxWidth);o=t.offset?this.maxWidth/i:u/(i-1),c+6>o&&(o=u/(i-(t.offset?.5:1)),a=this.maxHeight-Js(t.grid)-e.padding-Zs(t.title,this.chart.options.font),r=Math.sqrt(c*c+d*d),l=Y(Math.min(Math.asin(Z((h.highest.height+6)/o,-1,1)),Math.asin(Z(a/r,-1,1))-Math.asin(Z(d/r,-1,1)))),l=Math.max(s,Math.min(n,l))),this.labelRotation=l}afterCalculateLabelRotation(){d(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){d(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:e,options:{ticks:i,title:s,grid:n}}=this,o=this._isVisible(),a=this.isHorizontal();if(o){const o=Zs(s,e.options.font);if(a?(t.width=this.maxWidth,t.height=Js(n)+o):(t.height=this.maxHeight,t.width=Js(n)+o),i.display&&this.ticks.length){const{first:e,last:s,widest:n,highest:o}=this._getLabelSizes(),r=2*i.padding,l=$(this.labelRotation),h=Math.cos(l),c=Math.sin(l);if(a){const e=i.mirror?0:c*n.width+h*o.height;t.height=Math.min(this.maxHeight,t.height+e+r)}else{const e=i.mirror?0:h*n.width+c*o.height;t.width=Math.min(this.maxWidth,t.width+e+r)}this._calculatePadding(e,s,c,h)}}this._handleMargins(),a?(this.width=this._length=e.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=e.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,e,i,s){const{ticks:{align:n,padding:o},position:a}=this.options,r=0!==this.labelRotation,l="top"!==a&&"x"===this.axis;if(this.isHorizontal()){const a=this.getPixelForTick(0)-this.left,h=this.right-this.getPixelForTick(this.ticks.length-1);let c=0,d=0;r?l?(c=s*t.width,d=i*e.height):(c=i*t.height,d=s*e.width):"start"===n?d=e.width:"end"===n?c=t.width:"inner"!==n&&(c=t.width/2,d=e.width/2),this.paddingLeft=Math.max((c-a+o)*this.width/(this.width-a),0),this.paddingRight=Math.max((d-h+o)*this.width/(this.width-h),0)}else{let i=e.height/2,s=t.height/2;"start"===n?(i=0,s=t.height):"end"===n&&(i=e.height,s=0),this.paddingTop=i+o,this.paddingBottom=s+o}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){d(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){let e,i;for(this.beforeTickToLabelConversion(),this.generateTickLabels(t),e=0,i=t.length;e{const i=t.gc,s=i.length/2;let n;if(s>e){for(n=0;n({width:r[t]||0,height:l[t]||0});return{first:P(0),last:P(e-1),widest:P(k),highest:P(S),widths:r,heights:l}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const e=this._startPixel+t*this._length;return Q(this._alignToPixels?Ae(this.chart,e,0):e)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this.ticks||[];if(t>=0&&ta*s?a/i:r/s:r*s0}_computeGridLineItems(t){const e=this.axis,i=this.chart,s=this.options,{grid:n,position:a,border:r}=s,h=n.offset,c=this.isHorizontal(),d=this.ticks.length+(h?1:0),u=Js(n),f=[],g=r.setContext(this.getContext()),p=g.display?g.width:0,m=p/2,x=function(t){return Ae(i,t,p)};let b,_,y,v,M,w,k,S,P,D,C,O;if("top"===a)b=x(this.bottom),w=this.bottom-u,S=b-m,D=x(t.top)+m,O=t.bottom;else if("bottom"===a)b=x(this.top),D=t.top,O=x(t.bottom)-m,w=b+m,S=this.top+u;else if("left"===a)b=x(this.right),M=this.right-u,k=b-m,P=x(t.left)+m,C=t.right;else if("right"===a)b=x(this.left),P=t.left,C=x(t.right)-m,M=b+m,k=this.left+u;else if("x"===e){if("center"===a)b=x((t.top+t.bottom)/2+.5);else if(o(a)){const t=Object.keys(a)[0],e=a[t];b=x(this.chart.scales[t].getPixelForValue(e))}D=t.top,O=t.bottom,w=b+m,S=w+u}else if("y"===e){if("center"===a)b=x((t.left+t.right)/2);else if(o(a)){const t=Object.keys(a)[0],e=a[t];b=x(this.chart.scales[t].getPixelForValue(e))}M=b-m,k=M-u,P=t.left,C=t.right}const A=l(s.ticks.maxTicksLimit,d),T=Math.max(1,Math.ceil(d/A));for(_=0;_0&&(o-=s/2)}d={left:o,top:n,width:s+e.width,height:i+e.height,color:t.backdropColor}}x.push({label:v,font:P,textOffset:O,options:{rotation:m,color:i,strokeColor:o,strokeWidth:h,textAlign:f,textBaseline:A,translation:[M,w],backdrop:d}})}return x}_getXAxisLabelAlignment(){const{position:t,ticks:e}=this.options;if(-$(this.labelRotation))return"top"===t?"left":"right";let i="center";return"start"===e.align?i="left":"end"===e.align?i="right":"inner"===e.align&&(i="inner"),i}_getYAxisLabelAlignment(t){const{position:e,ticks:{crossAlign:i,mirror:s,padding:n}}=this.options,o=t+n,a=this._getLabelSizes().widest.width;let r,l;return"left"===e?s?(l=this.right+n,"near"===i?r="left":"center"===i?(r="center",l+=a/2):(r="right",l+=a)):(l=this.right-o,"near"===i?r="right":"center"===i?(r="center",l-=a/2):(r="left",l=this.left)):"right"===e?s?(l=this.left+n,"near"===i?r="right":"center"===i?(r="center",l-=a/2):(r="left",l-=a)):(l=this.left+o,"near"===i?r="left":"center"===i?(r="center",l+=a/2):(r="right",l=this.right)):r="right",{textAlign:r,x:l}}_computeLabelArea(){if(this.options.ticks.mirror)return;const t=this.chart,e=this.options.position;return"left"===e||"right"===e?{top:0,left:this.left,bottom:t.height,right:this.right}:"top"===e||"bottom"===e?{top:this.top,left:0,bottom:this.bottom,right:t.width}:void 0}drawBackground(){const{ctx:t,options:{backgroundColor:e},left:i,top:s,width:n,height:o}=this;e&&(t.save(),t.fillStyle=e,t.fillRect(i,s,n,o),t.restore())}getLineWidthForValue(t){const e=this.options.grid;if(!this._isVisible()||!e.display)return 0;const i=this.ticks.findIndex((e=>e.value===t));if(i>=0){return e.setContext(this.getContext(i)).lineWidth}return 0}drawGrid(t){const e=this.options.grid,i=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let n,o;const a=(t,e,s)=>{s.width&&s.color&&(i.save(),i.lineWidth=s.width,i.strokeStyle=s.color,i.setLineDash(s.borderDash||[]),i.lineDashOffset=s.borderDashOffset,i.beginPath(),i.moveTo(t.x,t.y),i.lineTo(e.x,e.y),i.stroke(),i.restore())};if(e.display)for(n=0,o=s.length;n{this.drawBackground(),this.drawGrid(t),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:e,draw:t=>{this.drawLabels(t)}}]:[{z:e,draw:t=>{this.draw(t)}}]}getMatchingVisibleMetas(t){const e=this.chart.getSortedVisibleDatasetMetas(),i=this.axis+"AxisID",s=[];let n,o;for(n=0,o=e.length;n{const s=i.split("."),n=s.pop(),o=[t].concat(s).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");ue.route(o,n,l,r)}))}(e,t.defaultRoutes);t.descriptors&&ue.describe(e,t.descriptors)}(t,o,i),this.override&&ue.override(t.id,t.overrides)),o}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,s=this.scope;i in e&&delete e[i],s&&i in ue[s]&&(delete ue[s][i],this.override&&delete re[i])}}class sn{constructor(){this.controllers=new en(js,"datasets",!0),this.elements=new en($s,"elements"),this.plugins=new en(Object,"plugins"),this.scales=new en(tn,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){[...e].forEach((e=>{const s=i||this._getRegistryForType(e);i||s.isForType(e)||s===this.plugins&&e.id?this._exec(t,s,e):u(e,(e=>{const s=i||this._getRegistryForType(e);this._exec(t,s,e)}))}))}_exec(t,e,i){const s=w(t);d(i["before"+s],[],i),e[t](i),d(i["after"+s],[],i)}_getRegistryForType(t){for(let e=0;et.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(s(e,i),t,"stop"),this._notify(s(i,e),t,"start")}}function an(t,e){return e||!1!==t?!0===t?{}:t:null}function rn(t,{plugin:e,local:i},s,n){const o=t.pluginScopeKeys(e),a=t.getOptionScopes(s,o);return i&&e.defaults&&a.push(e.defaults),t.createResolver(a,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function ln(t,e){const i=ue.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function hn(t){if("x"===t||"y"===t||"r"===t)return t}function cn(t,...e){if(hn(t))return t;for(const s of e){const e=s.axis||("top"===(i=s.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.length>1&&hn(t[0].toLowerCase());if(e)return e}var i;throw new Error(`Cannot determine type of '${t}' axis. Please provide 'axis' or 'position' option.`)}function dn(t,e,i){if(i[e+"AxisID"]===t)return{axis:e}}function un(t,e){const i=re[t.type]||{scales:{}},s=e.scales||{},n=ln(t.type,e),a=Object.create(null);return Object.keys(s).forEach((e=>{const r=s[e];if(!o(r))return console.error(`Invalid scale configuration for scale: ${e}`);if(r._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${e}`);const l=cn(e,r,function(t,e){if(e.data&&e.data.datasets){const i=e.data.datasets.filter((e=>e.xAxisID===t||e.yAxisID===t));if(i.length)return dn(t,"x",i[0])||dn(t,"y",i[0])}return{}}(e,t),ue.scales[r.type]),h=function(t,e){return t===e?"_index_":"_value_"}(l,n),c=i.scales||{};a[e]=b(Object.create(null),[{axis:l},r,c[l],c[h]])})),t.data.datasets.forEach((i=>{const n=i.type||t.type,o=i.indexAxis||ln(n,e),r=(re[n]||{}).scales||{};Object.keys(r).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,o),n=i[e+"AxisID"]||e;a[n]=a[n]||Object.create(null),b(a[n],[{axis:e},s[n],r[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];b(e,[ue.scales[e.type],ue.scale])})),a}function fn(t){const e=t.options||(t.options={});e.plugins=l(e.plugins,{}),e.scales=un(t,e)}function gn(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const pn=new Map,mn=new Set;function xn(t,e){let i=pn.get(t);return i||(i=e(),pn.set(t,i),mn.add(i)),i}const bn=(t,e,i)=>{const s=M(e,i);void 0!==s&&t.add(s)};class _n{constructor(t){this._config=function(t){return(t=t||{}).data=gn(t.data),fn(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=gn(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),fn(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return xn(t,(()=>[[`datasets.${t}`,""]]))}datasetAnimationScopeKeys(t,e){return xn(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]]))}datasetElementScopeKeys(t,e){return xn(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]]))}pluginScopeKeys(t){const e=t.id;return xn(`${this.type}-plugin-${e}`,(()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let s=i.get(t);return s&&!e||(s=new Map,i.set(t,s)),s}getOptionScopes(t,e,i){const{options:s,type:n}=this,o=this._cachedScopes(t,i),a=o.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>bn(r,t,e)))),e.forEach((t=>bn(r,s,t))),e.forEach((t=>bn(r,re[n]||{},t))),e.forEach((t=>bn(r,ue,t))),e.forEach((t=>bn(r,le,t)))}));const l=Array.from(r);return 0===l.length&&l.push(Object.create(null)),mn.has(e)&&o.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,re[e]||{},ue.datasets[e]||{},{type:e},ue,le]}resolveNamedOptions(t,e,i,s=[""]){const o={$shared:!0},{resolver:a,subPrefixes:r}=yn(this._resolverCache,t,s);let l=a;if(function(t,e){const{isScriptable:i,isIndexable:s}=Ye(t);for(const o of e){const e=i(o),a=s(o),r=(a||e)&&t[o];if(e&&(S(r)||vn(r))||a&&n(r))return!0}return!1}(a,e)){o.$shared=!1;l=$e(a,i=S(i)?i():i,this.createResolver(t,i,r))}for(const t of e)o[t]=l[t];return o}createResolver(t,e,i=[""],s){const{resolver:n}=yn(this._resolverCache,t,i);return o(e)?$e(n,e,void 0,s):n}}function yn(t,e,i){let s=t.get(e);s||(s=new Map,t.set(e,s));const n=i.join();let o=s.get(n);if(!o){o={resolver:je(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},s.set(n,o)}return o}const vn=t=>o(t)&&Object.getOwnPropertyNames(t).some((e=>S(t[e])));const Mn=["top","bottom","left","right","chartArea"];function wn(t,e){return"top"===t||"bottom"===t||-1===Mn.indexOf(t)&&"x"===e}function kn(t,e){return function(i,s){return i[t]===s[t]?i[e]-s[e]:i[t]-s[t]}}function Sn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),d(i&&i.onComplete,[t],e)}function Pn(t){const e=t.chart,i=e.options.animation;d(i&&i.onProgress,[t],e)}function Dn(t){return fe()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const Cn={},On=t=>{const e=Dn(t);return Object.values(Cn).filter((t=>t.canvas===e)).pop()};function An(t,e,i){const s=Object.keys(t);for(const n of s){const s=+n;if(s>=e){const o=t[n];delete t[n],(i>0||s>e)&&(t[s+i]=o)}}}class Tn{static defaults=ue;static instances=Cn;static overrides=re;static registry=nn;static version="4.5.0";static getChart=On;static register(...t){nn.add(...t),Ln()}static unregister(...t){nn.remove(...t),Ln()}constructor(t,e){const s=this.config=new _n(e),n=Dn(t),o=On(n);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const a=s.createResolver(s.chartOptionScopes(),this.getContext());this.platform=new(s.platform||Ps(n)),this.platform.updateConfig(s);const r=this.platform.acquireContext(n,a.aspectRatio),l=r&&r.canvas,h=l&&l.height,c=l&&l.width;this.id=i(),this.ctx=r,this.canvas=l,this.width=c,this.height=h,this._options=a,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new on,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=dt((t=>this.update(t)),a.resizeDelay||0),this._dataChanges=[],Cn[this.id]=this,r&&l?(bt.listen(this,"complete",Sn),bt.listen(this,"progress",Pn),this._initialize(),this.attached&&this.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:n,_aspectRatio:o}=this;return s(t)?e&&o?o:n?i/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return nn}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():ke(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Te(this.canvas,this.ctx),this}stop(){return bt.stop(this),this}resize(t,e){bt.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this.options,s=this.canvas,n=i.maintainAspectRatio&&this.aspectRatio,o=this.platform.getMaximumSize(s,t,e,n),a=i.devicePixelRatio||this.platform.getDevicePixelRatio(),r=this.width?"resize":"attach";this.width=o.width,this.height=o.height,this._aspectRatio=this.aspectRatio,ke(this,a,!0)&&(this.notifyPlugins("resize",{size:o}),d(i.onResize,[this,o],this),this.attached&&this._doResize(r)&&this.render())}ensureScalesHaveIDs(){u(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this.options,e=t.scales,i=this.scales,s=Object.keys(i).reduce(((t,e)=>(t[e]=!1,t)),{});let n=[];e&&(n=n.concat(Object.keys(e).map((t=>{const i=e[t],s=cn(t,i),n="r"===s,o="x"===s;return{options:i,dposition:n?"chartArea":o?"bottom":"left",dtype:n?"radialLinear":o?"category":"linear"}})))),u(n,(e=>{const n=e.options,o=n.id,a=cn(o,n),r=l(n.type,e.dtype);void 0!==n.position&&wn(n.position,a)===wn(e.dposition)||(n.position=e.dposition),s[o]=!0;let h=null;if(o in i&&i[o].type===r)h=i[o];else{h=new(nn.getScale(r))({id:o,type:r,ctx:this.ctx,chart:this}),i[h.id]=h}h.init(n,t)})),u(s,((t,e)=>{t||delete i[e]})),u(i,(t=>{ls.configure(this,t,t.options),ls.addBox(this,t)}))}_updateMetasets(){const t=this._metasets,e=this.data.datasets.length,i=t.length;if(t.sort(((t,e)=>t.index-e.index)),i>e){for(let t=e;te.length&&delete this._stacks,t.forEach(((t,i)=>{0===e.filter((e=>e===t._dataset)).length&&this._destroyDatasetMeta(i)}))}buildOrUpdateControllers(){const t=[],e=this.data.datasets;let i,s;for(this._removeUnreferencedMetasets(),i=0,s=e.length;i{this.getDatasetMeta(e).controller.reset()}),this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this.config;e.update();const i=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!i.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),!1===this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const n=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let o=0;for(let t=0,e=this.data.datasets.length;t{t.reset()})),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(kn("z","_idx"));const{_active:a,_lastEvent:r}=this;r?this._eventHandler(r,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){u(this.scales,(t=>{ls.removeBox(this,t)})),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,e=new Set(Object.keys(this._listeners)),i=new Set(t.events);P(e,i)&&!!this._responsiveListeners===t.responsive||(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(const{method:i,start:s,count:n}of e){An(t,s,"_removeElements"===i?-n:n)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const e=this.data.datasets.length,i=e=>new Set(t.filter((t=>t[0]===e)).map(((t,e)=>e+","+t.splice(1).join(",")))),s=i(0);for(let t=1;tt.split(","))).map((t=>({method:t[1],start:+t[2],count:+t[3]})))}_updateLayout(t){if(!1===this.notifyPlugins("beforeLayout",{cancelable:!0}))return;ls.update(this,this.width,this.height,t);const e=this.chartArea,i=e.width<=0||e.height<=0;this._layers=[],u(this.boxes,(t=>{i&&"chartArea"===t.position||(t.configure&&t.configure(),this._layers.push(...t._layers()))}),this),this._layers.forEach(((t,e)=>{t._idx=e})),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(!1!==this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let t=0,e=this.data.datasets.length;t=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this.ctx,i={meta:t,index:t.index,cancelable:!0},s=Ni(this,t);!1!==this.notifyPlugins("beforeDatasetDraw",i)&&(s&&Ie(e,s),t.controller.draw(),s&&ze(e),i.cancelable=!1,this.notifyPlugins("afterDatasetDraw",i))}isPointInArea(t){return Re(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,e,i,s){const n=Ki.modes[e];return"function"==typeof n?n(this,t,i,s):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let s=i.filter((t=>t&&t._dataset===e)).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},i.push(s)),s}getContext(){return this.$context||(this.$context=Ci(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,i){const s=i?"show":"hide",n=this.getDatasetMeta(t),o=n.controller._resolveAnimations(void 0,s);k(e)?(n.data[e].hidden=!i,this.update()):(this.setDatasetVisibility(t,i),o.update(n,{visible:i}),this.update((e=>e.datasetIndex===t?s:void 0)))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){const e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),bt.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,i,s),t[i]=s},s=(t,e,i)=>{t.offsetX=e,t.offsetY=i,this._eventHandler(t)};u(this.options.events,(t=>i(t,s)))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,e=this.platform,i=(i,s)=>{e.addEventListener(this,i,s),t[i]=s},s=(i,s)=>{t[i]&&(e.removeEventListener(this,i,s),delete t[i])},n=(t,e)=>{this.canvas&&this.resize(t,e)};let o;const a=()=>{s("attach",a),this.attached=!0,this.resize(),i("resize",n),i("detach",o)};o=()=>{this.attached=!1,s("resize",n),this._stop(),this._resize(0,0),i("attach",a)},e.isAttached(this.canvas)?a():o()}unbindEvents(){u(this._listeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._listeners={},u(this._responsiveListeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._responsiveListeners=void 0}updateHoverStyle(t,e,i){const s=i?"set":"remove";let n,o,a,r;for("dataset"===e&&(n=this.getDatasetMeta(t[0].datasetIndex),n.controller["_"+s+"DatasetHoverStyle"]()),a=0,r=t.length;a{const i=this.getDatasetMeta(t);if(!i)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:i.data[e],index:e}}));!f(i,e)&&(this._active=i,this._lastEvent=null,this._updateHoverStyles(i,e))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}isPluginEnabled(t){return 1===this._plugins._cache.filter((e=>e.plugin.id===t)).length}_updateHoverStyles(t,e,i){const s=this.options.hover,n=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),o=n(e,t),a=i?t:n(t,e);o.length&&this.updateHoverStyle(o,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,e){const i={event:t,replay:e,cancelable:!0,inChartArea:this.isPointInArea(t)},s=e=>(e.options.events||this.options.events).includes(t.native.type);if(!1===this.notifyPlugins("beforeEvent",i,s))return;const n=this._handleEvent(t,e,i.inChartArea);return i.cancelable=!1,this.notifyPlugins("afterEvent",i,s),(n||i.changed)&&this.render(),this}_handleEvent(t,e,i){const{_active:s=[],options:n}=this,o=e,a=this._getActiveElements(t,s,i,o),r=D(t),l=function(t,e,i,s){return i&&"mouseout"!==t.type?s?e:t:null}(t,this._lastEvent,i,r);i&&(this._lastEvent=null,d(n.onHover,[t,a,this],this),r&&d(n.onClick,[t,a,this],this));const h=!f(a,s);return(h||e)&&(this._active=a,this._updateHoverStyles(a,s,e)),this._lastEvent=l,h}_getActiveElements(t,e,i,s){if("mouseout"===t.type)return[];if(!i)return e;const n=this.options.hover;return this.getElementsAtEventForMode(t,n.mode,n,s)}}function Ln(){return u(Tn.instances,(t=>t._plugins.invalidate()))}function En(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class Rn{static override(t){Object.assign(Rn.prototype,t)}options;constructor(t){this.options=t||{}}init(){}formats(){return En()}parse(){return En()}format(){return En()}add(){return En()}diff(){return En()}startOf(){return En()}endOf(){return En()}}var In={_date:Rn};function zn(t){const e=t.iScale,i=function(t,e){if(!t._cache.$bar){const i=t.getMatchingVisibleMetas(e);let s=[];for(let e=0,n=i.length;et-e)))}return t._cache.$bar}(e,t.type);let s,n,o,a,r=e._length;const l=()=>{32767!==o&&-32768!==o&&(k(a)&&(r=Math.min(r,Math.abs(o-a)||r)),a=o)};for(s=0,n=i.length;sMath.abs(r)&&(l=r,h=a),e[i.axis]=h,e._custom={barStart:l,barEnd:h,start:n,end:o,min:a,max:r}}(t,e,i,s):e[i.axis]=i.parse(t,s),e}function Vn(t,e,i,s){const n=t.iScale,o=t.vScale,a=n.getLabels(),r=n===o,l=[];let h,c,d,u;for(h=i,c=i+s;ht.x,i="left",s="right"):(e=t.base"spacing"!==t,_indexable:t=>"spacing"!==t&&!t.startsWith("borderDash")&&!t.startsWith("hoverBorderDash")};static overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length){const{labels:{pointStyle:i,color:s}}=t.legend.options;return e.labels.map(((e,n)=>{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}}};constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,s=this._cachedMeta;if(!1===this._parsing)s._parsed=i;else{let n,a,r=t=>+i[t];if(o(i[t])){const{key:t="value"}=this._parsing;r=e=>+M(i[e],t)}for(n=t,a=t+e;nJ(t,r,l,!0)?1:Math.max(e,e*i,s,s*i),g=(t,e,s)=>J(t,r,l,!0)?-1:Math.min(e,e*i,s,s*i),p=f(0,h,d),m=f(E,c,u),x=g(C,h,d),b=g(C+E,c,u);s=(p-x)/2,n=(m-b)/2,o=-(p+x)/2,a=-(m+b)/2}return{ratioX:s,ratioY:n,offsetX:o,offsetY:a}}(u,d,r),x=(i.width-o)/f,b=(i.height-o)/g,_=Math.max(Math.min(x,b)/2,0),y=c(this.options.radius,_),v=(y-Math.max(y*r,0))/this._getVisibleDatasetWeightTotal();this.offsetX=p*y,this.offsetY=m*y,s.total=this.calculateTotal(),this.outerRadius=y-v*this._getRingWeightOffset(this.index),this.innerRadius=Math.max(this.outerRadius-v*l,0),this.updateElements(n,0,n.length,t)}_circumference(t,e){const i=this.options,s=this._cachedMeta,n=this._getCircumference();return e&&i.animation.animateRotate||!this.chart.getDataVisibility(t)||null===s._parsed[t]||s.data[t].hidden?0:this.calculateCircumference(s._parsed[t]*n/O)}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.chartArea,r=o.options.animation,l=(a.left+a.right)/2,h=(a.top+a.bottom)/2,c=n&&r.animateScale,d=c?0:this.innerRadius,u=c?0:this.outerRadius,{sharedOptions:f,includeOptions:g}=this._getSharedOptions(e,s);let p,m=this._getRotation();for(p=0;p0&&!isNaN(t)?O*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t],i.options.locale);return{label:s[t]||"",value:n}}getMaxBorderWidth(t){let e=0;const i=this.chart;let s,n,o,a,r;if(!t)for(s=0,n=i.data.datasets.length;s{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t].r,i.options.locale);return{label:s[t]||"",value:n}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}getMinMax(){const t=this._cachedMeta,e={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach(((t,i)=>{const s=this.getParsed(i).r;!isNaN(s)&&this.chart.getDataVisibility(i)&&(se.max&&(e.max=s))})),e}_updateRadius(){const t=this.chart,e=t.chartArea,i=t.options,s=Math.min(e.right-e.left,e.bottom-e.top),n=Math.max(s/2,0),o=(n-Math.max(i.cutoutPercentage?n/100*i.cutoutPercentage:1,0))/t.getVisibleDatasetCount();this.outerRadius=n-o*this.index,this.innerRadius=this.outerRadius-o}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.options.animation,r=this._cachedMeta.rScale,l=r.xCenter,h=r.yCenter,c=r.getIndexAngle(0)-.5*C;let d,u=c;const f=360/this.countVisibleElements();for(d=0;d{!isNaN(this.getParsed(i).r)&&this.chart.getDataVisibility(i)&&e++})),e}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?$(this.resolveDataElementOptions(t,e).angle||i):0}}var Un=Object.freeze({__proto__:null,BarController:class extends js{static id="bar";static defaults={datasetElementType:!1,dataElementType:"bar",categoryPercentage:.8,barPercentage:.9,grouped:!0,animations:{numbers:{type:"number",properties:["x","y","base","width","height"]}}};static overrides={scales:{_index_:{type:"category",offset:!0,grid:{offset:!0}},_value_:{type:"linear",beginAtZero:!0}}};parsePrimitiveData(t,e,i,s){return Vn(t,e,i,s)}parseArrayData(t,e,i,s){return Vn(t,e,i,s)}parseObjectData(t,e,i,s){const{iScale:n,vScale:o}=t,{xAxisKey:a="x",yAxisKey:r="y"}=this._parsing,l="x"===n.axis?a:r,h="x"===o.axis?a:r,c=[];let d,u,f,g;for(d=i,u=i+s;dt.controller.options.grouped)),o=i.options.stacked,a=[],r=this._cachedMeta.controller.getParsed(e),l=r&&r[i.axis],h=t=>{const e=t._parsed.find((t=>t[i.axis]===l)),n=e&&e[t.vScale.axis];if(s(n)||isNaN(n))return!0};for(const i of n)if((void 0===e||!h(i))&&((!1===o||-1===a.indexOf(i.stack)||void 0===o&&void 0===i.stack)&&a.push(i.stack),i.index===t))break;return a.length||a.push(void 0),a}_getStackCount(t){return this._getStacks(void 0,t).length}_getAxisCount(){return this._getAxis().length}getFirstScaleIdForIndexAxis(){const t=this.chart.scales,e=this.chart.options.indexAxis;return Object.keys(t).filter((i=>t[i].axis===e)).shift()}_getAxis(){const t={},e=this.getFirstScaleIdForIndexAxis();for(const i of this.chart.data.datasets)t[l("x"===this.chart.options.indexAxis?i.xAxisID:i.yAxisID,e)]=!0;return Object.keys(t)}_getStackIndex(t,e,i){const s=this._getStacks(t,i),n=void 0!==e?s.indexOf(e):-1;return-1===n?s.length-1:n}_getRuler(){const t=this.options,e=this._cachedMeta,i=e.iScale,s=[];let n,o;for(n=0,o=e.data.length;n=i?1:-1)}(u,e,r)*a,f===r&&(x-=u/2);const t=e.getPixelForDecimal(0),s=e.getPixelForDecimal(1),o=Math.min(t,s),h=Math.max(t,s);x=Math.max(Math.min(x,h),o),d=x+u,i&&!c&&(l._stacks[e.axis]._visualValues[n]=e.getValueForPixel(d)-e.getValueForPixel(x))}if(x===e.getPixelForValue(r)){const t=F(u)*e.getLineWidthForValue(r)/2;x+=t,u-=t}return{size:u,base:x,head:d,center:d+u/2}}_calculateBarIndexPixels(t,e){const i=e.scale,n=this.options,o=n.skipNull,a=l(n.maxBarThickness,1/0);let r,h;const c=this._getAxisCount();if(e.grouped){const i=o?this._getStackCount(t):e.stackCount,d="flex"===n.barThickness?function(t,e,i,s){const n=e.pixels,o=n[t];let a=t>0?n[t-1]:null,r=t=0;--i)e=Math.max(e,t[i].size(this.resolveDataElementOptions(i))/2);return e>0&&e}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart.data.labels||[],{xScale:s,yScale:n}=e,o=this.getParsed(t),a=s.getLabelForValue(o.x),r=n.getLabelForValue(o.y),l=o._custom;return{label:i[t]||"",value:"("+a+", "+r+(l?", "+l:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,s){const n="reset"===s,{iScale:o,vScale:a}=this._cachedMeta,{sharedOptions:r,includeOptions:l}=this._getSharedOptions(e,s),h=o.axis,c=a.axis;for(let d=e;d0&&this.getParsed(e-1);for(let i=0;i<_;++i){const g=t[i],_=x?g:{};if(i=b){_.skip=!0;continue}const v=this.getParsed(i),M=s(v[f]),w=_[u]=a.getPixelForValue(v[u],i),k=_[f]=o||M?r.getBasePixel():r.getPixelForValue(l?this.applyStack(r,v,l):v[f],i);_.skip=isNaN(w)||isNaN(k)||M,_.stop=i>0&&Math.abs(v[u]-y[u])>m,p&&(_.parsed=v,_.raw=h.data[i]),d&&(_.options=c||this.resolveDataElementOptions(i,g.active?"active":n)),x||this.updateElement(g,i,_,n),y=v}}getMaxOverflow(){const t=this._cachedMeta,e=t.dataset,i=e.options&&e.options.borderWidth||0,s=t.data||[];if(!s.length)return i;const n=s[0].size(this.resolveDataElementOptions(0)),o=s[s.length-1].size(this.resolveDataElementOptions(s.length-1));return Math.max(i,n,o)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}},PieController:class extends $n{static id="pie";static defaults={cutout:0,rotation:0,circumference:360,radius:"100%"}},PolarAreaController:Yn,RadarController:class extends js{static id="radar";static defaults={datasetElementType:"line",dataElementType:"point",indexAxis:"r",showLine:!0,elements:{line:{fill:"start"}}};static overrides={aspectRatio:1,scales:{r:{type:"radialLinear"}}};getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta,i=e.dataset,s=e.data||[],n=e.iScale.getLabels();if(i.points=s,"resize"!==t){const e=this.resolveDatasetElementOptions(t);this.options.showLine||(e.borderWidth=0);const o={_loop:!0,_fullLoop:n.length===s.length,options:e};this.updateElement(i,void 0,o,t)}this.updateElements(s,0,s.length,t)}updateElements(t,e,i,s){const n=this._cachedMeta.rScale,o="reset"===s;for(let a=e;a0&&this.getParsed(e-1);for(let c=e;c0&&Math.abs(i[f]-_[f])>x,m&&(p.parsed=i,p.raw=h.data[c]),u&&(p.options=d||this.resolveDataElementOptions(c,e.active?"active":n)),b||this.updateElement(e,c,p,n),_=i}this.updateSharedOptions(d,n,c)}getMaxOverflow(){const t=this._cachedMeta,e=t.data||[];if(!this.options.showLine){let t=0;for(let i=e.length-1;i>=0;--i)t=Math.max(t,e[i].size(this.resolveDataElementOptions(i))/2);return t>0&&t}const i=t.dataset,s=i.options&&i.options.borderWidth||0;if(!e.length)return s;const n=e[0].size(this.resolveDataElementOptions(0)),o=e[e.length-1].size(this.resolveDataElementOptions(e.length-1));return Math.max(s,n,o)/2}}});function Xn(t,e,i,s){const n=vi(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const o=(i-e)/2,a=Math.min(o,s*e/2),r=t=>{const e=(i-Math.min(o,t))*s/2;return Z(t,0,Math.min(o,e))};return{outerStart:r(n.outerStart),outerEnd:r(n.outerEnd),innerStart:Z(n.innerStart,0,a),innerEnd:Z(n.innerEnd,0,a)}}function qn(t,e,i,s){return{x:i+t*Math.cos(e),y:s+t*Math.sin(e)}}function Kn(t,e,i,s,n,o){const{x:a,y:r,startAngle:l,pixelMargin:h,innerRadius:c}=e,d=Math.max(e.outerRadius+s+i-h,0),u=c>0?c+s+i+h:0;let f=0;const g=n-l;if(s){const t=((c>0?c-s:0)+(d>0?d-s:0))/2;f=(g-(0!==t?g*t/(t+s):g))/2}const p=(g-Math.max(.001,g*d-i/C)/d)/2,m=l+p+f,x=n-p-f,{outerStart:b,outerEnd:_,innerStart:y,innerEnd:v}=Xn(e,u,d,x-m),M=d-b,w=d-_,k=m+b/M,S=x-_/w,P=u+y,D=u+v,O=m+y/P,A=x-v/D;if(t.beginPath(),o){const e=(k+S)/2;if(t.arc(a,r,d,k,e),t.arc(a,r,d,e,S),_>0){const e=qn(w,S,a,r);t.arc(e.x,e.y,_,S,x+E)}const i=qn(D,x,a,r);if(t.lineTo(i.x,i.y),v>0){const e=qn(D,A,a,r);t.arc(e.x,e.y,v,x+E,A+Math.PI)}const s=(x-v/u+(m+y/u))/2;if(t.arc(a,r,u,x-v/u,s,!0),t.arc(a,r,u,s,m+y/u,!0),y>0){const e=qn(P,O,a,r);t.arc(e.x,e.y,y,O+Math.PI,m-E)}const n=qn(M,m,a,r);if(t.lineTo(n.x,n.y),b>0){const e=qn(M,k,a,r);t.arc(e.x,e.y,b,m-E,k)}}else{t.moveTo(a,r);const e=Math.cos(k)*d+a,i=Math.sin(k)*d+r;t.lineTo(e,i);const s=Math.cos(S)*d+a,n=Math.sin(S)*d+r;t.lineTo(s,n)}t.closePath()}function Gn(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r,options:l}=e,{borderWidth:h,borderJoinStyle:c,borderDash:d,borderDashOffset:u,borderRadius:f}=l,g="inner"===l.borderAlign;if(!h)return;t.setLineDash(d||[]),t.lineDashOffset=u,g?(t.lineWidth=2*h,t.lineJoin=c||"round"):(t.lineWidth=h,t.lineJoin=c||"bevel");let p=e.endAngle;if(o){Kn(t,e,i,s,p,n);for(let e=0;en?(h=n/l,t.arc(o,a,l,i+h,s-h,!0)):t.arc(o,a,n,i+E,s-E),t.closePath(),t.clip()}(t,e,p),l.selfJoin&&p-a>=C&&0===f&&"miter"!==c&&function(t,e,i){const{startAngle:s,x:n,y:o,outerRadius:a,innerRadius:r,options:l}=e,{borderWidth:h,borderJoinStyle:c}=l,d=Math.min(h/a,G(s-i));if(t.beginPath(),t.arc(n,o,a-h/2,s+d/2,i-d/2),r>0){const e=Math.min(h/r,G(s-i));t.arc(n,o,r+h/2,i-e/2,s+e/2,!0)}else{const e=Math.min(h/2,a*G(s-i));if("round"===c)t.arc(n,o,e,i-C/2,s+C/2,!0);else if("bevel"===c){const a=2*e*e,r=-a*Math.cos(i+C/2)+n,l=-a*Math.sin(i+C/2)+o,h=a*Math.cos(s+C/2)+n,c=a*Math.sin(s+C/2)+o;t.lineTo(r,l),t.lineTo(h,c)}}t.closePath(),t.moveTo(0,0),t.rect(0,0,t.canvas.width,t.canvas.height),t.clip("evenodd")}(t,e,p),o||(Kn(t,e,i,s,p,n),t.stroke())}function Jn(t,e,i=e){t.lineCap=l(i.borderCapStyle,e.borderCapStyle),t.setLineDash(l(i.borderDash,e.borderDash)),t.lineDashOffset=l(i.borderDashOffset,e.borderDashOffset),t.lineJoin=l(i.borderJoinStyle,e.borderJoinStyle),t.lineWidth=l(i.borderWidth,e.borderWidth),t.strokeStyle=l(i.borderColor,e.borderColor)}function Zn(t,e,i){t.lineTo(i.x,i.y)}function Qn(t,e,i={}){const s=t.length,{start:n=0,end:o=s-1}=i,{start:a,end:r}=e,l=Math.max(n,a),h=Math.min(o,r),c=nr&&o>r;return{count:s,start:l,loop:e.loop,ilen:h(a+(h?r-t:t))%o,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=n[b(0)],t.moveTo(d.x,d.y)),c=0;c<=r;++c){if(d=n[b(c)],d.skip)continue;const e=d.x,i=d.y,s=0|e;s===u?(ig&&(g=i),m=(x*m+e)/++x):(_(),t.lineTo(e,i),u=s,x=0,f=g=i),p=i}_()}function io(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?eo:to}const so="function"==typeof Path2D;function no(t,e,i,s){so&&!e.options.segment?function(t,e,i,s){let n=e._path;n||(n=e._path=new Path2D,e.path(n,i,s)&&n.closePath()),Jn(t,e.options),t.stroke(n)}(t,e,i,s):function(t,e,i,s){const{segments:n,options:o}=e,a=io(e);for(const r of n)Jn(t,o,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+s-1})&&t.closePath(),t.stroke()}(t,e,i,s)}class oo extends $s{static id="line";static defaults={borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderWidth:3,capBezierPoints:!0,cubicInterpolationMode:"default",fill:!1,spanGaps:!1,stepped:!1,tension:0};static defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};static descriptors={_scriptable:!0,_indexable:t=>"borderDash"!==t&&"fill"!==t};constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){const i=this.options;if((i.tension||"monotone"===i.cubicInterpolationMode)&&!i.stepped&&!this._pointsUpdated){const s=i.spanGaps?this._loop:this._fullLoop;hi(this._points,i,t,s,e),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=zi(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this.options,s=t[e],n=this.points,o=Ii(this,{property:e,start:s,end:s});if(!o.length)return;const a=[],r=function(t){return t.stepped?pi:t.tension||"monotone"===t.cubicInterpolationMode?mi:gi}(i);let l,h;for(l=0,h=o.length;l"borderDash"!==t};circumference;endAngle;fullCircles;innerRadius;outerRadius;pixelMargin;startAngle;constructor(t){super(),this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,t&&Object.assign(this,t)}inRange(t,e,i){const s=this.getProps(["x","y"],i),{angle:n,distance:o}=X(s,{x:t,y:e}),{startAngle:a,endAngle:r,innerRadius:h,outerRadius:c,circumference:d}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],i),u=(this.options.spacing+this.options.borderWidth)/2,f=l(d,r-a),g=J(n,a,r)&&a!==r,p=f>=O||g,m=tt(o,h+u,c+u);return p&&m}getCenterPoint(t){const{x:e,y:i,startAngle:s,endAngle:n,innerRadius:o,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],t),{offset:r,spacing:l}=this.options,h=(s+n)/2,c=(o+a+l+r)/2;return{x:e+Math.cos(h)*c,y:i+Math.sin(h)*c}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const{options:e,circumference:i}=this,s=(e.offset||0)/4,n=(e.spacing||0)/2,o=e.circular;if(this.pixelMargin="inner"===e.borderAlign?.33:0,this.fullCircles=i>O?Math.floor(i/O):0,0===i||this.innerRadius<0||this.outerRadius<0)return;t.save();const a=(this.startAngle+this.endAngle)/2;t.translate(Math.cos(a)*s,Math.sin(a)*s);const r=s*(1-Math.sin(Math.min(C,i||0)));t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor,function(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r}=e;let l=e.endAngle;if(o){Kn(t,e,i,s,l,n);for(let e=0;e("string"==typeof e?(i=t.push(e)-1,s.unshift({index:i,label:e})):isNaN(e)&&(i=null),i))(t,e,i,s);return n!==t.lastIndexOf(e)?i:n}function mo(t){const e=this.getLabels();return t>=0&&ts=e?s:t,a=t=>n=i?n:t;if(t){const t=F(s),e=F(n);t<0&&e<0?a(0):t>0&&e>0&&o(0)}if(s===n){let e=0===n?1:Math.abs(.05*n);a(n+e),t||o(s-e)}this.min=s,this.max=n}getTickLimit(){const t=this.options.ticks;let e,{maxTicksLimit:i,stepSize:s}=t;return s?(e=Math.ceil(this.max/s)-Math.floor(this.min/s)+1,e>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${e} ticks. Limiting to 1000.`),e=1e3)):(e=this.computeTickLimit(),i=i||11),i&&(e=Math.min(i,e)),e}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,e=t.ticks;let i=this.getTickLimit();i=Math.max(2,i);const n=function(t,e){const i=[],{bounds:n,step:o,min:a,max:r,precision:l,count:h,maxTicks:c,maxDigits:d,includeBounds:u}=t,f=o||1,g=c-1,{min:p,max:m}=e,x=!s(a),b=!s(r),_=!s(h),y=(m-p)/(d+1);let v,M,w,k,S=B((m-p)/g/f)*f;if(S<1e-14&&!x&&!b)return[{value:p},{value:m}];k=Math.ceil(m/S)-Math.floor(p/S),k>g&&(S=B(k*S/g/f)*f),s(l)||(v=Math.pow(10,l),S=Math.ceil(S*v)/v),"ticks"===n?(M=Math.floor(p/S)*S,w=Math.ceil(m/S)*S):(M=p,w=m),x&&b&&o&&H((r-a)/o,S/1e3)?(k=Math.round(Math.min((r-a)/S,c)),S=(r-a)/k,M=a,w=r):_?(M=x?a:M,w=b?r:w,k=h-1,S=(w-M)/k):(k=(w-M)/S,k=V(k,Math.round(k),S/1e3)?Math.round(k):Math.ceil(k));const P=Math.max(U(S),U(M));v=Math.pow(10,s(l)?P:l),M=Math.round(M*v)/v,w=Math.round(w*v)/v;let D=0;for(x&&(u&&M!==a?(i.push({value:a}),Mr)break;i.push({value:t})}return b&&u&&w!==r?i.length&&V(i[i.length-1].value,r,xo(r,y,t))?i[i.length-1].value=r:i.push({value:r}):b&&w!==r||i.push({value:w}),i}({maxTicks:i,bounds:t.bounds,min:t.min,max:t.max,precision:e.precision,step:e.stepSize,count:e.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:e.minRotation||0,includeBounds:!1!==e.includeBounds},this._range||this);return"ticks"===t.bounds&&j(n,this,"value"),t.reverse?(n.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),n}configure(){const t=this.ticks;let e=this.min,i=this.max;if(super.configure(),this.options.offset&&t.length){const s=(i-e)/Math.max(t.length-1,1)/2;e-=s,i+=s}this._startValue=e,this._endValue=i,this._valueRange=i-e}getLabelForValue(t){return ne(t,this.chart.options.locale,this.options.ticks.format)}}class _o extends bo{static id="linear";static defaults={ticks:{callback:ae.formatters.numeric}};determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?t:0,this.max=a(e)?e:1,this.handleTickRangeOptions()}computeTickLimit(){const t=this.isHorizontal(),e=t?this.width:this.height,i=$(this.options.ticks.minRotation),s=(t?Math.sin(i):Math.cos(i))||.001,n=this._resolveTickFontOptions(0);return Math.ceil(e/Math.min(40,n.lineHeight/s))}getPixelForValue(t){return null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}const yo=t=>Math.floor(z(t)),vo=(t,e)=>Math.pow(10,yo(t)+e);function Mo(t){return 1===t/Math.pow(10,yo(t))}function wo(t,e,i){const s=Math.pow(10,i),n=Math.floor(t/s);return Math.ceil(e/s)-n}function ko(t,{min:e,max:i}){e=r(t.min,e);const s=[],n=yo(e);let o=function(t,e){let i=yo(e-t);for(;wo(t,e,i)>10;)i++;for(;wo(t,e,i)<10;)i--;return Math.min(i,yo(t))}(e,i),a=o<0?Math.pow(10,Math.abs(o)):1;const l=Math.pow(10,o),h=n>o?Math.pow(10,n):0,c=Math.round((e-h)*a)/a,d=Math.floor((e-h)/l/10)*l*10;let u=Math.floor((c-d)/Math.pow(10,o)),f=r(t.min,Math.round((h+d+u*Math.pow(10,o))*a)/a);for(;f=10?u=u<15?15:20:u++,u>=20&&(o++,u=2,a=o>=0?1:a),f=Math.round((h+d+u*Math.pow(10,o))*a)/a;const g=r(t.max,f);return s.push({value:g,major:Mo(g),significand:u}),s}class So extends tn{static id="logarithmic";static defaults={ticks:{callback:ae.formatters.logarithmic,major:{enabled:!0}}};constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(t,e){const i=bo.prototype.parse.apply(this,[t,e]);if(0!==i)return a(i)&&i>0?i:null;this._zero=!0}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?Math.max(0,t):null,this.max=a(e)?Math.max(0,e):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!a(this._userMin)&&(this.min=t===vo(this.min,0)?vo(this.min,-1):vo(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let i=this.min,s=this.max;const n=e=>i=t?i:e,o=t=>s=e?s:t;i===s&&(i<=0?(n(1),o(10)):(n(vo(i,-1)),o(vo(s,1)))),i<=0&&n(vo(s,-1)),s<=0&&o(vo(i,1)),this.min=i,this.max=s}buildTicks(){const t=this.options,e=ko({min:this._userMin,max:this._userMax},this);return"ticks"===t.bounds&&j(e,this,"value"),t.reverse?(e.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),e}getLabelForValue(t){return void 0===t?"0":ne(t,this.chart.options.locale,this.options.ticks.format)}configure(){const t=this.min;super.configure(),this._startValue=z(t),this._valueRange=z(this.max)-z(t)}getPixelForValue(t){return void 0!==t&&0!==t||(t=this.min),null===t||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(z(t)-this._startValue)/this._valueRange)}getValueForPixel(t){const e=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+e*this._valueRange)}}function Po(t){const e=t.ticks;if(e.display&&t.display){const t=ki(e.backdropPadding);return l(e.font&&e.font.size,ue.font.size)+t.height}return 0}function Do(t,e,i,s,n){return t===s||t===n?{start:e-i/2,end:e+i/2}:tn?{start:e-i,end:e}:{start:e,end:e+i}}function Co(t){const e={l:t.left+t._padding.left,r:t.right-t._padding.right,t:t.top+t._padding.top,b:t.bottom-t._padding.bottom},i=Object.assign({},e),s=[],o=[],a=t._pointLabels.length,r=t.options.pointLabels,l=r.centerPointLabels?C/a:0;for(let u=0;ue.r&&(r=(s.end-e.r)/o,t.r=Math.max(t.r,e.r+r)),n.starte.b&&(l=(n.end-e.b)/a,t.b=Math.max(t.b,e.b+l))}function Ao(t,e,i){const s=t.drawingArea,{extra:n,additionalAngle:o,padding:a,size:r}=i,l=t.getPointPosition(e,s+n+a,o),h=Math.round(Y(G(l.angle+E))),c=function(t,e,i){90===i||270===i?t-=e/2:(i>270||i<90)&&(t-=e);return t}(l.y,r.h,h),d=function(t){if(0===t||180===t)return"center";if(t<180)return"left";return"right"}(h),u=function(t,e,i){"right"===i?t-=e:"center"===i&&(t-=e/2);return t}(l.x,r.w,d);return{visible:!0,x:l.x,y:c,textAlign:d,left:u,top:c,right:u+r.w,bottom:c+r.h}}function To(t,e){if(!e)return!0;const{left:i,top:s,right:n,bottom:o}=t;return!(Re({x:i,y:s},e)||Re({x:i,y:o},e)||Re({x:n,y:s},e)||Re({x:n,y:o},e))}function Lo(t,e,i){const{left:n,top:o,right:a,bottom:r}=i,{backdropColor:l}=e;if(!s(l)){const i=wi(e.borderRadius),s=ki(e.backdropPadding);t.fillStyle=l;const h=n-s.left,c=o-s.top,d=a-n+s.width,u=r-o+s.height;Object.values(i).some((t=>0!==t))?(t.beginPath(),He(t,{x:h,y:c,w:d,h:u,radius:i}),t.fill()):t.fillRect(h,c,d,u)}}function Eo(t,e,i,s){const{ctx:n}=t;if(i)n.arc(t.xCenter,t.yCenter,e,0,O);else{let i=t.getPointPosition(0,e);n.moveTo(i.x,i.y);for(let o=1;ot,padding:5,centerPointLabels:!1}};static defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"};static descriptors={angleLines:{_fallback:"grid"}};constructor(t){super(t),this.xCenter=void 0,this.yCenter=void 0,this.drawingArea=void 0,this._pointLabels=[],this._pointLabelItems=[]}setDimensions(){const t=this._padding=ki(Po(this.options)/2),e=this.width=this.maxWidth-t.width,i=this.height=this.maxHeight-t.height;this.xCenter=Math.floor(this.left+e/2+t.left),this.yCenter=Math.floor(this.top+i/2+t.top),this.drawingArea=Math.floor(Math.min(e,i)/2)}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!1);this.min=a(t)&&!isNaN(t)?t:0,this.max=a(e)&&!isNaN(e)?e:0,this.handleTickRangeOptions()}computeTickLimit(){return Math.ceil(this.drawingArea/Po(this.options))}generateTickLabels(t){bo.prototype.generateTickLabels.call(this,t),this._pointLabels=this.getLabels().map(((t,e)=>{const i=d(this.options.pointLabels.callback,[t,e],this);return i||0===i?i:""})).filter(((t,e)=>this.chart.getDataVisibility(e)))}fit(){const t=this.options;t.display&&t.pointLabels.display?Co(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,e,i,s){this.xCenter+=Math.floor((t-e)/2),this.yCenter+=Math.floor((i-s)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,e,i,s))}getIndexAngle(t){return G(t*(O/(this._pointLabels.length||1))+$(this.options.startAngle||0))}getDistanceFromCenterForValue(t){if(s(t))return NaN;const e=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*e:(t-this.min)*e}getValueForDistanceFromCenter(t){if(s(t))return NaN;const e=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-e:this.min+e}getPointLabelContext(t){const e=this._pointLabels||[];if(t>=0&&t=0;n--){const e=t._pointLabelItems[n];if(!e.visible)continue;const o=s.setContext(t.getPointLabelContext(n));Lo(i,o,e);const a=Si(o.font),{x:r,y:l,textAlign:h}=e;Ne(i,t._pointLabels[n],r,l+a.lineHeight/2,a,{color:o.color,textAlign:h,textBaseline:"middle"})}}(this,o),s.display&&this.ticks.forEach(((t,e)=>{if(0!==e||0===e&&this.min<0){r=this.getDistanceFromCenterForValue(t.value);const i=this.getContext(e),a=s.setContext(i),l=n.setContext(i);!function(t,e,i,s,n){const o=t.ctx,a=e.circular,{color:r,lineWidth:l}=e;!a&&!s||!r||!l||i<0||(o.save(),o.strokeStyle=r,o.lineWidth=l,o.setLineDash(n.dash||[]),o.lineDashOffset=n.dashOffset,o.beginPath(),Eo(t,i,a,s),o.closePath(),o.stroke(),o.restore())}(this,a,r,o,l)}})),i.display){for(t.save(),a=o-1;a>=0;a--){const s=i.setContext(this.getPointLabelContext(a)),{color:n,lineWidth:o}=s;o&&n&&(t.lineWidth=o,t.strokeStyle=n,t.setLineDash(s.borderDash),t.lineDashOffset=s.borderDashOffset,r=this.getDistanceFromCenterForValue(e.reverse?this.min:this.max),l=this.getPointPosition(a,r),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(l.x,l.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){const t=this.ctx,e=this.options,i=e.ticks;if(!i.display)return;const s=this.getIndexAngle(0);let n,o;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(s),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach(((s,a)=>{if(0===a&&this.min>=0&&!e.reverse)return;const r=i.setContext(this.getContext(a)),l=Si(r.font);if(n=this.getDistanceFromCenterForValue(this.ticks[a].value),r.showLabelBackdrop){t.font=l.string,o=t.measureText(s.label).width,t.fillStyle=r.backdropColor;const e=ki(r.backdropPadding);t.fillRect(-o/2-e.left,-n-l.size/2-e.top,o+e.width,l.size+e.height)}Ne(t,s.label,0,-n,l,{color:r.color,strokeColor:r.textStrokeColor,strokeWidth:r.textStrokeWidth})})),t.restore()}drawTitle(){}}const Io={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},zo=Object.keys(Io);function Fo(t,e){return t-e}function Vo(t,e){if(s(e))return null;const i=t._adapter,{parser:n,round:o,isoWeekday:r}=t._parseOpts;let l=e;return"function"==typeof n&&(l=n(l)),a(l)||(l="string"==typeof n?i.parse(l,n):i.parse(l)),null===l?null:(o&&(l="week"!==o||!N(r)&&!0!==r?i.startOf(l,o):i.startOf(l,"isoWeek",r)),+l)}function Bo(t,e,i,s){const n=zo.length;for(let o=zo.indexOf(t);o=e?i[s]:i[n]]=!0}}else t[e]=!0}function No(t,e,i){const s=[],n={},o=e.length;let a,r;for(a=0;a=0&&(e[l].major=!0);return e}(t,s,n,i):s}class Ho extends tn{static id="time";static defaults={bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{source:"auto",callback:!1,major:{enabled:!1}}};constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e={}){const i=t.time||(t.time={}),s=this._adapter=new In._date(t.adapters.date);s.init(e),b(i.displayFormats,s.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:Vo(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this.options,e=this._adapter,i=t.time.unit||"day";let{min:s,max:n,minDefined:o,maxDefined:r}=this.getUserBounds();function l(t){o||isNaN(t.min)||(s=Math.min(s,t.min)),r||isNaN(t.max)||(n=Math.max(n,t.max))}o&&r||(l(this._getLabelBounds()),"ticks"===t.bounds&&"labels"===t.ticks.source||l(this.getMinMax(!1))),s=a(s)&&!isNaN(s)?s:+e.startOf(Date.now(),i),n=a(n)&&!isNaN(n)?n:+e.endOf(Date.now(),i)+1,this.min=Math.min(s,n-1),this.max=Math.max(s+1,n)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this.options,e=t.time,i=t.ticks,s="labels"===i.source?this.getLabelTimestamps():this._generate();"ticks"===t.bounds&&s.length&&(this.min=this._userMin||s[0],this.max=this._userMax||s[s.length-1]);const n=this.min,o=nt(s,n,this.max);return this._unit=e.unit||(i.autoSkip?Bo(e.minUnit,this.min,this.max,this._getLabelCapacity(n)):function(t,e,i,s,n){for(let o=zo.length-1;o>=zo.indexOf(i);o--){const i=zo[o];if(Io[i].common&&t._adapter.diff(n,s,i)>=e-1)return i}return zo[i?zo.indexOf(i):0]}(this,o.length,e.minUnit,this.min,this.max)),this._majorUnit=i.major.enabled&&"year"!==this._unit?function(t){for(let e=zo.indexOf(t)+1,i=zo.length;e+t.value)))}initOffsets(t=[]){let e,i,s=0,n=0;this.options.offset&&t.length&&(e=this.getDecimalForValue(t[0]),s=1===t.length?1-e:(this.getDecimalForValue(t[1])-e)/2,i=this.getDecimalForValue(t[t.length-1]),n=1===t.length?i:(i-this.getDecimalForValue(t[t.length-2]))/2);const o=t.length<3?.5:.25;s=Z(s,0,o),n=Z(n,0,o),this._offsets={start:s,end:n,factor:1/(s+1+n)}}_generate(){const t=this._adapter,e=this.min,i=this.max,s=this.options,n=s.time,o=n.unit||Bo(n.minUnit,e,i,this._getLabelCapacity(e)),a=l(s.ticks.stepSize,1),r="week"===o&&n.isoWeekday,h=N(r)||!0===r,c={};let d,u,f=e;if(h&&(f=+t.startOf(f,"isoWeek",r)),f=+t.startOf(f,h?"day":o),t.diff(i,e,o)>1e5*a)throw new Error(e+" and "+i+" are too far apart with stepSize of "+a+" "+o);const g="data"===s.ticks.source&&this.getDataTimestamps();for(d=f,u=0;d+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}format(t,e){const i=this.options.time.displayFormats,s=this._unit,n=e||i[s];return this._adapter.format(t,n)}_tickFormatFunction(t,e,i,s){const n=this.options,o=n.ticks.callback;if(o)return d(o,[t,e,i],this);const a=n.time.displayFormats,r=this._unit,l=this._majorUnit,h=r&&a[r],c=l&&a[l],u=i[e],f=l&&c&&u&&u.major;return this._adapter.format(t,s||(f?c:h))}generateTickLabels(t){let e,i,s;for(e=0,i=t.length;e0?a:1}getDataTimestamps(){let t,e,i=this._cache.data||[];if(i.length)return i;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(t=0,e=s.length;t=t[r].pos&&e<=t[l].pos&&({lo:r,hi:l}=it(t,"pos",e)),({pos:s,time:o}=t[r]),({pos:n,time:a}=t[l])):(e>=t[r].time&&e<=t[l].time&&({lo:r,hi:l}=it(t,"time",e)),({time:s,pos:o}=t[r]),({time:n,pos:a}=t[l]));const h=n-s;return h?o+(a-o)*(e-s)/h:o}var $o=Object.freeze({__proto__:null,CategoryScale:class extends tn{static id="category";static defaults={ticks:{callback:mo}};constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){const e=this._addedLabels;if(e.length){const t=this.getLabels();for(const{index:i,label:s}of e)t[i]===s&&t.splice(i,1);this._addedLabels=[]}super.init(t)}parse(t,e){if(s(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:Z(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:po(i,t,l(e,t),this._addedLabels),i.length-1)}determineDataLimits(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let{min:i,max:s}=this.getMinMax(!0);"ticks"===this.options.bounds&&(t||(i=0),e||(s=this.getLabels().length-1)),this.min=i,this.max=s}buildTicks(){const t=this.min,e=this.max,i=this.options.offset,s=[];let n=this.getLabels();n=0===t&&e===n.length-1?n:n.slice(t,e+1),this._valueRange=Math.max(n.length-(i?0:1),1),this._startValue=this.min-(i?.5:0);for(let i=t;i<=e;i++)s.push({value:i});return s}getLabelForValue(t){return mo.call(this,t)}configure(){super.configure(),this.isHorizontal()||(this._reversePixels=!this._reversePixels)}getPixelForValue(t){return"number"!=typeof t&&(t=this.parse(t)),null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}},LinearScale:_o,LogarithmicScale:So,RadialLinearScale:Ro,TimeScale:Ho,TimeSeriesScale:class extends Ho{static id="timeseries";static defaults=Ho.defaults;constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),e=this._table=this.buildLookupTable(t);this._minPos=jo(e,this.min),this._tableRange=jo(e,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:e,max:i}=this,s=[],n=[];let o,a,r,l,h;for(o=0,a=t.length;o=e&&l<=i&&s.push(l);if(s.length<2)return[{time:e,pos:0},{time:i,pos:1}];for(o=0,a=s.length;ot-e))}_getTimestampsForTable(){let t=this._cache.all||[];if(t.length)return t;const e=this.getDataTimestamps(),i=this.getLabelTimestamps();return t=e.length&&i.length?this.normalize(e.concat(i)):e.length?e:i,t=this._cache.all=t,t}getDecimalForValue(t){return(jo(this._table,t)-this._minPos)/this._tableRange}getValueForPixel(t){const e=this._offsets,i=this.getDecimalForPixel(t)/e.factor-e.end;return jo(this._table,i*this._tableRange+this._minPos,!0)}}});const Yo=["rgb(54, 162, 235)","rgb(255, 99, 132)","rgb(255, 159, 64)","rgb(255, 205, 86)","rgb(75, 192, 192)","rgb(153, 102, 255)","rgb(201, 203, 207)"],Uo=Yo.map((t=>t.replace("rgb(","rgba(").replace(")",", 0.5)")));function Xo(t){return Yo[t%Yo.length]}function qo(t){return Uo[t%Uo.length]}function Ko(t){let e=0;return(i,s)=>{const n=t.getDatasetMeta(s).controller;n instanceof $n?e=function(t,e){return t.backgroundColor=t.data.map((()=>Xo(e++))),e}(i,e):n instanceof Yn?e=function(t,e){return t.backgroundColor=t.data.map((()=>qo(e++))),e}(i,e):n&&(e=function(t,e){return t.borderColor=Xo(e),t.backgroundColor=qo(e),++e}(i,e))}}function Go(t){let e;for(e in t)if(t[e].borderColor||t[e].backgroundColor)return!0;return!1}var Jo={id:"colors",defaults:{enabled:!0,forceOverride:!1},beforeLayout(t,e,i){if(!i.enabled)return;const{data:{datasets:s},options:n}=t.config,{elements:o}=n,a=Go(s)||(r=n)&&(r.borderColor||r.backgroundColor)||o&&Go(o)||"rgba(0,0,0,0.1)"!==ue.borderColor||"rgba(0,0,0,0.1)"!==ue.backgroundColor;var r;if(!i.forceOverride&&a)return;const l=Ko(t);s.forEach(l)}};function Zo(t){if(t._decimated){const e=t._data;delete t._decimated,delete t._data,Object.defineProperty(t,"data",{configurable:!0,enumerable:!0,writable:!0,value:e})}}function Qo(t){t.data.datasets.forEach((t=>{Zo(t)}))}var ta={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void Qo(t);const n=t.width;t.data.datasets.forEach(((e,o)=>{const{_data:a,indexAxis:r}=e,l=t.getDatasetMeta(o),h=a||e.data;if("y"===Pi([r,t.options.indexAxis]))return;if(!l.controller.supportsDecimation)return;const c=t.scales[l.xAxisID];if("linear"!==c.type&&"time"!==c.type)return;if(t.options.parsing)return;let{start:d,count:u}=function(t,e){const i=e.length;let s,n=0;const{iScale:o}=t,{min:a,max:r,minDefined:l,maxDefined:h}=o.getUserBounds();return l&&(n=Z(it(e,o.axis,a).lo,0,i-1)),s=h?Z(it(e,o.axis,r).hi+1,n,i)-n:i-n,{start:n,count:s}}(l,h);if(u<=(i.threshold||4*n))return void Zo(e);let f;switch(s(a)&&(e._data=h,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":f=function(t,e,i,s,n){const o=n.samples||s;if(o>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(o-2);let l=0;const h=e+i-1;let c,d,u,f,g,p=e;for(a[l++]=t[p],c=0;cu&&(u=f,d=t[s],g=s);a[l++]=d,p=g}return a[l++]=t[h],a}(h,d,u,n,i);break;case"min-max":f=function(t,e,i,n){let o,a,r,l,h,c,d,u,f,g,p=0,m=0;const x=[],b=e+i-1,_=t[e].x,y=t[b].x-_;for(o=e;og&&(g=l,d=o),p=(m*p+a.x)/++m;else{const i=o-1;if(!s(c)&&!s(d)){const e=Math.min(c,d),s=Math.max(c,d);e!==u&&e!==i&&x.push({...t[e],x:p}),s!==u&&s!==i&&x.push({...t[s],x:p})}o>0&&i!==u&&x.push(t[i]),x.push(a),h=e,m=0,f=g=l,c=d=u=o}}return x}(h,d,u,n);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=f}))},destroy(t){Qo(t)}};function ea(t,e,i,s){if(s)return;let n=e[t],o=i[t];return"angle"===t&&(n=G(n),o=G(o)),{property:t,start:n,end:o}}function ia(t,e,i){for(;e>t;e--){const t=i[e];if(!isNaN(t.x)&&!isNaN(t.y))break}return e}function sa(t,e,i,s){return t&&e?s(t[i],e[i]):t?t[i]:e?e[i]:0}function na(t,e){let i=[],s=!1;return n(t)?(s=!0,i=t):i=function(t,e){const{x:i=null,y:s=null}=t||{},n=e.points,o=[];return e.segments.forEach((({start:t,end:e})=>{e=ia(t,e,n);const a=n[t],r=n[e];null!==s?(o.push({x:a.x,y:s}),o.push({x:r.x,y:s})):null!==i&&(o.push({x:i,y:a.y}),o.push({x:i,y:r.y}))})),o}(t,e),i.length?new oo({points:i,options:{tension:0},_loop:s,_fullLoop:s}):null}function oa(t){return t&&!1!==t.fill}function aa(t,e,i){let s=t[e].fill;const n=[e];let o;if(!i)return s;for(;!1!==s&&-1===n.indexOf(s);){if(!a(s))return s;if(o=t[s],!o)return!1;if(o.visible)return s;n.push(s),s=o.fill}return!1}function ra(t,e,i){const s=function(t){const e=t.options,i=e.fill;let s=l(i&&i.target,i);void 0===s&&(s=!!e.backgroundColor);if(!1===s||null===s)return!1;if(!0===s)return"origin";return s}(t);if(o(s))return!isNaN(s.value)&&s;let n=parseFloat(s);return a(n)&&Math.floor(n)===n?function(t,e,i,s){"-"!==t&&"+"!==t||(i=e+i);if(i===e||i<0||i>=s)return!1;return i}(s[0],e,n,i):["origin","start","end","stack","shape"].indexOf(s)>=0&&s}function la(t,e,i){const s=[];for(let n=0;n=0;--e){const i=n[e].$filler;i&&(i.line.updateControlPoints(o,i.axis),s&&i.fill&&ua(t.ctx,i,o))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const s=t.getSortedVisibleDatasetMetas();for(let e=s.length-1;e>=0;--e){const i=s[e].$filler;oa(i)&&ua(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const s=e.meta.$filler;oa(s)&&"beforeDatasetDraw"===i.drawTime&&ua(t.ctx,s,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const _a=(t,e)=>{let{boxHeight:i=e,boxWidth:s=e}=t;return t.usePointStyle&&(i=Math.min(i,e),s=t.pointStyleWidth||Math.min(s,e)),{boxWidth:s,boxHeight:i,itemHeight:Math.max(e,i)}};class ya extends $s{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){this.maxWidth=t,this.maxHeight=e,this._margins=i,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let e=d(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter((e=>t.filter(e,this.chart.data)))),t.sort&&(e=e.sort(((e,i)=>t.sort(e,i,this.chart.data)))),this.options.reverse&&e.reverse(),this.legendItems=e}fit(){const{options:t,ctx:e}=this;if(!t.display)return void(this.width=this.height=0);const i=t.labels,s=Si(i.font),n=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:r}=_a(i,n);let l,h;e.font=s.string,this.isHorizontal()?(l=this.maxWidth,h=this._fitRows(o,n,a,r)+10):(h=this.maxHeight,l=this._fitCols(o,s,a,r)+10),this.width=Math.min(l,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e,i,s){const{ctx:n,maxWidth:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.lineWidths=[0],h=s+a;let c=t;n.textAlign="left",n.textBaseline="middle";let d=-1,u=-h;return this.legendItems.forEach(((t,f)=>{const g=i+e/2+n.measureText(t.text).width;(0===f||l[l.length-1]+g+2*a>o)&&(c+=h,l[l.length-(f>0?0:1)]=0,u+=h,d++),r[f]={left:0,top:u,row:d,width:g,height:s},l[l.length-1]+=g+a})),c}_fitCols(t,e,i,s){const{ctx:n,maxHeight:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.columnSizes=[],h=o-t;let c=a,d=0,u=0,f=0,g=0;return this.legendItems.forEach(((t,o)=>{const{itemWidth:p,itemHeight:m}=function(t,e,i,s,n){const o=function(t,e,i,s){let n=t.text;n&&"string"!=typeof n&&(n=n.reduce(((t,e)=>t.length>e.length?t:e)));return e+i.size/2+s.measureText(n).width}(s,t,e,i),a=function(t,e,i){let s=t;"string"!=typeof e.text&&(s=va(e,i));return s}(n,s,e.lineHeight);return{itemWidth:o,itemHeight:a}}(i,e,n,t,s);o>0&&u+m+2*a>h&&(c+=d+a,l.push({width:d,height:u}),f+=d+a,g++,d=u=0),r[o]={left:f,top:u,col:g,width:p,height:m},d=Math.max(d,p),u+=m+a})),c+=d,l.push({width:d,height:u}),c}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:e,options:{align:i,labels:{padding:s},rtl:n}}=this,o=Oi(n,this.left,this.width);if(this.isHorizontal()){let n=0,a=ft(i,this.left+s,this.right-this.lineWidths[n]);for(const r of e)n!==r.row&&(n=r.row,a=ft(i,this.left+s,this.right-this.lineWidths[n])),r.top+=this.top+t+s,r.left=o.leftForLtr(o.x(a),r.width),a+=r.width+s}else{let n=0,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height);for(const r of e)r.col!==n&&(n=r.col,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height)),r.top=a,r.left+=this.left+s,r.left=o.leftForLtr(o.x(r.left),r.width),a+=r.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){if(this.options.display){const t=this.ctx;Ie(t,this),this._draw(),ze(t)}}_draw(){const{options:t,columnSizes:e,lineWidths:i,ctx:s}=this,{align:n,labels:o}=t,a=ue.color,r=Oi(t.rtl,this.left,this.width),h=Si(o.font),{padding:c}=o,d=h.size,u=d/2;let f;this.drawTitle(),s.textAlign=r.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=h.string;const{boxWidth:g,boxHeight:p,itemHeight:m}=_a(o,d),x=this.isHorizontal(),b=this._computeTitleHeight();f=x?{x:ft(n,this.left+c,this.right-i[0]),y:this.top+c+b,line:0}:{x:this.left+c,y:ft(n,this.top+b+c,this.bottom-e[0].height),line:0},Ai(this.ctx,t.textDirection);const _=m+c;this.legendItems.forEach(((y,v)=>{s.strokeStyle=y.fontColor,s.fillStyle=y.fontColor;const M=s.measureText(y.text).width,w=r.textAlign(y.textAlign||(y.textAlign=o.textAlign)),k=g+u+M;let S=f.x,P=f.y;r.setWidth(this.width),x?v>0&&S+k+c>this.right&&(P=f.y+=_,f.line++,S=f.x=ft(n,this.left+c,this.right-i[f.line])):v>0&&P+_>this.bottom&&(S=f.x=S+e[f.line].width+c,f.line++,P=f.y=ft(n,this.top+b+c,this.bottom-e[f.line].height));if(function(t,e,i){if(isNaN(g)||g<=0||isNaN(p)||p<0)return;s.save();const n=l(i.lineWidth,1);if(s.fillStyle=l(i.fillStyle,a),s.lineCap=l(i.lineCap,"butt"),s.lineDashOffset=l(i.lineDashOffset,0),s.lineJoin=l(i.lineJoin,"miter"),s.lineWidth=n,s.strokeStyle=l(i.strokeStyle,a),s.setLineDash(l(i.lineDash,[])),o.usePointStyle){const a={radius:p*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},l=r.xPlus(t,g/2);Ee(s,a,l,e+u,o.pointStyleWidth&&g)}else{const o=e+Math.max((d-p)/2,0),a=r.leftForLtr(t,g),l=wi(i.borderRadius);s.beginPath(),Object.values(l).some((t=>0!==t))?He(s,{x:a,y:o,w:g,h:p,radius:l}):s.rect(a,o,g,p),s.fill(),0!==n&&s.stroke()}s.restore()}(r.x(S),P,y),S=gt(w,S+g+u,x?S+k:this.right,t.rtl),function(t,e,i){Ne(s,i.text,t,e+m/2,h,{strikethrough:i.hidden,textAlign:r.textAlign(i.textAlign)})}(r.x(S),P,y),x)f.x+=k+c;else if("string"!=typeof y.text){const t=h.lineHeight;f.y+=va(y,t)+c}else f.y+=_})),Ti(this.ctx,t.textDirection)}drawTitle(){const t=this.options,e=t.title,i=Si(e.font),s=ki(e.padding);if(!e.display)return;const n=Oi(t.rtl,this.left,this.width),o=this.ctx,a=e.position,r=i.size/2,l=s.top+r;let h,c=this.left,d=this.width;if(this.isHorizontal())d=Math.max(...this.lineWidths),h=this.top+l,c=ft(t.align,c,this.right-d);else{const e=this.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);h=l+ft(t.align,this.top,this.bottom-e-t.labels.padding-this._computeTitleHeight())}const u=ft(a,c,c+d);o.textAlign=n.textAlign(ut(a)),o.textBaseline="middle",o.strokeStyle=e.color,o.fillStyle=e.color,o.font=i.string,Ne(o,e.text,u,h,i)}_computeTitleHeight(){const t=this.options.title,e=Si(t.font),i=ki(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){let i,s,n;if(tt(t,this.left,this.right)&&tt(e,this.top,this.bottom))for(n=this.legendHitBoxes,i=0;it.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:s,textAlign:n,color:o,useBorderRadius:a,borderRadius:r}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const l=t.controller.getStyle(i?0:void 0),h=ki(l.borderWidth);return{text:e[t.index].label,fillStyle:l.backgroundColor,fontColor:o,hidden:!t.visible,lineCap:l.borderCapStyle,lineDash:l.borderDash,lineDashOffset:l.borderDashOffset,lineJoin:l.borderJoinStyle,lineWidth:(h.width+h.height)/4,strokeStyle:l.borderColor,pointStyle:s||l.pointStyle,rotation:l.rotation,textAlign:n||l.textAlign,borderRadius:a&&(r||l.borderRadius),datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class wa extends $s{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this.options;if(this.left=0,this.top=0,!i.display)return void(this.width=this.height=this.right=this.bottom=0);this.width=this.right=t,this.height=this.bottom=e;const s=n(i.text)?i.text.length:1;this._padding=ki(i.padding);const o=s*Si(i.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:s,right:n,options:o}=this,a=o.align;let r,l,h,c=0;return this.isHorizontal()?(l=ft(a,i,n),h=e+t,r=n-i):("left"===o.position?(l=i+t,h=ft(a,s,e),c=-.5*C):(l=n-t,h=ft(a,e,s),c=.5*C),r=s-e),{titleX:l,titleY:h,maxWidth:r,rotation:c}}draw(){const t=this.ctx,e=this.options;if(!e.display)return;const i=Si(e.font),s=i.lineHeight/2+this._padding.top,{titleX:n,titleY:o,maxWidth:a,rotation:r}=this._drawArgs(s);Ne(t,e.text,0,0,i,{color:e.color,maxWidth:a,rotation:r,textAlign:ut(e.align),textBaseline:"middle",translation:[n,o]})}}var ka={id:"title",_element:wa,start(t,e,i){!function(t,e){const i=new wa({ctx:t.ctx,options:e,chart:t});ls.configure(t,i,e),ls.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;ls.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const s=t.titleBlock;ls.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Sa=new WeakMap;var Pa={id:"subtitle",start(t,e,i){const s=new wa({ctx:t.ctx,options:i,chart:t});ls.configure(t,s,i),ls.addBox(t,s),Sa.set(t,s)},stop(t){ls.removeBox(t,Sa.get(t)),Sa.delete(t)},beforeUpdate(t,e,i){const s=Sa.get(t);ls.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Da={average(t){if(!t.length)return!1;let e,i,s=new Set,n=0,o=0;for(e=0,i=t.length;et+e))/s.size,y:n/o}},nearest(t,e){if(!t.length)return!1;let i,s,n,o=e.x,a=e.y,r=Number.POSITIVE_INFINITY;for(i=0,s=t.length;i-1?t.split("\n"):t}function Aa(t,e){const{element:i,datasetIndex:s,index:n}=e,o=t.getDatasetMeta(s).controller,{label:a,value:r}=o.getLabelAndValue(n);return{chart:t,label:a,parsed:o.getParsed(n),raw:t.data.datasets[s].data[n],formattedValue:r,dataset:o.getDataset(),dataIndex:n,datasetIndex:s,element:i}}function Ta(t,e){const i=t.chart.ctx,{body:s,footer:n,title:o}=t,{boxWidth:a,boxHeight:r}=e,l=Si(e.bodyFont),h=Si(e.titleFont),c=Si(e.footerFont),d=o.length,f=n.length,g=s.length,p=ki(e.padding);let m=p.height,x=0,b=s.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(b+=t.beforeBody.length+t.afterBody.length,d&&(m+=d*h.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),b){m+=g*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(b-g)*l.lineHeight+(b-1)*e.bodySpacing}f&&(m+=e.footerMarginTop+f*c.lineHeight+(f-1)*e.footerSpacing);let _=0;const y=function(t){x=Math.max(x,i.measureText(t).width+_)};return i.save(),i.font=h.string,u(t.title,y),i.font=l.string,u(t.beforeBody.concat(t.afterBody),y),_=e.displayColors?a+2+e.boxPadding:0,u(s,(t=>{u(t.before,y),u(t.lines,y),u(t.after,y)})),_=0,i.font=c.string,u(t.footer,y),i.restore(),x+=p.width,{width:x,height:m}}function La(t,e,i,s){const{x:n,width:o}=i,{width:a,chartArea:{left:r,right:l}}=t;let h="center";return"center"===s?h=n<=(r+l)/2?"left":"right":n<=o/2?h="left":n>=a-o/2&&(h="right"),function(t,e,i,s){const{x:n,width:o}=s,a=i.caretSize+i.caretPadding;return"left"===t&&n+o+a>e.width||"right"===t&&n-o-a<0||void 0}(h,t,e,i)&&(h="center"),h}function Ea(t,e,i){const s=i.yAlign||e.yAlign||function(t,e){const{y:i,height:s}=e;return it.height-s/2?"bottom":"center"}(t,i);return{xAlign:i.xAlign||e.xAlign||La(t,e,i,s),yAlign:s}}function Ra(t,e,i,s){const{caretSize:n,caretPadding:o,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,h=n+o,{topLeft:c,topRight:d,bottomLeft:u,bottomRight:f}=wi(a);let g=function(t,e){let{x:i,width:s}=t;return"right"===e?i-=s:"center"===e&&(i-=s/2),i}(e,r);const p=function(t,e,i){let{y:s,height:n}=t;return"top"===e?s+=i:s-="bottom"===e?n+i:n/2,s}(e,l,h);return"center"===l?"left"===r?g+=h:"right"===r&&(g-=h):"left"===r?g-=Math.max(c,u)+n:"right"===r&&(g+=Math.max(d,f)+n),{x:Z(g,0,s.width-e.width),y:Z(p,0,s.height-e.height)}}function Ia(t,e,i){const s=ki(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-s.right:t.x+s.left}function za(t){return Ca([],Oa(t))}function Fa(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}const Va={beforeTitle:e,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,s=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(s>0&&e.dataIndex{const e={before:[],lines:[],after:[]},n=Fa(i,t);Ca(e.before,Oa(Ba(n,"beforeLabel",this,t))),Ca(e.lines,Ba(n,"label",this,t)),Ca(e.after,Oa(Ba(n,"afterLabel",this,t))),s.push(e)})),s}getAfterBody(t,e){return za(Ba(e.callbacks,"afterBody",this,t))}getFooter(t,e){const{callbacks:i}=e,s=Ba(i,"beforeFooter",this,t),n=Ba(i,"footer",this,t),o=Ba(i,"afterFooter",this,t);let a=[];return a=Ca(a,Oa(s)),a=Ca(a,Oa(n)),a=Ca(a,Oa(o)),a}_createItems(t){const e=this._active,i=this.chart.data,s=[],n=[],o=[];let a,r,l=[];for(a=0,r=e.length;at.filter(e,s,n,i)))),t.itemSort&&(l=l.sort(((e,s)=>t.itemSort(e,s,i)))),u(l,(e=>{const i=Fa(t.callbacks,e);s.push(Ba(i,"labelColor",this,e)),n.push(Ba(i,"labelPointStyle",this,e)),o.push(Ba(i,"labelTextColor",this,e))})),this.labelColors=s,this.labelPointStyles=n,this.labelTextColors=o,this.dataPoints=l,l}update(t,e){const i=this.options.setContext(this.getContext()),s=this._active;let n,o=[];if(s.length){const t=Da[i.position].call(this,s,this._eventPosition);o=this._createItems(i),this.title=this.getTitle(o,i),this.beforeBody=this.getBeforeBody(o,i),this.body=this.getBody(o,i),this.afterBody=this.getAfterBody(o,i),this.footer=this.getFooter(o,i);const e=this._size=Ta(this,i),a=Object.assign({},t,e),r=Ea(this.chart,i,a),l=Ra(i,a,r,this.chart);this.xAlign=r.xAlign,this.yAlign=r.yAlign,n={opacity:1,x:l.x,y:l.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==this.opacity&&(n={opacity:0});this._tooltipItems=o,this.$context=void 0,n&&this._resolveAnimations().update(this,n),t&&i.external&&i.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,i,s){const n=this.getCaretPosition(t,i,s);e.lineTo(n.x1,n.y1),e.lineTo(n.x2,n.y2),e.lineTo(n.x3,n.y3)}getCaretPosition(t,e,i){const{xAlign:s,yAlign:n}=this,{caretSize:o,cornerRadius:a}=i,{topLeft:r,topRight:l,bottomLeft:h,bottomRight:c}=wi(a),{x:d,y:u}=t,{width:f,height:g}=e;let p,m,x,b,_,y;return"center"===n?(_=u+g/2,"left"===s?(p=d,m=p-o,b=_+o,y=_-o):(p=d+f,m=p+o,b=_-o,y=_+o),x=p):(m="left"===s?d+Math.max(r,h)+o:"right"===s?d+f-Math.max(l,c)-o:this.caretX,"top"===n?(b=u,_=b-o,p=m-o,x=m+o):(b=u+g,_=b+o,p=m+o,x=m-o),y=b),{x1:p,x2:m,x3:x,y1:b,y2:_,y3:y}}drawTitle(t,e,i){const s=this.title,n=s.length;let o,a,r;if(n){const l=Oi(i.rtl,this.x,this.width);for(t.x=Ia(this,i.titleAlign,i),e.textAlign=l.textAlign(i.titleAlign),e.textBaseline="middle",o=Si(i.titleFont),a=i.titleSpacing,e.fillStyle=i.titleColor,e.font=o.string,r=0;r0!==t))?(t.beginPath(),t.fillStyle=n.multiKeyBackground,He(t,{x:e,y:g,w:h,h:l,radius:r}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),He(t,{x:i,y:g+1,w:h-2,h:l-2,radius:r}),t.fill()):(t.fillStyle=n.multiKeyBackground,t.fillRect(e,g,h,l),t.strokeRect(e,g,h,l),t.fillStyle=a.backgroundColor,t.fillRect(i,g+1,h-2,l-2))}t.fillStyle=this.labelTextColors[i]}drawBody(t,e,i){const{body:s}=this,{bodySpacing:n,bodyAlign:o,displayColors:a,boxHeight:r,boxWidth:l,boxPadding:h}=i,c=Si(i.bodyFont);let d=c.lineHeight,f=0;const g=Oi(i.rtl,this.x,this.width),p=function(i){e.fillText(i,g.x(t.x+f),t.y+d/2),t.y+=d+n},m=g.textAlign(o);let x,b,_,y,v,M,w;for(e.textAlign=o,e.textBaseline="middle",e.font=c.string,t.x=Ia(this,m,i),e.fillStyle=i.bodyColor,u(this.beforeBody,p),f=a&&"right"!==m?"center"===o?l/2+h:l+2+h:0,y=0,M=s.length;y0&&e.stroke()}_updateAnimationTarget(t){const e=this.chart,i=this.$animations,s=i&&i.x,n=i&&i.y;if(s||n){const i=Da[t.position].call(this,this._active,this._eventPosition);if(!i)return;const o=this._size=Ta(this,t),a=Object.assign({},i,this._size),r=Ea(e,t,a),l=Ra(t,a,r,e);s._to===l.x&&n._to===l.y||(this.xAlign=r.xAlign,this.yAlign=r.yAlign,this.width=o.width,this.height=o.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,l))}}_willRender(){return!!this.opacity}draw(t){const e=this.options.setContext(this.getContext());let i=this.opacity;if(!i)return;this._updateAnimationTarget(e);const s={width:this.width,height:this.height},n={x:this.x,y:this.y};i=Math.abs(i)<.001?0:i;const o=ki(e.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;e.enabled&&a&&(t.save(),t.globalAlpha=i,this.drawBackground(n,t,s,e),Ai(t,e.textDirection),n.y+=o.top,this.drawTitle(n,t,e),this.drawBody(n,t,e),this.drawFooter(n,t,e),Ti(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this._active,s=t.map((({datasetIndex:t,index:e})=>{const i=this.chart.getDatasetMeta(t);if(!i)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:i.data[e],index:e}})),n=!f(i,s),o=this._positionChanged(s,e);(n||o)&&(this._active=s,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,i=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,n=this._active||[],o=this._getActiveElements(t,n,e,i),a=this._positionChanged(o,t),r=e||!f(o,n)||a;return r&&(this._active=o,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),r}_getActiveElements(t,e,i,s){const n=this.options;if("mouseout"===t.type)return[];if(!s)return e.filter((t=>this.chart.data.datasets[t.datasetIndex]&&void 0!==this.chart.getDatasetMeta(t.datasetIndex).controller.getParsed(t.index)));const o=this.chart.getElementsAtEventForMode(t,n.mode,n,i);return n.reverse&&o.reverse(),o}_positionChanged(t,e){const{caretX:i,caretY:s,options:n}=this,o=Da[n.position].call(this,t,e);return!1!==o&&(i!==o.x||s!==o.y)}}var Na={id:"tooltip",_element:Wa,positioners:Da,afterInit(t,e,i){i&&(t.tooltip=new Wa({chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip;if(e&&e._willRender()){const i={tooltip:e};if(!1===t.notifyPlugins("beforeTooltipDraw",{...i,cancelable:!0}))return;e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i)}},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i,e.inChartArea)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:Va},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:t=>"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};return Tn.register(Un,$o,go,t),Tn.helpers={...Hi},Tn._adapters=In,Tn.Animation=As,Tn.Animations=Ts,Tn.animator=bt,Tn.controllers=nn.controllers.items,Tn.DatasetController=js,Tn.Element=$s,Tn.elements=go,Tn.Interaction=Ki,Tn.layouts=ls,Tn.platforms=Ds,Tn.Scale=tn,Tn.Ticks=ae,Object.assign(Tn,Un,$o,go,t,Ds),Tn.Chart=Tn,"undefined"!=typeof window&&(window.Chart=Tn),Tn})); +//# sourceMappingURL=chart.umd.min.js.map diff --git a/src/PostSharp.LicenseServer.Web/robots.txt b/src/PostSharp.LicenseServer.Web/wwwroot/robots.txt similarity index 100% rename from src/PostSharp.LicenseServer.Web/robots.txt rename to src/PostSharp.LicenseServer.Web/wwwroot/robots.txt diff --git a/tests/PostSharp.LicenseServer.Tests/BuildServerDetectionTests.cs b/tests/PostSharp.LicenseServer.Tests/BuildServerDetectionTests.cs index a9bc6c7..1d3e386 100644 --- a/tests/PostSharp.LicenseServer.Tests/BuildServerDetectionTests.cs +++ b/tests/PostSharp.LicenseServer.Tests/BuildServerDetectionTests.cs @@ -76,6 +76,8 @@ private sealed class StubRepository : ILeaseRepository { public IQueryable OpenLeases => Array.Empty().AsQueryable(); + public IQueryable Leases => Array.Empty().AsQueryable(); + public IQueryable Licenses => Array.Empty().AsQueryable(); public Lease? CreateLease( diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseAllocationTests.cs b/tests/PostSharp.LicenseServer.Tests/LeaseAllocationTests.cs index eb75cce..7278fbe 100644 --- a/tests/PostSharp.LicenseServer.Tests/LeaseAllocationTests.cs +++ b/tests/PostSharp.LicenseServer.Tests/LeaseAllocationTests.cs @@ -54,7 +54,7 @@ public async Task GetLease_GoodExistingLease_IsReusedWithoutInsertingARow() Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); Assert.Equal( existing.LeaseId, lease!.LeaseId ); - Assert.Empty( context.Db.ChangeTracker.Entries().Where( e => e.State == EntityState.Added ) ); + Assert.DoesNotContain( context.Db.ChangeTracker.Entries(), e => e.State == EntityState.Added ); } [Fact] From f0033bd66f756e7883d50b77d506900e5a25be7a Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 09:27:51 +0200 Subject: [PATCH 06/44] Make the database engine selectable and test the application end to end The engine is chosen by LicenseServer:DatabaseProvider, which accepts SqlServer (the default, and the supported engine for a production installation) or Sqlite. SQLite lets the server be evaluated without SQL Server, and lets the test suite host the real application against a database held in memory. The integration tests therefore exercise the production pipeline -- routing, model binding, authorization, the endpoints and the pages -- rather than a parallel arrangement built for testing. Only the license parser, the email sender, the audit key and Windows authentication are substituted, because they need a signed license key, an SMTP server, a key file and a domain controller respectively. Co-Authored-By: Claude Opus 5 --- .../DatabaseRegistration.cs | 46 ++++ .../PostSharp.LicenseServer.Web.csproj | 2 + src/PostSharp.LicenseServer.Web/Program.cs | 5 +- .../LicenseServerApplication.cs | 191 ++++++++++++++ .../LeaseEndpointTests.cs | 244 ++++++++++++++++++ .../PageTests.cs | 191 ++++++++++++++ .../PostSharp.LicenseServer.Tests.csproj | 2 + 7 files changed, 678 insertions(+), 3 deletions(-) create mode 100644 src/PostSharp.LicenseServer.Web/DatabaseRegistration.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/LeaseEndpointTests.cs create mode 100644 tests/PostSharp.LicenseServer.Tests/PageTests.cs diff --git a/src/PostSharp.LicenseServer.Web/DatabaseRegistration.cs b/src/PostSharp.LicenseServer.Web/DatabaseRegistration.cs new file mode 100644 index 0000000..3974554 --- /dev/null +++ b/src/PostSharp.LicenseServer.Web/DatabaseRegistration.cs @@ -0,0 +1,46 @@ +using Microsoft.EntityFrameworkCore; +using PostSharp.LicenseServer.Data; + +namespace PostSharp.LicenseServer; + +/// +/// Chooses the database engine the license server runs against. +/// +public static class DatabaseRegistration +{ + public const string ConnectionStringName = "SharpCrafters_LicenseServerConnectionString"; + + /// + /// Registers the database context against the engine named by LicenseServer:DatabaseProvider. + /// + /// + /// SQL Server is the supported engine for a production installation. SQLite is offered so that + /// the server can be evaluated, and so that the test suite can run the real application against + /// a database held in memory. + /// + public static IServiceCollection AddLicenseServerDatabase( + this IServiceCollection services, + IConfiguration configuration ) + { + string provider = configuration["LicenseServer:DatabaseProvider"] ?? "SqlServer"; + string? connectionString = configuration.GetConnectionString( ConnectionStringName ); + + if ( string.IsNullOrWhiteSpace( connectionString ) ) + { + throw new InvalidOperationException( + $"The connection string '{ConnectionStringName}' is not configured." ); + } + + return provider.ToLowerInvariant() switch + { + "sqlserver" => services.AddDbContext( + options => options.UseSqlServer( connectionString ) ), + + "sqlite" => services.AddDbContext( + options => options.UseSqlite( connectionString ) ), + + _ => throw new InvalidOperationException( + $"Unknown database provider '{provider}'. Use 'SqlServer' or 'Sqlite'." ) + }; + } +} diff --git a/src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj b/src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj index 75de047..1a314cf 100644 --- a/src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj +++ b/src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj @@ -11,6 +11,8 @@ + + diff --git a/src/PostSharp.LicenseServer.Web/Program.cs b/src/PostSharp.LicenseServer.Web/Program.cs index 1d3ef98..23a0e5b 100644 --- a/src/PostSharp.LicenseServer.Web/Program.cs +++ b/src/PostSharp.LicenseServer.Web/Program.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Server.IISIntegration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; +using PostSharp.LicenseServer; using PostSharp.LicenseServer.Data; using PostSharp.LicenseServer.Email; using PostSharp.LicenseServer.Endpoints; @@ -31,9 +32,7 @@ .ValidateDataAnnotations() .ValidateOnStart(); -builder.Services.AddDbContext( - options => options.UseSqlServer( - builder.Configuration.GetConnectionString( "SharpCrafters_LicenseServerConnectionString" ) ) ); +builder.Services.AddLicenseServerDatabase( builder.Configuration ); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs b/tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs new file mode 100644 index 0000000..f0e1851 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs @@ -0,0 +1,191 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using PostSharp.LicenseServer.Data; +using PostSharp.LicenseServer.Email; +using PostSharp.LicenseServer.Licensing; +using PostSharp.LicenseServer.Locking; +using PostSharp.LicenseServer.Options; +using PostSharp.LicenseServer.Security; +using PostSharp.LicenseServer.Tests.Fakes; + +namespace PostSharp.LicenseServer.Tests.Infrastructure; + +/// +/// Hosts the real application in memory, with the SQL Server database swapped for SQLite and the +/// license parser, clock and email sender swapped for test doubles. Everything else -- routing, +/// model binding, authorization, the endpoints themselves -- is the production pipeline. +/// +public sealed class LicenseServerApplication : WebApplicationFactory +{ + private readonly SqliteConnection connection; + + private readonly string connectionString; + + public LicenseServerApplication() + { + // A shared-cache in-memory database, so that the application can open its own connections by + // connection string while the database itself lives only as long as this one stays open. + this.connectionString = $"DataSource=licenseserver-{Guid.NewGuid():N};Mode=Memory;Cache=Shared"; + + this.connection = new SqliteConnection( this.connectionString ); + this.connection.Open(); + + // Created up front, because a test seeds licenses before it issues its first request, which + // is what actually starts the host. + using LicenseServerDbContext db = this.CreateDbContext(); + db.Database.EnsureCreated(); + } + + public FakeLicenseParser LicenseParser { get; } = new(); + + public InMemoryEmailSender EmailSender { get; } = new(); + + /// + /// Gets or sets the lock the lease endpoint uses, so that a test can force the overloaded path. + /// + public ILeaseLock LeaseLock { get; set; } = new InProcessLeaseLock(); + + protected override void ConfigureWebHost( IWebHostBuilder builder ) + { + builder.UseEnvironment( "Testing" ); + + // UseSetting rather than ConfigureAppConfiguration: with the minimal hosting model the + // application reads its configuration while Program.cs runs, which is before the + // ConfigureAppConfiguration callbacks are applied. + Dictionary settings = new() + { + ["LicenseServer:MachinesPerUser"] = "2", + ["LicenseServer:NewLeaseDays"] = "3", + ["LicenseServer:MinLeaseDays"] = "1", + ["LicenseServer:BuildServers"] = "buildagent", + ["LicenseServer:MutexTimeout"] = "5", + ["LicenseServer:DatabaseProvider"] = "Sqlite", + [$"ConnectionStrings:{DatabaseRegistration.ConnectionStringName}"] = this.connectionString, + ["Smtp:Enabled"] = "false" + }; + + foreach ( (string key, string? value) in settings ) + { + builder.UseSetting( key, value ); + } + + builder.ConfigureServices( + services => + { + // The database itself is not swapped here: the application selects SQLite from the + // configuration above, through the same code path a customer would use. + services.RemoveAll(); + services.AddSingleton( this.LicenseParser ); + + services.RemoveAll(); + services.AddSingleton( this.EmailSender ); + + services.RemoveAll(); + services.AddSingleton(); + + services.RemoveAll(); + services.AddSingleton( _ => this.LeaseLock ); + + // Windows authentication cannot be negotiated against an in-memory host. + services.AddAuthentication( TestAuthenticationHandler.SchemeName ) + .AddScheme( + TestAuthenticationHandler.SchemeName, + _ => { } ); + + services.PostConfigure( + options => + { + options.DefaultAuthenticateScheme = TestAuthenticationHandler.SchemeName; + options.DefaultChallengeScheme = TestAuthenticationHandler.SchemeName; + } ); + + services.AddLogging( logging => logging.SetMinimumLevel( LogLevel.Warning ) ); + } ); + } + + public LicenseServerDbContext CreateDbContext() + => new( + new DbContextOptionsBuilder() + .UseSqlite( this.connectionString ) + .Options ); + + /// + /// Registers a license both in the database and with the fake parser. + /// + public License AddLicense( LicenseBuilder builder ) + { + LicenseInfo info = builder.BuildInfo(); + string key = $"FAKE-KEY-{info.LicenseId}"; + + this.LicenseParser.Register( key, info ); + + using LicenseServerDbContext db = this.CreateDbContext(); + + License license = new() + { + LicenseId = info.LicenseId, + LicenseKey = key, + ProductCode = info.Product, + CreatedOn = TestClock.Origin + }; + + db.Licenses.Add( license ); + db.SaveChanges(); + + return license; + } + + protected override void Dispose( bool disposing ) + { + base.Dispose( disposing ); + + if ( disposing ) + { + this.connection.Dispose(); + } + } +} + +/// +/// Authenticates every request as the same Windows-style identity, so the tests exercise the +/// authenticated path without a domain controller. +/// +public sealed class TestAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder ) : AuthenticationHandler( options, logger, encoder ) +{ + public const string SchemeName = "Test"; + + /// + /// The header a test sets to be served anonymously instead. + /// + public const string AnonymousHeader = "X-Test-Anonymous"; + + protected override Task HandleAuthenticateAsync() + { + if ( this.Request.Headers.ContainsKey( AnonymousHeader ) ) + { + return Task.FromResult( AuthenticateResult.NoResult() ); + } + + ClaimsIdentity identity = new( + [new Claim( ClaimTypes.Name, "DOMAIN\\tester" )], + SchemeName ); + + return Task.FromResult( + AuthenticateResult.Success( + new AuthenticationTicket( new ClaimsPrincipal( identity ), SchemeName ) ) ); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseEndpointTests.cs b/tests/PostSharp.LicenseServer.Tests/LeaseEndpointTests.cs new file mode 100644 index 0000000..de0a8a7 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/LeaseEndpointTests.cs @@ -0,0 +1,244 @@ +using System.Net; +using Microsoft.EntityFrameworkCore; +using PostSharp.LicenseServer.Data; +using PostSharp.LicenseServer.Tests.Fakes; +using PostSharp.LicenseServer.Tests.Infrastructure; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// The contract with the PostSharp client: the URL, the query string, the status codes and the +/// body. Deployed clients depend on all four, so they are exercised through the real HTTP pipeline. +/// +public sealed class LeaseEndpointTests : IDisposable +{ + private readonly LicenseServerApplication application = new(); + + public void Dispose() => this.application.Dispose(); + + private static string Url( + string? user = "alice", + string? machine = "desktop-1", + string? product = "Ultimate", + string? version = "2025.1.0", + string? buildDate = null ) + { + List arguments = []; + + if ( user != null ) { arguments.Add( $"user={user}" ); } + if ( machine != null ) { arguments.Add( $"machine={machine}" ); } + if ( product != null ) { arguments.Add( $"product={product}" ); } + if ( version != null ) { arguments.Add( $"version={version}" ); } + if ( buildDate != null ) { arguments.Add( $"buildDate={buildDate}" ); } + + return "/Lease.ashx?" + string.Join( "&", arguments ); + } + + [Fact] + public async Task Lease_Succeeds_ReturnsTheSerializedLease() + { + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( Url() ); + string body = await response.Content.ReadAsStringAsync(); + + Assert.Equal( HttpStatusCode.OK, response.StatusCode ); + Assert.Equal( "text/plain", response.Content.Headers.ContentType?.MediaType ); + Assert.Contains( "License:", body, StringComparison.Ordinal ); + Assert.Contains( "StartTime:", body, StringComparison.Ordinal ); + Assert.Contains( "EndTime:", body, StringComparison.Ordinal ); + Assert.Contains( "RenewTime:", body, StringComparison.Ordinal ); + } + + [Fact] + public async Task Lease_Succeeds_PersistsExactlyOneLease() + { + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + HttpClient client = this.application.CreateClient(); + + await client.GetAsync( Url() ); + + await using LicenseServerDbContext db = this.application.CreateDbContext(); + Lease lease = await db.Leases.SingleAsync(); + + Assert.Equal( "alice", lease.UserName ); + Assert.Equal( "desktop-1", lease.Machine ); + Assert.Equal( "DOMAIN\\tester", lease.AuthenticatedUser ); + } + + [Fact] + public async Task Lease_MixedCaseUserAndMachine_ArePersistedInLowerCase() + { + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + HttpClient client = this.application.CreateClient(); + + await client.GetAsync( Url( user: "Alice", machine: "DESKTOP-1" ) ); + + await using LicenseServerDbContext db = this.application.CreateDbContext(); + Lease lease = await db.Leases.SingleAsync(); + + Assert.Equal( "alice", lease.UserName ); + Assert.Equal( "desktop-1", lease.Machine ); + } + + /// + /// An anonymous request must still be served, and must still be recorded, even though the + /// authenticated name is empty. The column does not accept null. + /// + [Fact] + public async Task Lease_AnonymousRequest_IsServedAndRecorded() + { + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + HttpClient client = this.application.CreateClient(); + client.DefaultRequestHeaders.Add( TestAuthenticationHandler.AnonymousHeader, "1" ); + + HttpResponseMessage response = await client.GetAsync( Url() ); + + Assert.Equal( HttpStatusCode.OK, response.StatusCode ); + + await using LicenseServerDbContext db = this.application.CreateDbContext(); + Lease lease = await db.Leases.SingleAsync(); + Assert.Equal( string.Empty, lease.AuthenticatedUser ); + } + + [Theory] + [InlineData( null, "desktop-1", "Missing query string argument: user." )] + [InlineData( "alice", null, "Missing query string argument: machine." )] + public async Task Lease_MissingArgument_Returns400( string? user, string? machine, string expected ) + { + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( Url( user, machine ) ); + + Assert.Equal( HttpStatusCode.BadRequest, response.StatusCode ); + Assert.Equal( expected, await response.Content.ReadAsStringAsync() ); + } + + [Fact] + public async Task Lease_UnparseableVersion_Returns400() + { + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( Url( version: "not-a-version" ) ); + + Assert.Equal( HttpStatusCode.BadRequest, response.StatusCode ); + Assert.Equal( "Cannot parse the argument: version.", await response.Content.ReadAsStringAsync() ); + } + + [Fact] + public async Task Lease_UnparseableBuildDate_Returns400() + { + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( Url( buildDate: "yesterday" ) ); + + Assert.Equal( HttpStatusCode.BadRequest, response.StatusCode ); + Assert.Equal( "Cannot parse the argument: buildDate.", await response.Content.ReadAsStringAsync() ); + } + + /// + /// Clients older than PostSharp 5 send no version at all, and are treated as 4.9.9. + /// + [Fact] + public async Task Lease_NoVersion_IsTreatedAsPostSharp499() + { + this.application.AddLicense( + LicenseBuilder.Default().WithUsers( 5 ).WithMinPostSharpVersion( new Version( 5, 0, 0 ) ) ); + + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( Url( version: null ) ); + string body = await response.Content.ReadAsStringAsync(); + + Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); + Assert.Contains( "the requested version is 4.9.9", body, StringComparison.Ordinal ); + } + + [Fact] + public async Task Lease_NoCapacity_Returns403WithTheReason() + { + this.application.AddLicense( LicenseBuilder.Default().NotLicenseServerEligible() ); + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( Url() ); + string body = await response.Content.ReadAsStringAsync(); + + Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); + Assert.StartsWith( "No license with free capacity. ", body, StringComparison.Ordinal ); + Assert.Contains( "cannot be used in the license server", body, StringComparison.Ordinal ); + } + + [Fact] + public async Task Lease_NoLicenseAtAll_Returns403() + { + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( Url() ); + + Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); + } + + [Fact] + public async Task Lease_LockTimesOut_Returns503() + { + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + this.application.LeaseLock = new NeverAcquiringLeaseLock(); + + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( Url() ); + + Assert.Equal( HttpStatusCode.ServiceUnavailable, response.StatusCode ); + Assert.Equal( "Service overloaded.", await response.Content.ReadAsStringAsync() ); + } + + /// + /// A build agent gets a licence key but never a stored lease, so that build machines cannot + /// consume the seats of the developers they build for. + /// + [Fact] + public async Task Lease_BuildAgent_IsServedWithoutConsumingASeat() + { + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( Url( machine: "buildagent-1f2e" ) ); + + Assert.Equal( HttpStatusCode.OK, response.StatusCode ); + + await using LicenseServerDbContext db = this.application.CreateDbContext(); + Assert.Equal( 0, await db.Leases.CountAsync() ); + } + + [Fact] + public async Task Lease_RequestedTwiceForTheSameMachine_ReusesTheLease() + { + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + HttpClient client = this.application.CreateClient(); + + await client.GetAsync( Url() ); + await client.GetAsync( Url() ); + + await using LicenseServerDbContext db = this.application.CreateDbContext(); + Assert.Equal( 1, await db.Leases.CountAsync() ); + } + + /// + /// Concurrent requests must not each decide that the last free seat is theirs. + /// + [Fact] + public async Task Lease_ConcurrentRequestsForTheSameMachine_GrantOneLease() + { + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage[] responses = await Task.WhenAll( + Enumerable.Range( 0, 8 ).Select( _ => client.GetAsync( Url() ) ) ); + + Assert.All( responses, r => Assert.Equal( HttpStatusCode.OK, r.StatusCode ) ); + + await using LicenseServerDbContext db = this.application.CreateDbContext(); + Assert.Equal( 1, await db.Leases.CountAsync() ); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/PageTests.cs b/tests/PostSharp.LicenseServer.Tests/PageTests.cs new file mode 100644 index 0000000..2cbc106 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/PageTests.cs @@ -0,0 +1,191 @@ +using System.Net; +using System.Text.Json; +using PostSharp.LicenseServer.Tests.Infrastructure; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// The pages an administrator uses, exercised through the real pipeline. +/// +public sealed class PageTests : IDisposable +{ + private readonly LicenseServerApplication application = new(); + + public void Dispose() => this.application.Dispose(); + + [Theory] + [InlineData( "/" )] + [InlineData( "/Admin/AddLicense" )] + [InlineData( "/Admin/Export" )] + public async Task Page_IsServed( string url ) + { + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( url ); + + Assert.Equal( HttpStatusCode.OK, response.StatusCode ); + } + + [Fact] + public async Task Index_NoLicenses_InvitesTheAdministratorToAddOne() + { + HttpClient client = this.application.CreateClient(); + + string body = await client.GetStringAsync( "/" ); + + Assert.Contains( "No license has been registered yet", body, StringComparison.Ordinal ); + } + + [Fact] + public async Task Index_ListsTheLicense() + { + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 42 ).WithUsers( 7 ) ); + HttpClient client = this.application.CreateClient(); + + string body = await client.GetStringAsync( "/" ); + + Assert.Contains( "42", body, StringComparison.Ordinal ); + Assert.Contains( "Ultimate", body, StringComparison.Ordinal ); + } + + [Fact] + public async Task Details_IsServed() + { + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ) ); + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( "/Admin/Details?id=3" ); + + Assert.Equal( HttpStatusCode.OK, response.StatusCode ); + } + + [Fact] + public async Task Details_UnknownLicense_Returns404() + { + HttpClient client = this.application.CreateClient(); + + Assert.Equal( HttpStatusCode.NotFound, ( await client.GetAsync( "/Admin/Details?id=999" ) ).StatusCode ); + } + + [Fact] + public async Task Graph_IsServedWithItsDataEmbedded() + { + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 5 ).WithUsers( 10 ).WithGracePercent( 20 ) ); + HttpClient client = this.application.CreateClient(); + + string body = await client.GetStringAsync( "/Graph?id=5&days=30" ); + + Assert.Contains( "usage-chart-data", body, StringComparison.Ordinal ); + + // Served from the application, not from a content delivery network. + Assert.Contains( "/lib/chartjs/chart.umd.min.js", body, StringComparison.Ordinal ); + Assert.DoesNotContain( "yui.yahooapis.com", body, StringComparison.Ordinal ); + Assert.DoesNotContain( "http://", body, StringComparison.Ordinal ); + } + + [Fact] + public async Task Graph_EmbeddedDataIsValidJsonCoveringTheWindow() + { + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 5 ).WithUsers( 10 ).WithGracePercent( 20 ) ); + HttpClient client = this.application.CreateClient(); + + string body = await client.GetStringAsync( "/Graph?id=5&days=90" ); + string json = ExtractChartData( body ); + + using JsonDocument document = JsonDocument.Parse( json ); + JsonElement root = document.RootElement; + + Assert.Equal( 90, root.GetProperty( "labels" ).GetArrayLength() ); + Assert.Equal( 90, root.GetProperty( "used" ).GetArrayLength() ); + Assert.Equal( 10, root.GetProperty( "maximum" ).GetInt32() ); + Assert.Equal( 12, root.GetProperty( "grace" ).GetInt32() ); + Assert.True( root.GetProperty( "axisMaximum" ).GetInt32() >= 12 ); + } + + [Fact] + public async Task Graph_UnlimitedLicense_OmitsTheCapacityLines() + { + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 6 ).WithUsers( null ) ); + HttpClient client = this.application.CreateClient(); + + string json = ExtractChartData( await client.GetStringAsync( "/Graph?id=6" ) ); + + using JsonDocument document = JsonDocument.Parse( json ); + + Assert.Equal( JsonValueKind.Null, document.RootElement.GetProperty( "maximum" ).ValueKind ); + Assert.Equal( JsonValueKind.Null, document.RootElement.GetProperty( "grace" ).ValueKind ); + } + + [Fact] + public async Task Graph_UnknownLicense_Returns404() + { + HttpClient client = this.application.CreateClient(); + + Assert.Equal( HttpStatusCode.NotFound, ( await client.GetAsync( "/Graph?id=999" ) ).StatusCode ); + } + + /// + /// The legacy page answered 500 for anything it could not parse. + /// + [Theory] + [InlineData( "/Graph?id=5&days=7" )] + [InlineData( "/Graph?id=5&days=99999" )] + public async Task Graph_UnsupportedWindow_Returns400( string url ) + { + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 5 ) ); + HttpClient client = this.application.CreateClient(); + + Assert.Equal( HttpStatusCode.BadRequest, ( await client.GetAsync( url ) ).StatusCode ); + } + + [Fact] + public async Task GetTime_ReportsTheTimeAndTheAcceleration() + { + HttpClient client = this.application.CreateClient(); + + string body = await client.GetStringAsync( "/GetTime.ashx" ); + string[] fields = body.Split( ';' ); + + // The simulator parses this positionally. + Assert.Equal( 2, fields.Length ); + Assert.EndsWith( "Z", fields[0], StringComparison.Ordinal ); + Assert.Equal( 1m, decimal.Parse( fields[1], System.Globalization.CultureInfo.InvariantCulture ) ); + } + + [Theory] + [InlineData( "/Default.aspx", "/" )] + [InlineData( "/Graph.aspx?id=5", "/Graph?id=5" )] + [InlineData( "/Admin/Details.aspx?id=5", "/Admin/Details?id=5" )] + [InlineData( "/Admin/AddLicense.aspx", "/Admin/AddLicense" )] + public async Task LegacyUrl_RedirectsPermanently( string legacy, string expected ) + { + HttpClient client = this.application.CreateClient( + new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions { AllowAutoRedirect = false } ); + + HttpResponseMessage response = await client.GetAsync( legacy ); + + Assert.Equal( HttpStatusCode.MovedPermanently, response.StatusCode ); + Assert.Equal( expected, response.Headers.Location?.OriginalString ); + } + + [Fact] + public async Task DemoDataGenerator_IsNotAvailableOutsideDevelopment() + { + HttpClient client = this.application.CreateClient(); + + Assert.Equal( HttpStatusCode.NotFound, ( await client.GetAsync( "/Admin/GenerateDemoData" ) ).StatusCode ); + } + + private static string ExtractChartData( string html ) + { + const string opening = "", start, StringComparison.Ordinal ); + + return System.Net.WebUtility.HtmlDecode( html[start..end] ); + } +} diff --git a/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj b/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj index 5694078..282b34c 100644 --- a/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj +++ b/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj @@ -14,12 +14,14 @@ + + From da6b9751cea87e7e7462188925f2cc85114042f8 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 09:36:18 +0200 Subject: [PATCH 07/44] Package the release, port the simulator and document the upgrade Replace the MSBuild Zip target, which depended on a Visual Studio web publishing extension and rewrote web.config at packaging time, with eng/Package.ps1. It runs the tests, publishes and produces the same PostSharp.LicenseServer.zip artifact. Publishing for the Windows runtime keeps the Linux and macOS native libraries out of the package, which takes it from 47 MB to 12 MB. Port the load simulator so that it builds against .NET 10, and leave it disabled: it needs a client that can download a lease, which is being written in SharpCrafters.Backstage. The reason is now recorded where the throw is. Rewrite the README, which still described Visual Studio 2015, and move the settings documentation out of the XML comment it lived in and into docs/configuration.md. Both state the four things that change for somebody upgrading: the hosting bundle is now required, settings moved to appsettings.json, connection strings may need Encrypt=False, and audit timestamps are no longer shifted on servers that do not run in UTC. The pages follow the reader's colour scheme, rather than rendering dark text on whatever background the browser chooses. Co-Authored-By: Claude Opus 5 --- .claude/launch.json | 12 ++ .gitignore | 6 +- Directory.Packages.props | 1 + PostSharp.LicenseServer.slnx | 1 + README.md | 75 +++++++++++- docs/configuration.md | 102 ++++++++++++++++ eng/Package.ps1 | 53 ++++++++ .../DatabaseRegistration.cs | 31 ++++- src/PostSharp.LicenseServer.Web/Program.cs | 14 ++- .../Properties/launchSettings.json | 2 +- .../appsettings.Development.json | 8 +- .../wwwroot/css/site.css | 55 +++++++-- .../wwwroot/js/graph.js | 5 + .../ClientSimulator.cs | 23 ++-- .../PostSharp.LicenseServer.Simulator.csproj | 21 ++++ .../PostSharp.LicenseServer.Test.csproj | 114 ------------------ .../Properties/AssemblyInfo.cs | 36 ------ .../app.config | 16 --- .../packages.config | 9 -- .../LeaseCountingPointsTests.cs | 24 ++++ 20 files changed, 403 insertions(+), 205 deletions(-) create mode 100644 .claude/launch.json create mode 100644 docs/configuration.md create mode 100644 eng/Package.ps1 create mode 100644 tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Simulator.csproj delete mode 100644 tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Test.csproj delete mode 100644 tests/PostSharp.LicenseServer.Simulator/Properties/AssemblyInfo.cs delete mode 100644 tests/PostSharp.LicenseServer.Simulator/app.config delete mode 100644 tests/PostSharp.LicenseServer.Simulator/packages.config diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..112cfdd --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "PostSharp.LicenseServer", + "runtimeExecutable": "dotnet", + "runtimeArgs": ["run", "--project", "src/PostSharp.LicenseServer.Web"], + "port": 44670, + "url": "http://localhost:44670" + } + ] +} diff --git a/.gitignore b/.gitignore index 4e7ff8f..9428fd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /packages +/artifacts /*.suo bin obj @@ -8,4 +9,7 @@ obj *.slnLaunch.user *.csproj.user *.DotSettings.user -/artifacts +*.db +*.db-shm +*.db-wal +/App_Data diff --git a/Directory.Packages.props b/Directory.Packages.props index 7c3c21c..089b3e2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,6 +10,7 @@ + diff --git a/PostSharp.LicenseServer.slnx b/PostSharp.LicenseServer.slnx index e5416b0..8d1bffe 100644 --- a/PostSharp.LicenseServer.slnx +++ b/PostSharp.LicenseServer.slnx @@ -4,6 +4,7 @@ + diff --git a/README.md b/README.md index 26f3c1a..37de310 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This repository contains the source code and releases of PostSharp License Serve The use of the license server is optional. Since all commercial licenses are floating ones, the license server can help teams knowing how many licenses they actually use. -The license server is a classic ASP.NET application with an MS SQL back-end. +The license server is an ASP.NET Core application with an MS SQL back-end. We at PostSharp consider that it is the customer's sole responsibility to respect the license agreement, and this is why we are providing the source code of the license server. Note that the use of licenses keys [is audited anyway](http://doc.postsharp.net/license-audit); if this is not an option for your organization, you can ask the PostSharp sales team for a license key with audit waiver. @@ -22,18 +22,81 @@ You can download the latest release from https://github.com/postsharp/PostSharp. * [Installing the license server](http://doc.postsharp.net/license-server-admin). * [Using the license server](http://doc.postsharp.net/license-server). +## Installing + +### Requirements + +* Windows Server with IIS. +* The [ASP.NET Core Hosting Bundle](https://dotnet.microsoft.com/download/dotnet/10.0) for .NET 10. +* SQL Server 2016 or later. + +### Instructions + +1. Install the ASP.NET Core Hosting Bundle on the web server, then restart IIS with `iisreset`. +2. Create the database and run `Database\CreateTables.sql` against it. +3. Unpack `PostSharp.LicenseServer.zip` into the directory of an IIS application. +4. Edit `appsettings.json`: set the connection string, the notification e-mail addresses and the + SMTP server. The settings are described in [docs/configuration.md](docs/configuration.md). +5. In IIS Manager, enable **Windows Authentication** on the application and disable + **Anonymous Authentication** if you want every lease request to be attributed to a user. +6. Browse to the application and add your license key. + +## Upgrading from version 2025.1 or earlier + +Earlier versions ran on .NET Framework and ASP.NET WebForms. The database schema has not changed, +so an existing database is used as it is, with no migration step. Four things do change. + +* **The ASP.NET Core Hosting Bundle is now required.** This is the one prerequisite the previous + version did not have. +* **Settings have moved from `Web.config` to `appsettings.json`.** The names are unchanged, so the + values can be copied across one by one. See [docs/configuration.md](docs/configuration.md). +* **Connection strings now need `Encrypt=False`** unless your SQL Server presents a certificate the + web server trusts, because the modern SQL client encrypts by default. A connection string that + worked before may otherwise fail with a certificate error. +* **Timestamps in the exported audit log are now correct on servers that do not run in UTC.** + Previously they were written as if local time were UTC, which shifted them by the server's offset. + Exports taken after the upgrade therefore differ from earlier ones by that offset. + +The audit log is signed with a key kept in `App_Data\audit-signing.key`, generated on first start. +Include it in your backups and preserve it across upgrades: losing it does not invalidate existing +rows, but it does start a new signature chain. + ## Building from source ### Requirements -* Visual Studio 2015 with Web Tools. +* The [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). ### Instructions -1. Open a Developer Command Prompt and go to repository directory. -2. Restore NuGet packages with the command: `.nuget\nuget.exe restore`. -3. Go to directory `src\PostSharp.LicenseServer`. -4. Execute `msbuild PostSharp.LicenseServer.csproj /t:Zip`. +``` +dotnet test +.\eng\Package.ps1 +``` + +The package is written to `artifacts\PostSharp.LicenseServer.zip`. + +### Running locally + +``` +dotnet run --project src\PostSharp.LicenseServer.Web +``` + +The development configuration uses a SQLite database created on first start, so no SQL Server is +needed. Once the server is running, `/Admin/GenerateDemoData` fills it with simulated activity; that +page exists only in a development environment. + +### Repository layout + +| Project | Contents | +|---|---| +| `src\PostSharp.LicenseServer.Core` | The licensing rules, the database model and the services they depend on. | +| `src\PostSharp.LicenseServer.Web` | The web application: the pages, the endpoints and the composition root. | +| `tests\PostSharp.LicenseServer.Tests` | The test suite. Runs against an in-memory database, so it needs no SQL Server. | +| `tests\PostSharp.LicenseServer.Simulator` | A manual load-testing tool. See the note below. | + +The simulator does not currently run: it needs a client that can download a lease, which is being +written in SharpCrafters.Backstage. It is kept building so that it is ready when that client is. ## Support diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..558df68 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,102 @@ +# Configuring the license server + +All settings live in `appsettings.json`, next to the application. Any of them can also be supplied +as an environment variable, where a colon becomes a double underscore: the connection string, for +instance, is `ConnectionStrings__SharpCrafters_LicenseServerConnectionString`. That is the usual way +to keep a password out of a file. + +The server validates its settings when it starts and refuses to start on an invalid combination, +rather than failing later on a lease request. + +## Database + +| Setting | Default | Meaning | +|---|---|---| +| `ConnectionStrings:SharpCrafters_LicenseServerConnectionString` | a local SQL Server | The database. | +| `LicenseServer:DatabaseProvider` | `SqlServer` | `SqlServer` or `Sqlite`. | + +SQL Server is the supported engine for a production installation. Create its schema by running +`Database\CreateTables.sql`; the server never creates or alters it, so an upgrade cannot surprise you. + +SQLite is offered for evaluation, and is what the test suite uses. Its database is created on first +start. A relative path is resolved against the application directory, not against whatever the +working directory happens to be. + +Note that the modern SQL client encrypts connections by default. Against a server whose certificate +the web server does not trust, add `Encrypt=False` to the connection string. + +## Licensing rules + +| Setting | Default | Meaning | +|---|---|---| +| `LicenseServer:MachinesPerUser` | 2 | How many devices one user may use on a single seat. Check your license agreement before changing it. | +| `LicenseServer:NewLeaseDays` | 3 | How long a new lease lasts. | +| `LicenseServer:MinLeaseDays` | 1 | How long before the end of a lease a client starts renewing it. If your developers work offline for weeks at a time, raise this above the number of days they are away. Must be smaller than `NewLeaseDays`. | +| `LicenseServer:BuildServers` | empty | The machine names of build agents, separated by semicolons, commas or spaces. A build agent is served a license but is not given a lease, so that it does not consume a developer's seat. A trailing hexadecimal identifier is ignored, so `buildagent-1f2e` matches `buildagent`. | + +## Notifications + +| Setting | Default | Meaning | +|---|---|---| +| `LicenseServer:GracePeriodWarningEmailTo` | empty | Who is told that the license is over capacity. | +| `LicenseServer:GracePeriodWarningEmailCC` | empty | Who else is told. | +| `LicenseServer:DeniedRequestEmailTo` | empty | Who is told that a request was denied. | +| `LicenseServer:GracePeriodWarningDays` | 1 | How many days to wait before repeating a warning. | +| `Smtp:Enabled` | `false` | Whether notifications are sent at all. | +| `Smtp:Host`, `Smtp:Port`, `Smtp:EnableSsl` | `localhost`, 25, `false` | The SMTP server. | +| `Smtp:FromAddress` | `sales@postsharp.net` | The sender. | +| `Smtp:UserName`, `Smtp:Password` | empty | Credentials, if the SMTP server needs them. Supply the password as an environment variable. | + +An address left empty suppresses that notification. A notification that cannot be delivered is +logged and never denies a developer their license. + +## Access + +| Setting | Default | Meaning | +|---|---|---| +| `Authentication:Scheme` | `IISIntegrated` | `IISIntegrated` behind IIS, `Negotiate` for self-hosting. | +| `LicenseServer:AdminRoles` | empty | The Windows groups allowed to reach the administrative pages, for example `["DOMAIN\\PostSharp Administrators"]`. | +| `LicenseServer:RequireAuthenticatedLeaseRequests` | `false` | Whether a lease request must be authenticated. | + +**Both default to open**, which is how the license server has always shipped, so that an upgrade +cannot lock an administrator out of their own server. The administrative pages are the only way to +add or revoke a license, so setting `AdminRoles` is worth doing; until it is set, the server says so +in its log every time it starts. + +## Concurrency + +| Setting | Default | Meaning | +|---|---|---| +| `LicenseServer:LeaseLockMode` | `InProcess` | How concurrent lease requests are serialized. | +| `LicenseServer:MutexTimeout` | 30 | How many seconds a request waits for its turn before the server answers 503. | + +Lease requests are serialized so that two of them cannot both conclude that the last free seat is +theirs. `InProcess` serializes them within one worker process, which is correct for the supported +deployment of a single process per database. + +If you run the license server as an IIS web garden, behind a load balancer, or as several +containers, that guarantee no longer holds and the server can over-allocate. Either run a single +worker process, or open an issue asking for `SqlApplicationLock`, which serializes through the +database and is reserved for exactly this case. + +## Auditing + +| Setting | Default | Meaning | +|---|---|---| +| `LicenseServer:AuditHmacKey` | empty | The base64 key that signs the audit log. | + +Each lease in the audit log is signed together with the signature of the previous one, so that a +removed or altered row breaks every signature after it. When no key is configured, one is generated +on first start and written to `App_Data\audit-signing.key`. + +Include that file in your backups and preserve it across upgrades. Losing it does not invalidate the +rows already written, but it does start a new chain. + +## Testing + +| Setting | Default | Meaning | +|---|---|---| +| `LicenseServer:TimeAcceleration` | 1 | How much faster than real time the server's clock runs. | + +Leave this at 1. Any other value exists so that a multi-day licensing scenario can be replayed in +minutes against a test server, and the server warns at startup when it is set. diff --git a/eng/Package.ps1 b/eng/Package.ps1 new file mode 100644 index 0000000..9043bc5 --- /dev/null +++ b/eng/Package.ps1 @@ -0,0 +1,53 @@ +<# +.SYNOPSIS + Builds the release package of the PostSharp License Server. + +.DESCRIPTION + Publishes the web application and zips it into artifacts/PostSharp.LicenseServer.zip, which is + the artifact attached to a GitHub release and which an administrator unpacks into an IIS site. + + The package is framework-dependent: the target machine needs the ASP.NET Core Hosting Bundle. + +.PARAMETER Configuration + The build configuration. Release by default. + +.PARAMETER OutputPath + Where to write the zip. artifacts/ by default. +#> +[CmdletBinding()] +param( + [string] $Configuration = 'Release', + [string] $OutputPath = (Join-Path $PSScriptRoot '..' 'artifacts') +) + +$ErrorActionPreference = 'Stop' + +$repositoryRoot = Resolve-Path (Join-Path $PSScriptRoot '..') +$project = Join-Path $repositoryRoot 'src' 'PostSharp.LicenseServer.Web' 'PostSharp.LicenseServer.Web.csproj' +$publishPath = Join-Path $repositoryRoot 'artifacts' 'publish' +$zipPath = Join-Path $OutputPath 'PostSharp.LicenseServer.zip' + +if ( Test-Path $publishPath ) { Remove-Item $publishPath -Recurse -Force } +New-Item -ItemType Directory -Force -Path $OutputPath | Out-Null + +Write-Host "Testing..." +Push-Location $repositoryRoot +try { dotnet test --configuration $Configuration --nologo } +finally { Pop-Location } +if ( $LASTEXITCODE -ne 0 ) { throw "The tests failed." } + +Write-Host "Publishing..." +# Targeting the Windows runtime keeps the Linux and macOS native libraries -- which an IIS +# deployment never loads -- out of the package. It stays framework-dependent: the target machine +# needs the ASP.NET Core Hosting Bundle. +dotnet publish $project --configuration $Configuration --output $publishPath --runtime win-x64 --self-contained false --nologo +if ( $LASTEXITCODE -ne 0 ) { throw "The publish failed." } + +# Development-only settings must not reach a customer's server. +Remove-Item (Join-Path $publishPath 'appsettings.Development.json') -Force -ErrorAction SilentlyContinue + +Write-Host "Packing $zipPath..." +if ( Test-Path $zipPath ) { Remove-Item $zipPath -Force } +Compress-Archive -Path (Join-Path $publishPath '*') -DestinationPath $zipPath + +Write-Host "Created $zipPath ($([math]::Round((Get-Item $zipPath).Length / 1MB, 1)) MB)." diff --git a/src/PostSharp.LicenseServer.Web/DatabaseRegistration.cs b/src/PostSharp.LicenseServer.Web/DatabaseRegistration.cs index 3974554..147d193 100644 --- a/src/PostSharp.LicenseServer.Web/DatabaseRegistration.cs +++ b/src/PostSharp.LicenseServer.Web/DatabaseRegistration.cs @@ -1,3 +1,4 @@ +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using PostSharp.LicenseServer.Data; @@ -20,15 +21,15 @@ public static class DatabaseRegistration /// public static IServiceCollection AddLicenseServerDatabase( this IServiceCollection services, - IConfiguration configuration ) + IConfiguration configuration, + IHostEnvironment environment ) { string provider = configuration["LicenseServer:DatabaseProvider"] ?? "SqlServer"; string? connectionString = configuration.GetConnectionString( ConnectionStringName ); if ( string.IsNullOrWhiteSpace( connectionString ) ) { - throw new InvalidOperationException( - $"The connection string '{ConnectionStringName}' is not configured." ); + throw new InvalidOperationException( $"The connection string '{ConnectionStringName}' is not configured." ); } return provider.ToLowerInvariant() switch @@ -37,10 +38,32 @@ public static IServiceCollection AddLicenseServerDatabase( options => options.UseSqlServer( connectionString ) ), "sqlite" => services.AddDbContext( - options => options.UseSqlite( connectionString ) ), + options => options.UseSqlite( ResolveSqliteFile( connectionString, environment ) ) ), _ => throw new InvalidOperationException( $"Unknown database provider '{provider}'. Use 'SqlServer' or 'Sqlite'." ) }; } + + /// + /// Makes a relative SQLite file path absolute, relative to the application rather than to + /// whatever the working directory happens to be when the process is started. + /// + private static string ResolveSqliteFile( string connectionString, IHostEnvironment environment ) + { + SqliteConnectionStringBuilder builder = new( connectionString ); + + // An in-memory database names a shared cache rather than a file. + if ( builder.Mode == SqliteOpenMode.Memory + || string.IsNullOrEmpty( builder.DataSource ) + || builder.DataSource == ":memory:" + || Path.IsPathRooted( builder.DataSource ) ) + { + return connectionString; + } + + builder.DataSource = Path.Combine( environment.ContentRootPath, builder.DataSource ); + + return builder.ToString(); + } } diff --git a/src/PostSharp.LicenseServer.Web/Program.cs b/src/PostSharp.LicenseServer.Web/Program.cs index 23a0e5b..204d06d 100644 --- a/src/PostSharp.LicenseServer.Web/Program.cs +++ b/src/PostSharp.LicenseServer.Web/Program.cs @@ -32,7 +32,7 @@ .ValidateDataAnnotations() .ValidateOnStart(); -builder.Services.AddLicenseServerDatabase( builder.Configuration ); +builder.Services.AddLicenseServerDatabase( builder.Configuration, builder.Environment ); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -170,6 +170,18 @@ app.MapLicenseServerEndpoints(); app.MapLegacyUrlRedirects(); +// On SQL Server the schema is created by Database/CreateTables.sql, which is the source of truth and +// which an administrator runs deliberately. A SQLite database is created on demand, because it is +// meant for evaluation and has no administrator to run a script. +if ( string.Equals( + app.Configuration["LicenseServer:DatabaseProvider"], + "Sqlite", + StringComparison.OrdinalIgnoreCase ) ) +{ + using IServiceScope scope = app.Services.CreateScope(); + scope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); +} + // The administrative pages are the only way to add or revoke a license, so an open default deserves // more than a comment in a configuration file. { diff --git a/src/PostSharp.LicenseServer.Web/Properties/launchSettings.json b/src/PostSharp.LicenseServer.Web/Properties/launchSettings.json index 92c24f7..b37673e 100644 --- a/src/PostSharp.LicenseServer.Web/Properties/launchSettings.json +++ b/src/PostSharp.LicenseServer.Web/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "PostSharp.LicenseServer": { "commandName": "Project", - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "http://localhost:44670", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/src/PostSharp.LicenseServer.Web/appsettings.Development.json b/src/PostSharp.LicenseServer.Web/appsettings.Development.json index 6f4988b..30db7bb 100644 --- a/src/PostSharp.LicenseServer.Web/appsettings.Development.json +++ b/src/PostSharp.LicenseServer.Web/appsettings.Development.json @@ -2,10 +2,16 @@ "Authentication": { "Scheme": "Negotiate" }, + "LicenseServer": { + "DatabaseProvider": "Sqlite" + }, + "ConnectionStrings": { + "SharpCrafters_LicenseServerConnectionString": "DataSource=licenseserver-dev.db" + }, "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.EntityFrameworkCore.Database.Command": "Information" + "Microsoft.AspNetCore": "Warning" } } } diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css b/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css index 8cbabcd..b9d778c 100644 --- a/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css +++ b/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css @@ -1,7 +1,41 @@ +/* The license server is an internal administration tool. It follows the reader's colour scheme + rather than forcing one, so it stays legible in a browser set to dark. */ +:root { + color-scheme: light dark; + + --background: #ffffff; + --foreground: #222222; + --muted: #666666; + --rule: #e0e0e0; + --rule-strong: #999999; + --accent: #58006e; + --danger: #b00020; + --panel: #f6f6f6; +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #1b1b1f; + --foreground: #e8e8ea; + --muted: #a0a0a8; + --rule: #35353c; + --rule-strong: #5a5a66; + --accent: #c58fd8; + --danger: #ff8a95; + --panel: #26262c; + } +} + body { font-family: "Segoe UI", Arial, sans-serif; padding: 20px; - color: #222; + margin: 0; + background: var(--background); + color: var(--foreground); +} + +a { + color: var(--accent); } h1 { @@ -18,6 +52,8 @@ header img { hr { margin-top: 50px; + border: 0; + border-top: 1px solid var(--rule); } table { @@ -27,21 +63,21 @@ table { td, th { padding: 10px; text-align: left; - border-bottom: 1px solid #e0e0e0; + border-bottom: 1px solid var(--rule); } th { - border-bottom: 2px solid #999; + border-bottom: 2px solid var(--rule-strong); } .no-licenses { padding: 20px; - background: #f6f6f6; - border-left: 4px solid #58006e; + background: var(--panel); + border-left: 4px solid var(--accent); } .validation-error, .field-validation-error, .validation-summary-errors { - color: #b00020; + color: var(--danger); } .actions form { @@ -52,7 +88,8 @@ th { float: right; } -#usage-chart { +#usage-chart-container { + position: relative; width: 100%; height: 400px; } @@ -61,3 +98,7 @@ button, input[type="submit"] { padding: 6px 14px; cursor: pointer; } + +label { + color: var(--muted); +} diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js b/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js index ea0d1ed..3af3d97 100644 --- a/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js +++ b/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js @@ -12,6 +12,11 @@ var chart = JSON.parse(dataElement.textContent); + // Follow the page's colour scheme, so axis labels and gridlines stay legible in a dark browser. + var styles = getComputedStyle(document.documentElement); + Chart.defaults.color = styles.getPropertyValue("--foreground").trim() || "#222"; + Chart.defaults.borderColor = styles.getPropertyValue("--rule").trim() || "#e0e0e0"; + var datasets = [{ label: "Used", data: chart.used, diff --git a/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs b/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs index c04e78b..7e10a09 100644 --- a/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs +++ b/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs @@ -86,23 +86,28 @@ public void Main() { Console.WriteLine("Could not get a valid lease: lease is null, downloading a new lease..."); - // TODO: Uncomment after the following method is public in PostSharp (likely 2025.1.6 or later) - // Upgrade dependencies. - // Remove the throw. + // TODO: This simulator cannot run until a client can download a + // lease. LicenseServerClient.TryDownloadLease is not public, + // and the replacement client is being written in + // SharpCrafters.Backstage, which this license server is to + // be migrated to. Restore the call below, remove the throw, + // and delete the pragma once one of the two is available. //lease = LicenseServerClient.TryDownloadLease(messageSink, url, registryKey); throw new Exception("Upgrade PostSharp to make these tests work (see above comment)."); - if (lease == null) +#pragma warning disable CS0162 // Unreachable until the download call above is restored. + if (lease == null) { - Console.WriteLine("Could not download a new lease."); + Console.WriteLine("Could not download a new lease."); } - else - { - Console.WriteLine($"Key {lease.LicenseString}:"); - Console.WriteLine($"Leased until: {lease.EndTime}:"); + else + { + Console.WriteLine($"Key {lease.LicenseString}:"); + Console.WriteLine($"Leased until: {lease.EndTime}:"); } +#pragma warning restore CS0162 } else if ( lease.EndTime < time ) { diff --git a/tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Simulator.csproj b/tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Simulator.csproj new file mode 100644 index 0000000..9b333b7 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Simulator.csproj @@ -0,0 +1,21 @@ + + + + Exe + net10.0 + SharpCrafters.LicenseServer.Test + PostSharp.LicenseServer.Simulator + disable + + false + false + + + + + + + + + + diff --git a/tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Test.csproj b/tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Test.csproj deleted file mode 100644 index bf2a325..0000000 --- a/tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Test.csproj +++ /dev/null @@ -1,114 +0,0 @@ - - - - - Debug - x86 - 8.0.30703 - 2.0 - {6A706191-B0A4-4B9A-A597-7A1DAF32EA50} - Exe - Properties - SharpCrafters.LicenseServer.Test - SharpCrafters.LicenseServer.Test - v4.7.2 - - - 512 - - - - - x86 - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - True - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - True - - - bin\x86\Documentation\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - false - True - - - - ..\..\packages\PostSharp.Redist.2025.1.5\lib\net45\PostSharp.dll - - - ..\..\packages\PostSharp.Compiler.Common.2025.1.5\lib\netstandard2.0\PostSharp.Compiler.Common.dll - - - ..\..\packages\PostSharp.Compiler.Engine.2025.1.5\lib\netstandard2.0\PostSharp.Compiler.Engine.dll - - - ..\..\packages\PostSharp.Compiler.Platforms.2025.1.5\lib\net472\PostSharp.Compiler.Platform.NetFramework.dll - - - ..\..\packages\PostSharp.Compiler.Settings.2025.1.5\lib\netstandard2.0\PostSharp.Compiler.Settings.dll - - - - - - - - - - - - - - - - - - - - - - - - - {3b511e09-1cfd-43eb-978f-70fa3dfec83b} - PostSharp.LicenseServer - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - \ No newline at end of file diff --git a/tests/PostSharp.LicenseServer.Simulator/Properties/AssemblyInfo.cs b/tests/PostSharp.LicenseServer.Simulator/Properties/AssemblyInfo.cs deleted file mode 100644 index 3dab666..0000000 --- a/tests/PostSharp.LicenseServer.Simulator/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SharpCrafters.LicenseServer.Test")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("SharpCrafters.LicenseServer.Test")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("cc948cb7-f2f4-4f00-9d6a-3d5474f72268")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/PostSharp.LicenseServer.Simulator/app.config b/tests/PostSharp.LicenseServer.Simulator/app.config deleted file mode 100644 index b691c5d..0000000 --- a/tests/PostSharp.LicenseServer.Simulator/app.config +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/tests/PostSharp.LicenseServer.Simulator/packages.config b/tests/PostSharp.LicenseServer.Simulator/packages.config deleted file mode 100644 index 43f43b9..0000000 --- a/tests/PostSharp.LicenseServer.Simulator/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseCountingPointsTests.cs b/tests/PostSharp.LicenseServer.Tests/LeaseCountingPointsTests.cs index 573f316..1351f25 100644 --- a/tests/PostSharp.LicenseServer.Tests/LeaseCountingPointsTests.cs +++ b/tests/PostSharp.LicenseServer.Tests/LeaseCountingPointsTests.cs @@ -160,6 +160,30 @@ public async Task GetLeaseCountingPoints_ExcludesLeasesOutsideTheWindow() Assert.All( points, p => Assert.Equal( "inside", p.Lease.UserName ) ); } + /// + /// The timeline assumes a user never holds two open leases on the same machine at once, which + /// is an invariant the lease service maintains by reusing or prolonging a lease instead of + /// granting a second one. Data that breaks it is reported rather than silently miscounted, + /// because an under-count in a licensing audit is worse than a failure. + /// + [Fact] + public async Task GetLeaseCountingPoints_OverlappingLeasesOnOneMachine_AreReported() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) + .From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) + .From( TestClock.Days( 1 ) ).Lasting( 3 ).AddTo( context ); + + InvalidOperationException exception = + Assert.Throws( () => Timeline( context, license ) ); + + Assert.Contains( "which is not open", exception.Message, StringComparison.Ordinal ); + } + [Fact] public async Task GetLeaseCountingPoints_ReturnsToZeroAfterEveryLeaseEnds() { From aa4f530293b029408fa4bd75016798dcb342e096 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 09:37:05 +0200 Subject: [PATCH 08/44] Hold the model to the schema existing installations already have There are no EF migrations, because CreateTables.sql is the source of truth and the server never creates or alters a SQL Server schema. These tests stand in for one: they generate the DDL from the model and check it against what CreateTables.sql produces -- the text and datetime column types, which identifier the application assigns and which the database generates, the constraint names, and the absence of cascading deletes. Without them a mapping could drift silently and be discovered by a customer, on their data. Verified by reintroducing the two mappings' absence and confirming that exactly the tests guarding them fail. No server is contacted, so the checks run anywhere. Co-Authored-By: Claude Opus 5 --- .../PostSharp.LicenseServer.Tests.csproj | 1 + .../SchemaCompatibilityTests.cs | 156 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 tests/PostSharp.LicenseServer.Tests/SchemaCompatibilityTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj b/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj index 282b34c..7a277a1 100644 --- a/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj +++ b/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/tests/PostSharp.LicenseServer.Tests/SchemaCompatibilityTests.cs b/tests/PostSharp.LicenseServer.Tests/SchemaCompatibilityTests.cs new file mode 100644 index 0000000..7bfddb5 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/SchemaCompatibilityTests.cs @@ -0,0 +1,156 @@ +using Microsoft.EntityFrameworkCore; +using PostSharp.LicenseServer.Data; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// The schema an existing installation already has, which this version must keep using unchanged. +/// +/// +/// There are deliberately no EF migrations: CreateTables.sql is the source of truth, and the +/// server never creates or alters a SQL Server schema. These tests are what stands in for a +/// migration, by holding the model to the column types, key generation and constraint names that +/// CreateTables.sql produces. A mapping that drifted would otherwise be discovered by a +/// customer, on their data. +/// +public sealed class SchemaCompatibilityTests +{ + /// + /// Generates the SQL Server DDL for the model. No server is contacted. + /// + private static string CreateScript() + { + DbContextOptions options = + new DbContextOptionsBuilder() + .UseSqlServer( "Server=none;Database=none;" ) + .Options; + + using LicenseServerDbContext db = new( options ); + + return db.Database.GenerateCreateScript(); + } + + [Fact] + public void Licenses_LicenseKeyIsStillText() + { + // CreateTables.sql declares [LicenseKey] [text]. Letting EF default to nvarchar(max) would + // generate a schema that does not match an existing database. + Assert.Contains( "[LicenseKey] text NOT NULL", CreateScript(), StringComparison.OrdinalIgnoreCase ); + } + + /// + /// A text column cannot appear in a comparison, an ORDER BY, a GROUP BY or a DISTINCT in + /// T-SQL, and the failure happens at run time. No query may therefore touch the license key + /// other than to project it. + /// + [Fact] + public void Licenses_LicenseKeyIsNeverFilteredOrSorted() + { + DbContextOptions options = + new DbContextOptionsBuilder() + .UseSqlServer( "Server=none;Database=none;" ) + .Options; + + using LicenseServerDbContext db = new( options ); + + string sql = db.Licenses.OrderBy( l => l.Priority ).ThenByDescending( l => l.LicenseId ).ToQueryString(); + + Assert.Contains( "[LicenseKey]", sql, StringComparison.OrdinalIgnoreCase ); + Assert.DoesNotContain( "ORDER BY [l].[LicenseKey]", sql, StringComparison.OrdinalIgnoreCase ); + } + + [Fact] + public void Licenses_LicenseIdIsAssignedByTheApplication() + { + // The identifier comes from the license key. Making it an identity column would both break + // an existing database and lose the link between the row and the key. + string script = CreateScript(); + int licensesTable = script.IndexOf( "CREATE TABLE [Licenses]", StringComparison.OrdinalIgnoreCase ); + + Assert.True( licensesTable >= 0 ); + + string licenses = script[licensesTable..script.IndexOf( ");", licensesTable, StringComparison.Ordinal )]; + + Assert.Contains( "[LicenseId] int NOT NULL", licenses, StringComparison.OrdinalIgnoreCase ); + Assert.DoesNotContain( "IDENTITY", licenses, StringComparison.OrdinalIgnoreCase ); + } + + [Fact] + public void Leases_LeaseIdIsGeneratedByTheDatabase() + { + Assert.Contains( "[LeaseId] int NOT NULL IDENTITY", CreateScript(), StringComparison.OrdinalIgnoreCase ); + } + + [Theory] + // Types as declared by CreateTables.sql. + [InlineData( "[ProductCode] varchar(50) NOT NULL" )] + [InlineData( "[Priority] int NOT NULL" )] + [InlineData( "[CreatedOn] datetime NOT NULL" )] + [InlineData( "[GraceStartTime] datetime NULL" )] + [InlineData( "[GraceLastWarningTime] datetime NULL" )] + [InlineData( "[StartTime] datetime NOT NULL" )] + [InlineData( "[EndTime] datetime NOT NULL" )] + [InlineData( "[UserName] nvarchar(200) NOT NULL" )] + [InlineData( "[Machine] nvarchar(200) NOT NULL" )] + [InlineData( "[AuthenticatedUser] nvarchar(200) NOT NULL" )] + [InlineData( "[HMAC] varchar(100) NULL" )] + [InlineData( "[Grace] bit NOT NULL" )] + [InlineData( "[OverwrittenLeaseId] int NULL" )] + [InlineData( "[LicenseId] int NOT NULL" )] + public void Column_KeepsItsType( string expected ) + => Assert.Contains( expected, CreateScript(), StringComparison.OrdinalIgnoreCase ); + + /// + /// Timestamps must stay datetime. EF would default them to datetime2, which makes + /// SQL Server convert the column on every comparison of a lease's start or end time. + /// + [Fact] + public void Timestamps_AreNeverDateTime2() + => Assert.DoesNotContain( "datetime2", CreateScript(), StringComparison.OrdinalIgnoreCase ); + + [Theory] + [InlineData( "PK_Licenses" )] + [InlineData( "PK_Leases" )] + [InlineData( "FK_Leases_Licenses" )] + [InlineData( "FK_Leases_Leases" )] + [InlineData( "IX_Leases_EndTime" )] + [InlineData( "IX_Leases_OverwrittenLeaseId" )] + public void Constraint_KeepsItsName( string expected ) + => Assert.Contains( expected, CreateScript(), StringComparison.Ordinal ); + + [Fact] + public void Tables_KeepTheirNames() + { + string script = CreateScript(); + + Assert.Contains( "CREATE TABLE [Licenses]", script, StringComparison.OrdinalIgnoreCase ); + Assert.Contains( "CREATE TABLE [Leases]", script, StringComparison.OrdinalIgnoreCase ); + } + + /// + /// Deleting a license must not silently cascade through the chain of replaced leases. + /// + [Fact] + public void ForeignKeys_DoNotCascade() + => Assert.DoesNotContain( "ON DELETE CASCADE", CreateScript(), StringComparison.OrdinalIgnoreCase ); + + [Fact] + public void Model_HasExactlyTheTwoExpectedTables() + { + DbContextOptions options = + new DbContextOptionsBuilder() + .UseSqlServer( "Server=none;Database=none;" ) + .Options; + + using LicenseServerDbContext db = new( options ); + + string[] tables = db.Model.GetEntityTypes() + .Select( e => e.GetTableName() ) + .Where( name => name != null ) + .Distinct() + .Order() + .ToArray()!; + + Assert.Equal( ["Leases", "Licenses"], tables ); + } +} From 1337f49ba13d33e67869ab5af958798a3f69b63b Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 09:49:25 +0200 Subject: [PATCH 09/44] Apply the PostSharp brand to the license server Take the design tokens, the logo and the page chrome from the PostSharp website (metalama-website) so that the license server looks like the rest of the product rather than like a 2011 administration page. - The tokens are copied from _sass/0-base/_tokens.scss as plain custom properties, so no build step is needed: the purple and cyan accents, the dark surfaces, the type scale and the radii. - The header reproduces the website's company bar: the deepest surface, 64px tall, with the PostSharp logo on the left. The footer carries the same legal line as the website. - Buttons follow the brand's signature: sharp, uppercase, two-pixel border. Headings are weight 400, because size does the work. - The usage graph moves onto the brand palette and takes its axis colours from the tokens, so it is part of the page rather than a white box dropped on it. The brand is dark-first and has no light palette, so neither has this any more. Monosten Pro, the website's display face, is deliberately NOT copied. It is a commercially licensed webfont marked "do not redistribute", licensed to the metalama.net owner, and this application is published under the MIT license and runs on customers' own servers. It is named first in the font stack and falls back to a monospace face, which keeps the character of the headings without redistributing anything. Co-Authored-By: Claude Opus 5 --- .../Pages/Admin/AddLicense.cshtml | 9 +- .../Pages/Admin/Cancel.cshtml | 9 +- .../Pages/Admin/Details.cshtml | 17 +- .../Pages/Admin/Export.cshtml | 9 +- .../Pages/Admin/GenerateDemoData.cshtml | 9 +- .../Pages/Graph.cshtml | 36 +- .../Pages/Index.cshtml | 22 +- .../Pages/Index.cshtml.cs | 17 +- .../Pages/Shared/_Layout.cshtml | 27 +- .../Img/PostSharpText_Light_240x33.png | Bin 5538 -> 0 bytes .../wwwroot/css/site.css | 433 +++++++++++++++--- .../wwwroot/favicon.ico | Bin 0 -> 15406 bytes .../img/icons/android-icon-192x192.png | Bin 0 -> 5142 bytes .../wwwroot/img/icons/android-icon-96x96.png | Bin 0 -> 3240 bytes .../wwwroot/img/icons/apple-icon-152x152.png | Bin 0 -> 5053 bytes .../wwwroot/img/icons/favicon-16x16.png | Bin 0 -> 1062 bytes .../wwwroot/img/icons/favicon-32x32.png | Bin 0 -> 1634 bytes .../wwwroot/img/postsharp-logo.svg | 36 ++ .../wwwroot/js/graph.js | 16 +- 19 files changed, 509 insertions(+), 131 deletions(-) delete mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/Img/PostSharpText_Light_240x33.png create mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/favicon.ico create mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/img/icons/android-icon-192x192.png create mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/img/icons/android-icon-96x96.png create mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/img/icons/apple-icon-152x152.png create mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/img/icons/favicon-16x16.png create mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/img/icons/favicon-32x32.png create mode 100644 src/PostSharp.LicenseServer.Web/wwwroot/img/postsharp-logo.svg diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/AddLicense.cshtml b/src/PostSharp.LicenseServer.Web/Pages/Admin/AddLicense.cshtml index f885c55..8bc9a01 100644 --- a/src/PostSharp.LicenseServer.Web/Pages/Admin/AddLicense.cshtml +++ b/src/PostSharp.LicenseServer.Web/Pages/Admin/AddLicense.cshtml @@ -4,11 +4,10 @@ ViewData["Title"] = "Add a license"; } -

- Back -

- -

Add a license

+

Paste the license key sent to you by PostSharp Technologies.

diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/Cancel.cshtml b/src/PostSharp.LicenseServer.Web/Pages/Admin/Cancel.cshtml index 0949ff4..d9700ee 100644 --- a/src/PostSharp.LicenseServer.Web/Pages/Admin/Cancel.cshtml +++ b/src/PostSharp.LicenseServer.Web/Pages/Admin/Cancel.cshtml @@ -4,11 +4,10 @@ ViewData["Title"] = "Cancel a lease"; } -

- Back -

- -

Cancel a lease

+

Cancel the lease of @Model.Lease!.UserName on diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/Details.cshtml b/src/PostSharp.LicenseServer.Web/Pages/Admin/Details.cshtml index f13aa3d..ad9ba42 100644 --- a/src/PostSharp.LicenseServer.Web/Pages/Admin/Details.cshtml +++ b/src/PostSharp.LicenseServer.Web/Pages/Admin/Details.cshtml @@ -4,11 +4,10 @@ ViewData["Title"] = $"License {Model.Id}"; } -

- Back -

- -

License @Model.Id

+

@Model.Leases.Count current lease(s), consuming @@ -22,7 +21,8 @@ } else { - +
+
@@ -48,12 +48,13 @@ else } -
Lease
+ + }

Manage this license

-

+

@if ( Model.IsDisabled ) {

+@section Scripts { + +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css index cc3eac2..fac05c9 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css @@ -194,6 +194,13 @@ main { flex-wrap: wrap; } +/* The link and the menu that act on the page, kept together at its top right. */ +.page-header__actions { + display: flex; + align-items: center; + gap: 20px; +} + .site-footer { max-width: var(--container); margin: 0 auto; @@ -295,6 +302,103 @@ input[type="submit"]:hover { line-height: 1; } +/* --- Menus -------------------------------------------------------------- */ + +/* A button that opens a short list of actions. The button is the of a
element, + which opens and closes without a script. */ +.menu { + position: relative; +} + +.menu > summary { + list-style: none; + user-select: none; +} + +.menu > summary::-webkit-details-marker { + display: none; +} + +.menu > summary::after { + content: " \25BE"; +} + +/* The list floats over the page rather than moving what is under it. */ +.menu__items { + position: absolute; + right: 0; + z-index: 10; + display: flex; + flex-direction: column; + min-width: 220px; + margin-top: 4px; + padding: 6px; + background: var(--bg-panel-2); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-sm); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45); +} + +.menu__items form { + margin: 0; +} + +/* An item reads as a line of the list, not as a button of its own, until it is pointed at. */ +.menu__items button { + width: 100%; + padding: 8px 12px; + line-height: var(--lh-normal); + font-weight: var(--fw-normal); + text-align: left; + text-transform: none; + letter-spacing: normal; + background-color: transparent; + border-color: transparent; + transition: background-color var(--transition-fast) ease; +} + +.menu__items button:hover { + background-color: var(--purple-medium-1); + border-color: transparent; +} + +/* An action that destroys data, wherever it is offered. */ +.is-destructive { + color: var(--orange); +} + +/* --- Dialogs ------------------------------------------------------------- */ + +.dialog { + max-width: 480px; + padding: 24px; + background: var(--doc-content-bg); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + color: var(--text-body); +} + +.dialog::backdrop { + background: rgba(0, 0, 0, 0.6); +} + +.dialog h3 { + margin-top: 0; +} + +.dialog form { + margin: 0; +} + +/* The button that carries out a destructive action, where orange text on the purple of a button + would not be legible. */ +.dialog .is-destructive, +.dialog .is-destructive:hover { + color: var(--ink); + background-color: var(--orange); + border-color: var(--orange); +} + /* --- Forms -------------------------------------------------------------- */ label { @@ -354,6 +458,12 @@ input:focus, select:focus, textarea:focus { margin: 0; } +/* The buttons of a dialog, which sit at its end rather than at its start. */ +.actions--end { + justify-content: flex-end; + margin-top: 16px; +} + /* An explanation of a term used just above it: present, but quieter than what it explains. */ .note { max-width: 68ch; @@ -429,6 +539,13 @@ input:focus, select:focus, textarea:focus { .page-header { gap: 8px; } + /* The header wraps here, which puts the menu button at the left of the page: the list opens + under its left edge rather than off the screen. */ + .menu__items { + right: auto; + left: 0; + } + /* A wide table scrolls inside its own box instead of widening the page. */ .table-scroll { overflow-x: auto; } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/admin-actions.js b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/admin-actions.js new file mode 100644 index 0000000..7dfab5d --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/admin-actions.js @@ -0,0 +1,75 @@ +// The management actions of a license: closing the menu when the administrator clicks outside it, +// and asking for confirmation before an action runs. +// +// Both are enhancements of markup that already works. The menu is a
element, which opens +// and closes on its own, and a form whose submission is not intercepted here submits as any form +// does. A browser that does not run this file therefore keeps every action, and loses only the +// confirmation. + +(function () { + "use strict"; + + var menus = Array.prototype.slice.call( document.querySelectorAll( "[data-menu]" ) ); + + function closeMenus( except ) { + menus.forEach( function ( menu ) { + if ( menu !== except ) { + menu.open = false; + } + } ); + } + + document.addEventListener( "click", function ( event ) { + var target = event.target; + + closeMenus( target && target.closest ? target.closest( "[data-menu]" ) : null ); + } ); + + document.addEventListener( "keydown", function ( event ) { + if ( event.key === "Escape" ) { + closeMenus( null ); + } + } ); + + var dialog = document.getElementById( "confirm-dialog" ); + + // A browser without keeps the direct submission rather than losing the action. + if ( !dialog || typeof dialog.showModal !== "function" ) { + return; + } + + var title = document.getElementById( "confirm-title" ); + var detail = document.getElementById( "confirm-detail" ); + var okButton = dialog.querySelector( "[data-confirm-ok]" ); + var pendingForm = null; + + document.querySelectorAll( "form[data-confirm]" ).forEach( function ( form ) { + form.addEventListener( "submit", function ( event ) { + event.preventDefault(); + + pendingForm = form; + title.textContent = form.dataset.confirm; + detail.textContent = form.dataset.confirmDetail || ""; + okButton.textContent = form.dataset.confirmAction || "Continue"; + okButton.classList.toggle( "is-destructive", form.dataset.confirmDestructive === "true" ); + + closeMenus( null ); + + // Cleared explicitly: a dialog closed with the Escape key keeps the value of the + // previous close in some browsers, which would confirm an action nobody confirmed. + dialog.returnValue = ""; + dialog.showModal(); + } ); + } ); + + dialog.addEventListener( "close", function () { + var form = pendingForm; + pendingForm = null; + + if ( form && dialog.returnValue === "confirm" ) { + // HTMLFormElement.submit does not raise the submit event, so the confirmation is not + // asked a second time. + form.submit(); + } + } ); +})(); diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs index dd17535..6f32ac4 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs @@ -145,6 +145,7 @@ public License AddLicense( LicenseBuilder builder ) LicenseId = info.LicenseId, LicenseKey = key, ProductCode = info.Product, + Priority = builder.Priority, CreatedOn = TestClock.Origin }; diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs index 32bafa2..9b19237 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs @@ -58,6 +58,11 @@ public LicenseBuilder WithUsers( int? value ) return this; } + /// + /// Gets the priority of the license, which is negative when the license is disabled. + /// + public int Priority => this.priority; + public LicenseBuilder WithPriority( int value ) { this.priority = value; diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs index 1c5627a..3292cdf 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs @@ -1,6 +1,7 @@ using System.Net; using System.Text.Json; using System.Text.RegularExpressions; +using SharpCrafters.Backstage.LicenseServer.Data; using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; namespace SharpCrafters.Backstage.LicenseServer.Tests; @@ -19,6 +20,16 @@ public sealed partial class PageTests : IDisposable [GeneratedRegex( @"\s+" )] private static partial Regex Whitespace(); + /// + /// Captures the action of a form and its contents, so that a test can submit it the way a browser + /// would. + /// + [GeneratedRegex( "]*action=\"([^\"]+)\"[^>]*>(.*?)", RegexOptions.Singleline )] + private static partial Regex Form(); + + [GeneratedRegex( "name=\"__RequestVerificationToken\"[^>]*value=\"([^\"]+)\"" )] + private static partial Regex AntiforgeryToken(); + public void Dispose() => this.application.Dispose(); [Theory] @@ -90,6 +101,77 @@ public async Task Details_ExplainsWhatASeatIs() body, StringComparison.Ordinal ); } + /// + /// The actions that change a license sit in one menu at the top of the page, and each of them + /// carries the text of the confirmation it asks for before it runs. + /// + [Fact] + public async Task Details_OffersItsActionsInAMenu() + { + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ) ); + HttpClient client = this.application.CreateClient(); + + string body = await client.GetStringAsync( "/Admin/Details?id=3" ); + + Assert.Contains( ">Manage
", body, StringComparison.Ordinal ); + Assert.Contains( "handler=Disable", body, StringComparison.Ordinal ); + Assert.Contains( "data-confirm=\"Disable license 3?\"", body, StringComparison.Ordinal ); + + // An enabled license is not offered for deletion: it is disabled first. + Assert.DoesNotContain( "handler=Delete", body, StringComparison.Ordinal ); + } + + /// + /// A disabled license says so on the page itself, because the menu that holds the state is closed + /// until the administrator opens it. + /// + [Fact] + public async Task Details_DisabledLicense_SaysSoAndOffersToEnableOrDeleteIt() + { + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ).WithPriority( -1 ) ); + HttpClient client = this.application.CreateClient(); + + string body = Whitespace().Replace( await client.GetStringAsync( "/Admin/Details?id=3" ), " " ); + + Assert.Contains( "This license is disabled: it serves no new lease.", body, StringComparison.Ordinal ); + Assert.Contains( "handler=Enable", body, StringComparison.Ordinal ); + Assert.Contains( "handler=Delete", body, StringComparison.Ordinal ); + } + /// + /// The action posts against the license the page is about. The license is named in the query + /// string, which a form does not inherit from the page that contains it, so an action that does + /// not name it again reaches no license at all. + /// + [Fact] + public async Task Details_Disable_DisablesThatLicense() + { + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ) ); + + HttpResponseMessage response = await this.SubmitDetailsActionAsync( 3, "handler=Disable" ); + + Assert.Equal( HttpStatusCode.Found, response.StatusCode ); + + using LicenseServerDbContext db = this.application.CreateDbContext(); + + Assert.True( db.Licenses.Single( l => l.LicenseId == 3 ).Priority < 0 ); + } + + [Fact] + public async Task Details_Delete_RemovesThatLicense() + { + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ).WithPriority( -1 ) ); + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 4 ) ); + + HttpResponseMessage response = await this.SubmitDetailsActionAsync( 3, "handler=Delete" ); + + Assert.Equal( HttpStatusCode.Found, response.StatusCode ); + + using LicenseServerDbContext db = this.application.CreateDbContext(); + + Assert.Equal( [4], db.Licenses.Select( l => l.LicenseId ).ToArray() ); + } + + [Fact] public async Task Details_UnknownLicense_Returns404() @@ -208,6 +290,29 @@ public async Task DemoDataGenerator_IsNotAvailableOutsideDevelopment() Assert.Equal( HttpStatusCode.NotFound, ( await client.GetAsync( "/Admin/GenerateDemoData" ) ).StatusCode ); } + /// + /// Submits one of the management forms of the details page as a browser would, with the action and + /// the antiforgery token the page itself supplies. + /// + private async Task SubmitDetailsActionAsync( int licenseId, string handler ) + { + HttpClient client = this.application.CreateClient( + new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions { AllowAutoRedirect = false } ); + + string page = await client.GetStringAsync( $"/Admin/Details?id={licenseId}" ); + + Match form = Form().Matches( page ).SingleOrDefault( m => m.Groups[1].Value.Contains( handler, StringComparison.Ordinal ) ) + ?? throw new InvalidOperationException( $"The page has no form posting to {handler}." ); + + Match token = AntiforgeryToken().Match( form.Groups[2].Value ); + Assert.True( token.Success, "The form carries no antiforgery token." ); + + return await client.PostAsync( + WebUtility.HtmlDecode( form.Groups[1].Value ), + new FormUrlEncodedContent( + new Dictionary { ["__RequestVerificationToken"] = token.Groups[1].Value } ) ); + } + private static string ExtractChartData( string html ) { const string opening = " diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs index e8c60bd..a6dde95 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs @@ -8,14 +8,14 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages; /// -/// The usage history of one license: how many seats were in use on each of the last N days, against -/// the capacity of the license and its grace allowance. +/// The usage history of one license. It reports the number of seats in use on each of the last days, +/// with the capacity of the license and the capacity of its grace period. /// /// -/// A seat is one user and the machines that user works on, up to MachinesPerUser of them; see -/// . The chart draws the same quantity the allocator compares to the -/// capacity, so the line and the two limits above it are in the same unit and the "In use" column of -/// the license list agrees with the chart. +/// A seat is one user and the machines that user works on, up to MachinesPerUser machines. +/// See . The chart draws the quantity that the allocator compares to +/// the capacity, so the line and the two limits above it use the same unit, and the chart agrees +/// with the column "In use" of the license list. /// public sealed class GraphModel( ILeaseRepository repository, @@ -23,8 +23,8 @@ public sealed class GraphModel( TimeProvider timeProvider ) : PageModel { /// - /// The windows offered by the page. Restricting them keeps an arbitrary value from turning into - /// an unbounded query. + /// The windows that the page offers. The list is closed, so that an arbitrary value cannot + /// produce an unbounded query. /// public static readonly int[] AllowedWindows = [30, 90, 180, 365]; @@ -65,9 +65,9 @@ public async Task OnGetAsync( CancellationToken cancellationToken { maximum = parsedLicense.UserNumber; - // The same arithmetic the allocator uses, rounding up. Integer division would floor it, - // so a one-seat license with 20% grace would be drawn as allowing one seat while the - // server actually grants two. + // The arithmetic of the allocator, which rounds up. An integer division would round + // down, and a license of one seat with a grace period of 20 per cent would then be drawn + // with a limit of one seat, while the server grants two. graceMaximum = (int) Math.Ceiling( maximum.Value * (100.0 + parsedLicense.GracePercent) / 100.0 ); axisMaximum = graceMaximum.Value; } @@ -80,8 +80,8 @@ public async Task OnGetAsync( CancellationToken cancellationToken Date = day.Key, Peak = day.Max( point => point.SeatCount ), - // The timeline is ordered, and grouping preserves that order within a group, so - // the last point of a day is the count the next day starts from. + // The timeline is ordered, and the grouping preserves that order inside a group, + // so the last point of a day carries the count that the next day starts from. AtEndOfDay = day.Last().SeatCount } ) .ToList(); @@ -111,7 +111,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken } } - // Days with no lease activity inherit the count the previous day ended on. + // A day without lease activity keeps the count of the end of the previous day. int lastValue = 0; for ( int i = 0; i < this.Days; i++ ) @@ -144,7 +144,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken } /// - /// The data handed to the chart script, serialized as JSON. + /// The data passed to the chart script, serialized as JSON. /// public sealed class UsageChart { @@ -161,7 +161,7 @@ public sealed class UsageChart public int? Maximum { get; init; } /// - /// Gets the number of seats tolerated during the grace period. + /// Gets the number of seats allowed during the grace period. /// public int? Grace { get; init; } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs index 4d270d1..f97896e 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs @@ -6,7 +6,7 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages; /// -/// The dashboard: every registered license, with how much of it is in use right now. +/// The home page. It lists every registered license with the part of its capacity that is in use. /// public sealed class IndexModel( ILeaseRepository repository, @@ -73,8 +73,8 @@ public sealed class LicenseSummary public DateTime? MaintenanceEndDate { get; init; } /// - /// Gets the modifier that colours the status: green for an active license, orange for a key - /// that cannot be parsed, amber while the grace period runs. + /// Gets the modifier that gives the status its color. An active license is green, a key that + /// cannot be parsed is orange, and a license in its grace period is amber. /// public string StatusModifier => this.LicenseType == "INVALID" ? "invalid" diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs index 3288c78..35030e4 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs @@ -40,8 +40,8 @@ builder.Services.AddScoped(); -// The health checks of the two probes. The liveness probe at /health/live runs none of them; see -// OperationsEndpoints. +// The health checks of the monitoring probe. The liveness probe at /health/live runs none of them. +// See OperationsEndpoints. builder.Services.AddHealthChecks() .AddCheck( "database" ) .AddCheck( "licenses" ); @@ -65,8 +65,8 @@ ? ActivatorUtilities.CreateInstance( services ) : new NullEmailSender() ); -// Time acceleration exists so that a multi-day licensing scenario can be replayed in minutes. It is -// off unless explicitly configured. +// The acceleration of the clock exists so that a licensing scenario that lasts several days can be +// replayed in minutes. It is disabled unless the configuration enables it. builder.Services.AddSingleton( services => { @@ -113,9 +113,9 @@ .GetSection( $"{LicenseServerOptions.SectionName}:AdminRoles" ) .Get() ?? []; - // No roles configured means the administrative pages are open, which is how the legacy - // Web.config shipped. Tightening this by default would lock administrators out of their - // own server on upgrade. A warning is logged at startup instead. + // When no role is configured, the administrative pages are open, as they were in the + // legacy Web.config. A restrictive default would lock administrators out of their own + // server during an upgrade. The server writes a warning to the log at startup instead. if ( roles.Length == 0 ) { policy.RequireAssertion( _ => true ); @@ -166,9 +166,9 @@ app.MapOperationsEndpoints(); app.MapLegacyUrlRedirects(); -// On SQL Server the schema is created by Database/CreateTables.sql, which is the source of truth and -// which an administrator runs deliberately. A SQLite database is created on demand, because it is -// meant for evaluation and has no administrator to run a script. +// On SQL Server, Database/CreateTables.sql creates the schema. That script defines the schema, and +// an administrator runs it. A SQLite database is created here, because it is used for evaluation and +// for tests, where no administrator runs a script. if ( string.Equals( app.Configuration["LicenseServer:DatabaseProvider"], "Sqlite", @@ -178,9 +178,9 @@ scope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); } -// A development server can issue itself the license keys it serves, so that a trial or a load -// simulation has something to lease. The registration has already refused to start if this is set -// outside the Development environment. +// A development server can issue to itself the license keys it serves, so that an evaluation or a +// load simulation has a license to lease. The registration has already refused to start when this +// setting is used outside the Development environment. { TestLicenseAuthority? testAuthority = app.Services.GetService(); @@ -196,8 +196,8 @@ } } -// The administrative pages are the only way to add or revoke a license, so an open default deserves -// more than a comment in a configuration file. +// The administrative pages are the only way to add and to revoke a license. The server writes a +// warning, because a comment in a configuration file is not enough for an open default. { LicenseServerOptions options = app.Services.GetRequiredService>().Value; @@ -210,7 +210,8 @@ } } -// Which scheme was chosen is worth stating, because leases recorded under "None" carry no user. +// The server writes the selected scheme to the log, because the leases recorded under the scheme +// "None" carry no authenticated user. app.Logger.LogInformation( "Authenticating with the {Scheme} scheme.", authenticationScheme ); if ( authenticationScheme == AuthenticationRegistration.None ) @@ -241,6 +242,6 @@ public static class AuthorizationPolicies } /// -/// Exposed so that integration tests can host the application in memory. +/// Declared as a public type so that the integration tests can host the application in memory. /// public partial class Program; diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/admin-actions.js b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/admin-actions.js index 7dfab5d..8b8a2bf 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/admin-actions.js +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/admin-actions.js @@ -1,10 +1,10 @@ -// The management actions of a license: closing the menu when the administrator clicks outside it, -// and asking for confirmation before an action runs. +// The management actions of a license. This file closes the menu when the administrator clicks +// outside it, and it asks for a confirmation before an action runs. // -// Both are enhancements of markup that already works. The menu is a
element, which opens -// and closes on its own, and a form whose submission is not intercepted here submits as any form -// does. A browser that does not run this file therefore keeps every action, and loses only the -// confirmation. +// Both behaviours are additions to markup that already works. The menu is a
element, which +// opens and closes without a script, and a form whose submission this file does not intercept is +// submitted by the browser. A browser that does not run this file therefore keeps every action, and +// loses only the confirmation. (function () { "use strict"; @@ -33,7 +33,8 @@ var dialog = document.getElementById( "confirm-dialog" ); - // A browser without keeps the direct submission rather than losing the action. + // In a browser that does not support , the form is submitted directly, so the action is + // not lost. if ( !dialog || typeof dialog.showModal !== "function" ) { return; } @@ -55,8 +56,9 @@ closeMenus( null ); - // Cleared explicitly: a dialog closed with the Escape key keeps the value of the - // previous close in some browsers, which would confirm an action nobody confirmed. + // The value is cleared explicitly. In some browsers, a dialog closed with the Escape key + // keeps the value of the previous close, which would confirm an action that the + // administrator did not confirm. dialog.returnValue = ""; dialog.showModal(); } ); diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js index eee42de..f4fcc7f 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js @@ -1,5 +1,6 @@ -// Renders the licence usage history. The data is produced by GraphModel and embedded in the page as -// JSON; nothing is fetched at run time, so the page works on an isolated network. +// Draws the usage history of a license. GraphModel produces the data, and the page contains it as +// JSON. The page downloads nothing at run time, so it works on a network without access to the +// Internet. (function () { "use strict"; @@ -12,8 +13,8 @@ var chart = JSON.parse(dataElement.textContent); - // Take the axis and gridline colours from the design tokens, so the chart stays part of the - // page rather than a white box dropped onto it. + // The colors of the axes and of the grid lines come from the design tokens, so that the chart + // belongs to the page instead of appearing as a white rectangle on it. var styles = getComputedStyle(document.documentElement); Chart.defaults.color = styles.getPropertyValue("--text-muted").trim() || "#a0a0a0"; Chart.defaults.borderColor = styles.getPropertyValue("--doc-table-line").trim() || "#2c1a4f"; @@ -30,7 +31,7 @@ pointHitRadius: 8 }]; - // The capacity and grace lines are absent for an unlimited licence. + // A license without a seat limit has no capacity line and no grace line. if (chart.maximum !== null && chart.maximum !== undefined) { datasets.push({ label: "Authorized", @@ -70,8 +71,8 @@ autoSkip: false, maxRotation: 45, minRotation: 45, - // The original chart labelled Mondays only, which keeps a year-long window - // readable. + // The original chart labelled only the Mondays, which keeps a window of one + // year readable. callback: function (value, index) { var label = chart.labels[index]; return new Date(label + "T00:00:00Z").getUTCDay() === 1 ? label : ""; @@ -83,11 +84,11 @@ legend: { position: "bottom", - // Each series is a line, so the legend samples it as a line rather than as the - // filled rectangle Chart.js draws by default. The dash pattern and the width - // have to be carried over from the dataset, because the generated legend item - // does not take them, and without them the three samples differ only by colour - // while the lines on the chart are solid, dashed and dotted. + // Each series is a line, so the legend draws a line and not the filled rectangle + // that Chart.js draws by default. The dash pattern and the width are copied from + // the dataset, because the generated legend item does not contain them. Without + // them, the three samples of the legend would differ only by their color, while + // the lines of the chart are solid, dashed and dotted. labels: { usePointStyle: true, pointStyle: "line", diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs index 4fc243a..eef6bce 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs @@ -6,17 +6,16 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// The audit log export, which streams the rows to the response as they arrive rather than building -/// the whole file in memory. +/// The export of the audit log. It writes the rows to the response as it reads them, and it does not +/// build the whole file in memory. /// public sealed class AuditLogExportTests : IDisposable { /// - /// Enough leases that the writer of the response has to flush before the last one is written. A - /// buffers about a thousand characters and an audit line is about - /// ninety, so a handful of leases are written entirely from the buffer and exercise nothing. The - /// export was returning a truncated response in production while passing a test that wrote three - /// lines. + /// A number of leases large enough that the writer of the response flushes before it writes the + /// last one. A buffers about a thousand characters, and an audit line + /// has about ninety, so a few leases stay in the buffer and exercise nothing. The export returned + /// a truncated response in production while it passed a test that wrote three lines. /// private const int leaseCount = 60; @@ -81,8 +80,8 @@ public async Task Export_WithLeases_IsOfferedAsAFile() } /// - /// An empty range is served as an empty body. This is the path every earlier export test took, - /// because the database they exported held no lease at all. + /// A range that contains no lease is answered with an empty body. The earlier tests of the export + /// all followed this path, because the database they exported contained no lease. /// [Fact] public async Task Export_WithNoLease_IsEmpty() @@ -97,8 +96,8 @@ public async Task Export_WithNoLease_IsEmpty() } /// - /// The leases are written straight to the database rather than requested, so that their number - /// does not depend on the capacity of the license or on the rules of the allocator. + /// The leases are written directly to the database and not requested, so that their number does + /// not depend on the capacity of the license or on the rules of the allocator. /// private void SeedLeases( License license, int count ) { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs index 859e4d8..0c9eb98 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs @@ -6,9 +6,9 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// The licensing component, which is SharpCrafters.Backstage. Every other test in this suite works -/// against FakeLicenseParser, so this is the only place where a real license key is parsed and -/// the only place that would notice the package changing what it reports. +/// The licensing component, which is SharpCrafters.Backstage. Every other test of this suite runs +/// against FakeLicenseParser. These tests are the only ones that parse a real license key, +/// and therefore the only ones that detect a change in the values the package reports. /// public sealed class BackstageLicenseParserTests { @@ -44,9 +44,9 @@ public void SignedKey_IsParsedIntoTheFactsTheServerNeeds() } /// - /// A license key that SharpCrafters.Backstage issues for its own tests parses here too. This is - /// what shows that the server and the package agree on the whole of the format, and not only on - /// the parts a key built in this file happens to use. + /// The parser also reads a license key that SharpCrafters.Backstage creates for its own tests. + /// The server and the package therefore agree on the whole format, and not only on the fields + /// that a key built in this file uses. /// [Fact] public void KeyIssuedByTheBackstageTestProvider_IsParsed() @@ -59,17 +59,17 @@ public void KeyIssuedByTheBackstageTestProvider_IsParsed() } /// - /// The signature is what stops a customer from minting their own licenses, so a key signed with a - /// key the authority does not hold must not be served. + /// The signature prevents a customer from creating their own licenses, so the server must not + /// serve a key signed with a key that the authority does not hold. /// [Fact] public void KeySignedWithAForgedKey_IsRejected() => Assert.Null( parser.TryParse( TestLicenseKeys.Builder().SignWithAForgedKey() ) ); /// - /// A key whose signature names an authority nobody issued is an invalid key, not a crash. The - /// provider throws when it is asked for a key it does not hold, and an administrator pastes - /// license keys into a web form. + /// A key whose signature names an authority that was never issued is an invalid key, and not an + /// exception. The provider raises an exception when it is asked for a key it does not hold, and + /// an administrator pastes license keys into a web form. /// [Fact] public void KeySignedByAnUnknownAuthority_IsRejectedWithoutThrowing() @@ -133,9 +133,10 @@ public void KeyWithoutAGracePeriod_GetsThirtyDays() } /// - /// A key that carries no minimal client version is not served to every client regardless: the - /// version is derived from the other fields. An eligible key is readable by PostSharp 5.0.22 and - /// later, which is the version that introduced the license server. + /// A key that carries no minimal version of the client is not served to every client. The + /// licensing library derives the version from the other fields. A key that a license server may + /// serve is readable by PostSharp 5.0.22 and later, which is the version that introduced the + /// license server. /// [Fact] public void MinPostSharpVersion_IsDerivedWhenTheKeyDoesNotDeclareIt() @@ -180,8 +181,8 @@ public void CleanLicenseString_RemovesEveryKindOfWhitespace( string pasted, stri => Assert.Equal( expected, parser.CleanLicenseString( pasted ) ); /// - /// A key pasted with the whitespace an email adds still parses once it has been cleaned, which is - /// the sequence the Add License page performs. + /// A key pasted with the whitespace that an e-mail adds is parsed after it is cleaned. The page + /// that adds a license performs these two steps in this order. /// [Fact] public void PastedKey_ParsesAfterCleaning() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs index 065ad8d..b82bc49 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs @@ -4,8 +4,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// An administrator can end a lease early, which inserts a replacement ending now rather than -/// deleting anything. +/// An administrator can end a lease before its end time. The server inserts a replacement that ends +/// at the current instant, and it deletes nothing. /// public sealed class CancelLeaseTests { @@ -60,9 +60,9 @@ public async Task CancelLease_ReleasesTheSeat() } /// - /// Cancelling skips the end-time adjustment applied to new leases. Without that, a lease ending - /// "now" would be rejected for not extending beyond the current moment, and cancelling would - /// silently do nothing. + /// A cancellation skips the adjustment of the end time that a new lease receives. With that + /// adjustment, a lease that ends at the current instant would be rejected, because it does not + /// end after the current instant, and the cancellation would have no effect. /// [Fact] public async Task CancelLease_IsNotRejectedForEndingImmediately() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs index 8f2a0a3..921025b 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs @@ -26,8 +26,8 @@ private static ValidateOptionsResult Validate( Action conf public void Defaults_AreValid() => Assert.True( Validate( _ => { } ).Succeeded ); /// - /// A renewal time that is not before the end of the lease makes the client renew on every single - /// request. The legacy configuration documented this constraint but never enforced it. + /// When the renewal time is not before the end of the lease, the client renews the lease at + /// every request. The legacy configuration documented this constraint and never enforced it. /// [Theory] [InlineData( 3, 3 )] @@ -114,8 +114,8 @@ public void CachingLicenseParser_ParsesEachKeyOnlyOnce() } /// - /// The legacy cache returned before storing a failure, so an invalid key was re-parsed on every - /// request and on every render of the dashboard. + /// The legacy cache returned before it stored a failure, so it parsed an invalid key at every + /// request and at every display of the home page. /// [Fact] public void CachingLicenseParser_RemembersThatAKeyIsInvalid() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs index 86f5c1d..5c0a718 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs @@ -3,19 +3,20 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// -/// Resolves synthetic license keys to the facts a test wants them to carry. +/// Maps the synthetic license keys of a test to the properties that the test gives them. /// /// -/// Real PostSharp license keys are signed and are not present in this repository, so almost every -/// test works against this parser. PostSharpLicenseParser is covered separately, by an opt-in -/// test that needs a real key. +/// A real PostSharp license key is signed, and this repository contains none, so almost every test +/// uses this parser. BackstageLicenseParserTests covers the real parser with the license keys +/// of the test authority. /// public sealed class FakeLicenseParser : ILicenseParser { private readonly Dictionary licenses = new( StringComparer.Ordinal ); /// - /// Gets the number of times actually did work, to verify caching. + /// Gets the number of calls to that did the work, so that a test can + /// verify the cache. /// public int ParseCount { get; private set; } @@ -29,7 +30,7 @@ public sealed class FakeLicenseParser : ILicenseParser } /// - /// Mimics the real implementation, which strips whitespace from a pasted key. + /// Reproduces the real implementation, which removes the whitespace of a pasted key. /// public string CleanLicenseString( string licenseKey ) => new( licenseKey.Where( c => !char.IsWhiteSpace( c ) ).ToArray() ); diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs index 63339eb..5b6337a 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs @@ -3,8 +3,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// -/// Reports a fixed version of the licensing library, so that the "the license server itself is too -/// old" branch can be reached from a test. +/// Reports a fixed version of the licensing library, so that a test can reach the branch in which +/// the license server is older than the license key requires. /// public sealed class FixedServerVersion( Version version ) : ILicenseServerVersion { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs index 19a16b0..2400862 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs @@ -4,16 +4,16 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// -/// An that keeps the messages in memory instead of sending them, so that -/// tests can assert on what the license server would have notified the administrator about. +/// An that stores the messages in memory instead of sending them, so that +/// a test can assert on the notifications that the license server produces. /// public sealed class InMemoryEmailSender : IEmailSender { private readonly ConcurrentQueue sent = new(); /// - /// Gets or sets an exception to throw instead of recording the message, to verify that a broken - /// SMTP server never denies a developer their license. + /// Gets or sets an exception that this sender raises instead of recording the message, so that a + /// test can verify that an SMTP server that fails never denies a license. /// public Exception? ThrowOnSend { get; set; } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs index a5e9c9e..0498ba6 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs @@ -3,7 +3,7 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// -/// Never grants the lock, so that the "service overloaded" path can be exercised. +/// Never grants the lock, so that a test can exercise the path that answers "Service overloaded." /// public sealed class NeverAcquiringLeaseLock : ILeaseLock { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs index 1afba73..d35448d 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs @@ -5,8 +5,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// -/// Signs deterministically and keeps every payload it was asked to sign, so that tests can assert on -/// what exactly goes into the audit signature chain. +/// Produces a signature that depends only on the payload, and stores every payload it signed, so +/// that a test can assert on the content of the audit signature chain. /// public sealed class RecordingLeaseSigner : ILeaseSigner { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveSeatsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveSeatsTests.cs index 68f0bb0..57cf387 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveSeatsTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveSeatsTests.cs @@ -109,8 +109,8 @@ public async Task GetActiveSeats_ReplacedLease_IsNotCounted() } /// - /// SQL Server's default collation is case-insensitive. The in-memory database is configured to - /// match, so that a test cannot pass here and fail in production. + /// The default collation of SQL Server ignores the case. The database held in memory uses the + /// same collation, so that a test cannot pass here and fail in production. /// [Fact] public async Task GetActiveSeats_UserNameCasingDiffers_CountsAsOneUser() @@ -138,14 +138,14 @@ public async Task GetActiveSeats_HonoursMachinesPerUser() } /// - /// A seat is counted from the machines a user works on, not from the leases they hold. A user can - /// hold two leases on one machine, and charging them for a machine they do not have would deny a - /// colleague a lease the license has the capacity for. + /// A seat is counted from the machines a user works on, and not from the leases that user holds. + /// A user can hold two leases on one machine. Counting a second machine for that user would deny + /// a lease to a colleague, while the license still has a free seat. /// /// - /// The lease service normally prevents a second lease on one machine by prolonging the first, but - /// a server whose clock has moved backwards grants one. A load simulation produced exactly that - /// within minutes of a restart. + /// The lease service prevents a second lease on one machine: it prolongs the first lease. A + /// server whose clock moved backwards grants a second lease. A load simulation produced this + /// state a few minutes after a restart. /// [Fact] public async Task GetActiveSeats_TwoLeasesOnOneMachine_CountAsOneMachine() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs index c05af1d..63c665c 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs @@ -7,16 +7,16 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// Makes the response body of the test host refuse a synchronous write, as Kestrel does. /// /// -/// accepts a synchronous write whatever its -/// AllowSynchronousIO property says, so an endpoint that writes synchronously passes every -/// test and then fails against a real server with "Synchronous operations are disallowed". This -/// filter closes that gap by wrapping the body in a stream that throws the same way. +/// accepts a synchronous write whatever the +/// value of its AllowSynchronousIO property. An endpoint that writes synchronously therefore +/// passes every test, and then fails against a real server with the message "Synchronous operations +/// are disallowed". This filter wraps the body in a stream that raises the same exception. /// public sealed class AsyncOnlyResponseBody : IStartupFilter { /// - /// Gets or sets a value indicating whether the guard is active. It is off by default, so that a - /// test that does not care about the response body is unaffected. + /// Gets or sets a value indicating whether the stream refuses a synchronous write. The value is + /// false by default, so that a test that does not read the response body is not affected. /// public bool IsEnabled { get; set; } @@ -50,8 +50,8 @@ public Action Configure( Action next ) }; /// - /// Forwards every asynchronous write and throws on every synchronous one, with the message - /// Kestrel uses. + /// Forwards every asynchronous write, and raises an exception at every synchronous write, with + /// the message that Kestrel uses. /// private sealed class AsyncOnlyStream( Stream inner ) : Stream { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs index 6f32ac4..a9bc572 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs @@ -22,9 +22,9 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// -/// Hosts the real application in memory, with the SQL Server database swapped for SQLite and the -/// license parser, clock and email sender swapped for test doubles. Everything else -- routing, -/// model binding, authorization, the endpoints themselves -- is the production pipeline. +/// Hosts the real application in memory. SQLite replaces the SQL Server database, and test doubles +/// replace the license parser, the clock and the e-mail sender. Everything else is the production +/// pipeline: the routing, the model binding, the authorization and the endpoints. /// public sealed class LicenseServerApplication : WebApplicationFactory { @@ -34,15 +34,16 @@ public sealed class LicenseServerApplication : WebApplicationFactory public LicenseServerApplication() { - // A shared-cache in-memory database, so that the application can open its own connections by - // connection string while the database itself lives only as long as this one stays open. + // A database held in memory with a shared cache, so that the application can open its own + // connections from the connection string, while the database exists only as long as this + // connection stays open. this.connectionString = $"DataSource=licenseserver-{Guid.NewGuid():N};Mode=Memory;Cache=Shared"; this.connection = new SqliteConnection( this.connectionString ); this.connection.Open(); - // Created up front, because a test seeds licenses before it issues its first request, which - // is what actually starts the host. + // The schema is created here, because a test adds licenses before it sends its first + // request, and the first request is what starts the host. using LicenseServerDbContext db = this.CreateDbContext(); db.Database.EnsureCreated(); } @@ -66,9 +67,9 @@ protected override void ConfigureWebHost( IWebHostBuilder builder ) { builder.UseEnvironment( "Testing" ); - // UseSetting rather than ConfigureAppConfiguration: with the minimal hosting model the - // application reads its configuration while Program.cs runs, which is before the - // ConfigureAppConfiguration callbacks are applied. + // The settings are passed with UseSetting and not with ConfigureAppConfiguration. With the + // minimal hosting model, the application reads its configuration while Program.cs runs, + // which is before the callbacks of ConfigureAppConfiguration are applied. Dictionary settings = new() { ["LicenseServer:MachinesPerUser"] = "2", @@ -89,8 +90,8 @@ protected override void ConfigureWebHost( IWebHostBuilder builder ) builder.ConfigureServices( services => { - // The database itself is not swapped here: the application selects SQLite from the - // configuration above, through the same code path a customer would use. + // The database is not replaced here. The application selects SQLite from the + // configuration above, through the code path that a customer uses. services.RemoveAll(); services.AddSingleton( this.LicenseParser ); diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs index b191743..0fdd19c 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs @@ -63,9 +63,9 @@ private LicenseServerTestContext( public LicenseServerDbContext Db => this.db; /// - /// Creates a context over a database that is private to the calling test. Creating an in-memory - /// SQLite database takes well under a millisecond, so there is no reason to share one between - /// tests and then have to reset it. + /// Creates a context over a database that belongs to the calling test. Creating a SQLite database + /// in memory takes less than a millisecond, so the tests do not share one and no test has to + /// reset it. /// public static async Task CreateAsync( Action? configure = null ) { @@ -87,8 +87,8 @@ public static async Task CreateAsync( Action - /// Returns a repository over a fresh unit of work, sharing the same database. Use this to assert - /// on what was actually persisted, rather than on what the change tracker remembers. + /// Returns a repository over a new unit of work on the same database. Use it to assert on the + /// rows that were saved, and not on the state of the change tracker. /// public LeaseRepository CreateFreshRepository() => new( diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs index ee78028..c0bd712 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs @@ -5,14 +5,14 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// -/// An in-memory SQLite database, created from the EF Core model, that behaves like a real relational -/// database: foreign keys, transactions, and server-side query translation all apply. +/// A SQLite database held in memory and created from the EF Core model. It behaves like a relational +/// database: it enforces the foreign keys, it runs transactions, and it translates the queries. /// /// -/// A SQLite in-memory database lives exactly as long as a connection to it is open, so this fixture -/// holds one connection for its whole lifetime and hands out contexts that share it. Each context is -/// a separate unit of work with its own change tracker, which is what lets a test tell a pending -/// lease apart from a committed one. +/// A SQLite database held in memory exists as long as a connection to it is open, so this fixture +/// holds one connection during its whole lifetime and returns contexts that share that connection. +/// Each context is a separate unit of work with its own change tracker, so a test can distinguish a +/// lease that is pending from a lease that is saved. /// public sealed class SqliteDatabaseFixture : IAsyncDisposable { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs index 9b19237..f31e10d 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs @@ -11,8 +11,9 @@ public static class TestClock /// A Monday, at a whole second. /// /// - /// Monday exercises the weekday-label branch of the usage graph. Whole seconds keep the tests - /// independent of the 1/300-second rounding of the SQL datetime type. + /// Monday exercises the branch of the usage graph that labels the weekdays. Whole seconds keep + /// the tests independent of the rounding of the SQL type datetime, which is 1/300 of a + /// second. /// public static readonly DateTime Origin = new( 2026, 1, 5, 9, 0, 0, DateTimeKind.Utc ); @@ -22,7 +23,7 @@ public static class TestClock } /// -/// Builds a license together with the facts the fake parser will report for its key. +/// Builds a license, and the properties that the fake parser reports for its key. /// public sealed class LicenseBuilder { @@ -176,7 +177,7 @@ public License AddTo( LicenseServerTestContext context ) } /// -/// Builds a lease directly, bypassing the allocation rules, to set up a starting state. +/// Builds a lease directly, without the allocation rules, to create the initial state of a test. /// public sealed class LeaseBuilder { @@ -250,7 +251,7 @@ public Lease AddTo( LicenseServerTestContext context ) Grace = this.grace }; - // Saved through the repository, so the lease is signed the way a real one is. + // The lease is saved through the repository, so that it is signed as a real lease is. context.Db.Leases.Add( lease ); context.Repository.SaveChanges(); diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs index cad23db..a6a645f 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs @@ -11,16 +11,16 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// /// /// -/// The keys are signed by the test licensing authority of SharpCrafters.Backstage, reached through -/// . That authority generates its key pair in the current -/// process, so a license key signed here is valid in this process and nowhere else -- which is what -/// lets a public, MIT-licensed repository test the real signature path. What this cannot cover is -/// the production authority itself, whose public keys are constants of SharpCrafters.Backstage and -/// are covered by the tests of that package. +/// The test licensing authority of SharpCrafters.Backstage signs the keys, and +/// gives access to it. That authority generates its key pair in +/// the current process, so a license key signed here is valid in this process and in no other one. +/// A public repository under the MIT license can therefore test the real code path of the signature. +/// These tests do not cover the production authority, whose public keys are constants of +/// SharpCrafters.Backstage, and which the tests of that package cover. /// /// -/// holds the same authority object that signs, so the tests verify against -/// exactly what signed them rather than against a reconstruction of it. +/// holds the authority object that signs the keys, so a test verifies a key +/// against the authority that signed it, and not against another instance of that authority. /// /// public static class TestLicenseKeys @@ -28,20 +28,21 @@ public static class TestLicenseKeys private static readonly TestLicenseKeyProvider provider = new(); /// - /// The identifier of the key of the test authority. It is a constant of SharpCrafters.Backstage - /// but an internal one, so it is read back from a license key that the authority has signed. + /// The identifier of the key of the test authority. It is a constant of SharpCrafters.Backstage, + /// and that constant is internal, so this class reads the identifier from a license key that the + /// authority signed. /// private static readonly byte authorityKeyId; /// - /// Gets the authority that verifies the keys this class signs, which is what a parser under test - /// is constructed with. + /// Gets the authority that verifies the keys this class signs. A test passes it to the parser + /// that it exercises. /// public static ILicensingAuthorityProvider Authority { get; } /// - /// Gets the ready-made license keys that SharpCrafters.Backstage issues for its own tests, one - /// per product and license type it sells. + /// Gets the license keys that SharpCrafters.Backstage creates for its own tests. There is one + /// key per product and per type of license. /// public static TestLicenseKeyProvider Keys => provider; @@ -57,8 +58,8 @@ static TestLicenseKeys() } /// - /// Creates a builder for a license key that the license server accepts, which the caller modifies - /// before serializing it. + /// Creates a builder of a license key that the license server accepts. The caller modifies the + /// builder before it serializes the key. /// public static LicenseKeyDataBuilder Builder( int licenseId = 1, @@ -80,25 +81,26 @@ public static string Sign( this LicenseKeyDataBuilder builder ) => builder.SignAndSerialize( provider.Authority ); /// - /// Serializes a license key without signing it. Only the types that require no signature parse - /// this way. + /// Serializes a license key without signing it. Only the types of license that require no + /// signature can be parsed in this form. /// public static string Unsigned( this LicenseKeyDataBuilder builder ) => builder.Serialize(); /// - /// Signs a license key with a key of the test authority's identifier that the test authority does - /// not hold, which is a forgery: the parser looks the identifier up, finds the real key and the - /// signature does not verify against it. + /// Signs a license key with another key that carries the identifier of the test authority. The + /// result is a forgery: the parser reads the identifier, finds the real key, and the signature + /// does not verify against it. /// public static string SignWithAForgedKey( this LicenseKeyDataBuilder builder ) => builder.SignAndSerialize( CreateStandaloneAuthority( authorityKeyId ) ); /// - /// Signs a license key with an authority the parser has never heard of, so that the identifier of - /// the signature matches no key it holds. + /// Signs a license key with an authority that the parser does not know, so that the identifier of + /// the signature matches no key the parser holds. /// /// - /// The identifier is outside the range of the production keys and of the test keys of Backstage. + /// The identifier differs from the identifiers of the production keys and from the identifiers of + /// the test keys of Backstage. /// public static string SignWithAnUnknownAuthority( this LicenseKeyDataBuilder builder ) => builder.SignAndSerialize( CreateStandaloneAuthority( 200 ) ); @@ -118,12 +120,13 @@ private static LicensingAuthority CreateStandaloneAuthority( byte keyId ) } /// - /// Answers with the single authority that signed, for the identifier that authority's key carries. + /// Returns the authority that signed the keys, for the identifier that the key of that authority + /// carries. /// /// - /// cannot be used for this, because it builds an - /// authority from the XML representation of a key and the key of the test authority is generated - /// in the process rather than written down. + /// cannot do this, because it builds an + /// authority from the XML representation of a key, and the key of the test authority is generated + /// in the process instead of being written in the code. /// private sealed class TestAuthorityProvider( LicensingAuthority authority, byte keyId ) : ILicensingAuthorityProvider { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs index 62deb97..52af5e1 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs @@ -4,8 +4,9 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// The rules that decide whether a developer gets a license: reuse what they hold, grant spare -/// capacity, or fall back on the grace period before denying the request. +/// The rules that decide whether a developer receives a license. The server reuses the lease the +/// developer holds, then grants a free seat, then grants a seat of the grace period, and denies the +/// request when none of the three applies. /// public sealed class LeaseAllocationTests { @@ -252,8 +253,8 @@ public async Task GetLease_TriesLicensesInPriorityOrder() } /// - /// A warning is recorded even when it could not be delivered, so a broken SMTP server cannot - /// turn every subsequent request into another attempt. + /// The server records a warning even when it could not send it, so that an SMTP server that + /// fails does not turn every later request into another attempt. /// [Fact] public async Task GetLease_WarningEmailFails_StillRecordsThatItWasAttempted() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs index c52e662..8c8295a 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs @@ -6,9 +6,9 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// The audit-log line is a serialization contract: exported files are archived by customers and -/// compared across years, so the format is pinned here against literal expected strings rather than -/// against a re-implementation of the same logic. +/// The line of the audit log is a serialization contract. Customers archive the exported files and +/// compare them across years. These tests therefore compare the format to literal strings, and not +/// to a second implementation of the same logic. /// public sealed class LeaseAuditLineTests { @@ -70,10 +70,10 @@ public void Write_NeverDisclosesTheUserOrMachineName() /// Timestamps must be absolute, whatever time zone the server keeps. /// /// - /// The legacy implementation serialized values whose was - /// -- which is what SQL Server returns -- in a mode that - /// treated them as local time and shifted them, so the exported file depended on the machine - /// that produced it. + /// SQL Server returns values whose is + /// . The legacy implementation serialized them in a mode + /// that treated them as local times and shifted them, so the exported file depended on the + /// machine that produced it. /// [Fact] public void Write_EmitsAbsoluteTimestamps() @@ -85,8 +85,8 @@ public void Write_EmitsAbsoluteTimestamps() } /// - /// A lease that has been through the database must still serialize as UTC. This is the half of - /// the guarantee that lives in the model rather than in Lease.Write. + /// A lease read from the database is also serialized as UTC. The value converter of the model + /// provides this part of the guarantee, and Lease.Write provides the other part. /// [Fact] public async Task Write_AfterReload_StillEmitsUtc() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs index 9dccb61..6789eaa 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs @@ -3,8 +3,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// The usage timeline behind the graph: a sequence of lease open and close events, each carrying the -/// running seat count. +/// The usage timeline that the graph draws. It is a sequence of events that open and close a lease, +/// and each event carries the number of seats in use after it. /// public sealed class LeaseCountingPointsTests { @@ -107,9 +107,9 @@ public async Task GetLeaseCountingPoints_OneUserThreeMachines_ReachesTwoSeats() } /// - /// When one lease ends at the exact instant another begins, the machine must be released before - /// it is claimed again. This is what the numeric values of - /// encode, and it is the reason they must not be renumbered. + /// When one lease ends at the instant at which another lease begins, the machine is released + /// before it is taken again. The numeric values of produce + /// this order, and this is the reason why they must not change. /// [Fact] public async Task GetLeaseCountingPoints_CloseIsProcessedBeforeOpenAtTheSameInstant() @@ -213,12 +213,13 @@ public async Task GetLeaseCountingPoints_ExcludesLeasesOutsideTheWindow() /// returns to zero once both have ended. /// /// - /// The lease service normally prevents this by reusing or prolonging a lease instead of granting - /// a second one, and an earlier version of this test recorded the situation as data the timeline - /// was entitled to refuse. It is not: a server whose clock moves backwards -- a restart with - /// TimeAcceleration set, a correction from a time server, a restored snapshot -- grants a - /// second lease while the first is still open, and a load simulation produced exactly that within - /// minutes. The usage page answered with HTTP 500 for as long as the older lease ran. + /// The lease service prevents this state: it reuses or prolongs a lease instead of granting a + /// second one. An earlier version of this test treated the state as data that the timeline could + /// refuse. The timeline cannot refuse it. A server whose clock moves backwards grants a second + /// lease while the first one is open, and the clock moves backwards after a restart with + /// TimeAcceleration, after a correction from a time server, and after the restore of a + /// snapshot. A load simulation produced this state in a few minutes, and the usage page answered + /// with the status 500 until the older lease ended. /// [Fact] public async Task GetLeaseCountingPoints_TwoOpenLeasesOnOneMachine_CountAsOneSeat() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs index f073506..323b11a 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs @@ -194,8 +194,8 @@ public async Task Lease_LockTimesOut_Returns503() } /// - /// A build agent gets a licence key but never a stored lease, so that build machines cannot - /// consume the seats of the developers they build for. + /// A build agent receives a license key, and the server stores no lease for it, so that build + /// machines do not consume the seats of the developers they build for. /// [Fact] public async Task Lease_BuildAgent_IsServedWithoutConsumingASeat() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs index 3c13a77..efb0062 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs @@ -3,8 +3,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// The body of a lease response. Every deployed client parses it, so it is pinned against a literal -/// expected string rather than against a re-implementation of the same formatting. +/// The body of the response of a lease request. Every deployed client parses it, so these tests +/// compare it to a literal string, and not to a second implementation of the same formatting. /// /// /// The expected values are the ones the LicenseLease.Serialize of the PostSharp SDK produced, @@ -26,8 +26,8 @@ public void Serialize_ProducesTheExpectedBody() serializer.Serialize( "1-ABCDEF", start, start.AddDays( 3 ), start.AddDays( 2 ) ) ); /// - /// A time read from a datetime column carries no kind. It is a UTC instant all the same, - /// and must not be shifted by the time zone the server happens to keep. + /// An instant read from a datetime column carries no kind. It is a UTC instant, and the + /// time zone of the server must not shift it. /// [Fact] public void Serialize_TreatsAnUntaggedTimeAsUtc() @@ -40,9 +40,9 @@ public void Serialize_TreatsAnUntaggedTimeAsUtc() } /// - /// The client splits the body on ; and each part at its first :, so a key may not - /// contain either character. A license key is an identifier, a hyphen and Base32, so it never - /// does -- this pins the assumption rather than the behaviour. + /// The client splits the body at every ;, and each part at its first :, so a key + /// must contain neither character. A license key contains an identifier, a hyphen and Base32 + /// characters, so it contains neither. This test verifies that assumption. /// [Fact] public void Serialize_ProducesFourPartsTheClientCanSplit() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs index 8b6a554..9fdaf40 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs @@ -8,8 +8,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// it, so removing or altering a row breaks every signature after it. /// /// -/// These assertions encode behaviour that is documented nowhere and that is invisible in a code -/// review of the migration diff, which is why they are pinned explicitly. +/// These assertions describe behaviour that no document describes, and that a review of the +/// migration diff does not show. This is the reason why the tests state it explicitly. /// public sealed class LeaseSignatureTests { @@ -49,8 +49,9 @@ public async Task Signature_FirstLeaseEver_ChainsFromAnEmptySignature() } /// - /// Leases saved together must chain to each other, not all to the same predecessor. Were they - /// siblings, any one of them could be removed without breaking a later signature. + /// The leases saved together chain to each other, and not all to the same predecessor. If they + /// chained to the same predecessor, one of them could be removed without invalidating a later + /// signature. /// [Fact] public async Task Signature_LeasesSavedTogether_ChainToEachOther() @@ -78,8 +79,9 @@ public async Task Signature_LeasesSavedTogether_ChainToEachOther() } /// - /// The signature covers the lease identifier the database assigned, so an auditor can recompute - /// the chain from an exported file. Signing before the insert would put a zero there. + /// The signature covers the identifier that the database assigned to the lease, so an auditor can + /// recompute the chain from an exported file. A signature computed before the insert would + /// contain a zero in that position. /// [Fact] public async Task Signature_CoversTheAssignedLeaseId() @@ -96,8 +98,8 @@ public async Task Signature_CoversTheAssignedLeaseId() } /// - /// The whole point of the chain: an exported line plus the previous signature reproduce the - /// signature, so a tampered row is detectable. + /// An exported line and the previous signature reproduce the signature of that line, so a + /// modified row can be detected. This property is the purpose of the chain. /// [Fact] public async Task Signature_CanBeRecomputedFromTheExportedLine() @@ -197,7 +199,7 @@ public async Task Signature_FitsTheDatabaseColumn() } /// - /// A lease must never be readable without its signature, so the insert and the signature share a + /// A lease is never readable without its signature, so the insert and the signature run in one /// transaction. /// [Fact] diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseAvailabilityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseAvailabilityTests.cs index fa87bf0..dce53a9 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseAvailabilityTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseAvailabilityTests.cs @@ -5,12 +5,12 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// Whether the server can serve a lease at all, which is what the health check reports. +/// Whether the server can serve a lease, which is what the health check reports. /// /// -/// The last tests here ask the allocator the same question by allocating, because this service -/// applies the rules of without allocating and the two must not drift -/// apart. +/// The last tests of this class ask the allocator the same question, by allocating a lease. This +/// service applies the rules of without allocating, and the two +/// implementations must give the same answer. /// public sealed class LicenseAvailabilityTests { @@ -93,8 +93,8 @@ public async Task Availability_Expired_CannotServe() } /// - /// A license key the server cannot parse counts as invalid, which is what an administrator sees - /// on the home page as well. + /// A license key that the server cannot parse counts as invalid. The home page reports the same + /// state. /// [Fact] public async Task Availability_UnparsableKey_CannotServe() @@ -113,7 +113,8 @@ public async Task Availability_UnparsableKey_CannotServe() } /// - /// Full, but the grace period still has a seat and days left, so the next request is served. + /// The license is full, and its grace period still has a free seat and remaining days, so the + /// server serves the next request. /// [Fact] public async Task Availability_FullWithGraceLeft_CanServe() @@ -161,9 +162,9 @@ public async Task Availability_GracePeriodOver_CannotServe() } /// - /// Reading the availability must not start the grace period of a license. The allocator starts it - /// when it falls back on it, and a probe that did the same would run the clock of that period - /// against a server nobody is using. + /// Reading the availability does not start the grace period of a license. The allocator starts + /// that period when it grants a lease within it. A probe that did the same would let the period + /// elapse while the server is idle. /// [Fact] public async Task Availability_FullLicense_DoesNotStartTheGracePeriod() @@ -178,8 +179,8 @@ public async Task Availability_FullLicense_DoesNotStartTheGracePeriod() } /// - /// The health check and the allocator answer the same question, so a server the check calls - /// available grants a lease, and one it calls unavailable denies it. + /// The health check and the allocator answer the same question. A server that the check reports + /// as available grants a lease, and a server that it reports as unavailable denies the request. /// [Theory] [InlineData( 3, false, true )] diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs index 779bf3c..10ef327 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs @@ -3,8 +3,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// Why a license may refuse to serve a request. Each reason is reported back to the developer in the -/// body of the 403 response, so the wording matters. +/// The reasons why a license does not serve a request. The server reports each reason to the +/// developer in the body of the response 403, so these tests verify the text. /// public sealed class LicenseValidationTests { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs index 94751b4..4e28a1a 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs @@ -4,8 +4,9 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// Leases are never updated in place: prolonging or cancelling one inserts a replacement that points -/// back at it. "Open" leases are those nothing points back at. +/// The server never updates a lease. Prolonging a lease and cancelling a lease both insert a +/// replacement that references the previous lease. A lease is open when no other lease references +/// it. /// public sealed class OpenLeasesTests { @@ -52,8 +53,8 @@ public async Task OpenLeases_ChainOfReplacements_LeavesOnlyTheLast() } /// - /// Nothing in the schema prevents two leases from replacing the same lease. The legacy - /// left-join query returned such a lease once per replacement; an anti-join returns it once. + /// No constraint of the schema prevents two leases from replacing the same lease. The legacy + /// query, a left join, returned such a lease once per replacement. An anti-join returns it once. /// [Fact] public async Task OpenLeases_LeaseReplacedTwice_IsStillListedOnlyOnce() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs index 47fb570..2eae5a5 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs @@ -7,8 +7,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// The endpoints a monitoring system talks to. What matters to a probe is the status code, so each -/// test asserts it together with the body. +/// The endpoints of a monitoring system. A probe reads the status code, so each test asserts the +/// status code together with the body. /// public sealed class OperationsEndpointTests : IDisposable { @@ -49,9 +49,9 @@ public async Task Health_LicenseWithFreeCapacity_IsHealthy() } /// - /// A server with no license answers every lease request with 403 while looking perfectly well - /// from the outside, which is the state the check exists to make visible. It warns rather than - /// fails: the probe stays green, because nothing that watches a probe can add a license. + /// A server without a license answers every lease request with 403 while its process and its + /// database work. The check reports that state. It warns and does not fail, so the probe still + /// succeeds, because no system that reads a probe can add a license. /// [Fact] public async Task Health_NoLicense_WarnsAndStaysServed() @@ -81,14 +81,14 @@ public async Task Health_ExpiredLicense_WarnsAndStaysServed() } /// - /// A database without the schema is the deployment mistake the check exists for: the server never - /// runs CreateTables.sql itself. The database is what fails the probe; the license check can only - /// warn, and here it warns because it cannot read the state at all. + /// A database without the schema is the deployment error that this check detects, because the + /// server never runs CreateTables.sql itself. The database check fails the probe. The license + /// check only warns, and here it warns because it cannot read the state. /// /// - /// Neither check may answer with the message of the exception, because a database exception - /// carries the name of the server and sometimes the whole connection string, and this endpoint is - /// anonymous. + /// Neither check returns the message of the exception. A database exception contains the name of + /// the server, and sometimes the whole connection string, and this endpoint requires no + /// authentication. /// [Fact] public async Task Health_DatabaseWithoutSchema_FailsAndSaysNothingMore() @@ -115,8 +115,8 @@ public async Task Health_DatabaseWithoutSchema_FailsAndSaysNothingMore() } /// - /// The liveness probe answers on the state of the process alone. A license that has expired is - /// not a reason to restart the server, and an orchestrator that restarted it would do so for ever. + /// The liveness probe reports the state of the process only. An expired license is not a reason + /// to restart the server, and an orchestrator that restarted it would restart it indefinitely. /// [Fact] public async Task Liveness_NoLicense_IsHealthy() @@ -146,8 +146,8 @@ public async Task Version_ReportsTheProductAndTheLicensingLibrary() } /// - /// Both endpoints are served anonymously: a load balancer holds no Windows credentials, and a - /// probe answered with 401 reads as a server that is down. + /// Both endpoints require no authentication. A load balancer has no Windows credentials, and a + /// probe that receives 401 reports the server as unavailable. /// [Theory] [InlineData( "/health/live" )] diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs index 111b684..7d43f3f 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs @@ -27,8 +27,8 @@ public void RenamedProduct_IsMatchedUnderBothSpellings( LicenseProduct product, } /// - /// The products that postdate the rename, and any value the server does not recognize, are matched - /// by themselves alone. + /// The products created after the renaming, and every value that the server does not know, match + /// only themselves. /// [Theory] [InlineData( "MetalamaProfessional" )] @@ -38,9 +38,9 @@ public void ProductWithOneSpelling_IsMatchedByItself( string productCode ) => Assert.Equal( [productCode], ProductCodes.Matching( productCode ) ); /// - /// A license added by a previous version of this server is served to a client that asks for the - /// same product by its Backstage name. Without the mapping the request would be denied although - /// the license is there, which is what an upgraded installation would have seen. + /// The server serves a license added by an earlier version to a client that asks for the same + /// product by its Backstage name. Without the mapping, the server would deny the request while + /// the license is present, which is what an upgraded installation would observe. /// [Fact] public async Task LicenseStoredUnderTheLegacyName_IsServedToAClientAskingForTheBackstageName() @@ -62,7 +62,8 @@ public async Task LicenseStoredUnderTheLegacyName_IsServedToAClientAskingForTheB } /// - /// The mapping widens what a request matches; it does not make every product match every other. + /// The mapping adds the other spelling of a product to a request. It does not make one product + /// match another product. /// [Fact] public async Task LicenseOfAnotherProduct_IsStillNotServed() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs index ac40636..f3c53a8 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs @@ -7,8 +7,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// Defects found while reviewing the migration. Each of these passed unnoticed because the legacy -/// implementation behaved the same way. +/// The defects found during the review of the migration. Each of them passed unnoticed, because the +/// legacy implementation behaved in the same way. /// public sealed class ReviewRegressionTests : IDisposable { @@ -17,8 +17,8 @@ public sealed class ReviewRegressionTests : IDisposable public void Dispose() => this.application.Dispose(); /// - /// A build agent is exempt from consuming a seat, not from the rules about which licenses may be - /// served. The legacy code only checked that the key parsed. + /// A build agent is exempt from consuming a seat. It is not exempt from the rules that decide + /// which licenses may be served. The legacy code verified only that the key parsed. /// [Fact] public async Task BuildAgent_IneligibleLicense_IsNotServed() @@ -51,7 +51,8 @@ public async Task BuildAgent_LicenseRequiringANewerClient_IsNotServed() } /// - /// An expired license must not be handed to a build agent with three more days on it. + /// The server must not give an expired license to a build agent with three more days of + /// validity. /// [Fact] public async Task BuildAgent_ExpiredLicense_IsNotServed() @@ -83,9 +84,9 @@ public async Task BuildAgent_ValidLicense_IsStillServedWithoutALease() } /// - /// A license with no seat limit has no capacity to exceed, so there is no grace period. Reaching - /// the grace pass with one used to dereference a null maximum and answer 500 instead of denying - /// the request. + /// A license with no seat limit has no capacity to exceed, so it has no grace period. When such + /// a license reached the grace pass, the code read a maximum that was null and the server + /// answered with the status 500 instead of denying the request. /// [Fact] public async Task UnlimitedButExpiredLicense_IsDeniedRatherThanFailing() @@ -104,8 +105,8 @@ public async Task UnlimitedButExpiredLicense_IsDeniedRatherThanFailing() } /// - /// A year outside the range of a date used to pass validation and then throw while the date was - /// being constructed. + /// A year outside the range of a date passed the validation, and the construction of the date + /// then raised an exception. /// [Theory] [InlineData( "fy=10000&fm=1&ty=10000&tm=2" )] @@ -132,8 +133,8 @@ public async Task Export_ValidRange_IsStillServed() } /// - /// Installed below a site root, a redirect has to keep the path base or it lands on the parent - /// site. + /// When the server is installed below the root of a site, a redirection must contain the path + /// base. Otherwise it reaches the parent site. /// [Theory] [InlineData( "/LicenseServer", "/Graph", "?id=5", "/LicenseServer/Graph?id=5" )] diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs index 0544f22..6f50c56 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs @@ -7,11 +7,11 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// The schema an existing installation already has, which this version must keep using unchanged. /// /// -/// There are deliberately no EF migrations: CreateTables.sql is the source of truth, and the -/// server never creates or alters a SQL Server schema. These tests are what stands in for a -/// migration, by holding the model to the column types, key generation and constraint names that -/// CreateTables.sql produces. A mapping that drifted would otherwise be discovered by a -/// customer, on their data. +/// The project contains no EF migration. CreateTables.sql defines the schema, and the server +/// never creates and never modifies a SQL Server schema. These tests replace a migration: they +/// verify that the model uses the column types, the generation of the keys and the names of the +/// constraints that CreateTables.sql produces. Without them, a customer would discover a +/// mapping that changed, on their own data. /// public sealed class SchemaCompatibilityTests { @@ -39,9 +39,9 @@ public void Licenses_LicenseKeyIsStillText() } /// - /// A text column cannot appear in a comparison, an ORDER BY, a GROUP BY or a DISTINCT in - /// T-SQL, and the failure happens at run time. No query may therefore touch the license key - /// other than to project it. + /// In Transact-SQL, a text column cannot appear in a comparison, in an ORDER BY clause, in + /// a GROUP BY clause, or in a DISTINCT clause, and the query fails at run time. A query may + /// therefore only read the license key. /// [Fact] public void Licenses_LicenseKeyIsNeverFilteredOrSorted() @@ -101,8 +101,9 @@ public void Column_KeepsItsType( string expected ) => Assert.Contains( expected, CreateScript(), StringComparison.OrdinalIgnoreCase ); /// - /// Timestamps must stay datetime. EF would default them to datetime2, which makes - /// SQL Server convert the column on every comparison of a lease's start or end time. + /// The timestamps keep the type datetime. The default type of EF is datetime2, and + /// SQL Server then converts the column at every comparison of the start time or of the end time + /// of a lease. /// [Fact] public void Timestamps_AreNeverDateTime2() @@ -128,7 +129,7 @@ public void Tables_KeepTheirNames() } /// - /// Deleting a license must not silently cascade through the chain of replaced leases. + /// The deletion of a license must not cascade through the chain of replaced leases. /// [Fact] public void ForeignKeys_DoNotCascade() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs index d4181ad..28693a3 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs @@ -3,8 +3,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// The rounding rule that decides how many seats a set of users consumes. This arithmetic used to -/// live inside a SQL GROUP BY, where it could not be tested at all. +/// The rounding rule that decides how many seats a group of users consumes. This arithmetic used to +/// run inside a SQL GROUP BY clause, where no test could reach it. /// public sealed class SeatCountingTests { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs index 17f129c..6f5bb81 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs @@ -113,8 +113,8 @@ public async Task Seed_Twice_AddsNothingTheSecondTime() } /// - /// Outside Development the server refuses to start rather than quietly serving license keys it - /// issued to itself. + /// Outside the Development environment, the server refuses to start instead of serving license + /// keys that it issued to itself. /// [Theory] [InlineData( "Production" )] diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs index 08b3059..496a06e 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs @@ -9,8 +9,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// The licensing authorities a development server accepts license keys from besides the production -/// one, which is what lets a load simulation run against license keys that nobody sells. +/// The licensing authorities whose license keys a development server accepts, in addition to the +/// production authority. They let a load simulation run against license keys that are not sold. /// public sealed class TestLicensingAuthorityTests { @@ -46,9 +46,9 @@ public void UnconfiguredAuthority_InDevelopment_IsRejected() } /// - /// Outside Development the server refuses to start rather than ignoring the setting. An - /// administrator who set it is told, and nobody holding the private key gets a server that - /// honours what they sign. + /// Outside the Development environment, the server refuses to start instead of ignoring the + /// setting. The administrator who set it is informed, and no server accepts the license keys of + /// whoever holds the private key. /// [Theory] [InlineData( "Production" )] From da77f1b092e7fc6279a0d35e8545f85c8b136f62 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Fri, 18 Sep 2026 08:09:49 +0200 Subject: [PATCH 36/44] Remove the signature of the audit log The server signed each lease of the audit log with a chained HMAC, and nothing verified it. The feature is removed: ILeaseSigner, HmacLeaseSigner, IAuditKeyProvider, FileAuditKeyProvider, the property Lease.HMAC and its mapping, the setting LicenseServer:AuditHmacKey and its validation. Saving a lease is one insert again. The signature was computed after the insert, because the audit line starts with the identifier that the database assigns, so a save ran two statements inside a transaction. The repository now calls SaveChanges once. An exported line has seven fields instead of eight. The eighth field carried the signature. It could not be verified even in principle: the legacy implementation called the parameterless HMAC.Create(), which generates a random key at every call. An existing database is unaffected. The column HMAC is nullable, so the server writes to such a database without a schema change, and the values already written stay as they are. CreateTables.sql keeps the column, with a comment, so that the script creates the table that deployed installations have. SchemaCompatibilityTests adds the column to the test database and inserts a lease through the repository. Verified against the database of the last end-to-end run, which has 268 leases with the column populated: every exported line has seven fields, and a lease request inserted lease 269. Co-Authored-By: Claude Opus 5 --- README.md | 8 +- docs/configuration.md | 29 ++- docs/docker.md | 16 +- docs/protocol.md | 31 +-- .../Data/Lease.Audit.cs | 12 +- .../Data/Lease.Entity.cs | 5 - .../Data/LeaseConfiguration.cs | 1 - .../Data/LeaseRepository.cs | 103 +------- .../Options/LicenseServerOptions.cs | 15 +- .../Options/LicenseServerOptionsValidator.cs | 7 - .../Security/FileAuditKeyProvider.cs | 74 ------ .../Security/HmacLeaseSigner.cs | 26 --- .../Security/IAuditKeyProvider.cs | 9 - .../Security/ILeaseSigner.cs | 10 - .../Database/CreateTables.sql | 2 + .../Endpoints/LicenseServerEndpoints.cs | 6 +- .../Program.cs | 13 -- .../appsettings.json | 1 - .../AuditLogExportTests.cs | 10 +- .../CancelLeaseTests.cs | 14 -- .../ConfigurationTests.cs | 8 - .../Fakes/RecordingLeaseSigner.cs | 32 --- .../Fakes/StaticAuditKeyProvider.cs | 14 -- .../LicenseServerApplication.cs | 4 - .../LicenseServerTestContext.cs | 13 +- .../LeaseAuditLineTests.cs | 28 +-- .../LeaseSignatureTests.cs | 220 ------------------ .../OpenLeasesTests.cs | 3 +- .../SchemaCompatibilityTests.cs | 20 +- 29 files changed, 89 insertions(+), 645 deletions(-) delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Security/FileAuditKeyProvider.cs delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Security/HmacLeaseSigner.cs delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Security/IAuditKeyProvider.cs delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Security/ILeaseSigner.cs delete mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs delete mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs delete mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs diff --git a/README.md b/README.md index c1733b3..713d586 100644 --- a/README.md +++ b/README.md @@ -128,9 +128,11 @@ points in the interpretation of a license key. `PostSharpUltimate` where it used to be stored as `Ultimate`. The existing rows are not modified, and a request that names either spelling finds both, so there is nothing to migrate. -The server signs the audit log with a key stored in `App_Data\audit-signing.key`, which it generates -at the first start. Include this file in your backups and keep it across upgrades. Its loss does not -invalidate the existing rows, but it starts a new signature chain. +One more change concerns the exported audit log. A line now has seven fields instead of eight. The +eighth field contained a signature of the line, and that signature could not be verified: the server +generated a random key at every call, so no two lines were signed with the same key. The server no +longer writes that field, and it no longer writes the `HMAC` column of the `Leases` table. The column +is left in place, and the values already written are left as they are. ## Building from source diff --git a/docs/configuration.md b/docs/configuration.md index 47ce7dc..4d90120 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -221,17 +221,12 @@ that asks for `SqlApplicationLock`, which serializes through the database. ## Auditing -| Setting | Default | Meaning | -|---|---|---| -| `LicenseServer:AuditHmacKey` | empty | The base64 key that signs the audit log. | +The `Leases` table is the audit log. The server never updates a lease and never deletes one: +prolonging a lease and cancelling a lease both insert a new row. Export the log from the page +[Audit log](protocol.md#exporting-the-audit-log-get-adminexportashx), which writes one line per lease. -The server signs each lease of the audit log. The signature covers the lease and the signature of the -previous lease, so a removed or modified row invalidates the signature of every row after it. When -`AuditHmacKey` is empty, the server generates a key at the first start and writes it to -`App_Data\audit-signing.key`. - -Include this file in your backups and keep it across upgrades. The loss of the key does not -invalidate the rows already written, but it starts a new signature chain. +The audit log has no setting. Protect it as you protect the database: with the permissions of the +database and with your backups. ## Storage @@ -239,13 +234,15 @@ invalidate the rows already written, but it starts a new signature chain. |---|---|---| | `LicenseServer:DataDirectory` | `App_Data` | Where the server stores the files it generates. | -The directory contains the audit signing key, and the test licensing authority when the server has -one. A relative path is resolved against the application directory, so the default value works -wherever you unpack the release package. Set an absolute path to store these files on another volume. +The directory contains the test licensing authority, when the server has one. A relative path is +resolved against the application directory, so the default value works wherever you unpack the +release package. Set an absolute path to store these files on another volume. + +A server that serves the license keys of a production authority writes nothing to this directory. -In a container, this directory must be a volume. The image declares a volume at `/app/App_Data`, so -the files survive the replacement of the container even when no volume is named. Name the volume in a -real deployment, and back it up together with the database. See [docker.md](docker.md). +In a container, this directory must be a volume when the server uses a test licensing authority. The +image declares a volume at `/app/App_Data`, so the files survive the replacement of the container +even when no volume is named. See [docker.md](docker.md). ## Monitoring diff --git a/docs/docker.md b/docs/docker.md index a0f33d5..c532dd8 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -42,15 +42,13 @@ docker compose down --volumes | `licenseserver` | The application, built from `Dockerfile`. It waits for the schema job to finish. | The application stores the files it generates in the `licenseserver-data` volume, mounted at -`/app/App_Data`. These files are the audit signing key, and the test licensing authority when the -server has one. They must survive the container. The loss of the audit signing key does not -invalidate the rows already written, but it starts a new signature chain. The loss of the test -authority invalidates the license keys that this authority signed. The database stores its own files -in `database-data`. +`/app/App_Data`. The only such file is the test licensing authority, which a server that serves +license keys of the production authority does not have. That file must survive the container, +because its loss invalidates the license keys that the authority signed. The database stores its own +files in `database-data`. The image declares `/app/App_Data` as a volume, so a container started with `docker run` and no -explicit mount still stores these files outside its writable layer. Name the volume in a real -deployment, because an anonymous volume is easy to delete by accident. +explicit mount still stores these files outside its writable layer. `LicenseServer__DataDirectory` moves the directory to another location. ## Probing it @@ -102,8 +100,8 @@ backwards and leaves the existing leases dated in the future. - The administrative pages are open, as they are in the default configuration of every deployment. The container does not restrict them. See [Securing the administrative pages](configuration.md#securing-the-administrative-pages). -- Back up the `licenseserver-data` volume together with the database. It contains the audit signing - key. +- Back up the database. A deployment that serves license keys of the production authority keeps + everything it must not lose in the database, and the `licenseserver-data` volume stays empty. ## The image on its own diff --git a/docs/protocol.md b/docs/protocol.md index 39c0533..b08b2b2 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -281,10 +281,10 @@ rows. ### Format -One line per lease, with eight fields separated by `;`: +One line per lease, with seven fields separated by `;`: ``` -40;;900001;2026-09-17T11:02:14.1234567Z;2026-09-20T11:02:14.1234567Z;d97556dbab6becaa;93cf1e71b44530bd;J+QFuzRr4fT+mkwzkYOQ/NZtkVy2EHWj1b9t82h/xB8= +40;;900001;2026-09-17T11:02:14.1234567Z;2026-09-20T11:02:14.1234567Z;d97556dbab6becaa;93cf1e71b44530bd ``` | Field | Meaning | @@ -296,7 +296,6 @@ One line per lease, with eight fields separated by `;`: | 5 | The instant the lease ended, in UTC. | | 6 | The hash of the machine name. | | 7 | The hash of the user name. | -| 8 | The signature. | Names appear only as hashes, so the file can be shared without disclosing who works where. The hash is the hexadecimal representation, in lower case, of an unkeyed 64-bit hash of the name. The name is @@ -306,26 +305,14 @@ are irrelevant to an anonymizing hash. It is used because the values must be equ PostSharp has produced since 2013, and because the license audit of Backstage hashes the same names in the same way. -### The signature chain - -Each signature covers the signature of the previous lease, a semicolon, and the seven fields of its -own line: - -``` -signature(n) = base64( HMAC-SHA256( key, signature(n-1) + ";" + fields 1 to 7 of line n ) ) -``` - -The first lease of a database is chained to an empty string. The key is described under Auditing in -[configuration.md](configuration.md). - -An auditor can recompute the chain from an exported file alone, because the signed payload is the -exported line. Earlier versions did not allow this. They called the parameterless `HMAC.Create()`, -which generates a random key at every call, so the signatures they wrote could never be verified. +A version earlier than 2027.0 wrote an eighth field, which contained a signature of the line. That +signature could not be verified: the server generated a random key at every call, so no two lines +were signed with the same key. The server no longer writes the field, and it no longer writes the +`HMAC` column of the `Leases` table. The server resolves a range of months to a range of lease identifiers, and exports every lease in -that range. The result is a contiguous section of the log and not a filtered selection, so the chain -remains verifiable. This is also the reason why a few leases outside the requested months appear in -the file. +that range. The result is a contiguous section of the log and not a filtered selection. This is the +reason why a few leases outside the requested months appear in the file. ## Compatibility @@ -340,4 +327,4 @@ breaks a client that is already deployed. | The four part names of a lease. | A client matches the parts by name and ignores a part it does not know. | | The status codes, and the body of a 403. | A client displays the body of a 403 to the user and treats every other status than 200 as a failure. | | The trailing hexadecimal suffix of a machine name is removed before a build server is matched. | The list of build servers in an existing configuration names the machines without it. | -| The order of the fields of an audit line, and the hash of the names. | Customers archive exported files and compare them across years. | +| The order of the first seven fields of an audit line, and the hash of the names. | Customers archive exported files and compare them across years. | diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs index f17c0ae..5bd7215 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs @@ -16,7 +16,7 @@ public partial class Lease /// database. Without that kind, would treat them as local times and /// shift them. /// - public void Write( TextWriter textWriter, bool includeHmac ) + public void Write( TextWriter textWriter ) { ArgumentNullException.ThrowIfNull( textWriter ); @@ -33,21 +33,15 @@ public void Write( TextWriter textWriter, bool includeHmac ) textWriter.Write( StringHash.ComputeStringHash64( this.Machine ).ToString( "x" ) ); textWriter.Write( ';' ); textWriter.Write( StringHash.ComputeStringHash64( this.UserName ).ToString( "x" ) ); - - if ( includeHmac ) - { - textWriter.Write( ';' ); - textWriter.Write( this.HMAC ); - } } /// /// Returns this lease as a line of the audit log. /// - public string ToAuditLine( bool includeHmac ) + public string ToAuditLine() { StringWriter writer = new(); - this.Write( writer, includeHmac ); + this.Write( writer ); return writer.ToString(); } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs index 9ac732c..281c7a6 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs @@ -34,11 +34,6 @@ public partial class Lease /// public string AuthenticatedUser { get; set; } = null!; - /// - /// Gets or sets the signature that chains this lease to the previous lease of the audit log. - /// - public string? HMAC { get; set; } - /// /// Gets or sets a value indicating whether the server granted this lease during the grace /// period, that is, above the capacity of the license. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs index 05004c2..f649745 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs @@ -17,7 +17,6 @@ public void Configure( EntityTypeBuilder builder ) builder.Property( x => x.UserName ).IsRequired().HasMaxLength( 200 ); builder.Property( x => x.Machine ).IsRequired().HasMaxLength( 200 ); builder.Property( x => x.AuthenticatedUser ).IsRequired().HasMaxLength( 200 ); - builder.Property( x => x.HMAC ).IsUnicode( false ).HasMaxLength( 100 ); builder.Property( x => x.Grace ).IsRequired(); builder.HasOne( x => x.License ) diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs index 49f71e9..abc8856 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs @@ -1,9 +1,7 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Options; using SharpCrafters.Backstage.LicenseServer.Licensing; using SharpCrafters.Backstage.LicenseServer.Options; -using SharpCrafters.Backstage.LicenseServer.Security; namespace SharpCrafters.Backstage.LicenseServer.Data; @@ -11,8 +9,7 @@ namespace SharpCrafters.Backstage.LicenseServer.Data; public sealed class LeaseRepository( LicenseServerDbContext db, IOptions options, - ILicenseParser licenseParser, - ILeaseSigner signer ) : ILeaseRepository + ILicenseParser licenseParser ) : ILeaseRepository { private readonly LicenseServerOptions settings = options.Value; @@ -22,51 +19,6 @@ public sealed class LeaseRepository( public IQueryable Licenses => db.Licenses; - /// - /// Signs one lease, chaining it to the signature of the lease before it. - /// - /// - /// The payload is the previous signature, a semicolon, and the audit line of the lease. The - /// signed payload is the line that the export writes, so an auditor can recompute the chain from - /// an exported file. - /// - public string ComputeSignature( string? previousSignature, Lease lease ) - => signer.Sign( (previousSignature ?? string.Empty) + ";" + lease.ToAuditLine( false ) ); - - /// - /// Signs the leases that have just been inserted, in the order the database assigned them. - /// - /// - /// The leases are signed after they are inserted, and not before, because the audit line starts - /// with the identifier of the lease, and the database assigns that identifier. A signature - /// computed before the insert would contain a zero in that position, so an auditor could not - /// recompute it from an exported line. The leases saved together would also chain to the same - /// predecessor instead of chaining to each other, and one of them could then be removed without - /// invalidating any later signature. - /// - private void SignInsertedLeases( IReadOnlyList inserted ) - { - if ( inserted.Count == 0 ) - { - return; - } - - int firstInsertedId = inserted.Min( l => l.LeaseId ); - - string? previousSignature = db.Leases - .AsNoTracking() - .Where( l => l.LeaseId < firstInsertedId ) - .OrderByDescending( l => l.LeaseId ) - .Select( l => l.HMAC ) - .FirstOrDefault(); - - foreach ( Lease lease in inserted.OrderBy( l => l.LeaseId ) ) - { - previousSignature = this.ComputeSignature( previousSignature, lease ); - lease.HMAC = previousSignature; - } - } - public Lease? CreateLease( License license, string user, @@ -299,55 +251,10 @@ public IEnumerable GetLeaseCountingPoints( } /// - /// Saves the unit of work, signing any newly inserted leases. + /// Saves the unit of work. /// - /// - /// The insert and the signature are two statements, so they run in one transaction: a lease must - /// never be readable without its signature. - /// - public async Task SaveChangesAsync( CancellationToken cancellationToken = default ) - { - List inserted = this.GetPendingLeases(); - - if ( inserted.Count == 0 ) - { - return await db.SaveChangesAsync( cancellationToken ); - } - - // The caller may already have opened a transaction, as deleting a license does. - IDbContextTransaction? transaction = db.Database.CurrentTransaction == null - ? await db.Database.BeginTransactionAsync( cancellationToken ) - : null; - - try - { - int result = await db.SaveChangesAsync( cancellationToken ); - - this.SignInsertedLeases( inserted ); - await db.SaveChangesAsync( cancellationToken ); - - if ( transaction != null ) - { - await transaction.CommitAsync( cancellationToken ); - } + public Task SaveChangesAsync( CancellationToken cancellationToken = default ) + => db.SaveChangesAsync( cancellationToken ); - return result; - } - finally - { - if ( transaction != null ) - { - await transaction.DisposeAsync(); - } - } - } - - public int SaveChanges() - => this.SaveChangesAsync().GetAwaiter().GetResult(); - - private List GetPendingLeases() - => db.ChangeTracker.Entries() - .Where( e => e.State == EntityState.Added ) - .Select( e => e.Entity ) - .ToList(); + public int SaveChanges() => db.SaveChanges(); } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs index 1d02366..c72e337 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs @@ -77,12 +77,6 @@ public sealed class LicenseServerOptions /// public string? BuildServers { get; set; } - /// - /// Gets or sets the base64 key that signs the lease audit log. When the value is null, the server - /// generates a key at the first start and stores it in . - /// - public string? AuditHmacKey { get; set; } - /// /// Gets or sets the Windows groups allowed to open the administrative pages, for example /// DOMAIN\PostSharp Administrators. When the value is empty, the administrative pages are @@ -123,10 +117,11 @@ public sealed class LicenseServerOptions public bool SeedTestLicenses { get; set; } /// - /// Gets or sets the directory that contains the files the server generates: the audit signing - /// key, and the test licensing authority when the server has one. A relative path is resolved - /// against the application directory. In a container, this directory must be a volume. Otherwise - /// the audit signature chain restarts every time the container is replaced. + /// Gets or sets the directory that contains the files the server generates, which is the test + /// licensing authority when the server has one. A relative path is resolved against the + /// application directory. In a container, this directory must be a volume. Otherwise the server + /// generates a new test licensing authority every time the container is replaced, and the + /// license keys of the previous authority stop being valid. /// public string DataDirectory { get; set; } = "App_Data"; diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs index f19b261..b1be691 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs @@ -26,13 +26,6 @@ public ValidateOptionsResult Validate( string? name, LicenseServerOptions option failures.Add( $"TimeAcceleration ({options.TimeAcceleration}) cannot be negative." ); } - if ( options.AuditHmacKey != null && !IsBase64( options.AuditHmacKey ) ) - { - failures.Add( "AuditHmacKey must be a base64-encoded string." ); - } - return failures.Count == 0 ? ValidateOptionsResult.Success : ValidateOptionsResult.Fail( failures ); } - - private static bool IsBase64( string value ) => Convert.TryFromBase64String( value, new byte[value.Length], out _ ); } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/FileAuditKeyProvider.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/FileAuditKeyProvider.cs deleted file mode 100644 index 428f063..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/FileAuditKeyProvider.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Security.Cryptography; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using SharpCrafters.Backstage.LicenseServer.Options; - -namespace SharpCrafters.Backstage.LicenseServer.Security; - -/// -/// Supplies the audit signing key. It reads the key from the configuration when the configuration -/// contains one. Otherwise it generates a key at the first use and stores it in the data directory. -/// -/// -/// The key file must be kept across deployments and included in the backups. Its loss does not -/// invalidate the existing rows, but it starts a new signature chain. -/// -public sealed class FileAuditKeyProvider : IAuditKeyProvider -{ - private const int keySizeInBytes = 32; - - private readonly string keyFilePath; - private readonly LicenseServerOptions options; - private readonly ILogger logger; - private readonly Lock sync = new(); - - private byte[]? key; - - public FileAuditKeyProvider( - IOptions options, - ILogger logger, - string keyFilePath ) - { - this.options = options.Value; - this.logger = logger; - this.keyFilePath = keyFilePath; - } - - public byte[] GetKey() - { - if ( this.key != null ) - { - return this.key; - } - - lock ( this.sync ) - { - return this.key ??= this.LoadOrCreateKey(); - } - } - - private byte[] LoadOrCreateKey() - { - if ( !string.IsNullOrWhiteSpace( this.options.AuditHmacKey ) ) - { - return Convert.FromBase64String( this.options.AuditHmacKey ); - } - - if ( File.Exists( this.keyFilePath ) ) - { - return Convert.FromBase64String( File.ReadAllText( this.keyFilePath ).Trim() ); - } - - byte[] newKey = RandomNumberGenerator.GetBytes( keySizeInBytes ); - - Directory.CreateDirectory( Path.GetDirectoryName( this.keyFilePath )! ); - File.WriteAllText( this.keyFilePath, Convert.ToBase64String( newKey ) ); - - this.logger.LogInformation( - "Generated a new audit signing key in {Path}. Include this file in your backups and preserve " - + "it across upgrades, otherwise the audit log signature chain restarts.", - this.keyFilePath ); - - return newKey; - } -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/HmacLeaseSigner.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/HmacLeaseSigner.cs deleted file mode 100644 index 8669092..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/HmacLeaseSigner.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Security.Cryptography; -using System.Text; - -namespace SharpCrafters.Backstage.LicenseServer.Security; - -/// -/// Signs the audit log with HMAC-SHA256. -/// -/// -/// The legacy implementation called the parameterless HMAC.Create(). That method raises -/// on .NET 5 and later. On .NET Framework, it produced -/// an HMAC-SHA1 under a key that it generated at random at every call, so the audit chain could not -/// be verified. A key that the server stores makes the chain verifiable. The base64 representation -/// of a SHA-256 hash has 44 characters, which the existing varchar(100) column accepts, so -/// the schema does not change. -/// -public sealed class HmacLeaseSigner( IAuditKeyProvider keyProvider ) : ILeaseSigner -{ - public string Sign( string payload ) - { - ArgumentNullException.ThrowIfNull( payload ); - - return Convert.ToBase64String( - HMACSHA256.HashData( keyProvider.GetKey(), Encoding.UTF8.GetBytes( payload ) ) ); - } -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/IAuditKeyProvider.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/IAuditKeyProvider.cs deleted file mode 100644 index a9d3d87..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/IAuditKeyProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace SharpCrafters.Backstage.LicenseServer.Security; - -/// -/// Supplies the key used to sign the lease audit log. -/// -public interface IAuditKeyProvider -{ - byte[] GetKey(); -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/ILeaseSigner.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/ILeaseSigner.cs deleted file mode 100644 index 1c30c41..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/ILeaseSigner.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SharpCrafters.Backstage.LicenseServer.Security; - -/// -/// Signs the audit log of the leases. The signature of a lease covers the signature of the previous -/// lease, so that a removed row and a modified row both break the chain. -/// -public interface ILeaseSigner -{ - string Sign( string payload ); -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Database/CreateTables.sql b/src/SharpCrafters.Backstage.LicenseServer.Web/Database/CreateTables.sql index 07b6325..462e2b5 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Database/CreateTables.sql +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Database/CreateTables.sql @@ -38,6 +38,8 @@ CREATE TABLE [dbo].[Leases]( [UserName] [nvarchar](200) NOT NULL, [Machine] [nvarchar](200) NOT NULL, [AuthenticatedUser] [nvarchar](200) NOT NULL, + -- The server no longer writes this column. It held a signature that nothing verified. The column + -- is kept so that this script creates the same table as the databases of existing installations. [HMAC] [varchar](100) NULL, [Grace] [bit] NOT NULL, CONSTRAINT [PK_Leases] PRIMARY KEY CLUSTERED diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs index f18bad4..9687845 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs @@ -191,8 +191,8 @@ private static async Task ExportAsync( DateTime toTime = new DateTime( ty.Value, tm.Value, 1 ).AddMonths( 1 ); // The range of months is resolved to a range of lease identifiers, and every lease in that - // range is exported. The result is a contiguous section of the log and not a filtered - // selection, which keeps the signature chain verifiable. This is also the reason why a few + // range is exported. The legacy server selected the rows in the same way, and customers + // compare the files they archived, so the selection is kept. It is the reason why a few // leases outside the requested months appear in the file. var bounds = await repository.Leases .Where( l => l.EndTime >= fromTime && l.StartTime <= toTime ) @@ -233,7 +233,7 @@ private static async Task ExportAsync( // directly to the writer makes the writer flush synchronously when its buffer is // full, and Kestrel refuses a synchronous write to a response body. One line is // a hundred bytes. The constraint applies to the whole log, not to one line. - await writer.WriteLineAsync( lease.ToAuditLine( true ) ); + await writer.WriteLineAsync( lease.ToAuditLine() ); } }, "text/plain" ); diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs index 35030e4..057e188 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs @@ -8,7 +8,6 @@ using SharpCrafters.Backstage.LicenseServer.Licensing; using SharpCrafters.Backstage.LicenseServer.Locking; using SharpCrafters.Backstage.LicenseServer.Options; -using SharpCrafters.Backstage.LicenseServer.Security; using SharpCrafters.Backstage.LicenseServer.Services; using SharpCrafters.Backstage.LicenseServer.Time; @@ -48,18 +47,6 @@ builder.Services.AddSingleton(); -builder.Services.AddSingleton( - services => new FileAuditKeyProvider( - services.GetRequiredService>(), - services.GetRequiredService>(), - Path.Combine( - LicensingRegistration.ResolveDataDirectory( - builder.Configuration.GetSection( LicenseServerOptions.SectionName ), - services.GetRequiredService() ), - "audit-signing.key" ) ) ); - -builder.Services.AddSingleton(); - builder.Services.AddSingleton( services => services.GetRequiredService>().Value.Enabled ? ActivatorUtilities.CreateInstance( services ) diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.json b/src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.json index 575c925..5f82180 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.json +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.json @@ -16,7 +16,6 @@ "MutexTimeout": 30, "TimeAcceleration": 1, "BuildServers": "", - "AuditHmacKey": null, "AdminRoles": [], "RequireAuthenticatedLeaseRequests": false, "LeaseLockMode": "InProcess" diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs index eef6bce..1df7599 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs @@ -47,12 +47,11 @@ public async Task Export_WithMoreLeasesThanTheWriterBuffers_StreamsThemAll() foreach ( string line in lines ) { - // The identifier, the overwritten lease, the license, the two instants, the two hashed - // names and the signature. + // The identifier, the overwritten lease, the license, the two instants and the two + // hashed names. string[] fields = line.TrimEnd( '\r' ).Split( ';' ); - Assert.Equal( 8, fields.Length ); - Assert.NotEmpty( fields[7] ); + Assert.Equal( 7, fields.Length ); } Assert.DoesNotContain( "alice", string.Join( "", lines ), StringComparison.OrdinalIgnoreCase ); @@ -115,8 +114,7 @@ private void SeedLeases( License license, int count ) EndTime = start.AddDays( 3 ), UserName = $"alice.{i:D3}", Machine = $"desktop-{i:D3}", - AuthenticatedUser = "DOMAIN\\tester", - HMAC = $"SIGNATURE-{i:D3}" + AuthenticatedUser = "DOMAIN\\tester" } ); } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs index b82bc49..2acd75c 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs @@ -78,20 +78,6 @@ public async Task CancelLease_IsNotRejectedForEndingImmediately() Assert.Equal( TestClock.Days( 1 ), replacement.EndTime ); } - [Fact] - public async Task CancelLease_SignsTheReplacement() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease original = LeaseBuilder.For( license ).AddTo( context ); - - context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); - await context.Repository.SaveChangesAsync(); - - Lease replacement = await context.Db.Leases.SingleAsync( l => l.LeaseId != original.LeaseId ); - Assert.False( string.IsNullOrEmpty( replacement.HMAC ) ); - } - [Fact] public async Task CancelLease_ThenRequestAgain_GrantsAFreshLease() { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs index 921025b..16aa2e5 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs @@ -49,14 +49,6 @@ public void MinLeaseDaysNotBelowNewLeaseDays_IsRejected( int minLeaseDays, int n public void NegativeTimeAcceleration_IsRejected() => Assert.True( Validate( o => o.TimeAcceleration = -1 ).Failed ); - [Fact] - public void NonBase64AuditKey_IsRejected() - => Assert.True( Validate( o => o.AuditHmacKey = "not base64 !!!" ).Failed ); - - [Fact] - public void Base64AuditKey_IsAccepted() - => Assert.True( Validate( o => o.AuditHmacKey = Convert.ToBase64String( new byte[32] ) ).Succeeded ); - [Theory] [InlineData( 0 )] [InlineData( -1 )] diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs deleted file mode 100644 index d35448d..0000000 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using SharpCrafters.Backstage.LicenseServer.Security; - -namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; - -/// -/// Produces a signature that depends only on the payload, and stores every payload it signed, so -/// that a test can assert on the content of the audit signature chain. -/// -public sealed class RecordingLeaseSigner : ILeaseSigner -{ - private static readonly byte[] key = Encoding.UTF8.GetBytes( "test-audit-signing-key" ); - - private readonly List payloads = []; - - /// - /// Gets the payloads signed so far, in order. - /// - public IReadOnlyList Payloads => this.payloads; - - public string? LastPayload => this.payloads.Count == 0 ? null : this.payloads[^1]; - - public string Sign( string payload ) - { - this.payloads.Add( payload ); - - return Convert.ToBase64String( HMACSHA256.HashData( key, Encoding.UTF8.GetBytes( payload ) ) ); - } - - public void Clear() => this.payloads.Clear(); -} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs deleted file mode 100644 index 9dbe16c..0000000 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Text; -using SharpCrafters.Backstage.LicenseServer.Security; - -namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; - -/// -/// Supplies a constant audit signing key. -/// -public sealed class StaticAuditKeyProvider : IAuditKeyProvider -{ - private readonly byte[] key = Encoding.UTF8.GetBytes( "constant-test-key-for-audit-hmac" ); - - public byte[] GetKey() => this.key; -} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs index a9bc572..677435e 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs @@ -16,7 +16,6 @@ using SharpCrafters.Backstage.LicenseServer.Licensing; using SharpCrafters.Backstage.LicenseServer.Locking; using SharpCrafters.Backstage.LicenseServer.Options; -using SharpCrafters.Backstage.LicenseServer.Security; using SharpCrafters.Backstage.LicenseServer.Tests.Fakes; namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -98,9 +97,6 @@ protected override void ConfigureWebHost( IWebHostBuilder builder ) services.RemoveAll(); services.AddSingleton( this.EmailSender ); - services.RemoveAll(); - services.AddSingleton(); - services.RemoveAll(); services.AddSingleton( _ => this.LeaseLock ); diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs index 0fdd19c..1646edd 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs @@ -10,8 +10,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// /// A license server wired up for a test: a real and -/// over an in-memory database, with the license parser, the -/// clock, the signer and the email sender replaced by test doubles. +/// over a database held in memory, with the license parser, the +/// clock and the e-mail sender replaced by test doubles. /// public sealed class LicenseServerTestContext : IAsyncDisposable { @@ -28,14 +28,12 @@ private LicenseServerTestContext( this.Options = options; this.LicenseParser = new FakeLicenseParser(); this.EmailSender = new InMemoryEmailSender(); - this.Signer = new RecordingLeaseSigner(); this.ServerVersion = new FixedServerVersion(); this.Repository = new LeaseRepository( db, Microsoft.Extensions.Options.Options.Create( options ), - this.LicenseParser, - this.Signer ); + this.LicenseParser ); this.LeaseService = new LeaseService( this.Repository, @@ -52,8 +50,6 @@ private LicenseServerTestContext( public InMemoryEmailSender EmailSender { get; } - public RecordingLeaseSigner Signer { get; } - public FixedServerVersion ServerVersion { get; } public LeaseRepository Repository { get; } @@ -94,8 +90,7 @@ public LeaseRepository CreateFreshRepository() => new( this.fixture.CreateContext(), Microsoft.Extensions.Options.Options.Create( this.Options ), - this.LicenseParser, - this.Signer ); + this.LicenseParser ); public LicenseServerDbContext CreateFreshContext() => this.fixture.CreateContext(); diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs index 8c8295a..c73e7fb 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs @@ -12,7 +12,7 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// public sealed class LeaseAuditLineTests { - // Hashes produced by CryptoUtilities.ComputeStringHash64, which is what anonymises the names. + // The hashes that CryptoUtilities.ComputeStringHash64 produces. They anonymize the names. private const string aliceHash = "f5cb4b18b2e28463"; private const string desktop1Hash = "da7251349d0ffa49"; @@ -26,24 +26,15 @@ private static Lease CreateLease() EndTime = new DateTime( 2026, 1, 8, 9, 0, 0, DateTimeKind.Utc ), UserName = "alice", Machine = "desktop-1", - AuthenticatedUser = "DOMAIN\\alice", - HMAC = "SIGNATURE==" + AuthenticatedUser = "DOMAIN\\alice" }; [Fact] - public void Write_WithoutHmac_ProducesTheExpectedLine() + public void Write_ProducesTheExpectedLine() { Assert.Equal( $"42;41;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;{desktop1Hash};{aliceHash}", - CreateLease().ToAuditLine( false ) ); - } - - [Fact] - public void Write_WithHmac_AppendsTheSignature() - { - Assert.Equal( - $"42;41;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;{desktop1Hash};{aliceHash};SIGNATURE==", - CreateLease().ToAuditLine( true ) ); + CreateLease().ToAuditLine() ); } [Fact] @@ -54,13 +45,13 @@ public void Write_NoOverwrittenLease_LeavesTheFieldEmpty() Assert.Equal( $"42;;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;{desktop1Hash};{aliceHash}", - lease.ToAuditLine( false ) ); + lease.ToAuditLine() ); } [Fact] public void Write_NeverDisclosesTheUserOrMachineName() { - string line = CreateLease().ToAuditLine( true ); + string line = CreateLease().ToAuditLine(); Assert.DoesNotContain( "alice", line, StringComparison.OrdinalIgnoreCase ); Assert.DoesNotContain( "desktop", line, StringComparison.OrdinalIgnoreCase ); @@ -78,7 +69,7 @@ public void Write_NeverDisclosesTheUserOrMachineName() [Fact] public void Write_EmitsAbsoluteTimestamps() { - string[] fields = CreateLease().ToAuditLine( false ).Split( ';' ); + string[] fields = CreateLease().ToAuditLine().Split( ';' ); Assert.EndsWith( "Z", fields[3], StringComparison.Ordinal ); Assert.EndsWith( "Z", fields[4], StringComparison.Ordinal ); @@ -100,7 +91,7 @@ public async Task Write_AfterReload_StillEmitsUtc() Assert.Equal( DateTimeKind.Utc, reloaded.StartTime.Kind ); Assert.Equal( DateTimeKind.Utc, reloaded.EndTime.Kind ); - Assert.Equal( saved.ToAuditLine( false ), reloaded.ToAuditLine( false ) ); + Assert.Equal( saved.ToAuditLine(), reloaded.ToAuditLine() ); } [Fact] @@ -114,12 +105,11 @@ public void Write_UsesInvariantFormatting() Assert.Equal( $"42;41;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;{desktop1Hash};{aliceHash}", - CreateLease().ToAuditLine( false ) ); + CreateLease().ToAuditLine() ); } finally { CultureInfo.CurrentCulture = original; } } - } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs deleted file mode 100644 index 9fdaf40..0000000 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs +++ /dev/null @@ -1,220 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; - -namespace SharpCrafters.Backstage.LicenseServer.Tests; - -/// -/// The audit log is a chain: each lease is signed together with the signature of the lease before -/// it, so removing or altering a row breaks every signature after it. -/// -/// -/// These assertions describe behaviour that no document describes, and that a review of the -/// migration diff does not show. This is the reason why the tests state it explicitly. -/// -public sealed class LeaseSignatureTests -{ - private static string[] Fields( string payload ) => payload.Split( ';' ); - - /// - /// The payload of the signature applied to a given lease. - /// - private static string PayloadFor( LicenseServerTestContext context, int leaseId ) - => context.Signer.Payloads.Single( p => Fields( p )[1] == leaseId.ToString() ); - - [Fact] - public async Task Signature_ChainsFromThePreviousLease() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 10 ).AddTo( context ); - Lease first = LeaseBuilder.For( license ).AddTo( context ); - - context.Signer.Clear(); - context.Repository.CreateLease( license, "bob", "desktop-2", "bob", TestClock.Days( 1 ), false ); - await context.Repository.SaveChangesAsync(); - - Assert.StartsWith( first.HMAC + ";", context.Signer.LastPayload!, StringComparison.Ordinal ); - } - - [Fact] - public async Task Signature_FirstLeaseEver_ChainsFromAnEmptySignature() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - - context.Signer.Clear(); - context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); - await context.Repository.SaveChangesAsync(); - - Assert.StartsWith( ";", context.Signer.LastPayload!, StringComparison.Ordinal ); - } - - /// - /// The leases saved together chain to each other, and not all to the same predecessor. If they - /// chained to the same predecessor, one of them could be removed without invalidating a later - /// signature. - /// - [Fact] - public async Task Signature_LeasesSavedTogether_ChainToEachOther() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 10 ).AddTo( context ); - - context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); - context.Repository.CreateLease( license, "bob", "desktop-2", "bob", TestClock.Origin, false ); - context.Repository.CreateLease( license, "carol", "desktop-3", "carol", TestClock.Origin, false ); - await context.Repository.SaveChangesAsync(); - - List leases = await context.CreateFreshContext().Leases.OrderBy( l => l.LeaseId ).ToListAsync(); - - Assert.Equal( 3, leases.Count ); - - // Each lease's payload opens with the signature of the one before it. - for ( int i = 1; i < leases.Count; i++ ) - { - Assert.StartsWith( - leases[i - 1].HMAC + ";", - PayloadFor( context, leases[i].LeaseId ), - StringComparison.Ordinal ); - } - } - - /// - /// The signature covers the identifier that the database assigned to the lease, so an auditor can - /// recompute the chain from an exported file. A signature computed before the insert would - /// contain a zero in that position. - /// - [Fact] - public async Task Signature_CoversTheAssignedLeaseId() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - - context.Signer.Clear(); - Lease? lease = context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); - await context.Repository.SaveChangesAsync(); - - Assert.NotEqual( 0, lease!.LeaseId ); - Assert.Equal( lease.LeaseId.ToString(), Fields( context.Signer.LastPayload! )[1] ); - } - - /// - /// An exported line and the previous signature reproduce the signature of that line, so a - /// modified row can be detected. This property is the purpose of the chain. - /// - [Fact] - public async Task Signature_CanBeRecomputedFromTheExportedLine() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 10 ).AddTo( context ); - - LeaseBuilder.For( license ).User( "alice" ).AddTo( context ); - LeaseBuilder.For( license ).User( "bob" ).Machine( "desktop-2" ).AddTo( context ); - - List leases = await context.CreateFreshContext().Leases.OrderBy( l => l.LeaseId ).ToListAsync(); - - string? previous = null; - - foreach ( Lease lease in leases ) - { - Assert.Equal( lease.HMAC, context.Repository.ComputeSignature( previous, lease ) ); - previous = lease.HMAC; - } - } - - [Fact] - public async Task Signature_AlteredLease_NoLongerMatches() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease lease = LeaseBuilder.For( license ).AddTo( context ); - - string recorded = lease.HMAC!; - lease.Machine = "somebody-elses-machine"; - - Assert.NotEqual( recorded, context.Repository.ComputeSignature( null, lease ) ); - } - - [Fact] - public async Task CreateLease_SignedPayloadCarriesTheLicenseId() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithLicenseId( 7 ).AddTo( context ); - - context.Signer.Clear(); - context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); - await context.Repository.SaveChangesAsync(); - - Assert.Equal( "7", Fields( context.Signer.LastPayload! )[3] ); - } - - [Fact] - public async Task ProlongLease_SignedPayloadCarriesTheOverwrittenLeaseId() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease original = LeaseBuilder.For( license ).AddTo( context ); - - context.Signer.Clear(); - context.Repository.ProlongLease( original, "alice", TestClock.Days( 2.5 ) ); - await context.Repository.SaveChangesAsync(); - - Assert.Equal( original.LeaseId.ToString(), Fields( context.Signer.LastPayload! )[2] ); - } - - [Fact] - public async Task CreateLease_SignedPayloadHasNoOverwrittenLeaseId() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - - context.Signer.Clear(); - context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); - await context.Repository.SaveChangesAsync(); - - Assert.Equal( "", Fields( context.Signer.LastPayload! )[2] ); - } - - [Fact] - public async Task Signature_IsDeterministic() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease lease = LeaseBuilder.For( license ).AddTo( context ); - - Assert.Equal( - context.Repository.ComputeSignature( null, lease ), - context.Repository.ComputeSignature( null, lease ) ); - } - - [Fact] - public async Task Signature_FitsTheDatabaseColumn() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease lease = LeaseBuilder.For( license ).AddTo( context ); - - // The HMAC column is varchar(100) and is not being widened by this migration. - Assert.NotNull( lease.HMAC ); - Assert.InRange( lease.HMAC.Length, 1, 100 ); - } - - /// - /// A lease is never readable without its signature, so the insert and the signature run in one - /// transaction. - /// - [Fact] - public async Task Signature_EveryPersistedLeaseIsSigned() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 10 ).AddTo( context ); - - for ( int i = 0; i < 5; i++ ) - { - context.Repository.CreateLease( license, $"user{i}", $"machine-{i}", $"user{i}", TestClock.Origin, false ); - } - - await context.Repository.SaveChangesAsync(); - - Assert.Empty( await context.CreateFreshContext().Leases.Where( l => l.HMAC == null ).ToListAsync() ); - } -} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs index 4e28a1a..b68315e 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs @@ -76,8 +76,7 @@ public async Task OpenLeases_LeaseReplacedTwice_IsStillListedOnlyOnce() Machine = original.Machine, AuthenticatedUser = original.AuthenticatedUser, StartTime = original.StartTime, - EndTime = TestClock.Days( 4 ), - HMAC = "x" + EndTime = TestClock.Days( 4 ) } ); } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs index 6f50c56..fbc71d2 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; namespace SharpCrafters.Backstage.LicenseServer.Tests; @@ -93,7 +94,6 @@ public void Leases_LeaseIdIsGeneratedByTheDatabase() [InlineData( "[UserName] nvarchar(200) NOT NULL" )] [InlineData( "[Machine] nvarchar(200) NOT NULL" )] [InlineData( "[AuthenticatedUser] nvarchar(200) NOT NULL" )] - [InlineData( "[HMAC] varchar(100) NULL" )] [InlineData( "[Grace] bit NOT NULL" )] [InlineData( "[OverwrittenLeaseId] int NULL" )] [InlineData( "[LicenseId] int NOT NULL" )] @@ -135,6 +135,24 @@ public void Tables_KeepTheirNames() public void ForeignKeys_DoNotCascade() => Assert.DoesNotContain( "ON DELETE CASCADE", CreateScript(), StringComparison.OrdinalIgnoreCase ); + /// + /// The database of an existing installation contains the column HMAC, which held the + /// signature of the audit log. The model no longer maps that column. The column is nullable, so + /// the server keeps writing to such a database, and it leaves the column empty. + /// + [Fact] + public async Task LegacyHmacColumn_IsIgnored() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + await context.Db.Database.ExecuteSqlRawAsync( "ALTER TABLE Leases ADD COLUMN HMAC varchar(100) NULL" ); + + License license = LicenseBuilder.Default().AddTo( context ); + Lease lease = LeaseBuilder.For( license ).AddTo( context ); + + Assert.Equal( 1, await context.Db.Leases.CountAsync( l => l.LeaseId == lease.LeaseId ) ); + } + [Fact] public void Model_HasExactlyTheTwoExpectedTables() { From ab6f85d52b3c34444af40e830b09fce8f16ba502 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Fri, 18 Sep 2026 08:11:27 +0200 Subject: [PATCH 37/44] Answer the review on the configuration document Subheadings for the two mechanisms that restrict the administrative pages, instead of a paragraph that opens with "The first mechanism" and one that opens with "The second mechanism". The document no longer mentions a load balancer. The operating requirements of this server are low: a developer sends about one request per day, and a client that cannot reach the server keeps the lease it holds, so an interruption of a few hours affects nobody. The Monitoring section says this, and it says that the purpose of monitoring is to learn that the server needs attention, and not to fail over. The same text is removed from the health check, from the endpoints and from their tests. The Concurrency section still names the deployments that run several worker processes against one database, because they can grant more leases than the capacity of the license, but it now recommends one worker process first and names the deployments second. Co-Authored-By: Claude Opus 5 --- docs/configuration.md | 47 ++++++++++++------- .../Options/LeaseLockMode.cs | 4 +- .../Endpoints/OperationsEndpoints.cs | 8 ++-- .../Health/LicenseHealthCheck.cs | 5 +- .../OperationsEndpointTests.cs | 4 +- 5 files changed, 39 insertions(+), 29 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 4d90120..19e9c65 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -178,13 +178,17 @@ Restrict these pages. They are the only way to add and to revoke a license, and at `/Admin/Export.ashx` returns the whole audit log. There are two mechanisms, and you can combine them. -The first mechanism is `LicenseServer:AdminRoles`. It covers every page under `/Admin` and the export -endpoint. It requires an authentication scheme that reports the Windows groups of the caller, so it -works with `IISIntegrated` and with `Negotiate`, and not with `None`. +#### The AdminRoles setting -The second mechanism is a restriction on the path, configured in the web server. It works with any -scheme. Under IIS, enable Windows authentication on the site, then add a URL authorization rule for -the `Admin` path to the `web.config` of the application, which is in the published output: +`LicenseServer:AdminRoles` covers every page under `/Admin` and the export endpoint. It requires an +authentication scheme that reports the Windows groups of the caller, so it works with +`IISIntegrated` and with `Negotiate`, and not with `None`. + +#### A restriction on the path, in the web server + +A restriction configured in the web server works with any authentication scheme. Under IIS, enable +Windows authentication on the site, then add a URL authorization rule for the `Admin` path to the +`web.config` of the application, which is in the published output: ```xml @@ -214,10 +218,13 @@ The server serializes lease requests, so that two concurrent requests cannot bot seat. `InProcess` serializes the requests of one worker process. This is correct for the supported deployment, which is one worker process per database. -An IIS web garden, a load balancer, or several containers run several worker processes against one -database. The serialization then covers each process separately, and the server can grant more leases -than the capacity of the license. Run a single worker process. If you need several, open an issue -that asks for `SqlApplicationLock`, which serializes through the database. +Run one worker process. One process is enough: a developer sends about one request per day, and the +requests are short. + +Two deployments run several worker processes against one database: an IIS web garden, and several +instances of the server. The serialization then covers each process separately, and the server can +grant more leases than the capacity of the license. If you need such a deployment, open an issue that +asks for `SqlApplicationLock`, which serializes through the database. ## Auditing @@ -252,10 +259,15 @@ even when no volume is named. See [docker.md](docker.md). | `/health` | Whether the process answers, the database can be queried, and a license can serve a lease. | | `/version` | Which build is deployed, and which version of the licensing library it uses to parse license keys. | -The server serves these three endpoints without authentication, as it serves `Lease.ashx`. A load -balancer and a monitoring agent have no Windows credentials, and a probe that receives 401 reports -the server as unavailable. The responses contain no license key, no user name and no connection -string. The version number is readable by anyone who can reach the server. +The server serves these three endpoints without authentication, as it serves `Lease.ashx`. A +monitoring agent has no Windows credentials, and a probe that receives 401 reports the server as +unavailable. The responses contain no license key, no user name and no connection string. The +version number is readable by anyone who can reach the server. + +The operating requirements of this server are low. One developer sends about one request per day, +because the client stores its lease and renews it after `NewLeaseDays` minus `MinLeaseDays` days. A +client that cannot reach the server keeps the lease it holds, so an interruption of a few hours +affects nobody. Monitor the server to learn that it needs attention, and not to fail over. `/health` reports the result of each check in its body. It has three states: @@ -284,10 +296,9 @@ server has no license, and when every license is expired, disabled, unparsable, grace period over. A server in that state answers every lease request with 403 while its process and its database are healthy. -The license check warns and never fails. An expired license is a problem for an administrator, and -not for a load balancer: restarting the server and failing over to another server do not add a -license, and the server continues to serve the leases it has already granted. Only the process and -the database can fail the probe. +The license check warns and never fails. An expired license needs an administrator, and restarting +the server does not add a license, so the state is reported and the probe still succeeds. Only the +process and the database can fail the probe. Configure your monitoring system to report the status in the body, and not only the status code. The server also writes a warning to its log at each degraded check: diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs index 6003a64..ebe0d73 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs @@ -13,8 +13,8 @@ public enum LeaseLockMode /// /// A SQL Server application lock, shared by every process connected to the same database. - /// Required when the license server runs in a web garden, behind a load balancer, or in several - /// containers. + /// Required when several worker processes serve one database, which happens in an IIS web garden + /// and with several instances of the server. /// SqlApplicationLock, diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/OperationsEndpoints.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/OperationsEndpoints.cs index de94592..f49a023 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/OperationsEndpoints.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/OperationsEndpoints.cs @@ -11,10 +11,10 @@ namespace SharpCrafters.Backstage.LicenseServer.Endpoints; /// /// /// These endpoints require no authentication, as Lease.ashx does not, and unlike the -/// administrative pages. A load balancer and a monitoring agent have no Windows credentials, so an -/// endpoint behind authentication would answer 401, and the probe would report the server as -/// unavailable. The responses contain no license key, no user name and no connection string. The -/// version number is readable by anyone who can reach the server. +/// administrative pages. A monitoring agent has no Windows credentials, so an endpoint behind +/// authentication would answer 401, and the probe would report the server as unavailable. The +/// responses contain no license key, no user name and no connection string. The version number is +/// readable by anyone who can reach the server. /// public static class OperationsEndpoints { diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs index 01cc457..f31eee3 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs @@ -14,9 +14,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Health; /// /// It warns and never fails. The result is , so the endpoint /// answers 200, and the body of the response and the log of the server name the problem. An expired -/// license requires an action from an administrator. Restarting the server, failing over to another -/// server, and removing this server from a load balancer do not improve that state. Only the process -/// and the database can make the probe fail. +/// license requires an action from an administrator, and restarting the server does not add a +/// license. Only the process and the database can make the probe fail. /// /// public sealed class LicenseHealthCheck( diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs index 2eae5a5..8b54ece 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs @@ -146,8 +146,8 @@ public async Task Version_ReportsTheProductAndTheLicensingLibrary() } /// - /// Both endpoints require no authentication. A load balancer has no Windows credentials, and a - /// probe that receives 401 reports the server as unavailable. + /// Both endpoints require no authentication. A monitoring agent has no Windows credentials, and + /// a probe that receives 401 reports the server as unavailable. /// [Theory] [InlineData( "/health/live" )] From 97cc811d92aef142f743a156729cf9509c926b75 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Fri, 18 Sep 2026 09:34:41 +0200 Subject: [PATCH 38/44] Take the lease lock in the database, and test on SQL Server The lock that serializes the lease requests is no longer held in the process. On SQL Server the request calls sp_getapplock with the owner Session and releases it at the end; on SQLite it opens its transaction with BEGIN IMMEDIATE, which takes the write lock of the file at once. The engine chooses the mechanism, so LeaseLockMode is gone, together with InProcessLeaseLock and NullLeaseLock, and a deployment of several worker processes on one database is serialized. MutexTimeout stays: a timeout is configuration. The test suite runs a second time against SQL Server when LICENSESERVER_TEST_SQLSERVER holds a connection string. No test is duplicated, because the engine is a property of the run. Each test receives a database of its own, created from CreateTables.sql, which also proves that the script and the model agree, and the databases are pooled because creating one costs about half a second. Two tests covering the lock hold a request at a named synchronization point, through ITestSynchronizationProvider, which the tests register and production does not have. The first test asserts that the second request waits and then sees the lease of the first; the second asserts the status 503 when the wait exceeds the timeout. The SQL Server run found two defects of its own. SeedTestLicensesTests sorted the column LicenseKey in the database, which Transact-SQL refuses for the type text, so the keys are sorted after they are read. The health check test that drops both tables returned its database to the pool, which left the next test without a schema, so a test that modifies the schema now calls DoNotReuse and the pool drops that database. The pool also reseeds the identity of the leases only when the table has received a row: on a table that has received none, DBCC CHECKIDENT makes the next row take the value itself, which is zero. The build now runs in a container, as the builds of the other products of the family do. That is what lets the build definition generate the second image, a Linux one carrying SQL Server 2022, which the configuration Tests on SQL Server builds and runs eng/TestSqlServer.ps1 in. The script also serves a developer machine: it starts the database service of docker-compose.yml when it finds no other server. Co-Authored-By: Claude Opus 5 --- .teamcity/settings.kts | 263 +- DockerBuild.ps1 | 3148 +++++++++++++++++ README.md | 29 +- docker-compose.yml | 6 +- docs/configuration.md | 28 +- eng/RunClaude.ps1 | 608 ++++ eng/TestSqlServer.ps1 | 130 + eng/docker/build.Dockerfile | 75 + eng/docker/claude-pre.Dockerfile | 20 + eng/docker/claude.Dockerfile | 39 + eng/docker/sqlserver-build.Dockerfile | 73 + eng/src/Docker/SqlServerComponent.cs | 58 + eng/src/Program.cs | 78 +- .../Locking/InProcessLeaseLock.cs | 44 - .../Locking/NullLeaseLock.cs | 19 - .../Options/LeaseLockMode.cs | 25 - .../Options/LicenseServerOptions.cs | 5 - .../DatabaseRegistration.cs | 39 +- .../Endpoints/LicenseServerEndpoints.cs | 18 + .../Locking/SqlServerLeaseLock.cs | 150 + .../Locking/SqliteLeaseLock.cs | 116 + .../Program.cs | 17 - .../appsettings.json | 3 +- .../LicenseServerApplication.cs | 91 +- .../LicenseServerTestContext.cs | 29 +- .../Infrastructure/SqlServerTestDatabase.cs | 308 ++ .../Infrastructure/SqliteDatabaseFixture.cs | 49 - .../Infrastructure/SqliteTestDatabase.cs | 59 + .../Infrastructure/TestDatabases.cs | 74 + .../LeaseLockTests.cs | 94 + .../OperationsEndpointTests.cs | 3 + .../SchemaCompatibilityTests.cs | 7 +- .../SeedTestLicensesTests.cs | 11 +- ...fters.Backstage.LicenseServer.Tests.csproj | 8 + 34 files changed, 5439 insertions(+), 285 deletions(-) create mode 100644 DockerBuild.ps1 create mode 100644 eng/RunClaude.ps1 create mode 100644 eng/TestSqlServer.ps1 create mode 100644 eng/docker/build.Dockerfile create mode 100644 eng/docker/claude-pre.Dockerfile create mode 100644 eng/docker/claude.Dockerfile create mode 100644 eng/docker/sqlserver-build.Dockerfile create mode 100644 eng/src/Docker/SqlServerComponent.cs delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Locking/InProcessLeaseLock.cs delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Locking/NullLeaseLock.cs delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs delete mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteTestDatabase.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseLockTests.cs diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index 24c134a..0bd0763 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -16,8 +16,9 @@ project { buildType(PublicBuild) buildType(PublicDeployment) buildType(VersionBump) + buildType(SqlServerTests) - buildTypesOrder = arrayListOf(DebugBuild,ReleaseBuild,PublicBuild,PublicDeployment,VersionBump) + buildTypesOrder = arrayListOf(DebugBuild,ReleaseBuild,PublicBuild,PublicDeployment,VersionBump,SqlServerTests) } @@ -36,7 +37,7 @@ object DebugBuild : BuildType({ text( "Build.Arguments", "", - label ="Build.ps1 Arguments", + label ="DockerBuild.ps1 Arguments", description = "Arguments to append to the 'Build' build step.", allowEmpty = true) param("Build.Timeout", "30") } @@ -57,39 +58,41 @@ object DebugBuild : BuildType({ noProfile = false } powerShell { - name = "Kill background processes before cleanup" - id = "PreKill" + name = "Prepare Docker image backstagelicenseserver-2027.0" + id = "PrepareImage" edition = PowerShellStep.Edition.Core scriptMode = file { - path = "Build.ps1" + path = "DockerBuild.ps1" } noProfile = false - scriptArgs = "tools kill " + scriptArgs = "-BuildImage -ImageName backstagelicenseserver-2027.0 " } powerShell { name = "Build" id = "Build" edition = PowerShellStep.Edition.Core scriptMode = file { - path = "Build.ps1" + path = "DockerBuild.ps1" } noProfile = false - scriptArgs = "test --configuration Debug --buildNumber %build.number% --buildType %system.teamcity.buildType.id% --timeout %Build.Timeout% %Build.Arguments%" + scriptArgs = "-Script Build.ps1 -ImageName backstagelicenseserver-2027.0 -NoBuildImage -Label %system.teamcity.buildType.id%_%build.number% test --configuration Debug --buildNumber %build.number% --buildType %system.teamcity.buildType.id% --timeout %Build.Timeout% %Build.Arguments%" } powerShell { - name = "Kill background processes before next build" - id = "PostKill" + name = "Cleanup Docker containers" + id = "DockerCleanup" + executionMode = BuildStep.ExecutionMode.ALWAYS edition = PowerShellStep.Edition.Core - scriptMode = file { - path = "Build.ps1" + scriptMode = script { + content = "${'$'}label = \"%system.teamcity.buildType.id%_%build.number%\"; ${'$'}ids = docker ps -a -q --filter \"label=postsharp.build=${'$'}label\"; if (${'$'}ids) { docker rm -f ${'$'}ids 2>&1 | Out-Null }" } noProfile = false - scriptArgs = "tools kill " } } requirements { - equals("env.BuildAgentType", "caravela04cloud") + matches("teamcity.agent.jvm.os.family", "Windows") + matches("teamcity.agent.jvm.os.arch", "amd64") + equals("env.BuildAgentType", "docker-win-x64-md") } features { @@ -159,7 +162,7 @@ object ReleaseBuild : BuildType({ text( "Build.Arguments", "", - label ="Build.ps1 Arguments", + label ="DockerBuild.ps1 Arguments", description = "Arguments to append to the 'Build' build step.", allowEmpty = true) param("Build.Timeout", "30") } @@ -180,39 +183,41 @@ object ReleaseBuild : BuildType({ noProfile = false } powerShell { - name = "Kill background processes before cleanup" - id = "PreKill" + name = "Prepare Docker image backstagelicenseserver-2027.0" + id = "PrepareImage" edition = PowerShellStep.Edition.Core scriptMode = file { - path = "Build.ps1" + path = "DockerBuild.ps1" } noProfile = false - scriptArgs = "tools kill " + scriptArgs = "-BuildImage -ImageName backstagelicenseserver-2027.0 " } powerShell { name = "Build" id = "Build" edition = PowerShellStep.Edition.Core scriptMode = file { - path = "Build.ps1" + path = "DockerBuild.ps1" } noProfile = false - scriptArgs = "test --configuration Release --buildNumber %build.number% --buildType %system.teamcity.buildType.id% --timeout %Build.Timeout% %Build.Arguments%" + scriptArgs = "-Script Build.ps1 -ImageName backstagelicenseserver-2027.0 -NoBuildImage -Label %system.teamcity.buildType.id%_%build.number% test --configuration Release --buildNumber %build.number% --buildType %system.teamcity.buildType.id% --timeout %Build.Timeout% %Build.Arguments%" } powerShell { - name = "Kill background processes before next build" - id = "PostKill" + name = "Cleanup Docker containers" + id = "DockerCleanup" + executionMode = BuildStep.ExecutionMode.ALWAYS edition = PowerShellStep.Edition.Core - scriptMode = file { - path = "Build.ps1" + scriptMode = script { + content = "${'$'}label = \"%system.teamcity.buildType.id%_%build.number%\"; ${'$'}ids = docker ps -a -q --filter \"label=postsharp.build=${'$'}label\"; if (${'$'}ids) { docker rm -f ${'$'}ids 2>&1 | Out-Null }" } noProfile = false - scriptArgs = "tools kill " } } requirements { - equals("env.BuildAgentType", "caravela04cloud") + matches("teamcity.agent.jvm.os.family", "Windows") + matches("teamcity.agent.jvm.os.arch", "amd64") + equals("env.BuildAgentType", "docker-win-x64-md") } features { @@ -271,7 +276,7 @@ object PublicBuild : BuildType({ text( "Build.Arguments", "", - label ="Build.ps1 Arguments", + label ="DockerBuild.ps1 Arguments", description = "Arguments to append to the 'Build' build step.", allowEmpty = true) param("Build.Timeout", "30") } @@ -292,39 +297,41 @@ object PublicBuild : BuildType({ noProfile = false } powerShell { - name = "Kill background processes before cleanup" - id = "PreKill" + name = "Prepare Docker image backstagelicenseserver-2027.0" + id = "PrepareImage" edition = PowerShellStep.Edition.Core scriptMode = file { - path = "Build.ps1" + path = "DockerBuild.ps1" } noProfile = false - scriptArgs = "tools kill " + scriptArgs = "-BuildImage -ImageName backstagelicenseserver-2027.0 " } powerShell { name = "Build" id = "Build" edition = PowerShellStep.Edition.Core scriptMode = file { - path = "Build.ps1" + path = "DockerBuild.ps1" } noProfile = false - scriptArgs = "test --configuration Public --buildNumber %build.number% --buildType %system.teamcity.buildType.id% --timeout %Build.Timeout% %Build.Arguments%" + scriptArgs = "-Script Build.ps1 -ImageName backstagelicenseserver-2027.0 -NoBuildImage -Label %system.teamcity.buildType.id%_%build.number% test --configuration Public --buildNumber %build.number% --buildType %system.teamcity.buildType.id% --timeout %Build.Timeout% %Build.Arguments%" } powerShell { - name = "Kill background processes before next build" - id = "PostKill" + name = "Cleanup Docker containers" + id = "DockerCleanup" + executionMode = BuildStep.ExecutionMode.ALWAYS edition = PowerShellStep.Edition.Core - scriptMode = file { - path = "Build.ps1" + scriptMode = script { + content = "${'$'}label = \"%system.teamcity.buildType.id%_%build.number%\"; ${'$'}ids = docker ps -a -q --filter \"label=postsharp.build=${'$'}label\"; if (${'$'}ids) { docker rm -f ${'$'}ids 2>&1 | Out-Null }" } noProfile = false - scriptArgs = "tools kill " } } requirements { - equals("env.BuildAgentType", "caravela04cloud") + matches("teamcity.agent.jvm.os.family", "Windows") + matches("teamcity.agent.jvm.os.arch", "amd64") + equals("env.BuildAgentType", "docker-win-x64-md") } features { @@ -378,7 +385,7 @@ object PublicDeployment : BuildType({ text( "Publish.Arguments", "", - label ="Build.ps1 Arguments", + label ="DockerBuild.ps1 Arguments", description = "Arguments to append to the 'Publish' build step.", allowEmpty = true) param("Publish.Timeout", "30") } @@ -398,20 +405,42 @@ object PublicDeployment : BuildType({ } noProfile = false } + powerShell { + name = "Prepare Docker image backstagelicenseserver-2027.0" + id = "PrepareImage" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "DockerBuild.ps1" + } + noProfile = false + scriptArgs = "-BuildImage -ImageName backstagelicenseserver-2027.0 " + } powerShell { name = "Publish" id = "Publish" edition = PowerShellStep.Edition.Core scriptMode = file { - path = "Build.ps1" + path = "DockerBuild.ps1" + } + noProfile = false + scriptArgs = "-Script Build.ps1 -ImageName backstagelicenseserver-2027.0 -NoBuildImage -Label %system.teamcity.buildType.id%_%build.number% publish --configuration Public --deployment default --timeout %Publish.Timeout% %Publish.Arguments%" + } + powerShell { + name = "Cleanup Docker containers" + id = "DockerCleanup" + executionMode = BuildStep.ExecutionMode.ALWAYS + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "${'$'}label = \"%system.teamcity.buildType.id%_%build.number%\"; ${'$'}ids = docker ps -a -q --filter \"label=postsharp.build=${'$'}label\"; if (${'$'}ids) { docker rm -f ${'$'}ids 2>&1 | Out-Null }" } noProfile = false - scriptArgs = "publish --configuration Public --deployment default --timeout %Publish.Timeout% %Publish.Arguments%" } } requirements { - equals("env.BuildAgentType", "caravela04cloud") + matches("teamcity.agent.jvm.os.family", "Windows") + matches("teamcity.agent.jvm.os.arch", "amd64") + equals("env.BuildAgentType", "docker-win-x64-md") } features { @@ -459,7 +488,7 @@ object VersionBump : BuildType({ text( "Bump.Arguments", "", - label ="Build.ps1 Arguments", + label ="DockerBuild.ps1 Arguments", description = "Arguments to append to the 'Bump' build step.", allowEmpty = true) param("Bump.Timeout", "15") } @@ -479,20 +508,139 @@ object VersionBump : BuildType({ } noProfile = false } + powerShell { + name = "Prepare Docker image backstagelicenseserver-2027.0" + id = "PrepareImage" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "DockerBuild.ps1" + } + noProfile = false + scriptArgs = "-BuildImage -ImageName backstagelicenseserver-2027.0 " + } powerShell { name = "Bump" id = "Bump" edition = PowerShellStep.Edition.Core scriptMode = file { - path = "Build.ps1" + path = "DockerBuild.ps1" + } + noProfile = false + scriptArgs = "-Script Build.ps1 -ImageName backstagelicenseserver-2027.0 -NoBuildImage -Label %system.teamcity.buildType.id%_%build.number% bump --timeout %Bump.Timeout% %Bump.Arguments%" + } + powerShell { + name = "Cleanup Docker containers" + id = "DockerCleanup" + executionMode = BuildStep.ExecutionMode.ALWAYS + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "${'$'}label = \"%system.teamcity.buildType.id%_%build.number%\"; ${'$'}ids = docker ps -a -q --filter \"label=postsharp.build=${'$'}label\"; if (${'$'}ids) { docker rm -f ${'$'}ids 2>&1 | Out-Null }" + } + noProfile = false + } + } + + requirements { + matches("teamcity.agent.jvm.os.family", "Windows") + matches("teamcity.agent.jvm.os.arch", "amd64") + equals("env.BuildAgentType", "docker-win-x64-md") + } + + features { + swabra { + filesCleanup = Swabra.FilesCleanup.BEFORE_BUILD + lockingProcesses = Swabra.LockingProcessPolicy.KILL + verbose = true + } + gitHubAppBuildScopedToken { + parameterName = "env.GITHUB_TOKEN" + connectionId = "%GITHUB_CONNECTION_POSTSHARP_OPS%" + targetRepositories = "SharpCrafters.Backstage.LicenseServer" + } + } + +}) + +object SqlServerTests : BuildType({ + + name = "Tests on SQL Server" + + params { + text( + "Exec.Arguments", + "", + label ="DockerBuild.ps1 Arguments", + description = "Arguments to append to the 'Execute ./eng/TestSqlServer.ps1' build step.", allowEmpty = true) + } + + vcs { + root(AbsoluteId("Backstage_BackstageLicenseServer20270")) + checkoutMode = CheckoutMode.ON_AGENT + } + + steps { + powerShell { + name = "Clean NuGet cache of produced and dependency packages" + id = "CleanNuGetCache" + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "${'$'}nugetPackages = if ( ${'$'}env:NUGET_PACKAGES ) { ${'$'}env:NUGET_PACKAGES } else { Join-Path ${'$'}HOME '.nuget' 'packages' }; ${'$'}removedDirs = 0; ${'$'}removedFiles = 0; if ( Test-Path -LiteralPath ${'$'}nugetPackages ) { foreach ( ${'$'}pattern in @('metalama.backstage*', 'postsharp.engineering', 'postsharp.engineering.*', 'sharpcrafters.backstage*', 'sharpcrafters.backstage.licenseserver*', 'sharpcrafters.common*') ) { Get-ChildItem -LiteralPath ${'$'}nugetPackages -Directory -Filter ${'$'}pattern -ErrorAction SilentlyContinue | ForEach-Object { ${'$'}files = @( Get-ChildItem -LiteralPath ${'$'}_.FullName -Recurse -File -ErrorAction SilentlyContinue ).Count; Write-Host \"Removing NuGet cache directory: ${'$'}(${'$'}_.FullName) (${'$'}files file(s))\"; Remove-Item -LiteralPath ${'$'}_.FullName -Recurse -Force -ErrorAction SilentlyContinue; if ( -not ( Test-Path -LiteralPath ${'$'}_.FullName ) ) { ${'$'}removedDirs++; ${'$'}removedFiles += ${'$'}files } } } Write-Host \"Removed ${'$'}removedDirs package directory(ies) and ${'$'}removedFiles file(s) from the NuGet cache.\"; } else { Write-Host \"NuGet packages folder not found: ${'$'}nugetPackages\" }" + } + noProfile = false + } + powerShell { + name = "Copy nuget.restored.config to nuget.config" + id = "CopyNuGetConfig" + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "Copy-Item -Path \"artifacts/publish/private/nuget.restored.config\" -Destination \"nuget.config\" -Force;" + } + noProfile = false + } + powerShell { + name = "Create eng/Versions.g.props" + id = "CreateVersionsFile" + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "New-Item -Path \"eng/Versions.g.props\" -ItemType File -Force -Value \"\" | Out-Null;" + } + noProfile = false + } + powerShell { + name = "Prepare Docker image backstagelicenseserver-2027.0-sqlservertests" + id = "PrepareImage" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "DockerBuild.ps1" + } + noProfile = false + scriptArgs = "-BuildImage -ImageName backstagelicenseserver-2027.0-sqlservertests -Dockerfile eng/docker/sqlserver-build.Dockerfile " + } + powerShell { + name = "Execute ./eng/TestSqlServer.ps1" + id = "Exec" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "DockerBuild.ps1" + } + noProfile = false + scriptArgs = "-Script ./eng/TestSqlServer.ps1 -ImageName backstagelicenseserver-2027.0-sqlservertests -Dockerfile eng/docker/sqlserver-build.Dockerfile -NoBuildImage -Label %system.teamcity.buildType.id%_%build.number% %Exec.Arguments%" + } + powerShell { + name = "Cleanup Docker containers" + id = "DockerCleanup" + executionMode = BuildStep.ExecutionMode.ALWAYS + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "${'$'}label = \"%system.teamcity.buildType.id%_%build.number%\"; ${'$'}ids = docker ps -a -q --filter \"label=postsharp.build=${'$'}label\"; if (${'$'}ids) { docker rm -f ${'$'}ids 2>&1 | Out-Null }" } noProfile = false - scriptArgs = "bump --timeout %Bump.Timeout% %Bump.Arguments%" } } requirements { - equals("env.BuildAgentType", "caravela04cloud") + equals("teamcity.agent.jvm.os.name", "Linux") + equals("teamcity.agent.jvm.os.arch", "amd64") } features { @@ -508,5 +656,24 @@ object VersionBump : BuildType({ } } + dependencies { + snapshot(DebugBuild) { + onDependencyFailure = FailureAction.FAIL_TO_START + } + + artifacts(DebugBuild) { + cleanDestination = true + artifactRules = "+:artifacts/publish/private/**/*=>artifacts/publish/private" + } + snapshot(AbsoluteId("Backstage_Backstage20270_DebugBuild")) { + onDependencyFailure = FailureAction.FAIL_TO_START + } + + artifacts(AbsoluteId("Backstage_Backstage20270_DebugBuild")) { + cleanDestination = true + artifactRules = "+:artifacts/publish/private/**/*=>dependencies/Backstage" + } + } + }) diff --git a/DockerBuild.ps1 b/DockerBuild.ps1 new file mode 100644 index 0000000..fbe5584 --- /dev/null +++ b/DockerBuild.ps1 @@ -0,0 +1,3148 @@ +# The original of this file is in /src/PostSharp.Engineering.BuildTools/Resources/DockerBuild.ps1. +# You can generate this file using `./Build.ps1 generate-scripts`. +# Documentation: https://raw.githubusercontent.com/postsharp/PostSharp.Engineering/HEAD/doc/dockerbuild.md + +<# +.SYNOPSIS + Builds and runs a Docker container for building the product or running Claude CLI. + +.DESCRIPTION + Builds a Docker image from the repository's Dockerfile, then runs the build script + (or Claude CLI) inside a container with the source tree and dependencies mounted. + + The script automatically: + - Collects environment variables and generates Init.g.ps1 for container startup + - Mounts the source directory, NuGet cache, source-dependencies, and sibling repos + - Handles non-C: drive letters on Windows via subst + - Supports registry image caching for faster CI builds + +.PARAMETER Interactive + Opens an interactive PowerShell session inside the container. + +.PARAMETER BuildImage + Only builds the Docker image without running the build. + +.PARAMETER NoBuildImage + Skips building the Docker image (assumes it already exists). + +.PARAMETER Clean + Performs cleanup of bin and obj directories before building. + +.PARAMETER NoNuGetCache + Does not mount the host NuGet cache in the container. + +.PARAMETER KeepInit + Does not regenerate Init.g.ps1 (keeps the existing one as-is). + The existing Init.g.ps1 is still executed. Cannot be combined with -PostInit. + +.PARAMETER PostInit + Path to a script to execute at the end of Init.g.ps1. + The build fails if the PostInit script returns a non-zero exit code. + Cannot be combined with -KeepInit or -NoInit. + +.PARAMETER Claude + Runs Claude CLI instead of Build.ps1. Use -Claude for interactive mode, + or pass a prompt as a trailing argument for non-interactive mode. + +.PARAMETER NoMcp + Do not connect to the MCP approval server (for -Claude mode). + +.PARAMETER Update + Force full timestamp update to invalidate Docker cache and force Claude/plugin updates. + +.PARAMETER ImageName + Docker image name. Defaults to a content-hash-based name. + +.PARAMETER BuildAgentPath + Path to build agent directory. Defaults based on platform. + +.PARAMETER LoadEnvFromKeyVault + Forces loading environment variables from the PostSharpBuildEnv key vault. + +.PARAMETER StartVsmon + Mounts and enables the Visual Studio remote debugger in the container. + +.PARAMETER Script + The build script to execute inside Docker. Defaults to 'Build.ps1'. + +.PARAMETER Dockerfile + Path to a custom Dockerfile. Defaults to Dockerfile or Dockerfile.claude based on -Claude. + +.PARAMETER RegistryImage + Use a pre-built image from a registry, skipping Dockerfile build entirely. + +.PARAMETER NoRegistry + Ignore DOCKER_REGISTRY, DOCKER_USERNAME and DOCKER_PASSWORD, and build every image locally under a local + tag: no authentication, no pull of an ancestor image, no push of what is built. Use it on a host that + cannot reach the registry, or cannot verify its certificate, so that the build proceeds without the layer + reuse the registry would otherwise give it. + +.PARAMETER NoInit + Do not generate or call Init.g.ps1 (skips environment variables, git config, safe.directory, etc). + +.PARAMETER Isolation + Docker isolation mode: 'process' or 'hyperv'. Windows only; ignored on Linux and macOS. + When not specified, defaults to 'hyperv' on Windows Desktop and 'process' on Windows Server. + On Windows, -Memory and a static -Cpus only apply under hyperv isolation. + +.PARAMETER Memory + Docker memory limit (e.g., "8g"). Applied on Linux and macOS, and on Windows under + hyperv isolation; Windows process isolation ignores it. + Clamped to the memory that the Docker engine reports, so a default larger than the + machine does not produce a limit the engine cannot honour. The MSBuild node count + passed to the container as MAX_BUILD_PARALLELISM is derived from the result, at one + node per 4 GB. + Defaults to $env:BuildAgentMemory (an integer in GB) if set, otherwise 24g. + +.PARAMETER Cpus + Docker CPU limit. Use a positive integer for a static limit, or "dynamic" for + automatic allocation that rebalances CPUs across all managed containers. + A static limit is applied wherever -Memory is; "dynamic" applies under any isolation. + Defaults to $env:BuildAgentCpus if set, otherwise the host processor count. + +.PARAMETER Mount + Additional directories to mount from the host (readonly by default, append :w for writable). + Supports * and ** glob patterns. + +.PARAMETER Env + Additional environment variables to pass from host to container. + Supports "NAME" (read from host) and "NAME=VALUE" (literal) forms. + +.PARAMETER Ports + Port mappings from host to container (e.g., "8080:80", "3000"). + +.PARAMETER Label + Label to apply to the container for identification (e.g., for cleanup of orphaned build containers). + The label is set as "postsharp.build=" on the container. + +.PARAMETER MaxImageSpace + Budget for the Docker image store, in gigabytes. Before the image chain is built, if the image + store exceeds this budget, unused images are removed oldest first until the store is back within + the budget. + The budget is compared to the size that `docker system df` reports for images, which counts a + layer shared by several images only once. + The removal covers every image on the Docker engine, not only the images of this repository. + It never removes an image that this run needs, an image that any container references, or an + image created in the last two hours. + Gigabytes are decimal (1 GB = 1e9 bytes), which is the unit `docker system df` prints. + Set it to 0 to disable the cleanup. + Defaults to $env:DOCKER_MAX_IMAGE_SPACE if set, otherwise 100. + +.PARAMETER BuildArgs + Arguments passed to Build.ps1 within the container (or Claude prompt if -Claude is specified). + +.EXAMPLE + .\DockerBuild.ps1 build + Builds the image and runs Build.ps1 inside the container. + +.EXAMPLE + .\DockerBuild.ps1 -Claude + Builds the image and starts an interactive Claude CLI session. + +.EXAMPLE + .\DockerBuild.ps1 -Claude "Fix the failing tests" + Runs Claude CLI with the given prompt in non-interactive mode. + +.EXAMPLE + .\DockerBuild.ps1 -Interactive + Opens an interactive PowerShell session inside the container. + +.EXAMPLE + .\DockerBuild.ps1 build -PostInit eng/SetupLocalDb.ps1 + Runs the build with a PostInit script that executes after Init.g.ps1. +#> + +[CmdletBinding(PositionalBinding = $false)] +param( + [switch]$Interactive, # Opens an interactive PowerShell session + [switch]$BuildImage, # Only builds the image, but does not build the product. + [switch]$NoBuildImage, # Does not build the image. + [switch]$Clean, # Performs cleanup of bin and obj directories. + [switch]$NoNuGetCache, # Does not mount the host nuget cache in the container. + [switch]$KeepInit, # Does not regenerate Init.g.ps1 (keeps the existing one as-is). + [string]$PostInit, # Script to execute at the end of Init.g.ps1 (fails the build if it fails). + [switch]$Claude, # Run Claude CLI instead of Build.ps1. Use -Claude for interactive, -Claude "prompt" for non-interactive. + [switch]$NoMcp, # Do not start the MCP approval server (for -Claude mode). + [switch]$Update, # Force full timestamp update to invalidate Docker cache and force Claude/plugin updates. + [string]$ImageName, # Image name (defaults to a name based on the directory). + [string]$BuildAgentPath, # Path to build agent directory (defaults based on platform). + [switch]$LoadEnvFromKeyVault, # Forces loading environment variables form the key vault. + [switch]$StartVsmon, # Enable the remote debugger. + [string]$Script = 'Build.ps1', # The build script to be executed inside Docker. + [string]$Dockerfile, # Path to custom Dockerfile (defaults to Dockerfile or Dockerfile.claude based on -Claude). + [string]$RegistryImage, # Use a pre-built image from a registry, skipping Dockerfile build entirely. + [switch]$NoRegistry, # Ignore DOCKER_REGISTRY and its credentials; build locally without pulling or pushing. + [switch]$NoInit, # Do not generate or call Init.g.ps1 (skips git config, safe.directory, etc). + [string]$Isolation = 'process', # Docker isolation mode (process or hyperv). Windows only. When not specified, defaults to hyperv on Windows Desktop and process on Windows Server. Memory/CPU limits only apply to hyperv. + [string]$Memory = $(if ($env:BuildAgentMemory) { "${env:BuildAgentMemory}g" } else { '24g' }), # Docker memory limit (e.g., "8g"). Applied except under Windows process isolation, and clamped to the memory reported by the Docker engine. Defaults to $env:BuildAgentMemory (in GB) or 24g. + [string]$Cpus = $(if ($env:BuildAgentCpus) { $env:BuildAgentCpus } else { [Environment]::ProcessorCount }), # Docker CPU limit. Use a positive integer or "dynamic". Defaults to $env:BuildAgentCpus or host processor count. + [string[]]$Mount, # Additional directories to mount from host (readonly by default, append :w for writable). Supports * and ** glob patterns. + [string[]]$Env, # Additional environment variables to pass from host to container. + [string[]]$Ports, # Port mappings from host to container (e.g., "8080:80", "3000"). + [string]$Label, # Label to apply to the container (e.g., for identifying build containers for cleanup). + [string]$MaxImageSpace = $(if ($env:DOCKER_MAX_IMAGE_SPACE) { $env:DOCKER_MAX_IMAGE_SPACE } else { '100' }), # Budget for the Docker image store, in decimal GB. Unused images are removed oldest first before the build when the store exceeds it. 0 disables the cleanup. Defaults to $env:DOCKER_MAX_IMAGE_SPACE or 100. + [Parameter(ValueFromRemainingArguments)] + [string[]]$BuildArgs # Arguments passed to `Build.ps1` within the container (or Claude prompt if -Claude is specified). +) + +# Require PowerShell 7.5 or higher (run with pwsh, not powershell) +if ($PSVersionTable.PSVersion -lt [Version]'7.5') +{ + Write-Error "This script requires PowerShell 7.5 or higher (run with 'pwsh', not 'powershell'). Current version: $( $PSVersionTable.PSVersion )" + exit 1 +} + + +# A NuGet packages directory under C:\Windows\System32 is unsafe. 32-bit build tools restored there hit WOW64 +# file-system redirection, which rewrites C:\Windows\System32 -> C:\Windows\SysWOW64 for a 32-bit process - so +# the tool's own image resolves to a non-existent SysWOW64 path and the CLR shim aborts with exit -2146232576 +# (0x80131700, CLR_E_SHIM_RUNTIMELOAD). This happens silently when the agent service runs as SYSTEM (whose profile +# is under System32) and NUGET_PACKAGES is unset, so fail fast with the fix instead of a cryptic build failure. +function Assert-NuGetPackagesPathSafe([string]$path) +{ + if ($IsUnix -or [string]::IsNullOrEmpty($path)) { return } + if (($path -replace '/', '\') -match '(?i)^[a-z]:\\windows\\system32(\\|$)') + { + Write-Host "NUGET_PACKAGES resolves to '$path', under C:\Windows\System32 - 32-bit build tools fail" -ForegroundColor Red + Write-Host "there via WOW64 System32->SysWOW64 redirection (exit 0x80131700, CLR_E_SHIM_RUNTIMELOAD)." -ForegroundColor Red + Write-Host "Set a machine-level NUGET_PACKAGES off System32, e.g.:" -ForegroundColor Red + Write-Host " md C:\packages; [Environment]::SetEnvironmentVariable('NUGET_PACKAGES','C:\packages','Machine')" -ForegroundColor Red + exit 1 + } +} + +#### +# These settings are replaced by the generate-scripts command. +$EngPath = 'eng' +$EnvironmentVariables = 'AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AZ_IDENTITY_USERNAME,AZURE_CLIENT_ID,AZURE_CLIENT_SECRET,AZURE_DEVOPS_TOKEN,AZURE_DEVOPS_USER,AZURE_TENANT_ID,CLAUDE_CODE_OAUTH_TOKEN,DOC_API_KEY,DOWNLOADS_API_KEY,ENG_USERNAME,GIT_USER_EMAIL,GIT_USER_NAME,GITHUB_APP_ID,GITHUB_APP_PRIVATE_KEY,GITHUB_AUTHOR_EMAIL,GITHUB_REVIEWER_TOKEN,GITHUB_TOKEN,IS_POSTSHARP_OWNED,IS_TEAMCITY_AGENT,MetalamaLicense,NUGET_ORG_API_KEY,PostSharpLicense,SIGNSERVER_SECRET,TEAMCITY_CLOUD_TOKEN,TYPESENSE_API_KEY,VS_MARKETPLACE_ACCESS_TOKEN,VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' +$DockerImagePrefix = 'backstagelicenseserver-2027.0' +$OvercommitRatio = 1.0 +#### + +$ErrorActionPreference = "Stop" +$dockerContextDirectory = "$EngPath/docker-context" + +# Detect platform (use built-in variables if available, fallback for older PowerShell) +if ($null -eq $IsWindows) +{ + $IsWindows = [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT +} +$IsUnix = -not $IsWindows # Covers both Linux and macOS + +# Docker isolation is Windows-only. Windows Server supports process isolation (faster, +# no per-container VM); Windows Desktop (client) only reliably runs hyperv isolation. +# Auto-detect by Windows edition unless -Isolation was passed explicitly. +if ($IsWindows -and -not $PSBoundParameters.ContainsKey('Isolation')) +{ + # Win32_OperatingSystem.ProductType: 1 = Workstation (Desktop), 2/3 = Server. + $productType = (Get-CimInstance -ClassName Win32_OperatingSystem).ProductType + $Isolation = if ($productType -eq 1) { 'hyperv' } else { 'process' } + Write-Host "Detected Windows ProductType=$productType; using --isolation=$Isolation" -ForegroundColor Cyan +} +$isolationArg = if ($IsWindows) +{ + "--isolation=$Isolation" +} +else +{ + "" +} + +# --memory and --cpus are honoured by the Linux and macOS engines whatever $Isolation says: isolation modes +# are a Windows concept and nothing outside $isolationArg acts on the value there. On Windows the limits only +# take effect under hyperv isolation - a process-isolated container shares the host kernel and the daemon +# silently drops both flags. Guarding on $Isolation alone would therefore leave every Linux container +# unlimited, which also loses MAX_BUILD_PARALLELISM (msbuild.ps1 derives the node count from the memory +# budget, and falls back to one node per CPU when there is none). +$supportsResourceLimits = $IsUnix -or $Isolation -ne 'process' + +# Set BuildAgentPath default based on platform +if ( [string]::IsNullOrEmpty($BuildAgentPath)) +{ + if ($env:TEAMCITY_JRE) + { + $BuildAgentPath = Split-Path $env:TEAMCITY_JRE -Parent + } + elseif ($IsUnix) + { + $BuildAgentPath = '/build-agent' + } + else + { + $BuildAgentPath = 'C:\BuildAgent' + } +} + +# Capture the calling directory (where the user invoked the script from) +# This will be used as the working directory in the container +$CallingDirectory = (Get-Location).Path + +# Resolve Dockerfile path relative to original current directory (before changing location) +# This must be done before Set-Location to preserve the user's intended relative path +if ($Dockerfile -and -not [System.IO.Path]::IsPathRooted($Dockerfile)) +{ + $Dockerfile = Join-Path $CallingDirectory $Dockerfile +} + +# Resolve PostInit path relative to original current directory (before changing location) +if ($PostInit -and -not [System.IO.Path]::IsPathRooted($PostInit)) +{ + $PostInit = Join-Path $CallingDirectory $PostInit +} + +# Save current location and restore on exit +Push-Location +try +{ + Set-Location $PSScriptRoot + + # Validate parameter combinations + if ($PostInit -and $NoInit) + { + Write-Error "-PostInit cannot be used with -NoInit." + exit 1 + } + if ($PostInit -and $KeepInit) + { + Write-Error "-PostInit cannot be used with -KeepInit." + exit 1 + } + + # Validate and parse -Cpus parameter + $isDynamicCpus = $false + if ($Cpus -eq 'dynamic') + { + $isDynamicCpus = $true + $TotalCpus = if ($env:BuildAgentCpus) { [int]$env:BuildAgentCpus } else { [Environment]::ProcessorCount } + Write-Host "Dynamic CPU allocation enabled. Total CPUs: $TotalCpus, Overcommit ratio: $OvercommitRatio" -ForegroundColor Cyan + } + else + { + $cpuInt = 0 + if (-not [int]::TryParse($Cpus, [ref]$cpuInt) -or $cpuInt -le 0) + { + Write-Error "-Cpus must be a positive integer or 'dynamic'. Got: '$Cpus'" + exit 1 + } + $Cpus = $cpuInt + } + + # Validate and parse -MaxImageSpace. An empty value or 0 disables the image-space cleanup; anything else + # must be a whole number of gigabytes. An unparseable value is a hard error, as for -Cpus: this variable is + # normally set once for a whole build agent, so a typo that silently disabled the cleanup would only be + # discovered as a full disk, weeks later. + $maxImageSpaceBytes = [long]0 + if (-not [string]::IsNullOrWhiteSpace($MaxImageSpace)) + { + $maxImageSpaceGb = 0 + if (-not [int]::TryParse($MaxImageSpace.Trim(), [ref]$maxImageSpaceGb) -or $maxImageSpaceGb -lt 0) + { + Write-Error "-MaxImageSpace must be a whole number of gigabytes, or 0 to disable the image-space cleanup. Got: '$MaxImageSpace'" + exit 1 + } + + # Docker prints sizes in DECIMAL gigabytes, so the budget uses the same unit as the number it is + # compared to. PowerShell's 1GB is binary, and would silently turn a budget of 100 into 107.4 of the + # gigabytes that `docker system df` reports. + $maxImageSpaceBytes = [long]$maxImageSpaceGb * 1000000000 + } + + # Images created within this window are never removed, and `docker image prune` is given the same window. + # This is what makes the cleanup safe against the concurrent DockerBuild runs that share a build agent. A + # sibling run's freshly built or pulled chain image, and its boot image between `docker build` and + # `docker run`, are referenced by no container, are absent from this run's keep set, and are invisible to + # everything else here. Two hours is much longer than that window, and costs nothing on an agent whose + # image store has grown over weeks. + $ImageCleanupGraceHours = 2 + + # How many measure-and-remove passes the cleanup makes. Each pass costs one `docker system df`, which is + # the expensive part, and each pass necessarily removes too little, because the size reported for an image + # includes the layers it shares with images that survive. A chain therefore loses one level per pass. The + # chains here are three deep, so four passes leave one to spare, and whatever is not freed by then is freed + # by the next build. + $ImageCleanupMaxPasses = 4 + + # Wall-clock budget for the whole cleanup. `docker system df` walks the layer store and can take minutes on + # an agent that holds hundreds of images. Freeing disk must never become the slowest part of the build. + $ImageCleanupTimeoutMinutes = 10 + + # msbuild.ps1 budgets one MSBuild node per 4 GB of the container's memory. + $MinMemoryPerCpuGb = 4 + + # -Memory defaults to 24g, which is more than several agents have. A limit above what the engine can honour is + # worse than no limit at all on Linux: the cgroup ceiling is then unreachable, so nothing constrains the build, + # and the node count below would be derived from memory the container can never use. Ask the engine what it + # actually has and clamp to it. A failure to reach the engine leaves the requested value untouched. + $memoryGb = 0 + if ($supportsResourceLimits -and $Memory -match '^\s*(\d+(?:\.\d+)?)\s*([gm])b?\s*$') + { + $memoryGb = [double]$Matches[1] + if ($Matches[2] -eq 'm') { $memoryGb = $memoryGb / 1024 } + + $engineMemoryBytes = [long]0 + $engineMemoryRaw = "$( docker info --format '{{.MemTotal}}' 2>$null )".Trim() + if ([long]::TryParse($engineMemoryRaw, [ref]$engineMemoryBytes) -and $engineMemoryBytes -gt 0) + { + $engineMemoryGb = $engineMemoryBytes / 1GB + if ($memoryGb -gt $engineMemoryGb) + { + $clampedGb = [int][Math]::Max(1, [Math]::Floor($engineMemoryGb)) + Write-Host "Requested --memory=$Memory exceeds the $( [Math]::Round($engineMemoryGb, 1) )g reported by the Docker engine; clamping to ${clampedGb}g" -ForegroundColor Yellow + $Memory = "${clampedGb}g" + $memoryGb = $clampedGb + } + } + } + + # Derive the node count here, where the container's memory budget is known. Inside the container msbuild.ps1 + # cannot read the cgroup limit, so without this it falls back to the processor count and over-subscribes a + # small agent - 16 nodes against 7 GB on the cell that reported this. + $maxBuildParallelism = 0 + if ($memoryGb -gt 0) + { + $maxBuildParallelism = [int][Math]::Max(1, [Math]::Floor($memoryGb / $MinMemoryPerCpuGb)) + } + + if ($env:IS_TEAMCITY_AGENT) + { + Write-Host "Running on TeamCity agent at '$BuildAgentPath'" -ForegroundColor Cyan + } + + # Dynamic CPU allocation helpers + $DynamicCpuLabel = 'managed-by=DockerBuild' + + function Get-DynamicCpuAllocation + { + param( + [int]$AdditionalContainers = 0 + ) + + $budget = $TotalCpus * (1.0 + $OvercommitRatio) + + # Count running containers with the dynamic CPU label + $containerIds = @(docker ps -q --filter "label=$DynamicCpuLabel" 2>$null) + # Filter out empty strings from docker output + $containerIds = @($containerIds | Where-Object { $_ -and $_.Trim() -ne '' }) + $runningCount = $containerIds.Count + + $totalContainers = $runningCount + $AdditionalContainers + if ($totalContainers -le 0) { $totalContainers = 1 } + + $allocation = [Math]::Min($TotalCpus, [Math]::Floor($budget / $totalContainers)) + if ($allocation -lt 1) { $allocation = 1 } + + return @{ + Allocation = [int]$allocation + ContainerIds = $containerIds + } + } + + function Invoke-DynamicCpuRebalance + { + param( + [int]$AdditionalContainers = 0 + ) + + $result = Get-DynamicCpuAllocation -AdditionalContainers $AdditionalContainers + $allocation = $result.Allocation + $containerIds = $result.ContainerIds + + if ($containerIds.Count -gt 0) + { + Write-Host "Rebalancing $( $containerIds.Count ) managed container(s) to $allocation CPUs each" -ForegroundColor Cyan + foreach ($cid in $containerIds) + { + try + { + docker update --cpus=$allocation $cid 2>$null | Out-Null + } + catch + { + Write-Warning "Failed to rebalance container $cid`: $_" + } + } + } + else + { + Write-Host "Dynamic CPU allocation: $allocation CPUs (no other managed containers)" -ForegroundColor Cyan + } + + return $allocation + } + + # Function to collect environment variables for container + function New-EnvHashtable + { + param( + [string]$EnvironmentVariableList + ) + + # Parse comma-separated environment variable names + $envVarNames = $EnvironmentVariableList -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } + + # Build hashtable with environment variable values + $envVariables = @{ } + foreach ($envVarName in $envVarNames) + { + $value = [Environment]::GetEnvironmentVariable($envVarName) + if (-not [string]::IsNullOrEmpty($value)) + { + $envVariables[$envVarName] = $value + } + } + + # Process additional environment variables from -Env parameter + # Supports both "NAME" (read from host) and "NAME=VALUE" (literal value) forms + if ($Env -and $Env.Count -gt 0) + { + foreach ($envSpec in $Env) + { + if ($envSpec -match '^([^=]+)=(.*)$') + { + # NAME=VALUE form: use literal value + $envVarName = $Matches[1] + $value = $Matches[2] + $envVariables[$envVarName] = $value + } + else + { + # NAME form: read from host environment + $envVarName = $envSpec + $value = [Environment]::GetEnvironmentVariable($envVarName) + if (-not [string]::IsNullOrEmpty($value)) + { + $envVariables[$envVarName] = $value + } + } + } + } + + # Add NUGET_PACKAGES with default if not set + if (-not $envVariables.ContainsKey("NUGET_PACKAGES")) + { + $nugetPackages = $env:NUGET_PACKAGES + if ( [string]::IsNullOrEmpty($nugetPackages)) + { + if ($IsUnix) + { + $nugetPackages = Join-Path $env:HOME ".nuget/packages" + } + else + { + $nugetPackages = Join-Path $env:USERPROFILE ".nuget\packages" + } + } + $envVariables["NUGET_PACKAGES"] = $nugetPackages + Assert-NuGetPackagesPathSafe ($envVariables["NUGET_PACKAGES"]) + } + + # Add secrets from the PostSharpBuildEnv key vault, on our development machines. + # On CI agents, these environment variables are supposed to be set by the host. + # -BuildImage only builds the image; the secrets below are for the container run, so do not + # require an Azure login in that case. + if ($LoadEnvFromKeyVault -or ($env:IS_POSTSHARP_OWNED -and -not $env:IS_TEAMCITY_AGENT -and -not $BuildImage)) + { + $moduleName = "Az.KeyVault" + + if (-not (Get-Module -ListAvailable -Name $moduleName)) + { + Write-Error "The required module '$moduleName' is not installed. Please install it with: Install-Module -Name $moduleName" + exit 1 + } + + Import-Module $moduleName + foreach ($secret in Get-AzKeyVaultSecret -VaultName "PostSharpBuildEnv") + { + $secretWithValue = Get-AzKeyVaultSecret -VaultName "PostSharpBuildEnv" -Name $secret.Name + $envName = $secretWithValue.Name -Replace "-", "_" + $envValue = (ConvertFrom-SecureString $secretWithValue.SecretValue -AsPlainText) + $envVariables[$envName] = $envValue + } + } + + # Print sorted list of environment variables being passed + $sortedKeys = $envVariables.Keys | Sort-Object + Write-Host "Environment variables: $( $sortedKeys -join ', ' )" -ForegroundColor Gray + + # Store in script-level variable for Init.g.ps1 generation + $script:ContainerEnvironmentVariables = $envVariables + } + + # Function to collect Claude-specific environment variables for container + function New-ClaudeEnvHashtable + { + $claudeEnv = @{ } + + # Process $EnvironmentVariables list - only transfer variables that have CLAUDE_ prefix defined + # e.g., if CLAUDE_GITHUB_TOKEN is set, transfer it as GITHUB_TOKEN + $envVarNames = $EnvironmentVariables -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } + foreach ($envVarName in $envVarNames) + { + $claudeVarName = "CLAUDE_$envVarName" + $value = [Environment]::GetEnvironmentVariable($claudeVarName) + if (-not [string]::IsNullOrEmpty($value)) + { + $claudeEnv[$envVarName] = $value + } + } + + # Preserved variables (transferred as-is, without requiring CLAUDE_ prefix) + if ($env:ANTHROPIC_API_KEY) + { + $claudeEnv["ANTHROPIC_API_KEY"] = $env:ANTHROPIC_API_KEY + } + if ($env:CLAUDE_CODE_OAUTH_TOKEN) + { + $claudeEnv["CLAUDE_CODE_OAUTH_TOKEN"] = $env:CLAUDE_CODE_OAUTH_TOKEN + } + if ($env:IS_POSTSHARP_OWNED) + { + $claudeEnv["IS_POSTSHARP_OWNED"] = $env:IS_POSTSHARP_OWNED + } + if ($env:IS_TEAMCITY_AGENT) + { + $claudeEnv["IS_TEAMCITY_AGENT"] = $env:IS_TEAMCITY_AGENT + } + + # Git identity - CLAUDE_ prefixed vars take precedence, then GIT_USER_*, then git config + $gitUserName = $env:CLAUDE_GIT_USER_NAME + if (-not $gitUserName) + { + $gitUserName = $env:GIT_USER_NAME + } + if (-not $gitUserName) + { + $gitUserName = git config --global user.name + } + $gitUserEmail = $env:CLAUDE_GIT_USER_EMAIL + if (-not $gitUserEmail) + { + $gitUserEmail = $env:GIT_USER_EMAIL + } + if (-not $gitUserEmail) + { + $gitUserEmail = git config --global user.email + } + if ($gitUserName) + { + $claudeEnv["GIT_USER_NAME"] = $gitUserName + } + if ($gitUserEmail) + { + $claudeEnv["GIT_USER_EMAIL"] = $gitUserEmail + } + + # Add NUGET_PACKAGES with default if not set + $nugetPackages = $env:NUGET_PACKAGES + if ( [string]::IsNullOrEmpty($nugetPackages)) + { + if ($IsUnix) + { + $nugetPackages = Join-Path $env:HOME ".nuget/packages" + } + else + { + $nugetPackages = Join-Path $env:USERPROFILE ".nuget\packages" + } + } + $claudeEnv["NUGET_PACKAGES"] = $nugetPackages + Assert-NuGetPackagesPathSafe ($claudeEnv["NUGET_PACKAGES"]) + + # Process additional environment variables from -Env parameter + # Supports both "NAME" (read from host) and "NAME=VALUE" (literal value) forms + # In Claude mode, CLAUDE_FOO takes precedence over FOO + if ($Env -and $Env.Count -gt 0) + { + foreach ($envSpec in $Env) + { + if ($envSpec -match '^([^=]+)=(.*)$') + { + # NAME=VALUE form: use literal value + $envVarName = $Matches[1] + $value = $Matches[2] + $claudeEnv[$envVarName] = $value + } + else + { + # NAME form: read from host environment (with CLAUDE_ prefix support) + $envVarName = $envSpec + $claudeVarName = "CLAUDE_$envVarName" + $value = [Environment]::GetEnvironmentVariable($claudeVarName) + if ( [string]::IsNullOrEmpty($value)) + { + $value = [Environment]::GetEnvironmentVariable($envVarName) + } + if (-not [string]::IsNullOrEmpty($value)) + { + $claudeEnv[$envVarName] = $value + } + } + } + } + + # Print sorted list of environment variables being passed + $sortedKeys = $claudeEnv.Keys | Sort-Object + Write-Host "Environment variables: $( $sortedKeys -join ', ' )" -ForegroundColor Gray + + # Store in script-level variable for Init.g.ps1 generation + $script:ContainerEnvironmentVariables = $claudeEnv + } + + # Fixed port for MCP approval server (must match McpHttpServer.FixedPort) + $mcpFixedPort = 9847 + + # Function to check if the MCP approval server is running + function Test-McpServerRunning + { + param( + [int]$Port = $mcpFixedPort + ) + + try + { + $response = Invoke-WebRequest -Uri "http://localhost:$Port/health" -TimeoutSec 10 -ErrorAction Stop + return $response.StatusCode -eq 200 + } + catch + { + return $false + } + } + + function Get-TimestampFile + { + # Persists $script:DayStamp (the single source of truth, also mixed + # into the image tag by Get-ContentHash in Claude mode) to disk so + # Dockerfile.claude can COPY it in and invalidate inner layers on + # the same week boundary as the outer image tag. + + $timestampDir = if ($IsUnix) + { + Join-Path $env:HOME ".local/share/PostSharp.Engineering" + } + else + { + Join-Path $env:LOCALAPPDATA "PostSharp.Engineering" + } + $timestampFile = Join-Path $timestampDir "update.timestamp" + + # Ensure directory exists + if (-not (Test-Path $timestampDir)) + { + New-Item -ItemType Directory -Path $timestampDir -Force | Out-Null + } + + # Only rewrite the file if the content would actually change — avoids + # bumping mtime on every run, which would pointlessly invalidate the + # Docker COPY layer for the timestamp file. + $needsUpdate = $true + if (Test-Path $timestampFile) + { + $currentTimestamp = Get-Content $timestampFile -Raw -ErrorAction SilentlyContinue + if ($currentTimestamp -eq $script:DayStamp) + { + $needsUpdate = $false + } + } + + if ($needsUpdate) + { + Set-Content -Path $timestampFile -Value $script:DayStamp -NoNewline -Force + $label = if ($Update) { "forced" } else { "weekly" } + Write-Host "Timestamp file updated ($label): $script:DayStamp" -ForegroundColor Cyan + } + + return $timestampFile + } + + function Get-ContentHash + { + param( + [string]$DockerfilePath, + [string]$ContextDirectory, + [string]$DayStamp, # non-empty => mix into hash (used in -Claude mode) + [string]$ExtraInput # folded in so a base-image (or OS) change invalidates this image's hash + ) + + $hashInput = Get-Content $DockerfilePath -Raw -ErrorAction SilentlyContinue + if (-not $hashInput) + { + $hashInput = "" + } + + # Add context files (excluding generated .g/ directory, which holds + # per-invocation files like env.g.json and Init.g.ps1). + # Sort with the invariant culture so the file order (and therefore the hash) is identical regardless of the + # host's locale. The default Sort-Object uses the current culture, which can order non-ASCII names differently + # on different machines and yield a different tag for identical content. + $contextFiles = Get-ChildItem $ContextDirectory -Recurse -File -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -notmatch '[/\\]\.g[/\\]' } | + Sort-Object -Property FullName -Culture ([System.Globalization.CultureInfo]::InvariantCulture) + + foreach ($file in $contextFiles) + { + $content = Get-Content $file.FullName -Raw -ErrorAction SilentlyContinue + if ($content) + { + $hashInput += "`n--- $( $file.Name ) ---`n" + $hashInput += $content + } + } + + # When a week stamp is supplied (Claude mode), rotate the image tag once + # per UTC week so @latest npm installs of the Claude CLI and marketplace + # plug-ins actually get refreshed. Same string as update.timestamp. + if ($DayStamp) + { + $hashInput += "`n--- day-stamp ---`n$DayStamp" + } + + # Fold the base/OS discriminator so a parent-image change (or a different WINDOWS_VERSION) yields a + # different tag for this image and all its descendants. + if ($ExtraInput) + { + $hashInput += "`n--- base ---`n$ExtraInput" + } + + # Normalize line endings so the hash is identical whether files were checked out with LF (typical on a + # dev machine) or CRLF (git autocrlf on CI). Otherwise the same Dockerfile yields a different tag on CI + # than on dev, the registry cache never hits, and CI rebuilds the chain from scratch every time. + $hashInput = $hashInput -replace "`r", "" + + $hashBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash( + [System.Text.Encoding]::UTF8.GetBytes($hashInput) + ) + # Use 8 bytes (16 hex chars) for uniqueness + return [System.BitConverter]::ToString($hashBytes, 0, 8).Replace("-", "").ToLower() + } + + # --- Image chain resolution --------------------------------------------------------------------- + # A Dockerfile may declare its parent with `ARG BASE_IMAGE=.Dockerfile`. We resolve that to the + # parent's content-hash tag (building or pulling it first), fold the parent tag into this image's hash, and + # inject --build-arg BASE_IMAGE=. The image NAME is the Dockerfile stem (e.g. + # -build.Dockerfile -> image -build), so the product/version prefix lives in the file name. + $script:resolvedTags = @{ } + + function Get-DockerfileStem([string]$dfPath) + { + return [System.IO.Path]::GetFileNameWithoutExtension($dfPath) + } + + # Per-image build context: docker-context/. There is deliberately NO fallback to the shared docker-context + # root: stray machine-specific files living there (e.g. .credentials.json, claude.json) would otherwise be folded + # into the image content hash and produce different tags on different machines for identical content. A missing + # directory is treated as an empty context by Get-ContentHash, and Build-OneImage creates it before building. + function Get-ContextDirFor([string]$dfPath) + { + return Join-Path $dockerContextDirectory (Get-DockerfileStem $dfPath) + } + + # True when an image bakes the weekly cache-buster (`COPY .g/update.timestamp`). This is the single + # discriminator for "this is a Claude leaf": it decides that the day stamp folds into the tag, that the + # image is local-only, and that the timestamp file is staged into its context. It is derived from the + # Dockerfile body rather than from its name, because the stem carries a product-defined prefix + # (AdditionalDockerfile "agent" -> agent-claude.Dockerfile), so comparing the stem to "claude" silently + # misses every prefixed leaf. + function Test-BakesCacheBuster([string]$dfPath) + { + $body = Get-Content $dfPath -Raw -ErrorAction SilentlyContinue + return [bool]($body -and $body -match 'update\.timestamp') + } + + # Stage the cache-buster into the context of the image that actually declares the COPY, i.e. + # docker-context//.g/ - for whatever stem, prefixed or not. The .g/ directory is gitignored and is + # excluded from Get-ContentHash, so writing here neither becomes tracked nor perturbs any image tag. + function Copy-TimestampToContext([string]$dfPath) + { + if (-not (Test-BakesCacheBuster $dfPath)) + { + return + } + + # The run step (-NoBuildImage) skips the up-front timestamp creation, yet it still (re)builds the + # local-only Claude leaf when the daemon does not already carry it, so materialize the file on demand. + # Get-TimestampFile is idempotent and reads the same $script:DayStamp the tag was computed from. + if (-not $script:TimestampFile) + { + $script:TimestampFile = Get-TimestampFile + } + + $gDir = Join-Path (Get-ContextDirFor $dfPath) ".g" + if (-not (Test-Path $gDir)) + { + New-Item -ItemType Directory -Path $gDir -Force | Out-Null + } + + Copy-Item -Path $script:TimestampFile -Destination (Join-Path $gDir "update.timestamp") -Force + Write-Host "Staged cache-buster timestamp into the context of '$( Get-DockerfileStem $dfPath )'" -ForegroundColor Cyan + } + + # Parse the parent Dockerfile from `ARG BASE_IMAGE=.Dockerfile`; $null if this is a chain root. + function Get-BaseDockerfile([string]$dfPath) + { + foreach ($line in (Get-Content $dfPath -ErrorAction SilentlyContinue)) + { + if ($line -match '^\s*ARG\s+BASE_IMAGE\s*=\s*(\S+\.Dockerfile)\s*$') + { + return (Join-Path (Split-Path $dfPath -Parent) $Matches[1]) + } + } + return $null + } + + # Pure: compute the content-hash tag for a Dockerfile and (recursively) its ancestors. No docker calls. + function Resolve-ImageTag([string]$dfPath) + { + $key = $dfPath.ToLower() + if ($script:resolvedTags.ContainsKey($key)) { return $script:resolvedTags[$key] } + + $baseFold = $null + $baseDf = Get-BaseDockerfile $dfPath + if ($baseDf) + { + if (-not (Test-Path $baseDf)) { Write-Error "Base Dockerfile '$baseDf' referenced by '$dfPath' was not found."; exit 1 } + # Fold only the base's CONTENT HASH (the part after the last ':'), never the full tag - so the child + # hash is independent of the registry prefix and is identical in local and registry modes. + $baseFold = ((Resolve-ImageTag $baseDf) -split ':')[-1] + } + + # OS discriminator so ltsc2025 / ltsc2022 produce distinct tags of the same image name. Propagates to + # descendants through $baseFold. + $extra = "os=$windowsVersion|base=$baseFold" + + # Fold the weekly stamp only for images that bake the update.timestamp cache-buster (the Claude leaf), so + # @latest npm installs of the Claude CLI and plug-ins refresh once per UTC week. + $hashDayStamp = if (Test-BakesCacheBuster $dfPath) { $script:DayStamp } else { $null } + + $hash = Get-ContentHash -DockerfilePath $dfPath -ContextDirectory (Get-ContextDirFor $dfPath) -DayStamp $hashDayStamp -ExtraInput $extra + # The image NAME carries the product/version prefix ($DockerImagePrefix); the Dockerfile file stem does + # not. e.g. stem 'build' -> image '-build'. ARG BASE_IMAGE references stems (prefix-free). + $imageName = "$DockerImagePrefix-$( Get-DockerfileStem $dfPath )" + $tag = if ($dockerRegistry) { "${dockerRegistry}/${imageName}:${hash}" } else { "${imageName}:${hash}" } + $script:resolvedTags[$key] = $tag + return $tag + } + + # The platform-specific mountpoints-creation step. This is NEVER baked into a chain Dockerfile - it goes + # only into the dynamically generated boot image (see New-BootImage), so the chain images stay clean and + # free of the machine-specific mount set. + function Get-MountpointsBlock + { + if ($IsWindows) + { + return @" +ARG MOUNTPOINTS +RUN if (`$env:MOUNTPOINTS) { `` + `$mounts = `$env:MOUNTPOINTS -split ';'; `` + foreach (`$dir in `$mounts) { `` + if (`$dir) { `` + Write-Host "Creating directory `$dir``."; `` + New-Item -ItemType Directory -Path `$dir -Force | Out-Null; `` + } `` + } `` + } +"@ + } + else + { + return @" +ARG MOUNTPOINTS +RUN if [ -n "`$MOUNTPOINTS" ]; then \ + OLD_IFS="`$IFS"; \ + IFS=':'; \ + set -- `$MOUNTPOINTS; \ + IFS="`$OLD_IFS"; \ + for dir in "`$@"; do \ + if [ -n "`$dir" ]; then \ + echo "Creating directory `$dir."; \ + mkdir -p "`$dir"; \ + fi; \ + done; \ + fi +"@ + } + } + + # Parse the OS image a chain ROOT is built FROM, as declared by `ARG OS_IMAGE_REPOSITORY=` (the Windows + # default root, which takes its tag from WINDOWS_VERSION) or `ARG OS_IMAGE=` (every other root, which + # declares a complete reference). Returns $null for a Dockerfile that declares neither, which is every + # image that is not a chain root. + # + # Pure: it only reads the file. That is what lets the image-space cleanup protect the OS image before any + # image is built, without asking the daemon anything. ArgName tells the caller which build-arg the value + # belongs to, because the two spellings need different values (a repository, or a full reference). + function Get-OsImageSpec([string]$dfPath) + { + $content = Get-Content -Raw $dfPath -ErrorAction SilentlyContinue + if (-not $content) { return $null } + + if ($windowsVersion -and ($content -match 'ARG\s+OS_IMAGE_REPOSITORY=(\S+)')) + { + return [pscustomobject]@{ Repository = $Matches[1]; Tag = $windowsVersion; ArgName = 'OS_IMAGE_REPOSITORY' } + } + + if ($content -match 'ARG\s+OS_IMAGE=(\S+)') + { + # Split the trailing tag off the reference; a ':' before the last '/' belongs to a registry port. + $ref = $Matches[1] + $slash = $ref.LastIndexOf('/') + $colon = $ref.LastIndexOf(':') + if ($colon -gt $slash) { return [pscustomobject]@{ Repository = $ref.Substring(0, $colon); Tag = $ref.Substring($colon + 1); ArgName = 'OS_IMAGE' } } + + return [pscustomobject]@{ Repository = $ref; Tag = 'latest'; ArgName = 'OS_IMAGE' } + } + + return $null + } + + # Build one chain image from its STATIC Dockerfile, unmodified (per-image context, base build-arg). + function Build-OneImage([string]$dfPath, [string]$tag, [string[]]$baseBuildArg) + { + $content = Get-Content -Raw $dfPath # piped to docker build verbatim - the file on disk is never changed + $ctxDir = Get-ContextDirFor $dfPath + # The per-image context dir is normally created by generate-scripts, but it is not tracked by git (it is often + # empty), so ensure it exists here before handing it to docker build. + if (-not (Test-Path $ctxDir)) { New-Item -ItemType Directory -Path $ctxDir -Force | Out-Null } + # Staged here, against the Dockerfile actually being built, so every Claude leaf gets the cache-buster in + # its own context regardless of prefix, and images that do not bake it are left untouched. + Copy-TimestampToContext $dfPath + $cmd = @('build', '-t', $tag) + if ($isolationArg) { $cmd += $isolationArg } + if ($Memory -and $supportsResourceLimits) { $cmd += "--memory=$Memory" } + # Pass WINDOWS_VERSION only to the root image that declares it (avoids 'unconsumed build-arg' warnings). + if ($IsWindows -and $windowsVersion -and ($content -match 'ARG\s+WINDOWS_VERSION')) + { + $cmd += @('--build-arg', "WINDOWS_VERSION=$windowsVersion") + } + + # Same for the OS image: only a root image declares it, and resolving it here (rather than up front) + # means the mirror is only consulted when a root image is genuinely being built. The Windows default + # root takes its tag from WINDOWS_VERSION and so declares a repository; every other root (Linux, and + # any product that pins its own base image) declares a complete reference. + $osImageSpec = Get-OsImageSpec $dfPath + if ($osImageSpec) + { + $osImage = Get-OsImage $osImageSpec.Repository $osImageSpec.Tag + $osImageValue = if ($osImageSpec.ArgName -eq 'OS_IMAGE_REPOSITORY') { $osImage.Repository } else { $osImage.Reference } + $cmd += @('--build-arg', "$( $osImageSpec.ArgName )=$osImageValue") + } + $cmd += $baseBuildArg + $cmd += @('-f', '-', $ctxDir) + Write-Host "Building $tag" -ForegroundColor Green + Write-Host "Docker command: docker $( $cmd -join ' ' )" -ForegroundColor Cyan + # Pipe docker output to the host so it does NOT become this function's return value (which would + # otherwise pollute the tag string the caller folds into the next --build-arg BASE_IMAGE). + # + # The config dir must be passed here too: `docker build` resolves the FROM itself, and when that is the + # OS mirror (or any other image in the private registry) it needs the credentials that the login wrote + # into the temporary config. Without it the build fails with "no basic auth credentials" on any agent + # whose default config is not already logged in. + $content | & docker @dockerConfigArg @cmd 2>&1 | Out-Host + if ($LASTEXITCODE -ne 0) { Write-Host "Docker build failed for $tag (exit $LASTEXITCODE)" -ForegroundColor Red; exit $LASTEXITCODE } + $script:builtNewImage = $true + } + + # Build the local "boot" image: a thin layer over the resolved chain image that creates the bind-mount + # directories. The mount set is machine-specific, so this is kept out of the shared chain images and is + # never pushed. Returns the boot image tag, which is what `docker run` uses. + # + # The boot image is the leaf that `docker run` actually executes, so its tag must be GLOBALLY UNIQUE: + # concurrent invocations on the same host resolve to the same chain hash and would otherwise collide on a + # single boot tag, with one run rebuilding (or removing) the image out from under the other. A + # YYYYMMDDTHHmmss timestamp suffix keeps each run's leaf image distinct. The image is removed after the run + # (see the boot-image cleanup near the end), so unique tags do not accumulate. + function New-BootImage([string]$baseTag) + { + $ref = ($baseTag -split '/')[-1] # strip any registry prefix - the boot image is local only + $stamp = (Get-Date).ToString("yyyyMMdd'T'HHmmss") # local time; only needs to be unique per host run + if ($ref -match '^(.*):([^:]+)$') { $bootTag = "$( $Matches[1] )-boot:$( $Matches[2] )-$stamp" } else { $bootTag = "$ref-boot:$stamp" } + $script:BootImageTag = $bootTag # tracked so the run can remove this leaf image afterwards + + # On Windows the mountpoints RUN uses backtick line-continuations, so set `# escape=` + backtick. On + # Unix the block uses backslash continuations, so keep Docker's default escape char (emit no directive). + $escapeLine = if ($IsWindows) { "# escape=$([char]96)`n" } else { "" } + $content = $escapeLine + "FROM $baseTag`n" + (Get-MountpointsBlock) + + # The boot layer has no COPY, so build it against an empty context. + $bootCtx = Join-Path ([System.IO.Path]::GetTempPath()) "docker-boot-$( New-Guid )" + New-Item -ItemType Directory -Path $bootCtx -Force | Out-Null + try + { + $cmd = @('build', '-t', $bootTag) + if ($isolationArg) { $cmd += $isolationArg } + if ($Memory -and $supportsResourceLimits) { $cmd += "--memory=$Memory" } + $cmd += @('--build-arg', "MOUNTPOINTS=$mountPointsAsString", '-f', '-', $bootCtx) + Write-Host "Building boot image $bootTag (bind-mount dirs) over $baseTag" -ForegroundColor Green + # Its FROM is the local chain leaf, so no credentials are needed - but the config dir is passed for + # consistency with Build-OneImage, and costs nothing when it is empty. + $content | & docker @dockerConfigArg @cmd 2>&1 | Out-Host + if ($LASTEXITCODE -ne 0) { Write-Host "Boot image build failed for $bootTag (exit $LASTEXITCODE)" -ForegroundColor Red; exit $LASTEXITCODE } + } + finally { Remove-Item $bootCtx -Recurse -Force -ErrorAction SilentlyContinue } + return $bootTag + } + + # Push one image to the registry in a background job, without waiting for it. The jobs are collected in + # $script:RegistryPushJobs and waited for at the end of the script, where a failed push fails the build. + function Start-AsyncPush([string]$tag) + { + if (-not $script:PushedTags.Add($tag)) + { + return + } + + Write-Host " starting async push to registry" -ForegroundColor Cyan + + # Copied to a local so that $using: captures it: $dockerConfigArg belongs to the enclosing scope. + $configArg = $dockerConfigArg + + $pushJob = Start-Job -ScriptBlock { + docker @using:configArg push $using:tag 2>&1 + $LASTEXITCODE + } + + $script:RegistryPushJobs += [pscustomobject]@{ Tag = $tag; Job = $pushJob } + } + + # Wait for ALL async registry push jobs to complete (each image pushed in its own job). A push that failed + # or timed out fails the script: an image missing from the registry is silently rebuilt from scratch by + # every later build, which costs far more than a red build here. + # + # Called on the normal path and again from the `finally` block, so that the failure of a later step never + # abandons a push in flight: the jobs die with the process, and a half-pushed image never becomes a tag in + # the registry. The job list is emptied here, so the second call is a no-op after a normal completion. + function Wait-ForRegistryPushes + { + if ($script:RegistryPushJobs.Count -eq 0) + { + return + } + + Write-Host "" + Write-Host "Waiting for $( $script:RegistryPushJobs.Count ) registry push job(s) to complete..." -ForegroundColor Cyan + + foreach ($entry in $script:RegistryPushJobs) + { + $completed = Wait-Job -Job $entry.Job -Timeout 1800 # 30 minute timeout per job + if ($completed) + { + $jobOutput = Receive-Job -Job $entry.Job + $exitCode = $jobOutput[-1] # last item is the exit code + $output = if ($jobOutput.Count -gt 1) { $jobOutput[0..($jobOutput.Count - 2)] -join "`n" } else { "" } + + if ($exitCode -eq 0) + { + Write-Host "Registry push completed: $( $entry.Tag )" -ForegroundColor Green + } + else + { + Write-Host "Registry push FAILED (exit $exitCode): $( $entry.Tag )" -ForegroundColor Red + if ($output) { Write-Host "Push output: $output" -ForegroundColor Gray } + $script:PushFailed = $true + } + } + else + { + Write-Host "Registry push TIMED OUT after 30 minutes: $( $entry.Tag )" -ForegroundColor Red + Stop-Job -Job $entry.Job + $script:PushFailed = $true + } + Remove-Job -Job $entry.Job -Force + } + + $script:RegistryPushJobs = @() + } + + # The processor architecture of the Docker engine, in the naming Docker itself uses ('amd64', 'arm64'). + # It is the architecture of the OS image a mirror ends up holding, because `docker pull` of a + # multi-architecture manifest selects the engine's own platform. + # + # The engine is asked rather than the host, because the two differ on macOS: the engine runs in a Linux + # virtual machine (Docker Desktop, Colima, Rancher Desktop), and the images are of the virtual machine's + # architecture, not of the architecture the PowerShell process happens to run under. Queried once per run. + function Get-DockerArchitecture + { + if ($script:DockerArchitecture) + { + return $script:DockerArchitecture + } + + # '{{.Server.Arch}}' is the daemon's own Go architecture name, the same vocabulary an image manifest + # uses. `docker info --format '{{.Architecture}}'` is not interchangeable with it: that one reports the + # uname form, 'x86_64' rather than 'amd64', which is why the result is normalized below. + $architecture = (docker version --format '{{.Server.Arch}}' 2>$null | Select-Object -Last 1) + + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($architecture)) + { + # The daemon could not be asked: it is not running, or on Linux the user is not in the docker + # group. The host architecture is the next best answer, and is the right one wherever the daemon + # runs on the host itself, which covers Linux and Windows. + $architecture = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() + Write-Host "Could not read the Docker engine architecture; assuming the host's, $architecture." -ForegroundColor Yellow + } + + $architecture = $architecture.Trim() + + # Accept every spelling the two sources above can produce, including the .NET enum names ('X64', + # 'Arm64') that the fallback returns. + $script:DockerArchitecture = switch -Regex ($architecture) + { + '^(amd64|x64|x86_64)$' { 'amd64' } + '^(arm64|aarch64)$' { 'arm64' } + default { $architecture.ToLowerInvariant() } + } + + return $script:DockerArchitecture + } + + # Resolve the OS image a root image is built FROM, mirroring it into the configured registry the first time + # it is needed. On a fresh agent the OS image is the most expensive download of the whole build, and the + # registry is on the LAN, so building from the mirror replaces an internet transfer with a local one. + # Agents that configure no registry (the cloud ones) go straight to the upstream registry, as before. + # + # The repository the given upstream OS repository is mirrored under in $dockerRegistry. Split out of + # Get-OsImage so that the image-space cleanup can name the mirror without pulling or creating it. + function Get-OsMirrorRepository([string]$repository) + { + # Flatten the upstream repository into a single product-neutral name, dropping the registry host: + # 'mcr.microsoft.com/windows/servercore' -> 'windows-servercore', 'ubuntu' -> 'ubuntu'. Every product + # using this registry then shares the one mirror. + $segments = $repository -split '/' + if ($segments.Length -gt 1 -and ($segments[0] -match '[.:]')) + { + $segments = $segments[1..($segments.Length - 1)] + } + + # A repository holds one manifest per tag, and the mirror is single-platform: whichever agent creates + # it decides the architecture every later agent gets, with no error until a command runs in a + # container built from it. So the architecture belongs in the mirror name. amd64 keeps the unsuffixed + # name, which every mirror already in the registry was pushed under: + # amd64 -> /ubuntu, arm64 -> /ubuntu-arm64. + $architecture = Get-DockerArchitecture + $architectureSuffix = if ($architecture -eq 'amd64') + { + '' + } + else + { + "-$architecture" + } + + return "$dockerRegistry/$( $segments -join '-' )$architectureSuffix" + } + + # Takes the upstream repository and tag, and returns an object with the Repository and the full Reference to + # build from - either the mirror or, when there is no registry (or the mirror cannot be created), upstream. + # Called only when a ROOT image is actually being built, so a run that pulls its whole chain never touches + # the mirror. Each distinct image is resolved once per run. + function Get-OsImage([string]$repository, [string]$tag) + { + $upstreamRef = "${repository}:${tag}" + + if ($script:OsImages.ContainsKey($upstreamRef)) + { + return $script:OsImages[$upstreamRef] + } + + $upstream = [pscustomobject]@{ Repository = $repository; Reference = $upstreamRef } + + if (-not $dockerRegistry) + { + $script:OsImages[$upstreamRef] = $upstream + return $upstream + } + + $mirrorRepository = Get-OsMirrorRepository $repository + $mirror = [pscustomobject]@{ Repository = $mirrorRepository; Reference = "${mirrorRepository}:${tag}" } + + docker @dockerConfigArg manifest inspect $mirror.Reference *> $null + if ($LASTEXITCODE -eq 0) + { + # Pull it here, with the credentials from the temporary config, rather than leaving it to the FROM + # resolution inside `docker build`: the build only sees the credentials this script passes it, and + # an agent whose default config is not logged in would otherwise fail with "no basic auth + # credentials". Pulling it first also means the build never touches the registry at all. + Write-Host "Building from the OS image mirrored at $( $mirror.Reference )" -ForegroundColor Green + docker @dockerConfigArg pull $mirror.Reference 2>&1 | Out-Host + + if ($LASTEXITCODE -ne 0) + { + # The mirror is unusable on this agent; upstream still is. Not fatal. + Write-Host "Could not pull the mirrored OS image; building from $upstreamRef." -ForegroundColor Yellow + $script:OsImages[$upstreamRef] = $upstream + return $upstream + } + + $script:OsImages[$upstreamRef] = $mirror + return $mirror + } + + # Not mirrored yet: take it from upstream this once and mirror it, so that every later build on every + # agent gets it from the registry. The push is normally near-instant even though the image is gigabytes: + # those exact blobs already underlie every chain image pushed so far, so the registry mounts them across + # repositories instead of receiving them again. + Write-Host "The OS image is not mirrored yet; pulling $upstreamRef to mirror it" -ForegroundColor Cyan + docker pull $upstreamRef 2>&1 | Out-Host + + if ($LASTEXITCODE -ne 0) + { + # Not fatal: the build below pulls the same image from upstream anyway, and reports its own error. + Write-Host "Could not pull the OS image; building from $upstreamRef." -ForegroundColor Yellow + $script:OsImages[$upstreamRef] = $upstream + return $upstream + } + + docker tag $upstreamRef $mirror.Reference | Out-Null + Start-AsyncPush $mirror.Reference + $script:OsImages[$upstreamRef] = $mirror + + return $mirror + } + + # Ensure the image and its ancestors exist (parent first): use local, else pull, else build; start a push + # when building in registry mode. Returns the image tag. + # True when $tag exists in the registry. A miss is the ordinary case (the image has not been pushed yet) + # and stays quiet, but any OTHER failure - experimental CLI gating, an untrusted certificate, a lost + # session, an unreachable host - is reported once. Silencing those made a broken registry look exactly like + # a cache miss, so every agent rebuilt the whole ancestor chain on every run and nobody could see why. + function Test-ImageInRegistry([string]$tag) + { + $output = (docker @dockerConfigArg manifest inspect $tag 2>&1 | Out-String).Trim() + + if ($LASTEXITCODE -eq 0) + { + return $true + } + + # A genuine "not in the registry" answer. Anything else is a configuration or connectivity fault. + if ($output -notmatch 'manifest unknown|no such manifest|not found|manifest for .* not found') + { + if (-not $script:RegistryProbeWarned) + { + $script:RegistryProbeWarned = $true + Write-Host "Warning: cannot query the registry for '$tag', so cached images cannot be reused and every layer will be rebuilt locally. $output" -ForegroundColor Yellow + } + } + + return $false + } + + function Ensure-Image([string]$dfPath) + { + $baseBuildArg = @() + $baseDf = Get-BaseDockerfile $dfPath + if ($baseDf) + { + $baseTag = Ensure-Image $baseDf + $baseBuildArg = @('--build-arg', "BASE_IMAGE=$baseTag") + } + + $tag = Resolve-ImageTag $dfPath + + # The Claude leaf is ALWAYS built locally and is NEVER pulled from or pushed to the registry. It bakes a + # weekly cache-buster (update.timestamp) and `@latest` npm/plugin installs, so a registry copy is stale by + # design and sharing it saves nothing. Keeping it local-only also means a missing/unauthenticated registry + # (which only ever served the stable ancestor chain) can never fail a Claude run on pull/push. + $isClaudeLeaf = Test-BakesCacheBuster $dfPath + + Write-Host "Ensuring image: $tag" -ForegroundColor Cyan + + docker image inspect $tag *> $null + if ($LASTEXITCODE -eq 0) + { + Write-Host " found locally" -ForegroundColor Green + } + elseif (-not $isClaudeLeaf -and $dockerRegistry -and (Test-ImageInRegistry $tag)) + { + Write-Host " pulling from registry" -ForegroundColor Green + docker @dockerConfigArg pull $tag 2>&1 | Out-Host + if ($LASTEXITCODE -ne 0) { Write-Host "Docker pull failed for $tag" -ForegroundColor Red; exit 1 } + return $tag + } + else + { + Build-OneImage $dfPath $tag $baseBuildArg + } + + # Push the image as soon as it exists, if it isn't already in the registry: the push then overlaps the + # builds of the images above it in the chain instead of waiting for all of them. The job is waited for + # at the end of the script. The Claude leaf is excluded (see $isClaudeLeaf above): it is local-only and + # never enters the registry. + if ($dockerRegistry -and -not $isClaudeLeaf) + { + if (-not (Test-ImageInRegistry $tag)) + { + Start-AsyncPush $tag + } + } + return $tag + } + + # Docker prints sizes with go-units: "0B", "45.5kB", "12.34GB", "1.1TB". `system df`, `image ls` and + # `image prune` use the DECIMAL scale (kB = 1000); other Docker surfaces use the binary spellings ("1.1GiB"). + # Both are accepted, rather than guessing which one produced a given string. + # + # Returns -1, not 0, for anything that is not a size. Every caller has to tell "Docker reported zero" from + # "Docker reported something this script does not understand", because the second one must disable the + # cleanup instead of making it believe the image store is empty. + function ConvertFrom-DockerSize([string]$text) + { + if ("$text" -notmatch '^\s*(\d+(?:\.\d+)?)\s*([kKmMgGtTpP]?)(i?)B\s*$') + { + return [long]-1 + } + + $exponent = switch ($Matches[2].ToUpperInvariant()) + { + 'K' { 1 } + 'M' { 2 } + 'G' { 3 } + 'T' { 4 } + 'P' { 5 } + default { 0 } + } + + return [long]([double]$Matches[1] * [Math]::Pow($( if ($Matches[3]) { 1024 } else { 1000 } ), $exponent)) + } + + # Formats a byte count in the DECIMAL gigabytes Docker prints (1 GB = 1e9 bytes), not in PowerShell's binary + # 1GB, so that every line logged here agrees with the tool it quotes. + function Format-Gigabytes([long]$bytes) + { + return "$( [Math]::Round($bytes / 1e9, 1) ) GB" + } + + # The size of the whole image store in bytes, or -1 when it cannot be determined. + # + # The Images row of `docker system df` reports what the image store occupies on disk, counting a layer + # shared by several images only once. Adding up the sizes from `docker image ls` instead would count each + # shared layer once per image, and would report roughly three times the truth for a three-deep chain. + # + # Deliberately not `docker system df -v`, which recomputes the shared and unique size of every image and + # takes minutes on an agent that holds hundreds of images. Deliberately not `--format json`, which only + # recent versions of the Docker command line accept; the per-row template below is what the default table + # is built from and has worked ever since `system df` was introduced. + function Get-ImageStoreSize + { + $rows = @(docker system df --format '{{.Type}}|{{.Size}}' 2>&1) + + if ($LASTEXITCODE -ne 0) + { + Write-Host "Could not measure the Docker image store: $( ($rows -join ' ').Trim() )" -ForegroundColor Yellow + return [long]-1 + } + + foreach ($row in $rows) + { + $fields = "$row" -split '\|', 2 + if ($fields.Count -ne 2 -or $fields[0].Trim() -ne 'Images') + { + continue + } + + $size = ConvertFrom-DockerSize $fields[1] + if ($size -lt 0) + { + Write-Host "Could not parse the image store size '$( $fields[1].Trim() )' reported by 'docker system df'." -ForegroundColor Yellow + } + + return $size + } + + Write-Host "'docker system df' reported no Images row, so the image store cannot be measured." -ForegroundColor Yellow + return [long]-1 + } + + # Serializes the cleanup across the concurrent DockerBuild runs of one agent. Without it, two runs that both + # measure the store 50 GB over budget each remove 50 GB, and the agent loses twice what it had to. + # + # This is an optimization, not the guarantee of correctness: runs under different accounts resolve different + # temporary directories and never see each other's lock file. What actually keeps a sibling run's images + # alive is the grace window. The operating system releases the handle when the process ends, so the lock + # cannot go stale. Returns $null when another run holds it; the caller then skips the cleanup rather than + # waiting, because whatever the other run frees, it frees for both. + function Enter-ImageCleanupLock + { + try + { + # Resolved inside the try as well: a temporary directory that the platform rejects makes this throw + # rather than the Open below. + $lockPath = Join-Path ([System.IO.Path]::GetTempPath()) 'PostSharp.DockerBuild.ImageCleanup.lock' + + return [System.IO.File]::Open($lockPath, [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) + } + catch [System.IO.IOException] + { + # The expected case: another run holds the lock. Sharing violations, and every other input/output + # error, arrive here. + return $null + } + catch + { + # Anything else, such as a temporary directory that this account cannot write to, or a path the + # platform rejects. UnauthorizedAccessException does not derive from IOException, so it would + # otherwise escape, and $ErrorActionPreference is 'Stop' - which would fail the build over a + # cleanup that is only ever best effort. + Write-Host "Skipping the image-space cleanup: the lock file could not be opened. $( $_.Exception.Message )" -ForegroundColor Yellow + return $null + } + } + + # Every image reference this run is about to need, so that the cleanup does not remove what the build + # immediately rebuilds or pulls again. + # + # Docker already refuses to remove the ancestor of an image it keeps, so listing the whole chain only makes + # the log readable and saves failed removal attempts. The OS image is the entry that genuinely matters: on a + # run whose chain root is absent, no local image depends on the OS image yet, and on a Windows agent that + # image is both the largest and the oldest one on the machine. Without this it would be the first candidate + # removed, seconds before the root build asks for it. + function Get-ImageChainKeepSet + { + $keep = [System.Collections.Generic.List[string]]::new() + + if ($RegistryImage) + { + # -RegistryImage skips all Dockerfile logic, so there is no chain to resolve and the single image + # this run uses is the whole keep set. + $keep.Add($RegistryImage) + return $keep + } + + # Resolve-ImageTag is pure and memoized, and Get-BaseDockerfile only reads the file, so the complete set + # of tags this run needs is computed without a single call to the Docker engine. + $current = $dockerfileFullPath + $root = $current + while ($current) + { + $keep.Add((Resolve-ImageTag $current)) + $root = $current + $current = Get-BaseDockerfile $current + } + + # $root is now the chain root, the only Dockerfile that declares an OS image. + $osImageSpec = Get-OsImageSpec $root + if ($osImageSpec) + { + $keep.Add("$( $osImageSpec.Repository ):$( $osImageSpec.Tag )") + if ($dockerRegistry) + { + $keep.Add("$( Get-OsMirrorRepository $osImageSpec.Repository ):$( $osImageSpec.Tag )") + } + } + + return $keep + } + + # The images that may be removed, oldest first. + # + # Whatever Docker itself refuses to delete is left to Docker: `docker image rm` refuses to remove an image + # that a container references, or that a descendant is built on, and this function does not try to reproduce + # that reasoning. It only produces a sensible order and drops what is pointless to attempt. + # + # `docker image ls` without -a is deliberate: -a also lists the intermediate images of the classic builder, + # which are the whole build cache of the Windows engine. + function Get-EvictionCandidates([System.Collections.Generic.HashSet[string]]$keepReferences, [datetime]$graceCutoff) + { + $rows = @(docker image ls --no-trunc --format '{{.ID}}|{{.Repository}}|{{.Tag}}|{{.CreatedAt}}|{{.Size}}' 2>&1) + if ($LASTEXITCODE -ne 0) + { + Write-Host "Could not list the Docker images: $( ($rows -join ' ').Trim() )" -ForegroundColor Yellow + return @() + } + + # The image identifier of every container on the engine, running or stopped, which includes the boot + # images of the other DockerBuild runs that share this agent. `docker ps -a --format '{{.Image}}'` would + # give the reference as it was typed when the container was created, which is often a tag that has since + # moved; inspecting the containers gives the identifier the container is really pinned to, which is also + # what `docker image ls` reports. + $inUse = [System.Collections.Generic.HashSet[string]]::new( [StringComparer]::OrdinalIgnoreCase ) + $containerIds = @(docker ps -a -q 2>$null | Where-Object { $_ -and $_.Trim() -ne '' }) + if ($containerIds.Count -gt 0) + { + foreach ($imageId in @(docker inspect --format '{{.Image}}' @containerIds 2>$null)) + { + [void]$inUse.Add(("$imageId" -replace '^sha256:', '')) + } + } + + # One row per TAG, so two rows can carry the same image identifier (the same image tagged both locally + # and with the registry prefix). They are grouped by identifier because the size must be counted once, + # and because removing only some of an image's tags frees nothing at all: only the last tag deletes it. + $byId = [ordered]@{ } + $dateWarningIssued = $false + + foreach ($row in $rows) + { + $fields = "$row" -split '\|' + if ($fields.Count -lt 5) { continue } + + $id = $fields[0] -replace '^sha256:', '' + $repository = $fields[1] + $tag = $fields[2] + + # Dangling images are handled up front by `docker image prune`, which applies Docker's own + # definition of dangling. A '' repository here is therefore already gone, or is the parent of + # something, and is not a candidate either way. + if ($repository -eq '') { continue } + + if ($inUse.Contains($id)) { continue } + + $reference = "${repository}:${tag}" + + if (-not $byId.Contains($id)) + { + # Docker prints CreatedAt as "2026-05-13 09:21:33 +0200 CEST", a Go layout that .NET cannot + # parse as a whole. Every row is formatted in the same time zone, so the leading + # "yyyy-MM-dd HH:mm:ss" alone orders them correctly and is all that is read. A date that cannot + # be read is treated as brand new, and therefore never removed, rather than as ancient: if the + # format ever changes, the result must be "frees nothing", never "deletes the oldest image on + # the agent". + $created = [datetime]::MaxValue + if ($fields[3] -match '^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})') + { + [void][datetime]::TryParseExact($Matches[1], 'yyyy-MM-dd HH:mm:ss', [cultureinfo]::InvariantCulture, [Globalization.DateTimeStyles]::None, [ref]$created) + } + elseif (-not $dateWarningIssued) + { + $dateWarningIssued = $true + Write-Host "Cannot read the creation date '$( $fields[3] )' that 'docker image ls' reports; an image whose date cannot be read is never removed." -ForegroundColor Yellow + } + + $size = ConvertFrom-DockerSize $fields[4] + + $byId[$id] = [pscustomobject]@{ + Id = $id + References = [System.Collections.Generic.List[string]]::new() + Created = $created + Size = $( if ($size -lt 0) { [long]0 } else { $size } ) # an unreadable size contributes nothing to the target + Keep = $false + Removed = $false + } + } + + # A tag in the keep set protects the whole image, not only that one tag: removing its other tags + # would free nothing and would leave a half-untagged image behind. + if ($keepReferences.Contains($reference)) { $byId[$id].Keep = $true } + + if ($tag -ne '') { $byId[$id].References.Add($reference) } + } + + # Oldest first. The Docker API exposes no last-used time for an image, so age is the only signal + # available; the images this run is about to need are protected by the keep set instead. + return @($byId.Values | + Where-Object { -not $_.Keep -and $_.Created -lt $graceCutoff } | + Sort-Object Created) + } + + # Frees image disk space when the image store is over budget. Best effort throughout: this frees disk, it + # does not gate the build, so nothing here may change the exit code of the script. + # + # Runs BEFORE the image chain is built, so that the space it frees is space this build can use, and before + # the registry login, because everything it does is local to the Docker engine and needs no credentials. + function Invoke-ImageSpaceCleanup([long]$budgetBytes, [string[]]$keepReferences) + { + $total = Get-ImageStoreSize + if ($total -lt 0) + { + Write-Host "Skipping the image-space cleanup: the image store could not be measured." -ForegroundColor Yellow + return + } + + if ($total -le $budgetBytes) + { + Write-Host "Docker image store: $( Format-Gigabytes $total ) of the $( Format-Gigabytes $budgetBytes ) budget." -ForegroundColor Cyan + return + } + + Write-Host "Docker image store is $( Format-Gigabytes $total ), over the $( Format-Gigabytes $budgetBytes ) budget; freeing space." -ForegroundColor Yellow + + $lock = Enter-ImageCleanupLock + if (-not $lock) + { + Write-Host "Another DockerBuild run is already freeing image space; skipping the cleanup." -ForegroundColor Yellow + return + } + + try + { + $started = [System.Diagnostics.Stopwatch]::StartNew() + $graceCutoff = (Get-Date).AddHours(-$ImageCleanupGraceHours) + $initial = $total + + # Dangling images first, through Docker's own prune, which applies Docker's definition of dangling + # (untagged AND not the parent of anything) and so leaves the intermediate images of the Windows + # classic builder alone. The `until` filter applies the same grace window as the removal below, and + # closes the moment between a sibling run committing its last layer and tagging it, during which its + # image is briefly indistinguishable from an orphan. + # + # Done separately, and first, because a dangling image shares almost every layer with the image that + # replaced it. Inside the loop below its reported size would satisfy the whole overage on paper and + # free nothing at all, wasting a pass. + $pruneOutput = (docker image prune --force --filter "until=$( $ImageCleanupGraceHours )h" 2>&1 | Out-String) + if ($pruneOutput -match 'Total reclaimed space:\s*(\S+)') + { + $reclaimed = ConvertFrom-DockerSize $Matches[1] + if ($reclaimed -gt 0) + { + Write-Host " reclaimed $( Format-Gigabytes $reclaimed ) from dangling images" -ForegroundColor Gray + + # Measure again rather than subtract, so that the loop below never removes space that has + # already been freed. + $total = Get-ImageStoreSize + if ($total -lt 0) { return } + } + } + + # The ?? guards the HashSet constructor, which rejects a null collection: a caller that resolved no + # keep set at all must lose the cleanup, not the build. + $keep = [System.Collections.Generic.HashSet[string]]::new( [string[]]($keepReferences ?? @()), [StringComparer]::OrdinalIgnoreCase ) + $candidates = Get-EvictionCandidates $keep $graceCutoff + + for ($pass = 1; $pass -le $ImageCleanupMaxPasses -and $total -gt $budgetBytes; $pass++) + { + if ($started.Elapsed.TotalMinutes -ge $ImageCleanupTimeoutMinutes) + { + Write-Host "Giving up on the image-space cleanup after $ImageCleanupTimeoutMinutes minutes." -ForegroundColor Yellow + break + } + + # Accumulate against the overage that was just measured. The size reported for an image includes + # the layers it shares with images that survive, so this sum overstates what removing the batch + # frees: the batch is guaranteed to free at most the overage, never more. That is the right + # direction to be wrong in, because it costs passes and not images, and it is why the true total + # is measured again after every pass instead of being tracked by subtraction. + $overage = $total - $budgetBytes + $batch = [System.Collections.Generic.List[object]]::new() + $accumulated = [long]0 + + foreach ($candidate in $candidates) + { + if ($candidate.Removed) { continue } + $batch.Add($candidate) + $accumulated += $candidate.Size + if ($accumulated -ge $overage) { break } + } + + if ($batch.Count -eq 0) + { + Write-Host "No image is left to remove; the image store stays at $( Format-Gigabytes $total )." -ForegroundColor Yellow + break + } + + Write-Host "Pass $pass`: removing up to $( $batch.Count ) unused image(s) to reclaim $( Format-Gigabytes $overage )." -ForegroundColor Cyan + + # Issue the removals NEWEST first, although the order of selection is oldest first: an image + # cannot be removed while a descendant is built on it, and within a chain the descendant is the + # younger image. Issuing them oldest first would fail on every parent. Repeat while anything is + # still coming off, so that a parent freed by the removal of its child also goes in this pass. + $removedInPass = 0 + for ($attempt = 0; $attempt -lt 4; $attempt++) + { + $removedInAttempt = 0 + + foreach ($candidate in ($batch | Sort-Object Created -Descending)) + { + if ($candidate.Removed) { continue } + + # An image with no usable name and tag (a pull pinned to a digest) can only be addressed + # by its identifier. Docker refuses that when several repositories reference the image, + # which is handled below like any other refusal. + $references = if ($candidate.References.Count -gt 0) { $candidate.References } else { @($candidate.Id) } + + $failure = $null + foreach ($reference in $references) + { + $output = (docker image rm $reference 2>&1 | Out-String).Trim() + + # Deliberately not forced. `docker image rm -f` untags an image that a stopped + # container still holds, which is exactly how a concurrent run gets broken: the + # refusal below is the safety net that this whole function relies on. + # + # "No such image" means that another pass, or another run, got there first, which + # counts as success. + if ($LASTEXITCODE -ne 0 -and $output -notmatch 'No such image') + { + $failure = $output + } + } + + if ($failure) + { + # The two expected refusals are that a container holds the image, possibly a sibling + # run's, and that a descendant protected by the keep set or the grace window is + # built on it. Both mean the image was correctly skipped, and are reported as detail + # rather than as a problem. An image that carries several tags loses the tags that + # were removed before the refusal; that frees nothing, but it also breaks nothing, + # because a container and a child image are both pinned to the identifier. + if ($failure -notmatch 'being used by|dependent child images|No such image') + { + Write-Host " could not remove $( $references[0] ): $failure" -ForegroundColor Yellow + } + } + else + { + $candidate.Removed = $true + $removedInAttempt++ + Write-Host " removed $( $references -join ', ' )" -ForegroundColor Gray + } + } + + $removedInPass += $removedInAttempt + if ($removedInAttempt -eq 0) { break } + } + + if ($removedInPass -eq 0) + { + # Every image in the batch was refused, and the list of candidates only ever shrinks, so + # another pass would select the same images and be refused again. + Write-Host "Nothing could be removed; the image store stays at $( Format-Gigabytes $total )." -ForegroundColor Yellow + break + } + + $total = Get-ImageStoreSize + if ($total -lt 0) + { + # The store can no longer be measured, so there is no way to tell when to stop. Stopping is + # the only safe answer. + break + } + } + + $freed = $initial - $total + if ($total -le $budgetBytes) + { + Write-Host "Freed $( Format-Gigabytes $freed ); the image store is now $( Format-Gigabytes $total ) of the $( Format-Gigabytes $budgetBytes ) budget." -ForegroundColor Green + } + else + { + Write-Host "Freed $( Format-Gigabytes $freed ), but the image store is still $( Format-Gigabytes $total ), over the $( Format-Gigabytes $budgetBytes ) budget." -ForegroundColor Yellow + } + } + finally + { + $lock.Dispose() + } + } + + # Dictionary to track volume mounts with "writable wins" logic + $script:VolumeMountDict = @{ } + + # Async registry push: each image is pushed in its own background job, started by Ensure-Image as soon as + # the image exists, and waited for at the very end of the script. A push therefore overlaps the builds that + # follow it and the container run. + $script:RegistryPushJobs = @() + + # Set by Wait-ForRegistryPushes when any push failed, and read on both exit paths (normal completion and + # the `finally` block). + $script:PushFailed = $false + + # The exit code the script has decided on, so that the `finally` block can tell a build that is failing for + # another reason from one that would otherwise have succeeded. + $script:ExitCode = 0 + + # OS images the root images are built FROM, keyed by upstream reference and resolved on first use by + # Get-OsImage. + $script:OsImages = @{ } + + # The Docker engine architecture, resolved on first use by Get-DockerArchitecture. + $script:DockerArchitecture = $null + + # Tags already handed to Start-AsyncPush, so that resolving the same image twice (the run step resolves the + # chain again) does not push it twice. + $script:PushedTags = [System.Collections.Generic.HashSet[string]]::new( [StringComparer]::Ordinal ) + + # Tag of the local, run-specific boot image (set by New-BootImage); removed after the container exits. + $script:BootImageTag = $null + + function Add-VolumeMount + { + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [switch]$Writable + ) + + $normalizedPath = $Path.TrimEnd('\', '/') + $normalizedKey = $normalizedPath.ToLower() + $isGitDirectory = Test-Path (Join-Path $normalizedPath ".git") + + if ( $script:VolumeMountDict.ContainsKey($normalizedKey)) + { + if ($Writable) + { + $script:VolumeMountDict[$normalizedKey].Writable = $true + } + } + else + { + $script:VolumeMountDict[$normalizedKey] = @{ + HostPath = $normalizedPath + Writable = [bool]$Writable + IsGitDirectory = $isGitDirectory + } + } + } + + if ($env:RUNNING_IN_DOCKER) + { + Write-Error "Already running in Docker." + exit 1 + } + + if ($RegistryImage) + { + # Use the pre-built registry image directly, skip all Dockerfile logic + $ImageTag = $RegistryImage + $NoBuildImage = $true + Write-Host "Using registry image: $ImageTag" -ForegroundColor Cyan + } + else + { + # Single source of truth for the cache-busting stamp, shared by + # Get-ContentHash (image tag, Claude mode only) and Get-TimestampFile + # (update.timestamp file baked into the image). Computing it once here + # guarantees both consumers see the same value even if the wall clock + # crosses a week boundary mid-run. + # + # The stamp rotates once per UTC week (anchored to the Monday of the + # current week) so the @latest npm installs of the Claude CLI and + # marketplace plug-ins refresh weekly rather than daily. Use -Update to + # force an immediate refresh regardless of the week boundary. + $script:DayStamp = if ($Update) + { + [DateTime]::UtcNow.ToString("o") # full ISO 8601, seconds precision + } + else + { + # Monday of the current UTC week (DayOfWeek: Sunday=0 .. Saturday=6). + $utcToday = [DateTime]::UtcNow.Date + $daysSinceMonday = ([int]$utcToday.DayOfWeek + 6) % 7 + $utcToday.AddDays(-$daysSinceMonday).ToString("yyyy-MM-dd") + } + + # Determine which Dockerfile will be used. + $DockerfilesDir = "$EngPath/docker" + + # Detect the Windows base-image tag. The OS variant is delivered as the WINDOWS_VERSION build-arg (and + # folded into the content hash) rather than as a separate Dockerfile, so one chain serves both editions. + # Windows build < 26100 is Windows Server 2022; otherwise Windows Server 2025. + $windowsVersion = $null + if ($IsWindows) + { + $osBuild = [System.Environment]::OSVersion.Version.Build + $windowsVersion = if ($osBuild -lt 26100) { 'ltsc2022' } else { 'ltsc2025' } + Write-Host "Detected Windows build $osBuild; using base image tag '$windowsVersion'" -ForegroundColor Cyan + } + + if (-not $Dockerfile) + { + # Dockerfile names are prefix-free (".Dockerfile"). -Claude targets the claude leaf; otherwise + # the build leaf. The chain resolver walks ARG BASE_IMAGE to build/pull the ancestors first. + $layer = if ($Claude) { 'claude' } else { 'build' } + $Dockerfile = "$DockerfilesDir/$layer.Dockerfile" + } + + # Get the full path of the Dockerfile + if ( [System.IO.Path]::IsPathRooted($Dockerfile)) + { + $dockerfileFullPath = $Dockerfile + } + else + { + $dockerfileFullPath = Join-Path $PSScriptRoot $Dockerfile + } + + # Resolve the Docker registry for build images (env-based). Registry mode is off (local image tags) if + # not set. Set before Resolve-ImageTag, which uses it to form the tag. + # -NoRegistry suppresses the environment, which is how a host that cannot reach or verify the registry + # still builds: the cost is that it builds every layer itself instead of pulling the ones already made. + if ($NoRegistry) + { + Write-Host "Registry disabled by -NoRegistry; building images locally." -ForegroundColor Yellow + $dockerRegistry = $null + } + else + { + $dockerRegistry = $env:DOCKER_REGISTRY + } + + # Compute the target image tag (and, transitively, its ancestors' tags via ARG BASE_IMAGE). The image + # name is the Dockerfile stem; the tag is its content hash (folding the parent tag, OS and day-stamp). + $ImageTag = Resolve-ImageTag $dockerfileFullPath + Write-Host "Target image tag: $ImageTag" -ForegroundColor Cyan + } + + # Check MCP server availability for -Claude mode + # The MCP approval server is now a standalone GUI app that must be started separately + $mcpServerAvailable = $false + if ($Claude -and -not $NoMcp) + { + if (Test-McpServerRunning) + { + Write-Host "MCP approval server detected on port $mcpFixedPort" -ForegroundColor Cyan + $mcpServerAvailable = $true + } + else + { + Write-Warning "MCP approval server not running on port $mcpFixedPort." + Write-Warning "Start PostSharp.Engineering.McpApprovalServer.exe before using -Claude mode for host operations." + Write-Warning "Continuing without MCP server support." + } + } + + # When building locally (as opposed as on the build agent), we can optionally do a complete cleanup because + # obj files may point to the host filesystem. + if ($Clean) + { + Write-Host "Cleaning up." -ForegroundColor Green + Get-ChildItem "bin" -Recurse | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue + Get-ChildItem "obj" -Recurse | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue + } + + Write-Host "Preparing context and mounts." -ForegroundColor Green + # Collect environment variables for container (will be inlined in Init.g.ps1) + if (-not $KeepInit) + { + # Create timestamp file for cache invalidation (only if building image). Build-OneImage stages it into + # the context of each image that bakes it; when the run step rebuilds a Claude leaf without having come + # through here, Copy-TimestampToContext creates it on demand. + if (-not $NoBuildImage) + { + $script:TimestampFile = Get-TimestampFile + } + + if ($Claude) + { + # Use Claude-specific environment variables (filtered and renamed) + New-ClaudeEnvHashtable + } + else + { + # Use standard build environment variables + if (-not $env:ENG_USERNAME) + { + $env:ENG_USERNAME = $env:USERNAME + } + + # Add git identity to environment + if ($env:IS_TEAMCITY_AGENT) + { + # On TeamCity agents, check if the environment variables are set. + if (-not $env:GIT_USER_EMAIL -or -not $env:GIT_USER_NAME) + { + Write-Error "On TeamCity agents, the GIT_USER_EMAIL and GIT_USER_NAME environment variables must be set." + exit 1 + } + } + else + { + # On developer machines, use the current git user. + $env:GIT_USER_EMAIL = git config --global user.email + $env:GIT_USER_NAME = git config --global user.name + } + + New-EnvHashtable -EnvironmentVariableList $EnvironmentVariables + } + + # Allow the product repo to customize the container environment variables. + # The optional script mutates the hashtable in place (add / change / remove keys) + # and receives the leaf Dockerfile name and the mode as context. + $customizeEnvScript = Join-Path $EngPath 'CustomizeDockerEnvironment.ps1' + if (Test-Path $customizeEnvScript) + { + $dockerfileName = if ($Dockerfile) { Split-Path -Leaf $Dockerfile } else { '' } + Write-Host "Customizing environment variables from $customizeEnvScript" -ForegroundColor Cyan + . $customizeEnvScript ` + -ContainerEnvironmentVariables $script:ContainerEnvironmentVariables ` + -DockerfileName $dockerfileName ` + -Claude:([bool]$Claude) + + $sortedKeys = $script:ContainerEnvironmentVariables.Keys | Sort-Object + Write-Host "Environment variables after customization: $( $sortedKeys -join ', ' )" -ForegroundColor Gray + } + } + + # Get the source directory name from $PSScriptRoot (script location) + $SourceDirName = $PSScriptRoot + + # Start timing the entire process except cleaning + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + + # Ensure docker context directory exists (not needed for registry images) + if (-not $RegistryImage -and -not (Test-Path $dockerContextDirectory)) + { + New-Item -ItemType Directory -Path $dockerContextDirectory -Force | Out-Null + } + + + # Container user profile (matches actual user in container) + $containerUserProfile = if ($IsUnix) + { + "/root" + } + else + { + "C:\Users\ContainerAdministrator" + } + + # Initialize arrays for special mounts (those with different host/container paths) + $VolumeMappings = @() + $MountPoints = @() + $GitDirectories = @() + + # Prepare volume mappings using the dictionary + Add-VolumeMount -Path $SourceDirName -Writable + + # Define static Git system directory for mapping. This used by Teamcity as an LFS parent repo. + $gitSystemDir = "$BuildAgentPath\system\git" + + if (Test-Path $gitSystemDir) + { + Add-VolumeMount -Path $gitSystemDir + } + + # Mount the host NuGet cache in the container. + if (-not $NoNuGetCache) + { + # Use NUGET_PACKAGES from environment or default to user profile + $nugetCacheDir = $env:NUGET_PACKAGES + if ( [string]::IsNullOrEmpty($nugetCacheDir)) + { + if ($IsUnix) + { + $nugetCacheDir = Join-Path $env:HOME ".nuget/packages" + } + else + { + $nugetCacheDir = Join-Path $env:USERPROFILE ".nuget\packages" + } + } + + Write-Host "NuGet cache directory: $nugetCacheDir" -ForegroundColor Cyan + if (-not (Test-Path $nugetCacheDir)) + { + Write-Host "Creating NuGet cache directory on host: $nugetCacheDir" + New-Item -ItemType Directory -Force -Path $nugetCacheDir | Out-Null + } + + # Mount to the same path in the container (will be transformed by Get-ContainerPath later) + Add-VolumeMount -Path $nugetCacheDir -Writable + } + + # Mount PostSharp.Engineering data directory (for version counters) + $hostEngineeringDataDir = if ($IsUnix) + { + Join-Path $env:HOME ".local/share/PostSharp.Engineering" + } + else + { + Join-Path $env:LOCALAPPDATA "PostSharp.Engineering" + } + + if (-not (Test-Path $hostEngineeringDataDir)) + { + New-Item -ItemType Directory -Force -Path $hostEngineeringDataDir | Out-Null + } + + $containerEngineeringDataDir = if ($IsUnix) + { + Join-Path $containerUserProfile ".local/share/PostSharp.Engineering" + } + else + { + Join-Path $containerUserProfile "AppData\Local\PostSharp.Engineering" + } + $VolumeMappings += "${hostEngineeringDataDir}:${containerEngineeringDataDir}" + $MountPoints += $containerEngineeringDataDir + + # Mount VS Remote Debugger + if ($StartVsmon) + { + if (-not $env:DevEnvDir) + { + Write-Host "Environment variable 'DevEnvDir' is not defined." -ForegroundColor Red + exit 1 + } + + $remoteDebuggerHostDir = "$( $env:DevEnvDir )Remote Debugger\x64" + if (-not (Test-Path $remoteDebuggerHostDir)) + { + Write-Host "Directory '$remoteDebuggerHostDir' does not exist." -ForegroundColor Red + exit 1 + } + + $remoteDebuggerContainerDir = "C:\msvsmon" + $VolumeMappings += "${remoteDebuggerHostDir}:${remoteDebuggerContainerDir}:ro" + $MountPoints += $remoteDebuggerContainerDir + + } + + # Discover symbolic links in source-dependencies and add their targets to mount points + $sourceDependenciesDir = Join-Path $SourceDirName "source-dependencies" + if (Test-Path $sourceDependenciesDir) + { + $symbolicLinks = Get-ChildItem -Path $sourceDependenciesDir -Force | Where-Object { $_.LinkType -eq 'SymbolicLink' } + + foreach ($link in $symbolicLinks) + { + $targetPath = $link.Target + if (-not [string]::IsNullOrEmpty($targetPath) -and (Test-Path $targetPath)) + { + Write-Host "Found symbolic link '$( $link.Name )' -> '$targetPath'" -ForegroundColor Cyan + Add-VolumeMount -Path $targetPath + } + else + { + Write-Host "Warning: Symbolic link '$( $link.Name )' target '$targetPath' does not exist or is invalid" -ForegroundColor Yellow + } + } + + $sourceDirectories = Get-ChildItem -Path $sourceDependenciesDir -Force | Where-Object { $_.LinkType -eq $null } + foreach ($sourceDirectory in $sourceDirectories) + { + Write-Host "Mounting source-dependencies directory: $( $sourceDirectory.FullName )" -ForegroundColor Cyan + $GitDirectories += $sourceDirectory.FullName + } + } + + # Mount sibling directories from the product family (parent directory) + # Only if parent is a recognized product family (PostSharp* or Metalama*) + $parentDir = Split-Path $SourceDirName -Parent + $parentDirName = Split-Path $parentDir -Leaf + if ($parentDir -and (Test-Path $parentDir) -and ($parentDirName -like "PostSharp*" -or $parentDirName -like "Metalama*")) + { + Write-Host "Detected product family directory: $parentDirName" -ForegroundColor Cyan + $siblingDirs = Get-ChildItem -Path $parentDir -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -ne $SourceDirName } + + foreach ($sibling in $siblingDirs) + { + $siblingPath = $sibling.FullName + Write-Host "Mounting product family sibling: $siblingPath" -ForegroundColor Cyan + Add-VolumeMount -Path $siblingPath + } + } + + # Mount PostSharp.Engineering.* directories from grandparent + # This provides access to engineering tools and related repos + $grandparentDir = Split-Path $parentDir -Parent + if ($grandparentDir -and (Test-Path $grandparentDir)) + { + $engineeringDirs = Get-ChildItem -Path $grandparentDir -Directory -Filter "PostSharp.Engineering*" -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -ne $SourceDirName } + + foreach ($engDir in $engineeringDirs) + { + $engDirPath = $engDir.FullName + Write-Host "Mounting engineering repo: $engDirPath" -ForegroundColor Cyan + Add-VolumeMount -Path $engDirPath + } + } + + # Process -Mount parameter for additional directory mounts + if ($Mount -and $Mount.Count -gt 0) + { + foreach ($mountSpec in $Mount) + { + # Check if writable (ends with :w) + $isWritable = $false + $pattern = $mountSpec + if ($mountSpec -match ':w$') + { + $isWritable = $true + $pattern = $mountSpec -replace ':w$', '' + } + + # Trim trailing slashes + $pattern = $pattern.TrimEnd('\', '/') + + # Check if pattern contains glob characters + if ($pattern -match '\*') + { + # Expand glob pattern to match directories only + # Get the base directory (everything before the first glob) + $patternParts = $pattern -split '[\\/]' + $basePathParts = @() + $globStartIndex = -1 + + for ($i = 0; $i -lt $patternParts.Count; $i++) + { + if ($patternParts[$i] -match '\*') + { + $globStartIndex = $i + break + } + $basePathParts += $patternParts[$i] + } + + if ($basePathParts.Count -gt 0) + { + $basePath = $basePathParts -join [System.IO.Path]::DirectorySeparatorChar + } + else + { + $basePath = "." + } + + if (Test-Path $basePath) + { + # Determine if recursive search is needed (pattern contains **) + $isRecursive = $pattern -match '\*\*' + + # Build the glob pattern for the part after the base path + $globPart = ($patternParts[$globStartIndex..($patternParts.Count - 1)]) -join [System.IO.Path]::DirectorySeparatorChar + + # Get matching directories + $matchingDirs = @() + if ($isRecursive) + { + # For ** patterns, recurse and convert ** to * for -like matching + # Replace ** with a regex-friendly pattern for matching + $likePattern = $pattern -replace '\*\*', '*' + $matchingDirs = Get-ChildItem -Path $basePath -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -like $likePattern } + } + else + { + # For single * patterns, use direct matching without recursion + $matchingDirs = Get-ChildItem -Path $basePath -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -like $pattern } + } + + if ($matchingDirs.Count -eq 0) + { + Write-Host "Warning: No directories matched pattern '$pattern'" -ForegroundColor Yellow + } + else + { + foreach ($dir in $matchingDirs) + { + $dirPath = $dir.FullName + $rwStatus = if ($isWritable) + { + "writable" + } + else + { + "readonly" + } + Write-Host "Mounting from -Mount pattern '$pattern': $dirPath ($rwStatus)" -ForegroundColor Cyan + Add-VolumeMount -Path $dirPath -Writable:$isWritable + } + } + } + else + { + Write-Host "Warning: Base path '$basePath' for pattern '$pattern' does not exist" -ForegroundColor Yellow + } + } + else + { + # No glob - mount directly if it's a directory + if (Test-Path $pattern -PathType Container) + { + $rwStatus = if ($isWritable) + { + "writable" + } + else + { + "readonly" + } + Write-Host "Mounting from -Mount: $pattern ($rwStatus)" -ForegroundColor Cyan + Add-VolumeMount -Path $pattern -Writable:$isWritable + } + else + { + Write-Host "Warning: Mount path '$pattern' does not exist or is not a directory" -ForegroundColor Yellow + } + } + } + } + + # Convert dictionary entries to arrays (with "writable wins" deduplication already applied) + # Sort by key for deterministic ordering to optimize Docker image layer reuse + foreach ($key in $script:VolumeMountDict.Keys | Sort-Object) + { + $entry = $script:VolumeMountDict[$key] + $mountOption = if ($entry.Writable) + { + "" + } + else + { + ":ro" + } + $VolumeMappings += "$( $entry.HostPath ):$( $entry.HostPath )$mountOption" + $MountPoints += $entry.HostPath + if ($entry.IsGitDirectory) + { + $GitDirectories += $entry.HostPath + } + } + + # Execute auto-generated DockerMounts.g.ps1 script to add more directory mounts. + $dockerMountsScript = Join-Path $EngPath 'DockerMounts.g.ps1' + if (Test-Path $dockerMountsScript) + { + Write-Host "Importing Docker mount points from $dockerMountsScript" -ForegroundColor Cyan + . $dockerMountsScript + + # Check if we need to convert Windows paths to WSL paths + # This happens when DockerMounts.g.ps1 was generated on Windows but we're running on WSL + if ($IsUnix) + { + # Check if any volume mapping contains Windows-style paths (e.g., C:\) + $hasWindowsPaths = $VolumeMappings | Where-Object { $_ -match '^[A-Za-z]:\\' } + + if ($hasWindowsPaths) + { + Write-Host "Detected Windows paths in DockerMounts.g.ps1 while running on Unix. Converting paths to WSL format." -ForegroundColor Yellow + + # Function to convert Windows path to WSL path + function ConvertTo-WslPath + { + param([string]$WindowsPath) + + if ($WindowsPath -match '^([A-Za-z]):\\(.*)$') + { + $drive = $Matches[1].ToLower() + $path = $Matches[2] -replace '\\', '/' + return "/mnt/$drive/$path" + } + return $WindowsPath + } + + # Convert VolumeMappings + # Note: When running Docker Desktop for Windows from WSL, BOTH host and container paths + # need to be in WSL format (/mnt/c/...) because Docker is invoked from WSL context. + $convertedVolumeMappings = @() + foreach ($mapping in $VolumeMappings) + { + # Parse mapping: hostPath:containerPath[:options] + # Challenge: colons appear in Windows paths (C:\) and as delimiters + # Strategy: Split on : and reconstruct Windows paths (single letter followed by \ path) + $parts = $mapping -split ':' + + $i = 0 + + # Extract host path + if ($parts[$i].Length -eq 1 -and $i + 1 -lt $parts.Length -and $parts[$i + 1] -match '^[\\/]') + { + # Windows path: C:\path - convert to WSL format + $hostPath = "$( $parts[$i] ):$( $parts[$i + 1] )" + $hostPath = ConvertTo-WslPath $hostPath + $i += 2 + } + else + { + # Unix path: /path - keep as-is + $hostPath = $parts[$i] + $i += 1 + } + + # Extract container path + if ($i -lt $parts.Length) + { + if ($parts[$i].Length -eq 1 -and $i + 1 -lt $parts.Length -and $parts[$i + 1] -match '^[\\/]') + { + # Windows path - convert to WSL format + $containerPath = "$( $parts[$i] ):$( $parts[$i + 1] )" + $containerPath = ConvertTo-WslPath $containerPath + $i += 2 + } + else + { + # Unix path - keep as-is + $containerPath = $parts[$i] + $i += 1 + } + } + else + { + $containerPath = $hostPath # Fallback + } + + # Rest is options (:ro or :rw) + if ($i -lt $parts.Length) + { + $options = ':' + ($parts[$i..($parts.Length - 1)] -join ':') + } + else + { + $options = '' + } + + $convertedVolumeMappings += "${hostPath}:${containerPath}${options}" + } + $VolumeMappings = $convertedVolumeMappings + + # Convert MountPoints + $MountPoints = $MountPoints | ForEach-Object { ConvertTo-WslPath $_ } + + # Convert GitDirectories + $GitDirectories = $GitDirectories | ForEach-Object { ConvertTo-WslPath $_ } + } + } + } + elseif (-not $env:IS_TEAMCITY_AGENT) + { + Write-Error "DockerMounts.g.ps1 not found at '$dockerMountsScript'. Run './Build.ps1 prepare' or './Build.ps1 dependencies update' to generate it." + exit 1 + } + + # Handle path transformations (platform-specific) + $substCommandsInline = "" + + if ($IsWindows) + { + # Handle non-C: drive letters for Docker (Windows containers only have C: by default) + # We mount X:\foo to C:\X\foo in the container, then use subst to create the X: drive + $driveLetters = @{ } + + function Get-ContainerPath($hostPath) + { + if ($hostPath -match '^([A-Za-z]):(.*)$') + { + $driveLetter = $Matches[1].ToUpper() + $pathWithoutDrive = $Matches[2] + if ($driveLetter -ne 'C') + { + $driveLetters[$driveLetter] = $true + return "C:\$driveLetter$pathWithoutDrive" + } + } + return $hostPath + } + + # Transform all volume mappings to use container paths + $transformedVolumeMappings = @() + foreach ($mapping in $VolumeMappings) + { + # Parse volume mapping: hostPath:containerPath[:options] + if ($mapping -match '^([A-Za-z]:\\[^:]*):([A-Za-z]:\\[^:]*)(:.+)?$') + { + $hostPath = $Matches[1] + $containerPath = $Matches[2] + $options = $Matches[3] + $newContainerPath = Get-ContainerPath $containerPath + $transformedVolumeMappings += "${hostPath}:${newContainerPath}${options}" + } + else + { + $transformedVolumeMappings += $mapping + } + } + $VolumeMappings = $transformedVolumeMappings + + # Transform MountPoints, GitDirectories, SourceDirName, and CallingDirectory for the container + $MountPoints = $MountPoints | ForEach-Object { Get-ContainerPath $_ } + $GitDirectories = $GitDirectories | ForEach-Object { Get-ContainerPath $_ } + $ContainerSourceDir = Get-ContainerPath $SourceDirName + $ContainerCallingDir = Get-ContainerPath $CallingDirectory + if ($PostInit) + { + $ContainerPostInit = Get-ContainerPath $PostInit + } + + # Add both the unmapped (C:\X\...) and mapped (X:\...) paths to GitDirectories for safe.directory + # Git may resolve paths differently depending on how it's invoked + $expandedGitDirectories = @() + foreach ($dir in $GitDirectories) + { + $expandedGitDirectories += $dir + # If path is C:\\... (unmapped subst path), also add :\... (mapped path) + if ($dir -match '^C:\\([A-Za-z])\\(.*)$') + { + $letter = $Matches[1].ToUpper() + $rest = $Matches[2] + $expandedGitDirectories += "${letter}:\$rest" + } + } + $GitDirectories = $expandedGitDirectories + + # Deduplicate again after transformations and expansions (case-insensitive for Windows paths) + $VolumeMappings = $VolumeMappings | Group-Object { $_.ToLower() } | ForEach-Object { $_.Group[0] } + $MountPoints = $MountPoints | Group-Object { $_.ToLower() } | ForEach-Object { $_.Group[0] } + $GitDirectories = $GitDirectories | Group-Object { "$_".ToLower() } | ForEach-Object { $_.Group[0] } + + # Build subst commands string for inline execution in docker run + foreach ($letter in $driveLetters.Keys | Sort-Object) + { + $substCommandsInline += "C:\Windows\System32\subst.exe ${letter}: C:\$letter; " + } + if ($driveLetters.Keys.Count -gt 0) + { + Write-Host "Drive letter mappings for container: $( $driveLetters.Keys -join ', ' )" -ForegroundColor Cyan + } + } + else + { + # Unix (Linux/macOS): No drive letter mapping needed, paths remain as-is + $ContainerSourceDir = $SourceDirName + $ContainerCallingDir = $CallingDirectory + if ($PostInit) + { + $ContainerPostInit = $PostInit + } + + # Deduplicate (case-sensitive for Unix paths) + $VolumeMappings = $VolumeMappings | Sort-Object -Unique + $MountPoints = $MountPoints | Sort-Object -Unique + $GitDirectories = $GitDirectories | Sort-Object -Unique + } + + # Create Init.g.ps1 with environment variables, git configuration (safe.directory and user identity) + # This file is generated in $EngPath/.g/ (outside docker-context) and accessed via mounted directory + if (-not $NoInit -and -not $KeepInit) + { + $gDirectory = Join-Path $EngPath ".g" + if (-not (Test-Path $gDirectory)) + { + New-Item -ItemType Directory -Path $gDirectory -Force | Out-Null + } + $initScript = Join-Path $gDirectory "Init.g.ps1" + + # Generate inline environment variable assignments + $envVarAssignments = "" + if ($script:ContainerEnvironmentVariables -and $script:ContainerEnvironmentVariables.Count -gt 0) + { + $envVarAssignments = "# Set environment variables`n" + foreach ($key in $script:ContainerEnvironmentVariables.Keys | Sort-Object) + { + $value = $script:ContainerEnvironmentVariables[$key] + # Escape single quotes in the value + $escapedValue = $value -replace "'", "''" + $envVarAssignments += "Write-Host `"Setting environment variable: $key`" -ForegroundColor Green`n" + $envVarAssignments += "[Environment]::SetEnvironmentVariable('$key', '$escapedValue', [EnvironmentVariableTarget]::Machine)`n" + $envVarAssignments += "`$env:$key='$escapedValue'`n" + } + $envVarAssignments += "`n" + } + + # Generate git config commands directly from known values + $gitConfigCommands = "# Configure git identity and safe.directory`n" + + if ($script:ContainerEnvironmentVariables -and $script:ContainerEnvironmentVariables.ContainsKey('GIT_USER_NAME')) + { + $escapedName = $script:ContainerEnvironmentVariables['GIT_USER_NAME'] -replace "'", "''" + $gitConfigCommands += "git config --global user.name '$escapedName'`n" + } + if ($script:ContainerEnvironmentVariables -and $script:ContainerEnvironmentVariables.ContainsKey('GIT_USER_EMAIL')) + { + $escapedEmail = $script:ContainerEnvironmentVariables['GIT_USER_EMAIL'] -replace "'", "''" + $gitConfigCommands += "git config --global user.email '$escapedEmail'`n" + } + + # Generate git safe.directory commands directly + foreach ($dir in $GitDirectories) + { + if ($dir) + { + # Git compares safe.directory against the repository path exactly, and the path it reports has + # no trailing separator: registering only the trailing-slash form leaves the exception unmatched + # and the repository still refused as dubiously owned. Register both forms. + $normalizedDir = ($dir -replace '\\', '/').TrimEnd('/') + $gitConfigCommands += "git config --global --add safe.directory '$normalizedDir'`n" + $gitConfigCommands += "git config --global --add safe.directory '$normalizedDir/'`n" + } + } + + # Generate PostInit script call if specified + $postInitCommands = "" + if ($PostInit -and $ContainerPostInit) + { + $escapedPostInit = $ContainerPostInit -replace "'", "''" + $postInitCommands = "`n# Execute PostInit script`n" + $postInitCommands += "Write-Host `"Executing PostInit script: $ContainerPostInit`" -ForegroundColor Cyan`n" + $postInitCommands += "& '$escapedPostInit'`n" + $postInitCommands += "`$postInitExitCode = `$LASTEXITCODE`n" + $postInitCommands += "if (`$postInitExitCode -and `$postInitExitCode -ne 0) { Write-Host `"PostInit script failed with exit code `$postInitExitCode.`" -ForegroundColor Red; exit `$postInitExitCode }`n" + } + + $initScriptContent = @" +# Auto-generated initialization script for container startup + +$envVarAssignments$gitConfigCommands$postInitCommands +"@ + + # Write a test file with GUID first to check git tracking + @" +# Test file - checking git tracking +# GUID: $([System.Guid]::NewGuid().ToString() ) +"@ | Set-Content -Path $initScript -Encoding UTF8 + + # Check if Init.g.ps1 is tracked by git + $gitStatus = git status --porcelain $initScript 2> $null + if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($gitStatus)) + { + Write-Error "Init script '$initScript' is tracked by git. Please add '$gDirectory' to .gitignore first." + exit 1 + } + + $initScriptContent | Set-Content -Path $initScript -Encoding UTF8 + } + + # The cache-buster is staged by Build-OneImage, into the context of each image that actually declares the + # `COPY .g/update.timestamp`. Doing it there rather than here is what makes it prefix-agnostic: the + # destination is derived from the Dockerfile being built instead of from a hardcoded "claude" directory. + + # Path separator depends on platform (and container OS) + $pathSeparator = if ($IsUnix) + { + ":" + } + else + { + ";" + } + $mountPointsAsString = $MountPoints -Join $pathSeparator + $gitDirectoriesAsString = $GitDirectories -Join $pathSeparator + + Write-Host "Volume mappings: " @VolumeMappings -ForegroundColor Gray + Write-Host "Mount points: " $mountPointsAsString -ForegroundColor Gray + Write-Host "Git directories: " $gitDirectoriesAsString -ForegroundColor Gray + + # Check if a container is already running with this image (only for interactive scenarios) + $existingContainerId = $null + + if ($Interactive) + { + # Check for existing container + $existingContainerId = docker ps -q --filter "ancestor=$ImageTag" | Select-Object -First 1 + if ($existingContainerId) + { + Write-Host "Found existing container $existingContainerId running with image $ImageTag" -ForegroundColor Cyan + Write-Host "Will reuse existing container instead of starting a new one." -ForegroundColor Cyan + $ImageTag = $searchImageTag + } + else + { + Write-Host "No existing container for $ImageTag." + } + } + + # Free image disk space before the chain is resolved, so that what is freed is available to the builds and + # pulls below rather than only to the next run. Everything the cleanup does is local to the Docker engine, + # so it runs before the registry login and needs no credentials. + # + # Skipped when an existing container is reused: nothing is built or pulled then, so there is no space to + # make room for, and the image of that container must stay untouched. + if ($maxImageSpaceBytes -gt 0 -and -not $existingContainerId) + { + Invoke-ImageSpaceCleanup $maxImageSpaceBytes (Get-ImageChainKeepSet) + } + + # Registry authentication + image-chain resolution (build the ancestor chain; pull-or-build+push each level). + $builtNewImage = $false + $dockerConfigArg = @() + + # $RegistryImage is an explicit user override ("use this pre-built image, skip all Dockerfile logic"), so the + # chain is neither authenticated nor resolved for it. Every other path (build step, default run, and the CI run + # step with -NoBuildImage) resolves the chain so the local-only Claude leaf is guaranteed present below. + if (-not $existingContainerId -and -not $RegistryImage) + { + if ($dockerRegistry) + { + # Temporary Docker config dir to avoid credential-helper issues (e.g. docker-credential-desktop not + # found when using Docker Engine without Desktop), then authenticate. + # + # Authentication runs for BOTH the build step (-BuildImage) and the run step (-NoBuildImage). The run + # step still resolves the chain below, and when it executes on a different docker daemon than the build + # step it must PULL the stable ancestors (vs/build) from the registry - so credentials must be present + # here too. (The Claude leaf is never pulled; it is always built locally - see Ensure-Image.) + $tempDockerConfig = Join-Path ([System.IO.Path]::GetTempPath()) "docker-config-$( New-Guid )" + New-Item -ItemType Directory -Path $tempDockerConfig -Force | Out-Null + @{ auths = @{ } } | ConvertTo-Json | Set-Content (Join-Path $tempDockerConfig "config.json") + $dockerConfigArg = @("--config", $tempDockerConfig) + + $dockerPassword = $env:DOCKER_PASSWORD + $dockerUsername = $env:DOCKER_USERNAME + + # Setting DOCKER_REGISTRY without credentials is a configuration error, not a request for anonymous + # access: continuing would silently rebuild every ancestor locally (no pull) and then fail the pushes + # at the end of the build anyway, after a long and misleading build. + $missingCredentials = @() + if (-not $dockerUsername) { $missingCredentials += "DOCKER_USERNAME" } + if (-not $dockerPassword) { $missingCredentials += "DOCKER_PASSWORD" } + if ($missingCredentials) + { + Write-Error "DOCKER_REGISTRY is set to '$dockerRegistry' but $( $missingCredentials -join " and " ) $( if ($missingCredentials.Count -eq 1) { "is" } else { "are" } ) not set. Set the missing variable(s), or unset DOCKER_REGISTRY to build without a registry." + exit 1 + } + + Write-Host "Authenticating to registry..." -ForegroundColor Gray + $loginOutput = $dockerPassword | docker @dockerConfigArg login $dockerRegistry --username $dockerUsername --password-stdin 2>&1 + if ($LASTEXITCODE -ne 0) + { + Write-Error "Registry authentication to '$dockerRegistry' failed as user '$dockerUsername': $( $loginOutput -join [System.Environment]::NewLine )" + exit 1 + } + } + + # Resolve the whole chain (parent first): use local, else pull ancestors, else build; freshly built layers + # are queued for push. This runs in the run step (-NoBuildImage) too: Ensure-Image is idempotent (it is a + # no-op for images already present locally), so when the build step shared this daemon nothing is rebuilt. + # Its purpose here is to guarantee the local-only Claude leaf exists before the boot image's FROM resolves + # it - the leaf is never pushed, so it cannot be pulled and MUST be (re)built locally in the run step. + Ensure-Image $dockerfileFullPath | Out-Null + } + elseif ($existingContainerId) + { + Write-Host "Skipping image build (reusing existing container $existingContainerId)." -ForegroundColor Yellow + } + else + { + Write-Host "Skipping image build (using pre-built registry image $ImageTag)." -ForegroundColor Yellow + } + + # Build the local boot image over the resolved chain image (creates the bind-mount directories). The static + # chain images stay pure and shareable; this thin layer carries the machine-specific mount set and is never + # pushed. `docker run` below uses the boot image. Its `FROM` resolves the chain leaf locally: Ensure-Image + # above guarantees the leaf is present (the Claude leaf is built locally, ancestors are local-or-pulled), so + # the boot build never pulls and needs no registry credentials (same as the chain builds in Build-OneImage). + if (-not $BuildImage -and -not $existingContainerId -and $mountPointsAsString) + { + $ImageTag = New-BootImage $ImageTag + } + + # Run the build within the container + if (-not $BuildImage) + { + # Common setup for both Claude and normal build modes + $pwshPath = if ($IsUnix) + { + '/usr/bin/pwsh' + } + else + { + 'C:\Program Files\PowerShell\7\pwsh.exe' + } + # Init.g.ps1 is in the mounted source directory, not baked into the image + # Init.g.ps1 is in $EngPath/.g/ (outside docker-context), accessed via mounted source directory + $containerInitScript = "$ContainerSourceDir/$EngPath/.g/Init.g.ps1" + $initCall = if (-not $NoInit) + { + "& '$containerInitScript'; " + } + else + { + "" + } + + # Convert volume mappings to docker args format (interleave "-v" flags) + $volumeArgs = @() + foreach ($mapping in $VolumeMappings) + { + $volumeArgs += @("-v", $mapping) + } + + if ($Claude) + { + # MCP server configuration + $mcpPort = $null + if (-not $NoMcp -and $mcpServerAvailable) + { + $mcpPort = $mcpFixedPort + } + elseif (-not $NoMcp) + { + Write-Host "Skipping MCP (server not running)." -ForegroundColor Yellow + } + else + { + Write-Host "Skipping MCP approval server (-NoMcp specified)." -ForegroundColor Yellow + } + + # Run Claude mode + Write-Host "Running Claude in the container." -ForegroundColor Green + + # Container will have its own Claude profile (no mount, no copy from host) + $hostUserProfile = if ($IsUnix) + { + $env:HOME + } + else + { + $env:USERPROFILE + } + + # Mount Claude sessions directory to preserve history (but not plugins) + $hostClaudeSessions = Join-Path $hostUserProfile ".claude\.sessions" + $containerClaudeSessions = Join-Path $containerUserProfile ".claude\.sessions" + if (-not (Test-Path $hostClaudeSessions)) + { + New-Item -ItemType Directory -Path $hostClaudeSessions -Force | Out-Null + } + $volumeArgs += @("-v", "${hostClaudeSessions}:${containerClaudeSessions}") + Write-Host "Mounting Claude sessions directory: $hostClaudeSessions" -ForegroundColor Cyan + + # Mount Claude projects directory to share session history between container instances + $hostClaudeProjects = Join-Path $hostUserProfile ".claude\projects" + $containerClaudeProjects = Join-Path $containerUserProfile ".claude\projects" + if (-not (Test-Path $hostClaudeProjects)) + { + New-Item -ItemType Directory -Path $hostClaudeProjects -Force | Out-Null + } + $volumeArgs += @("-v", "${hostClaudeProjects}:${containerClaudeProjects}") + Write-Host "Mounting Claude projects directory: $hostClaudeProjects" -ForegroundColor Cyan + + # Extract Claude prompt from remaining arguments if present + # Usage: -Claude for interactive, -Claude "prompt" for non-interactive + $ClaudePrompt = $null + if ($BuildArgs -and $BuildArgs.Count -gt 0 -and $BuildArgs[0] -and -not $BuildArgs[0].StartsWith('-')) + { + $ClaudePrompt = $BuildArgs[0] + } + + # Build inline script: subst drives, copy claude.json, cd to source, run Claude + if ($ClaudePrompt) + { + # Non-interactive mode with prompt - no -it flags + $dockerArgs = @() + $mcpArg = if ($mcpPort) + { + " -McpPort $mcpPort" + } + else + { + "" + } + $inlineScript = "${substCommandsInline}${initCall}cd '$SourceDirName'; & .\eng\RunClaude.ps1 -Prompt `"$ClaudePrompt`"$mcpArg" + } + else + { + # Interactive mode - requires TTY + $dockerArgs = @("-it") + $mcpArg = if ($mcpPort) + { + " -McpPort $mcpPort" + } + else + { + "" + } + $inlineScript = "${substCommandsInline}${initCall}cd '$SourceDirName'; & .\eng\RunClaude.ps1$mcpArg" + } + + # Environment variables to pass to container + # No MCP secret needed - server binds to localhost only + $envArgs = @() + + # No pwshArgs for Claude mode + $pwshArgs = $null + # No MCP cleanup needed - server runs independently + $needsMcpCleanup = $false + } + else + { + # Run standard build mode + # Delete now and not in the container because it's much faster and lock error messages are more relevant. + Write-Host "Running the script in the container." -ForegroundColor Green + + # Prepare Build.ps1 arguments + if ($StartVsmon) + { + $BuildArgs = @("-StartVsmon") + $BuildArgs + } + + if ($Interactive) + { + $pwshArgs = "-NoExit" + $BuildArgs = @("-Interactive") + $BuildArgs + $dockerArgs = @("-it") + $pwshExitCommand = "" + } + else + { + $pwshArgs = "-NonInteractive" + $dockerArgs = @() + $pwshExitCommand = "exit `$LASTEXITCODE`;" + } + + $buildArgsString = $BuildArgs -join " " + + # Build inline script: subst drives, run init, cd to source, run build + # Get full script path (combine with container source dir if relative) + if ( [System.IO.Path]::IsPathRooted($Script)) + { + $scriptFullPath = $Script + } + else + { + $scriptFullPath = Join-Path $ContainerSourceDir $Script + } + # Fail if the script is not there. In -Command mode `& ` is a non-terminating error and + # leaves $LASTEXITCODE at 0, so without this guard the container exits 0 and the build is reported + # successful having run nothing at all. That is what an unmounted workspace looks like: the bind + # mount silently yields an empty directory when the engine is not allowed to share the host path. + $missingScriptMessage = "The script $scriptFullPath is not present in the container. The workspace " + + "is most likely not mounted -- check that the agent work directory is a path the container " + + "engine is allowed to share." + + $scriptInvocation = "if ( -not ( Test-Path -LiteralPath '$scriptFullPath' ) ) " + + "{ Write-Host '$missingScriptMessage' -ForegroundColor Red; exit 127 }; & '$scriptFullPath'" + $inlineScript = "${substCommandsInline}${initCall}cd '$SourceDirName'; $scriptInvocation $buildArgsString; $pwshExitCommand" + + # No environment args for normal build + $envArgs = @() + $needsMcpCleanup = $false + } + + # Common docker execution for both modes + $dockerArgsAsString = $dockerArgs -join " " + + # Execute docker command + if ($existingContainerId) + { + # Reuse existing container with docker exec + Write-Host "Executing: ``docker exec $existingContainerId $dockerArgsAsString -w $ContainerCallingDir $ImageTag `"$pwshPath`" $pwshArgs -Command `"$inlineScript`"" -ForegroundColor Cyan + docker exec $dockerArgs -w $ContainerCallingDir $existingContainerId $pwshPath $pwshArgs -Command $inlineScript + } + else + { + # Start new container with docker run + # Build docker command with proper argument handling (avoid empty strings) + $dockerCmd = @('run', '--rm') + + # Memory limit: everywhere except Windows process isolation, which ignores it. + if ($supportsResourceLimits -and $Memory) + { + $dockerCmd += "--memory=$Memory" + } + + # The MSBuild node count that matches that budget. msbuild.ps1 reads it inside the container, where the + # limit itself is not visible. + if ($maxBuildParallelism -gt 0) + { + $dockerCmd += @('-e', "MAX_BUILD_PARALLELISM=$maxBuildParallelism") + } + + # CPU limit: dynamic or static + if ($isDynamicCpus) + { + $dynamicAllocation = Invoke-DynamicCpuRebalance -AdditionalContainers 1 + $dockerCmd += "--cpus=$dynamicAllocation" + $dockerCmd += @('-e', "DOTNET_PROCESSOR_COUNT=$dynamicAllocation") + $dockerCmd += @('--label', "$DynamicCpuLabel") + } + elseif ($supportsResourceLimits) + { + $dockerCmd += "--cpus=$Cpus" + } + + if ($isolationArg) + { + $dockerCmd += $isolationArg + } + $dockerCmd += $dockerArgs + $dockerCmd += $volumeArgs + $dockerCmd += $envArgs + + # Add port mappings from -Ports parameter + if ($Ports -and $Ports.Count -gt 0) + { + foreach ($portMapping in $Ports) + { + $dockerCmd += @('-p', $portMapping) + } + } + + # Add label for container identification (used for cleanup of orphaned containers) + if ($Label) + { + $dockerCmd += @('--label', "postsharp.build=$Label") + } + + if ($pwshArgs) + { + $dockerCmd += @('-w', $ContainerCallingDir, $ImageTag, $pwshPath, $pwshArgs, '-Command', $inlineScript) + } + else + { + $dockerCmd += @('-w', $ContainerCallingDir, $ImageTag, $pwshPath, '-Command', $inlineScript) + } + + Write-Host "Executing: ``docker $( $dockerCmd -join ' ' )" -ForegroundColor Cyan + & docker @dockerCmd + } + $dockerExitCode = $LASTEXITCODE + + # Post-exit rebalance: when our container exits (--rm removes it), + # redistribute CPUs to remaining managed containers + if ($isDynamicCpus -and -not $existingContainerId) + { + Invoke-DynamicCpuRebalance -AdditionalContainers 0 | Out-Null + } + + # Check exit code + if ($dockerExitCode -ne 0) + { + Write-Host "Container failed with exit code $dockerExitCode" -ForegroundColor Red + } + } + else + { + Write-Host "Skipping container run (BuildImage specified)." -ForegroundColor Yellow + } + + Wait-ForRegistryPushes + + # Stop timing and display results + $elapsed = $stopwatch.Elapsed + Write-Host "" + Write-Host "Total build time: $($elapsed.ToString('hh\:mm\:ss\.fff') )" -ForegroundColor Cyan + Write-Host "Build completed at: $( Get-Date -Format 'yyyy-MM-dd HH:mm:ss' )" -ForegroundColor Cyan + + # The container's own exit code wins when both failed: it says more about what went wrong than the push does. + # $dockerExitCode is $null when the container was not run at all (-BuildImage). + if ($dockerExitCode) + { + $script:ExitCode = $dockerExitCode + } + elseif ($script:PushFailed) + { + Write-Host "The build failed because at least one registry push failed." -ForegroundColor Red + $script:ExitCode = 1 + } + + exit $script:ExitCode +} +finally +{ + # Never abandon a push that is still in flight. On the normal path the jobs have already been waited for + # (and the list emptied), so this only does something when a later step failed or was interrupted - exactly + # when the push would otherwise die with the process and leave the image out of the registry. + if ($script:RegistryPushJobs.Count -gt 0) + { + Wait-ForRegistryPushes + } + + # Safety-net rebalance on Ctrl+C or unexpected exit + if ($isDynamicCpus) + { + try { Invoke-DynamicCpuRebalance -AdditionalContainers 0 | Out-Null } catch { } + } + + # Remove the run-specific boot image. Its tag is unique per run (timestamp suffix), so leaving it behind + # would accumulate dangling leaf images. Running here (not after the run) guarantees removal even when the + # build throws or the user presses Ctrl+C - PowerShell still executes finally on a pipeline interrupt. The + # `docker run --rm` removes the container, so the image is normally unreferenced; `-f` untags it even if a + # stopped container still references it. Best-effort: a removal failure must not mask the build's exit code. + if ($script:BootImageTag) + { + Write-Host "Removing boot image $($script:BootImageTag)" -ForegroundColor Gray + docker image rm -f $script:BootImageTag *> $null + } + + # Restore original location + Pop-Location + + # A failed push must fail the build even when the script is unwinding from another failure. A non-zero + # $script:ExitCode means the script already decided to fail, and that reason is the more informative one. + if ($script:PushFailed -and $script:ExitCode -eq 0) + { + Write-Host "The build failed because at least one registry push failed." -ForegroundColor Red + exit 1 + } +} diff --git a/README.md b/README.md index 713d586..489914e 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,33 @@ the dependency at it, and build that repository first: ./Build.ps1 dependencies set local Backstage --path ``` +### Running the tests + +`./Build.ps1 test` runs the suite on SQLite, which needs no server and keeps the loop short. Run the +same tests a second time against SQL Server before you open a pull request. SQL Server is the engine +that customers use, and the two engines differ in the collation, in the column types and in the lock +that serializes the lease requests: + +``` +$env:MSSQL_SA_PASSWORD = '' +docker compose up -d database +$env:LICENSESERVER_TEST_SQLSERVER = "Server=127.0.0.1,1433;User Id=sa;Password=$env:MSSQL_SA_PASSWORD;TrustServerCertificate=True;Encrypt=False" +dotnet test tests\SharpCrafters.Backstage.LicenseServer.Tests +``` + +The tests read the connection string from `LICENSESERVER_TEST_SQLSERVER`, and they run on SQLite when +it is not set. The connection string names no database: each test receives a database of its own, +created from `Database\CreateTables.sql`, so this run also proves that the script and the Entity +Framework model agree. The databases are named `licenseserver_test_` followed by a hexadecimal +number. They are reused during the run, and the next run drops the ones an interrupted run left +behind. Point the variable at a SQL Server of your own if you prefer, as long as its login may create +a database. + +The continuous integration build runs the same second run in the configuration `Tests on SQL Server`. +It runs `eng\TestSqlServer.ps1` in an image that carries SQL Server, which `eng\src\Docker\SqlServerComponent.cs` +describes. The script also serves a developer machine: it starts the database service of +`docker-compose.yml` when it finds no other server. + ### Running locally ``` @@ -185,7 +212,7 @@ That page exists only in the Development environment. |---|---| | `src\SharpCrafters.Backstage.LicenseServer.Core` | The licensing rules, the database model, and the services they depend on. | | `src\SharpCrafters.Backstage.LicenseServer.Web` | The web application: the pages, the endpoints and the composition root. | -| `tests\SharpCrafters.Backstage.LicenseServer.Tests` | The test suite. It runs on an in-memory database and requires no SQL Server. | +| `tests\SharpCrafters.Backstage.LicenseServer.Tests` | The test suite. It runs on SQLite by default, and on SQL Server when the run is given one. | | `eng` | The product definition and the version files that PostSharp.Engineering builds from. | To put a server under load, use `LicenseServerLoadSimulator`, in the SharpCrafters.Backstage diff --git a/docker-compose.yml b/docker-compose.yml index 8cb469c..e6ce4d5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -83,9 +83,9 @@ services: Authentication__Scheme: None Smtp__Enabled: "false" volumes: - # The audit signing key lives here. It has to outlive the container: losing it does not - # invalidate the rows already written, but it does start a new signature chain. Back this - # volume up with the database. + # The files the server generates live here. With the test override, that is the key pair of + # the test licensing authority, which has to outlive the container: the license keys it signed + # are in the database and stop verifying when the pair changes. - licenseserver-data:/app/App_Data volumes: diff --git a/docs/configuration.md b/docs/configuration.md index 19e9c65..3c68ca3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -80,8 +80,8 @@ SQL Server is the engine supported in production. Create the schema by running `Database\CreateTables.sql`. The server never creates the schema and never modifies it, so an upgrade of the server makes no change to the database. -SQLite is supported for tests and for evaluation. The test suite of this repository runs on SQLite, -and so does the development configuration of the web project. The database file is created at the +SQLite is supported for tests and for evaluation. The test suite of this repository runs on SQLite by +default, and so does the development configuration of the web project. The database file is created at the first start. A relative path is resolved against the application directory, not against the working directory of the process. Do not use SQLite for a server that serves a team: SQLite accepts one writer at a time, and every lease request writes. @@ -211,20 +211,24 @@ requests. | Setting | Default | Meaning | |---|---|---| -| `LicenseServer:LeaseLockMode` | `InProcess` | How the server serializes concurrent lease requests. | | `LicenseServer:MutexTimeout` | 30 | How many seconds a request waits for its turn before the server answers 503. | -The server serializes lease requests, so that two concurrent requests cannot both take the last free -seat. `InProcess` serializes the requests of one worker process. This is correct for the supported -deployment, which is one worker process per database. +The server serializes the lease requests, so that two concurrent requests cannot both take the last +free seat. A request that waits longer than `MutexTimeout` receives the status 503 with the body +`Service overloaded.`. -Run one worker process. One process is enough: a developer sends about one request per day, and the -requests are short. +The lock is held by the database and not by the process. Several worker processes on one database are +therefore serialized against each other, which covers a web garden of Internet Information Services, +several containers, and two servers that share one database. -Two deployments run several worker processes against one database: an IIS web garden, and several -instances of the server. The serialization then covers each process separately, and the server can -grant more leases than the capacity of the license. If you need such a deployment, open an issue that -asks for `SqlApplicationLock`, which serializes through the database. +The mechanism depends on the database engine, and there is no setting to choose it. On SQL Server, +the server calls `sp_getapplock` at the beginning of the request and `sp_releaseapplock` at the end +of it. The lock belongs to the session, which is the connection of the request. It requires no +permission beyond the ones the server already needs on its database. + +On SQLite, the server opens the transaction of the request with `BEGIN IMMEDIATE`, which takes the +write lock of the database file at once. SQLite accepts one writer at a time, so the next request +waits for the transaction to be committed. ## Auditing diff --git a/eng/RunClaude.ps1 b/eng/RunClaude.ps1 new file mode 100644 index 0000000..2e72675 --- /dev/null +++ b/eng/RunClaude.ps1 @@ -0,0 +1,608 @@ +# The original of this file is in the PostSharp.Engineering repo. +# You can generate this file using `./Build.ps1 generate-scripts`. + +param( + [string]$Prompt, + [int]$McpPort, + + # Where this build's work lands, when it does not land in git. Repeatable, and also settable as + # CLAUDE_PROGRESS_PATHS (comma or semicolon separated) so DockerBuild can forward it. Relative paths are + # resolved against the repository root. + [string[]]$ProgressPath +) + +$ErrorActionPreference = "Stop" + +$Model = "opus" + +if ($env:RUNNING_IN_DOCKER -ne "true") +{ + Write-Error "This script must be run inside a Docker container. Set RUNNING_IN_DOCKER=true to override." + exit 1 +} + +# --- Output sanitization (matches ClaudeCodeHelper.SanitizeOutput) --- +function Sanitize-ClaudeOutput { + param([string]$Text) + + if ([string]::IsNullOrEmpty($Text)) { return "" } + + # Strip ANSI escape sequences + $stripped = $Text -replace '\x1b\[[0-9;]*m','' -replace '\[\d+(?:;\d+)*m','' + + $sb = [System.Text.StringBuilder]::new($stripped.Length) + foreach ($c in $stripped.ToCharArray()) { + $code = [int]$c + if (($code -ge 32 -and $code -le 126) -or $c -eq "`n" -or $c -eq "`r" -or $c -eq "`t") { + [void]$sb.Append($c) + } + elseif ([char]::IsWhiteSpace($c)) { + [void]$sb.Append(' ') + } + # Skip all other characters (including extended Unicode) + } + return $sb.ToString() +} + +# Tools whose output is silenced in the monitoring display +$script:SilentTools = @('Read', 'Glob', 'Grep', 'Edit') +$script:SilentToolIds = @{} + +# --- JSON stream line parser (matches ClaudeCodeHelper.TranslateJsonToHumanReadable) --- +function ConvertFrom-ClaudeJsonLine { + param([string]$Line) + + if ([string]::IsNullOrWhiteSpace($Line)) { return } + + try { + $json = $Line | ConvertFrom-Json + } catch { + Write-Host (Sanitize-ClaudeOutput $Line) + return + } + + switch ($json.type) { + 'system' { + if ($json.subtype -eq 'init') { + $model = if ($json.model) { $json.model } else { "unknown" } + Write-Host (Sanitize-ClaudeOutput "[Claude Code initialized - model: $model]") -ForegroundColor Green + } + } + 'assistant' { + if ($json.message -and $json.message.content) { + foreach ($block in $json.message.content) { + if ($block.type -eq 'text') { + Write-Host "" + Write-Host (Sanitize-ClaudeOutput $block.text) -ForegroundColor Cyan + } + elseif ($block.type -eq 'tool_use') { + $toolName = if ($block.name) { $block.name } else { "unknown" } + if ($script:SilentTools -contains $toolName) { + if ($block.id) { $script:SilentToolIds[$block.id] = $true } + continue + } + # Silence Bash calls for read-only commands (ls, grep) + if ($toolName -eq 'Bash' -and $block.input.command -match '^\s*(ls|grep|find)\b') { + if ($block.id) { $script:SilentToolIds[$block.id] = $true } + continue + } + Write-Host "" + Write-Host (Sanitize-ClaudeOutput "[Tool: $toolName]") -ForegroundColor Yellow + if ($block.input) { + # Display the most identifying property from the input + $displayProps = @( + @{ Key = 'file_path'; Label = 'File' } + @{ Key = 'command'; Label = '$' } + @{ Key = 'pattern'; Label = 'Pattern' } + @{ Key = 'query'; Label = 'Query' } + @{ Key = 'url'; Label = 'URL' } + @{ Key = 'skill'; Label = 'Skill' } + @{ Key = 'prompt'; Label = 'Prompt' } + @{ Key = 'description'; Label = 'Task' } + ) + $shown = $false + foreach ($dp in $displayProps) { + $val = $block.input.($dp.Key) + if ($val) { + $truncated = if ($val.Length -gt 1024) { $val.Substring(0, 1024) + "..." } else { $val } + Write-Host (Sanitize-ClaudeOutput " $($dp.Label): $truncated") -ForegroundColor Gray + $shown = $true + break + } + } + if (-not $shown) { + # Fallback: show the property names so the user at least sees what was passed + $keys = ($block.input.PSObject.Properties | Select-Object -ExpandProperty Name) -join ', ' + if ($keys) { + Write-Host (Sanitize-ClaudeOutput " [$keys]") -ForegroundColor Gray + } + } + } + } + } + } + } + 'user' { + if ($json.message -and $json.message.content) { + foreach ($block in $json.message.content) { + if ($block.type -eq 'tool_result') { + if ($block.tool_use_id -and $script:SilentToolIds.ContainsKey($block.tool_use_id)) { + $script:SilentToolIds.Remove($block.tool_use_id) + continue + } + $content = if ($block.content) { $block.content } else { "" } + # Strip system reminders and tool use markup + $content = $content -replace '(?s).*?', '' + $closingTag = '' + $content = $content -replace "(?s).*?$closingTag", '' + $sanitized = Sanitize-ClaudeOutput $content + $lines = $sanitized -split "`n" + $maxLines = 5 + $color = if ($block.is_error) { "Red" } else { "DarkGray" } + $prefix = if ($block.is_error) { " [ERROR] " } else { " ->" } + for ($i = 0; $i -lt [Math]::Min($lines.Count, $maxLines); $i++) { + Write-Host "$prefix$($lines[$i])" -ForegroundColor $color + } + if ($lines.Count -gt $maxLines) { + Write-Host " ... ($($lines.Count - $maxLines) more lines)" -ForegroundColor $color + } + } + } + } + } + 'result' { + Write-Host (Sanitize-ClaudeOutput "[Session completed]") -ForegroundColor Green + } + 'error' { + $msg = if ($json.error.message) { $json.error.message } elseif ($json.error) { $json.error } else { "Unknown error" } + Write-Host (Sanitize-ClaudeOutput "[ERROR] $msg") -ForegroundColor Red + } + } +} + +# --- Resume-loop helpers ------------------------------------------------------------------ +# In headless `-p` mode the process exits the instant the turn ends, with no way to wake it. +# The model occasionally ends its turn mid-build (e.g. "I'll wait for the background +# notification rather than poll"), which abandons all work. We cannot distinguish that from a +# genuine finish via the exit code (both are exit 0 / result.subtype == "success"), so we invert +# the logic: the run is considered DONE only when the model emits an explicit sentinel; any other +# ending is auto-resumed via `claude --resume `, bounded by the guards below. Each +# resume waits a short delay first (the exit may signal a transient condition), and the "give up" +# guards (no-progress / max-iterations) are held off until a minimum runtime floor so a burst of +# fast exits cannot abandon the run within the first few minutes. + +# Spawn one `claude` process, stream/log its stdout, and return what we need to decide whether +# to resume: the exit code, the (stable) session id, and any completion sentinel in the final text. +function Invoke-ClaudeOnce { + param( + [string]$Arguments, + [string]$StdinContent, + [string]$LogFile + ) + + $psi = [System.Diagnostics.ProcessStartInfo]::new() + $psi.FileName = $script:ClaudeExe + $psi.Arguments = $Arguments + $psi.RedirectStandardInput = $true + $psi.RedirectStandardOutput = $true + $psi.RedirectStandardError = $true + $psi.UseShellExecute = $false + $psi.CreateNoWindow = $true + + $process = [System.Diagnostics.Process]::Start($psi) + + # Send the prompt (initial issue prompt, or the resume nudge) via stdin. + if ($null -ne $StdinContent) { $process.StandardInput.Write($StdinContent) } + $process.StandardInput.Close() + + $logWriter = [System.IO.StreamWriter]::new($LogFile, $false, [System.Text.Encoding]::UTF8) + $logWriter.WriteLine("[") + $isFirstJsonLine = $true + + $sessionId = $null + $resultText = $null + $resultSubtype = $null + $resultIsError = $false + $lastAssistantText = $null + + # Read and parse stdout line by line (real-time streaming). + while ($null -ne ($line = $process.StandardOutput.ReadLine())) { + if (-not [string]::IsNullOrWhiteSpace($line)) { + try { + $obj = $line | ConvertFrom-Json + $indented = $obj | ConvertTo-Json -Depth 100 + if (-not $isFirstJsonLine) { $logWriter.WriteLine(",") } + $logWriter.Write($indented) + $isFirstJsonLine = $false + + # session_id rides on most events and is stable across resumes; capture the latest. + if ($obj.session_id) { $sessionId = $obj.session_id } + + # Final result event carries the model's final text + status. + if ($obj.type -eq 'result') { + if ($null -ne $obj.result) { $resultText = [string]$obj.result } + if ($obj.subtype) { $resultSubtype = [string]$obj.subtype } + if ($null -ne $obj.is_error) { $resultIsError = [bool]$obj.is_error } + } + + # Fallback for stream-json schema uncertainty: remember the last assistant text block. + if ($obj.type -eq 'assistant' -and $obj.message -and $obj.message.content) { + foreach ($block in $obj.message.content) { + if ($block.type -eq 'text' -and $block.text) { $lastAssistantText = [string]$block.text } + } + } + } catch { + # Non-JSON line - write as raw string + if (-not $isFirstJsonLine) { $logWriter.WriteLine(",") } + $logWriter.Write("`"$($line -replace '\\','\\\\' -replace '"','\"')`"") + $isFirstJsonLine = $false + } + $logWriter.Flush() + } + ConvertFrom-ClaudeJsonLine -Line $line + } + + $stderr = $process.StandardError.ReadToEnd() + if ($stderr) { Write-Host (Sanitize-ClaudeOutput $stderr) -ForegroundColor Red } + + $process.WaitForExit() + $exitCode = $process.ExitCode + + $logWriter.WriteLine() + $logWriter.WriteLine("]") + $logWriter.Close() + + # Detect the completion sentinel in the model's final message (result text, else last assistant text). + $scanText = if ($resultText) { $resultText } else { $lastAssistantText } + $sentinel = $null + if ($scanText) { + if ($scanText -match '') { $sentinel = 'done' } + elseif ($scanText -match '') { $sentinel = 'blocked' } + } + + return @{ + ExitCode = $exitCode + SessionId = $sessionId + Sentinel = $sentinel + ResultIsError = $resultIsError + ResultSubtype = $resultSubtype + + # Returned so the resume loop can tell "the model ran and did nothing" from "the model could not run + # at all". Those look identical from outside the process and must not be treated the same way. + ResultText = $scanText + } +} + +# Whether an iteration ended because the API could not serve it, rather than because the model did nothing +# useful. A 429, a 5xx or an explicit overload is a condition to wait out, not evidence of a stuck loop: the +# turn produced nothing because it never got to run. +function Test-TransientApiFailure { + param([string]$Text) + + if ([string]::IsNullOrEmpty($Text)) { return $false } + + return ($Text -match 'API Error:\s*(429|5\d\d)') -or + ($Text -match 'overloaded_error') -or + ($Text -match 'rate_limit_error') -or + ($Text -match 'Service Unavailable') +} + +# What counts as progress, as one comparable string. +# +# GIT ALONE IS NOT ENOUGH, and assuming it was cost a scheduled run two nights. A commit is the right signal +# for an agent whose job is to change code and the wrong one for an agent whose job is anything else: the CEIP +# triage build writes rows to a database and files under `artifacts`, never a commit, so every iteration of a +# healthy run reported "no progress" and the run was stopped after two of them. A build whose work lands +# somewhere else says where, with -ProgressPath. +function Get-ProgressFingerprint { + param([string[]]$Repos, [string[]]$Paths, [string]$ExcludePath) + + $parts = @() + + $heads = Get-RepoHeads -Repos $Repos + + foreach ($r in ($heads.Keys | Sort-Object)) { + $parts += "$r=$($heads[$r])" + } + + foreach ($p in $Paths) { + try { + if (-not (Test-Path $p)) { $parts += "$p=absent"; continue } + + # Count, newest write and total size. Between them they catch a file added, a file rewritten in + # place and a file truncated, which is every way a turn leaves a mark on a directory. + $files = @(Get-ChildItem -Path $p -Recurse -File -Force -ErrorAction SilentlyContinue) + + # Never count our own output. This loop writes a fresh transcript under artifacts\logs on every + # iteration, so a build watching `artifacts` would see a change every time and the guard would + # silently never fire again, which is worse than the false negative it is here to fix. + if ($ExcludePath) { + $files = @($files | Where-Object { -not $_.FullName.StartsWith($ExcludePath, [StringComparison]::OrdinalIgnoreCase) }) + } + $newest = ($files | Measure-Object -Property LastWriteTimeUtc -Maximum).Maximum + $bytes = ($files | Measure-Object -Property Length -Sum).Sum + $parts += "$p=$($files.Count):$($newest.Ticks):$bytes" + } + catch { + $parts += "$p=error" + } + } + + return ($parts -join '|') +} + +# Discover git repos to watch for progress (handles both the source-dependencies and sibling layouts). +function Get-GitRepos { + param([string]$RepoRoot) + + $candidates = New-Object System.Collections.Generic.List[string] + $candidates.Add($RepoRoot) + + $srcDeps = Join-Path $RepoRoot "source-dependencies" + if (Test-Path $srcDeps) { + Get-ChildItem -Path $srcDeps -Directory -ErrorAction SilentlyContinue | ForEach-Object { $candidates.Add($_.FullName) } + } + + $parent = Split-Path $RepoRoot -Parent + if ($parent -and (Test-Path $parent)) { + Get-ChildItem -Path $parent -Directory -ErrorAction SilentlyContinue | ForEach-Object { $candidates.Add($_.FullName) } + } + + $repos = @{} + foreach ($c in $candidates) { + if (Test-Path (Join-Path $c ".git")) { $repos[$c] = $true } + } + return $repos.Keys +} + +# Snapshot HEAD of each watched repo. A HEAD that advances == the model committed == progress. +function Get-RepoHeads { + param([string[]]$Repos) + + $heads = @{} + foreach ($r in $Repos) { + try { + $sha = (& git -C $r rev-parse HEAD 2>$null) + if ($LASTEXITCODE -eq 0 -and $sha) { $heads[$r] = $sha.Trim() } + } catch { } + } + return $heads +} + +# Configure MCP approval server if port is specified +$mcpConfigArg = "" +if ($McpPort -gt 0) +{ + # On Windows containers, host.docker.internal doesn't resolve. + # Use the default gateway IP which points to the host. + $hostIp = (Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Select-Object -First 1).NextHop + if ([string]::IsNullOrEmpty($hostIp)) + { + Write-Error "Could not determine host IP from default gateway." + exit 1 + } + Write-Host "Host IP (gateway): $hostIp" -ForegroundColor Cyan + + # Use HTTP Streamable transport - no authentication needed (server binds to localhost) + $mcpUrl = "http://${hostIp}:$McpPort" + Write-Host "Configuring MCP approval server at $mcpUrl" -ForegroundColor Cyan + + # Create temporary MCP config file (no authentication header - server binds to localhost only) + $mcpConfigPath = "$env:TEMP\mcp-config.json" + $mcpConfig = @{ + 'mcpServers' = @{ + 'host-approval' = @{ + 'type' = 'http' + 'url' = $mcpUrl + } + } + } + $mcpConfig | ConvertTo-Json -Depth 10 | Set-Content $mcpConfigPath -Encoding UTF8 + $mcpConfigArg = "--mcp-config `"$mcpConfigPath`"" + Write-Host "MCP config file created: $mcpConfigPath" -ForegroundColor Green +} + +Write-Host "Starting Claude CLI..." -ForegroundColor Green + +# Run Claude +if ($Prompt) +{ + # Write prompt to a temporary file to avoid command line length limits + $promptFile = "$env:TEMP\claude-prompt-$([System.Guid]::NewGuid().ToString('N').Substring(0, 8)).txt" + $Prompt | Set-Content -Path $promptFile -Encoding UTF8 -NoNewline + Write-Host "Running Claude with prompt from file: $promptFile" -ForegroundColor Cyan + + # Tag TeamCity build with the prompt + if ($env:IS_TEAMCITY_AGENT -eq "true" -or $env:IS_TEAMCITY_AGENT -eq "1") { + # Escape special characters for TeamCity service message format + $tagValue = $Prompt -replace '\|','||' -replace "'","|'" -replace '\[','|[' -replace '\]','|]' -replace "`n",'|n' -replace "`r",'|r' + # Truncate to avoid excessively long tags + if ($tagValue.Length -gt 200) { $tagValue = $tagValue.Substring(0, 200) + "..." } + Write-Host "##teamcity[addBuildTag '$tagValue']" + } + + # Stream JSON output for human-readable real-time monitoring. + # In headless `-p` mode the process exits at the `result` event, so a scheduled wakeup / + # cron / remote trigger can never fire and any work deferred to it is abandoned. We disallow + # those, AND -- because the model can still simply END its turn mid-build -- the resume loop + # below re-invokes `claude --resume` whenever a turn ends without a completion sentinel. + # Do NOT disallow Monitor or run_in_background -- those are the in-turn wait mechanisms long + # builds depend on. + $disallowedTools = "ScheduleWakeup CronCreate CronDelete CronList RemoteTrigger" + $commonArgs = "--output-format stream-json --verbose --model $Model --dangerously-skip-permissions --disallowedTools `"$disallowedTools`" $mcpConfigArg" + + # Resolve the Claude CLI launcher: npm ships claude.cmd on Windows, the native installer ships + # claude.exe, and on Linux/macOS the binary is plain "claude". Get-Command honors PATHEXT so + # asking for "claude" without an extension finds whichever variant is on PATH. + $claudeCommand = Get-Command claude.cmd -ErrorAction SilentlyContinue + if (-not $claudeCommand) { $claudeCommand = Get-Command claude.exe -ErrorAction SilentlyContinue } + if (-not $claudeCommand) { $claudeCommand = Get-Command claude -ErrorAction SilentlyContinue } + if (-not $claudeCommand) { + Write-Error "Claude CLI not found on PATH. Install it via 'npm i -g @anthropic-ai/claude-code' or the native installer." + exit 1 + } + $script:ClaudeExe = $claudeCommand.Source + Write-Host "Using Claude executable: $script:ClaudeExe" -ForegroundColor Cyan + + $promptContent = Get-Content -Path $promptFile -Raw + + $repoRoot = (Resolve-Path "$PSScriptRoot\..").Path + $logDir = Join-Path $repoRoot "artifacts\logs" + New-Item -ItemType Directory -Path $logDir -Force | Out-Null + + # Resume-loop guards (env-overridable) so a stuck model cannot loop forever. + $maxIterations = if ($env:CLAUDE_MAX_ITERATIONS) { [int]$env:CLAUDE_MAX_ITERATIONS } else { 8 } + $maxMinutes = if ($env:CLAUDE_MAX_MINUTES) { [int]$env:CLAUDE_MAX_MINUTES } else { 120 } + $maxNoProgress = 2 + # Floor before any "give up" guard (no-progress / max-iterations) may fire: a fast-exiting or + # crashing claude must be retried for at least this long before we conclude it is truly stuck. + $minMinutes = if ($env:CLAUDE_MIN_MINUTES) { [int]$env:CLAUDE_MIN_MINUTES } else { 10 } + # Pause before each resume -- claude exited for a reason (rate limit, transient error, etc.), + # so give that condition a moment to clear instead of hammering an immediate retry. + $retryDelaySeconds = if ($env:CLAUDE_RETRY_DELAY_SECONDS) { [int]$env:CLAUDE_RETRY_DELAY_SECONDS } else { 30 } + + # How many consecutive iterations may die on a transient API failure before the run gives up. Higher than + # the no-progress cap on purpose: an outage is not the model's fault and is usually over in minutes, and + # the wall-clock budget bounds the whole thing anyway. Two 529s in a row ended two real runs. + $maxTransient = if ($env:CLAUDE_MAX_TRANSIENT_FAILURES) { [int]$env:CLAUDE_MAX_TRANSIENT_FAILURES } else { 5 } + + $gitRepos = @(Get-GitRepos -RepoRoot $repoRoot) + + $watchedPaths = @() + + if ($ProgressPath) { $watchedPaths += $ProgressPath } + + if ($env:CLAUDE_PROGRESS_PATHS) { $watchedPaths += ($env:CLAUDE_PROGRESS_PATHS -split '[;,]') } + + $watchedPaths = @( + $watchedPaths | + ForEach-Object { $_.Trim() } | + Where-Object { $_ } | + ForEach-Object { if ([System.IO.Path]::IsPathRooted($_)) { $_ } else { Join-Path $repoRoot $_ } } | + Select-Object -Unique ) + + if ($watchedPaths.Count -gt 0) { + Write-Host "Monitoring $($gitRepos.Count) git repo(s) and $($watchedPaths.Count) path(s) for progress between iterations." -ForegroundColor Cyan + $watchedPaths | ForEach-Object { Write-Host " $_" -ForegroundColor Gray } + } + else { + Write-Host "Monitoring $($gitRepos.Count) git repo(s) for progress between iterations." -ForegroundColor Cyan + } + + $startTime = Get-Date + $sessionId = $null + $iteration = 0 + $noProgressStreak = 0 + $transientStreak = 0 + $finalExitCode = 1 + $stopReason = "unknown" + + while ($true) { + $iteration++ + $elapsedMin = [int]((Get-Date) - $startTime).TotalMinutes + $remainingMin = [Math]::Max(0, $maxMinutes - $elapsedMin) + + Write-Host "" + Write-Host "=== Claude iteration $iteration (elapsed ${elapsedMin}m, ~${remainingMin}m budget left) ===" -ForegroundColor Magenta + + # Snapshot before the turn so we can detect whether it made any progress. + $progressBefore = Get-ProgressFingerprint -Repos $gitRepos -Paths $watchedPaths -ExcludePath $logDir + + $timestamp = (Get-Date).ToString("yyyy-MM-dd-HHmmss") + $logFile = Join-Path $logDir "claude-$timestamp.log.json" + + if ($iteration -eq 1) { + # First turn: the original issue prompt, fresh session. + $claudeArgs = "-p $commonArgs" + $stdinContent = $promptContent + } + else { + # Resume turn: re-enter the SAME session and nudge the model to continue to completion. + $stdinContent = @" +Your previous turn ended without a completion sentinel, so your work is presumed incomplete. Resume exactly where you left off, following CLAUDE.md instructions and phases strictly. You are running headless: never wait for a notification, schedule a wakeup, or defer work across turns -- keep working WITHIN this turn and poll background builds until they finish. About $remainingMin minutes of budget remain. End your run ONLY by emitting the literal token (all CLAUDE.md phases complete and PRs ready) or (genuinely blocked after at least 5 distinct attempts and a blocker comment posted to the issue). +"@ + $claudeArgs = "--resume $sessionId -p $commonArgs" + } + + $result = Invoke-ClaudeOnce -Arguments $claudeArgs -StdinContent $stdinContent -LogFile $logFile + Write-Host "Claude output log: $logFile" -ForegroundColor Green + Write-Host "Iteration $iteration exited with code $($result.ExitCode); sentinel='$($result.Sentinel)'; session=$($result.SessionId)" -ForegroundColor Cyan + + if ($result.SessionId) { $sessionId = $result.SessionId } + + # Terminal: model emitted an explicit completion sentinel. + if ($result.Sentinel -eq 'done') { $finalExitCode = 0; $stopReason = "done"; break } + if ($result.Sentinel -eq 'blocked') { $finalExitCode = 0; $stopReason = "blocked"; break } + + # Cannot resume without a session id (e.g. claude crashed before emitting one). + if (-not $sessionId) { + Write-Host "No session id captured; cannot resume." -ForegroundColor Red + $finalExitCode = $result.ExitCode; $stopReason = "no-session-id"; break + } + + # Guard: wall-clock budget. + $elapsedMin = [int]((Get-Date) - $startTime).TotalMinutes + if ($elapsedMin -ge $maxMinutes) { + Write-Host "Wall-clock budget of ${maxMinutes}m exhausted." -ForegroundColor Red + $finalExitCode = 1; $stopReason = "budget-exhausted"; break + } + + # Guard: max iterations. Held off until the minimum runtime floor so a burst of fast + # exits cannot exhaust the iteration budget within the first few minutes. + if ($iteration -ge $maxIterations -and $elapsedMin -ge $minMinutes) { + Write-Host "Reached max iterations ($maxIterations) after ${elapsedMin}m." -ForegroundColor Red + $finalExitCode = 1; $stopReason = "max-iterations"; break + } + + # Guard: transient API failure. An iteration the API refused to serve says nothing about whether the + # model is stuck, so it is a retry rather than a strike, and it has its own budget. Checked BEFORE the + # no-progress guard, since such a turn is guaranteed to have produced no work. + if (Test-TransientApiFailure -Text $result.ResultText) { + $transientStreak++ + Write-Host "Iteration $iteration was refused by the API (transient failure $transientStreak/$maxTransient); it does not count as a lack of progress." -ForegroundColor Yellow + + if ($transientStreak -ge $maxTransient) { + Write-Host "The API refused $maxTransient consecutive iterations after ${elapsedMin}m; giving up on this run." -ForegroundColor Red + $finalExitCode = 1; $stopReason = "api-unavailable"; break + } + } + else { + $transientStreak = 0 + + # Guard: no-progress. Nothing the build declared as a place where work lands has changed. + $progressAfter = Get-ProgressFingerprint -Repos $gitRepos -Paths $watchedPaths -ExcludePath $logDir + $madeProgress = $progressBefore -ne $progressAfter + + if ($madeProgress) { $noProgressStreak = 0 } else { $noProgressStreak++ } + Write-Host "Progress this iteration: $madeProgress (no-progress streak: $noProgressStreak/$maxNoProgress)" -ForegroundColor Cyan + + # Held off until the minimum runtime floor: a model that exits fast without committing + # (e.g. repeated crashes) still gets at least $minMinutes of retries before we give up. + if ($noProgressStreak -ge $maxNoProgress -and $elapsedMin -ge $minMinutes) { + Write-Host "No progress for $maxNoProgress consecutive iterations after ${elapsedMin}m; stopping to avoid a stuck loop." -ForegroundColor Red + $finalExitCode = 1; $stopReason = "stuck-no-progress"; break + } + } + + # Pause before resuming -- claude exited for a reason, so let any transient condition clear. + if ($retryDelaySeconds -gt 0) { + Write-Host "Waiting ${retryDelaySeconds}s before resuming..." -ForegroundColor DarkGray + Start-Sleep -Seconds $retryDelaySeconds + } + Write-Host "No completion sentinel -- resuming Claude session $sessionId." -ForegroundColor Yellow + } + + # Clean up prompt file + Remove-Item $promptFile -ErrorAction SilentlyContinue + + $color = if ($finalExitCode -eq 0) { "Green" } else { "Red" } + Write-Host "Claude run finished: reason=$stopReason, iterations=$iteration, exitCode=$finalExitCode" -ForegroundColor $color + exit $finalExitCode +} +else +{ + Write-Host "Running Claude in interactive mode" -ForegroundColor Cyan + $cmd = "claude --model $Model --dangerously-skip-permissions $mcpConfigArg" + Invoke-Expression $cmd + exit $LASTEXITCODE +} diff --git a/eng/TestSqlServer.ps1 b/eng/TestSqlServer.ps1 new file mode 100644 index 0000000..2088b66 --- /dev/null +++ b/eng/TestSqlServer.ps1 @@ -0,0 +1,130 @@ +#Requires -Version 7 + +<# +.SYNOPSIS + Runs the test suite against SQL Server. + +.DESCRIPTION + The suite runs on SQLite by default, which needs no server. This script runs the same tests a + second time against SQL Server, the engine that customers use. The two engines differ in the + collation, in the column types and in the lock that serializes the lease requests. + + The script takes the first SQL Server it finds: + + 1. The connection string in LICENSESERVER_TEST_SQLSERVER, when it is already set. + 2. A SQL Server installed in the image the script runs in, which the continuous integration + build uses. The script starts it and stops it. + 3. The database service of docker-compose.yml, which is the shortest path on a developer + machine. The script starts it and leaves it running. + + The connection string names no database. Each test receives a database of its own, created from + Database\CreateTables.sql. + +.PARAMETER Password + The password of the sa login. It is read from MSSQL_SA_PASSWORD when the parameter is omitted, + and a password is generated when neither is given. + +.PARAMETER TimeoutInSeconds + How long to wait for the server to accept a query. + +.PARAMETER BuildArguments + The arguments that are passed on to Build.ps1 test. +#> + +[CmdletBinding()] +param( + [string] $Password = $env:MSSQL_SA_PASSWORD, + [int] $TimeoutInSeconds = 180, + + # Passed on to Build.ps1 test, for instance --configuration Public. + [Parameter( ValueFromRemainingArguments = $true )] + [string[]] $BuildArguments = @() +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repositoryDirectory = Split-Path -Parent $PSScriptRoot +$sqlServerExecutable = '/opt/mssql/bin/sqlservr' +$sqlCmd = '/opt/mssql-tools18/bin/sqlcmd' + +function Wait-ForServer( [scriptblock] $Query, [int] $Timeout ) { + $deadline = (Get-Date).AddSeconds( $Timeout ) + + while ( (Get-Date) -lt $deadline ) { + & $Query 2>&1 | Out-Null + + if ( $LASTEXITCODE -eq 0 ) { + return + } + + Start-Sleep -Seconds 2 + } + + throw "SQL Server did not accept a query within $Timeout seconds." +} + +$serverProcess = $null + +try { + if ( $env:LICENSESERVER_TEST_SQLSERVER ) { + Write-Host 'Using the SQL Server named by LICENSESERVER_TEST_SQLSERVER.' + } + else { + if ( -not $Password ) { + # The server is reachable from this machine alone, and it is deleted with the container, + # but it still refuses a password that does not meet its complexity rules. + $Password = 'Lease-' + [System.Guid]::NewGuid().ToString( 'N' ).Substring( 0, 12 ) + '-1' + } + + if ( Test-Path $sqlServerExecutable ) { + Write-Host 'Starting the SQL Server of this image.' + + $env:ACCEPT_EULA = 'Y' + $env:MSSQL_SA_PASSWORD = $Password + $env:MSSQL_PID = 'Developer' + + # The server refuses to run as root, and the image runs the build as root, so the server + # runs under the account its own package creates. + $serverProcess = Start-Process -FilePath 'runuser' ` + -ArgumentList '-u', 'mssql', '--', $sqlServerExecutable ` + -PassThru -NoNewWindow + + Wait-ForServer { & $sqlCmd -S 127.0.0.1 -U sa -P $Password -C -b -Q 'SELECT 1' } $TimeoutInSeconds + } + else { + Write-Host 'Starting the database service of docker-compose.yml.' + + $env:MSSQL_SA_PASSWORD = $Password + + & docker compose --file "$repositoryDirectory/docker-compose.yml" up --detach database + + if ( $LASTEXITCODE -ne 0 ) { + throw 'The database service did not start.' + } + + Wait-ForServer { + & docker compose --file "$repositoryDirectory/docker-compose.yml" exec -T database ` + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P $Password -C -b -Q 'SELECT 1' + } $TimeoutInSeconds + } + + # The address is written as 127.0.0.1 and not as localhost. On a host that resolves localhost + # to an address of version 6 first, the client reaches nothing, because the port is published + # on version 4. + $env:LICENSESERVER_TEST_SQLSERVER = + "Server=127.0.0.1,1433;User Id=sa;Password=$Password;TrustServerCertificate=True;Encrypt=False" + } + + & "$repositoryDirectory/Build.ps1" test @BuildArguments + + if ( $LASTEXITCODE -ne 0 ) { + throw "The tests failed with the exit code $LASTEXITCODE." + } +} +finally { + if ( $serverProcess ) { + Write-Host 'Stopping SQL Server.' + Stop-Process -InputObject $serverProcess -ErrorAction SilentlyContinue + } +} diff --git a/eng/docker/build.Dockerfile b/eng/docker/build.Dockerfile new file mode 100644 index 0000000..9cdaff2 --- /dev/null +++ b/eng/docker/build.Dockerfile @@ -0,0 +1,75 @@ +# escape=` + +# This file is auto-generated by PostSharp.Engineering. + +ARG WINDOWS_VERSION=ltsc2025 +ARG OS_IMAGE_REPOSITORY=mcr.microsoft.com/windows/servercore +FROM ${OS_IMAGE_REPOSITORY}:${WINDOWS_VERSION} + +# The initial shell is Windows PowerShell (use full path to avoid HCS issues) +SHELL ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "-Command"] + +# Prepare environment +ENV PSExecutionPolicyPreference=Bypass +ENV POWERSHELL_UPDATECHECK=Off +ENV TEMP=C:\Temp +ENV TMP=C:\Temp +ENV RUNNING_IN_DOCKER=TRUE + +# Set locale for consistent behavior regardless of host locale +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 +ENV DOTNET_CLI_UI_LANGUAGE=en +ENV VSLANG=1033 + +# Set base PATH explicitly to avoid issues with expansion +ENV PATH="C:\Windows\System32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0" + +# Enable long path support +RUN Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1 + + + +# Install Git +RUN Invoke-WebRequest -Uri https://github.com/git-for-windows/git/releases/download/v2.50.0.windows.1/PortableGit-2.50.0-64-bit.7z.exe -OutFile PortableGit.exe; ` + Start-Process -FilePath .\PortableGit.exe -ArgumentList '-o"C:\git"', '-y' -Wait; ` + Remove-Item PortableGit.exe + +# Add git to PATH using ENV directive (persists across shell switches) +ENV PATH="C:\git\cmd;C:\git\bin;C:\git\usr\bin;${PATH}" + +RUN git config --system core.longpaths true; git config --system core.autocrlf false + +# Set CLAUDE_CODE_GIT_BASH_PATH for Claude Code +ENV CLAUDE_CODE_GIT_BASH_PATH=C:\git\bin\bash.exe + + +# Install PowerShell 7 +RUN Invoke-WebRequest -Uri https://github.com/PowerShell/PowerShell/releases/download/v7.5.2/PowerShell-7.5.2-win-x64.msi -OutFile PowerShell.msi; ` + $process = Start-Process msiexec.exe -Wait -PassThru -ArgumentList '/I PowerShell.msi /quiet'; ` + if ($process.ExitCode -ne 0) { exit $process.ExitCode }; ` + Remove-Item PowerShell.msi + +ENV PATH="C:\Program Files\PowerShell\7;${PATH}" + + +# Download .NET Installer +RUN Invoke-WebRequest -Uri https://dot.net/v1/dotnet-install.ps1 -OutFile dotnet-install.ps1 + +# Add .NET to PATH using ENV directive (persists across shell switches) +ENV PATH="C:\Program Files\dotnet;${PATH}" + + +# Install .NET Sdk 10.0.102 +RUN & .\dotnet-install.ps1 -Version 10.0.102 -InstallDir 'C:\Program Files\dotnet' + + +# .NET Dump Tool +RUN dotnet tool install --global dotnet-dump; + +ENV PATH="C:\Users\ContainerAdministrator\.dotnet\tools;${PATH}" + + +# Epilogue +# Configure .NET SDK +ENV DOTNET_NOLOGO=1 diff --git a/eng/docker/claude-pre.Dockerfile b/eng/docker/claude-pre.Dockerfile new file mode 100644 index 0000000..7e5235f --- /dev/null +++ b/eng/docker/claude-pre.Dockerfile @@ -0,0 +1,20 @@ +# escape=` + +# This file is auto-generated by PostSharp.Engineering. + +ARG BASE_IMAGE=build.Dockerfile +FROM ${BASE_IMAGE} + +# The base image's shell is Windows PowerShell (use full path to avoid HCS issues) +SHELL ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "-Command"] + + + +# Install Node.js +RUN Invoke-WebRequest -Uri "https://nodejs.org/dist/v22.0.0/node-v22.0.0-win-x64.zip" -OutFile node.zip; ` + Expand-Archive node.zip -DestinationPath C:\; ` + Rename-Item "C:\node-v22.0.0-win-x64" "C:\nodejs"; ` + Remove-Item node.zip + +ENV NPM_CONFIG_PREFIX=C:\npm +ENV PATH="C:\nodejs;C:\npm;${PATH}" diff --git a/eng/docker/claude.Dockerfile b/eng/docker/claude.Dockerfile new file mode 100644 index 0000000..bccc57b --- /dev/null +++ b/eng/docker/claude.Dockerfile @@ -0,0 +1,39 @@ +# escape=` + +# This file is auto-generated by PostSharp.Engineering. + +ARG BASE_IMAGE=claude-pre.Dockerfile +FROM ${BASE_IMAGE} + +# The base image's shell is Windows PowerShell (use full path to avoid HCS issues) +SHELL ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "-Command"] + + + +# Timestamp +# Cache invalidation layer - changes when -Update is used +COPY .g/update.timestamp C:\docker-context\update.timestamp +RUN Write-Host "PostSharp.Engineering build timestamp: $(Get-Content C:\docker-context\update.timestamp)" + + +# Install Claude CLI +# Set HOME/USERPROFILE so Claude CLI finds credentials during build +ENV HOME=C:\\Users\\ContainerAdministrator +ENV USERPROFILE=C:\\Users\\ContainerAdministrator + +# Install Claude CLI and configure using cmd shell to avoid HCS issues with PowerShell +SHELL ["cmd", "/S", "/C"] +RUN C:\nodejs\npm.cmd install --global @anthropic-ai/claude-code@latest +RUN mkdir C:\Users\ContainerAdministrator\.claude && echo {"hasCompletedOnboarding": true} > C:\Users\ContainerAdministrator\.claude.json && echo {"alwaysThinkingEnabled": true, "spinnerTipsEnabled": false} > C:\Users\ContainerAdministrator\.claude\settings.json + +# Restore PowerShell shell using full path +SHELL ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "-Command"] + + +# Install Claude CLI Add-ins +# Install Claude plugins using cmd shell to avoid HCS issues with PowerShell +SHELL ["cmd", "/S", "/C"] +RUN echo Installing Claude plugins && C:\npm\claude plugin marketplace add https://github.com/metalama/Metalama.AI.Skills && C:\npm\claude plugin marketplace add https://github.com/postsharp-ops/PostSharp.Engineering.AISkills && C:\npm\claude plugin install metalama && C:\npm\claude plugin install metalama-dev && C:\npm\claude plugin install eng + +# Restore PowerShell shell using full path +SHELL ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "-Command"] diff --git a/eng/docker/sqlserver-build.Dockerfile b/eng/docker/sqlserver-build.Dockerfile new file mode 100644 index 0000000..79e838a --- /dev/null +++ b/eng/docker/sqlserver-build.Dockerfile @@ -0,0 +1,73 @@ +# This file is auto-generated by PostSharp.Engineering. + +# See the OS_IMAGE_REPOSITORY comment in the Windows branch: the base image is a build-arg so +# that DockerBuild.ps1 can redirect it to a registry-local mirror without changing this file. +ARG OS_IMAGE=ubuntu:22.04 +FROM ${OS_IMAGE} + +# Prepare environment +ENV DEBIAN_FRONTEND=noninteractive +ENV RUNNING_IN_DOCKER=TRUE + +# Set locale for consistent behavior regardless of host locale +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 +ENV DOTNET_CLI_UI_LANGUAGE=en + +# Install the prerequisites shared by the other components +RUN apt-get update && apt-get install -y \ + curl \ + wget \ + ca-certificates \ + libicu70 \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + + + +# Install Git +RUN apt-get update && apt-get install -y git \ + && rm -rf /var/lib/apt/lists/* + +RUN git config --system core.longpaths true && git config --system core.autocrlf false + + +# Install PowerShell 7 +RUN wget -q https://github.com/PowerShell/PowerShell/releases/download/v7.5.2/powershell_7.5.2-1.deb_amd64.deb \ + && dpkg -i powershell_7.5.2-1.deb_amd64.deb \ + && rm powershell_7.5.2-1.deb_amd64.deb + + +# Download .NET Installer +RUN curl -sSL https://dot.net/v1/dotnet-install.sh -o /usr/local/bin/dotnet-install.sh \ + && chmod +x /usr/local/bin/dotnet-install.sh + +ENV DOTNET_ROOT=/usr/share/dotnet +ENV PATH="${DOTNET_ROOT}:${PATH}" + + +# Install .NET Sdk 10.0.102 +RUN /usr/local/bin/dotnet-install.sh --version 10.0.102 --install-dir $DOTNET_ROOT + + +# .NET Dump Tool +RUN dotnet tool install --global dotnet-dump + +ENV PATH="/root/.dotnet/tools:${PATH}" + + +# Install SQL Server 2022 +RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc -o /etc/apt/trusted.gpg.d/microsoft.asc \ + && curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list -o /etc/apt/sources.list.d/mssql-server-2022.list \ + && curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/prod.list -o /etc/apt/sources.list.d/microsoft-prod.list \ + && apt-get update \ + && apt-get install -y mssql-server \ + && ACCEPT_EULA=Y apt-get install -y mssql-tools18 \ + && rm -rf /var/lib/apt/lists/* + +ENV PATH="/opt/mssql-tools18/bin:${PATH}" + + +# Epilogue +# Configure .NET SDK +ENV DOTNET_NOLOGO=1 diff --git a/eng/src/Docker/SqlServerComponent.cs b/eng/src/Docker/SqlServerComponent.cs new file mode 100644 index 0000000..45b964f --- /dev/null +++ b/eng/src/Docker/SqlServerComponent.cs @@ -0,0 +1,58 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using PostSharp.Engineering.BuildTools.Docker; +using System; +using System.IO; + +namespace BuildBackstageLicenseServer.Docker; + +/// +/// Installs SQL Server 2022 and its command-line tools, so that the image can run the test suite against the +/// engine that customers use. eng/TestSqlServer.ps1 starts the server and stops it. +/// +/// +/// The server is installed in the image rather than started as a second container. A build step runs inside a +/// container already, and reaching a sibling container from there would require the Docker socket of the agent. +/// +internal sealed class SqlServerComponent : ContainerComponent +{ + public override string Name => "Install SQL Server 2022"; + + /// + /// Gets the kind of this component. The enumeration belongs to PostSharp.Engineering and cannot be extended + /// from here, so this component takes the value of the nearest standard component, which is another external + /// tool installed into the image, and places itself with . + /// + public override ContainerComponentKind Kind => ContainerComponentKind.AzureCli; + + /// + /// Gets the position of this component, which is just before the epilogue. The standard components therefore + /// keep their place, and a change to this one invalidates no layer of theirs. + /// + public override int SortOrder => ((int) ContainerComponentKind.Epilogue * 100) - 50; + + public override void WriteDockerfile( TextWriter writer, ContainerOperatingSystem operatingSystem ) + { + if ( operatingSystem != ContainerOperatingSystem.Linux ) + { + throw new InvalidOperationException( + "SQL Server is installed into a Linux image only. Microsoft publishes no SQL Server for a Windows container after the 2019 version." ); + } + + // The package repository of Microsoft for Ubuntu 22.04, which is the base image of a Linux chain. The + // tools accept the license through ACCEPT_EULA, and the server accepts it at the first start, which is + // where TestSqlServer.ps1 passes it. + writer.WriteLine( + """ + RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc -o /etc/apt/trusted.gpg.d/microsoft.asc \ + && curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list -o /etc/apt/sources.list.d/mssql-server-2022.list \ + && curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/prod.list -o /etc/apt/sources.list.d/microsoft-prod.list \ + && apt-get update \ + && apt-get install -y mssql-server \ + && ACCEPT_EULA=Y apt-get install -y mssql-tools18 \ + && rm -rf /var/lib/apt/lists/* + + ENV PATH="/opt/mssql-tools18/bin:${PATH}" + """ ); + } +} diff --git a/eng/src/Program.cs b/eng/src/Program.cs index bf142cb..024528c 100644 --- a/eng/src/Program.cs +++ b/eng/src/Program.cs @@ -1,17 +1,53 @@ // Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. +using BuildBackstageLicenseServer.Docker; using PostSharp.Engineering.BuildTools; +using PostSharp.Engineering.BuildTools.Build; using PostSharp.Engineering.BuildTools.Build.Model; using PostSharp.Engineering.BuildTools.Build.Solutions; +using PostSharp.Engineering.BuildTools.ContinuousIntegration; +using PostSharp.Engineering.BuildTools.ContinuousIntegration.Model; +using PostSharp.Engineering.BuildTools.ContinuousIntegration.TeamCity; +using PostSharp.Engineering.BuildTools.Docker; using BackstageDependencies = PostSharp.Engineering.BuildTools.Dependencies.Definitions.BackstageDependencies.V2027_0; +var dotNetSdkVersion = BackstageDependencies.Family.PreferredVersions.DotNetSdk.V_10_0; + var product = new Product( BackstageDependencies.BackstageLicenseServer ) { // The product consumes SharpCrafters.Backstage, whose packages carry a prefix that does not match the // default source, so the generated nuget.config has to carry the source mapping of the dependency. GenerateNuGetConfig = true, - DotNetSdkVersion = new DotNetSdkVersion( BackstageDependencies.Family.PreferredVersions.DotNetSdk.V_10_0 ), + DotNetSdkVersion = new DotNetSdkVersion( dotNetSdkVersion ), + + // The build runs in a container, as the builds of the other products of the family do. The image carries the + // .NET SDK and nothing else: the product is a set of SDK-style projects, and its dependencies come from + // nuget.org and from the artifacts of the build it depends on. + OverriddenBuildAgentRequirements = new ContainerRequirements( ContainerHostKind.Windows ) + { + Components = [new DotNetComponent( dotNetSdkVersion, DotNetComponentKind.Sdk )] + }, + + // The image of the SQL Server test run. It is a Linux image, because Microsoft publishes no SQL Server for a + // Windows container after the 2019 version, and it carries the server itself rather than starting a second + // container, which a build step running inside a container cannot do without the Docker socket of the agent. + AdditionalDockerfiles = + [ + new AdditionalDockerfile( SqlServerTests.DockerfileName, [] ) + { + Requirements = new ContainerRequirements( ContainerHostKind.Linux ) + { + OperatingSystem = ContainerOperatingSystem.Linux, + Components = [new DotNetComponent( dotNetSdkVersion, DotNetComponentKind.Sdk ), new SqlServerComponent()] + } + } + ], + + // The second test run. The build itself runs the suite on SQLite, which needs no server; this configuration + // runs the same tests against SQL Server, and it is what proves the lock, the collation and the column types + // of the engine that customers use. See eng/TestSqlServer.ps1 and docs in README.md. + AdditionalCiBuildConfigurations = [SqlServerTests.Configuration], // Built rather than packed: the product ships a deployable archive and no NuGet package, so the Pack // target of every project would be a no-op. @@ -40,3 +76,43 @@ }; return new EngineeringApp( product ).Run( args ); + +/// +/// The continuous integration configuration that runs the test suite against SQL Server, in the image that +/// carries the server. +/// +internal static class SqlServerTests +{ + /// + /// The name of the additional image. PostSharp.Engineering writes the Dockerfile of its build layer to + /// eng/docker/{name}-build.Dockerfile. + /// + public const string DockerfileName = "sqlserver"; + + public static PowershellAdditionalCiBuildConfiguration Configuration { get; } = + new( "SqlServerTests", "Tests on SQL Server", "./eng/TestSqlServer.ps1", "" ) + { + BuildAgentRequirements = LinuxContainerHost, + Dockerfile = $"eng/docker/{DockerfileName}-build.Dockerfile", + BuildSnapshotDependency = BuildConfiguration.Debug + }; + + /// + /// Gets the agent this configuration runs on, which is a Linux host of an amd64 container. + /// + /// + /// The requirements that PostSharp.Engineering derives for a Linux container host ask for an + /// env.BuildAgentType that no agent of this farm publishes, so nothing would be compatible and the + /// build would wait instead of failing. The operating system and the architecture are what the agents offer. + /// The architecture is named because SQL Server runs on amd64 only. + /// + private static ContainerHostRequirements LinuxContainerHost + => new ContainerHostRequirements( ContainerHostKind.Linux ) with + { + Items = + [ + new BuildAgentRequirement( "teamcity.agent.jvm.os.name", "Linux" ), + new BuildAgentRequirement( "teamcity.agent.jvm.os.arch", "amd64" ) + ] + }; +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/InProcessLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/InProcessLeaseLock.cs deleted file mode 100644 index 74ad3bc..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/InProcessLeaseLock.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace SharpCrafters.Backstage.LicenseServer.Locking; - -/// -/// Serializes the lease requests of the current process. This is the default implementation, and it -/// is correct for the supported deployment, which is one worker process per database. -/// -/// -/// A request that waits here does not block a thread of the thread pool, and the named Mutex -/// that this class replaces did block one. A queued request therefore costs less, and the timeout -/// elapses less often. -/// -public sealed class InProcessLeaseLock : ILeaseLock, IDisposable -{ - private readonly SemaphoreSlim semaphore = new( 1, 1 ); - - public async ValueTask TryAcquireAsync( - TimeSpan timeout, - CancellationToken cancellationToken = default ) - { - if ( !await this.semaphore.WaitAsync( timeout, cancellationToken ) ) - { - return null; - } - - return new Handle( this.semaphore ); - } - - public void Dispose() => this.semaphore.Dispose(); - - private sealed class Handle( SemaphoreSlim semaphore ) : IAsyncDisposable - { - private int released; - - public ValueTask DisposeAsync() - { - if ( Interlocked.Exchange( ref this.released, 1 ) == 0 ) - { - semaphore.Release(); - } - - return ValueTask.CompletedTask; - } - } -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/NullLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/NullLeaseLock.cs deleted file mode 100644 index f98ecc7..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/NullLeaseLock.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace SharpCrafters.Backstage.LicenseServer.Locking; - -/// -/// Serializes nothing. This implementation exists for the tests that do not exercise concurrency. -/// -public sealed class NullLeaseLock : ILeaseLock -{ - private static readonly IAsyncDisposable handle = new Handle(); - - public ValueTask TryAcquireAsync( - TimeSpan timeout, - CancellationToken cancellationToken = default ) - => ValueTask.FromResult( handle ); - - private sealed class Handle : IAsyncDisposable - { - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs deleted file mode 100644 index ebe0d73..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace SharpCrafters.Backstage.LicenseServer.Options; - -/// -/// Determines how the license server serializes concurrent lease requests. -/// -public enum LeaseLockMode -{ - /// - /// A semaphore shared by every request that the current process serves. This is the default - /// value. It is correct when one process serves the database, which is the supported deployment. - /// - InProcess, - - /// - /// A SQL Server application lock, shared by every process connected to the same database. - /// Required when several worker processes serve one database, which happens in an IIS web garden - /// and with several instances of the server. - /// - SqlApplicationLock, - - /// - /// No lock. This value exists for the tests. - /// - None -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs index c72e337..bd9a9ce 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs @@ -91,11 +91,6 @@ public sealed class LicenseServerOptions /// public bool RequireAuthenticatedLeaseRequests { get; set; } - /// - /// Gets or sets the mechanism that serializes concurrent lease requests. - /// - public LeaseLockMode LeaseLockMode { get; set; } = LeaseLockMode.InProcess; - /// /// Gets or sets the licensing authorities whose license keys this server accepts in addition to /// the production authority. This setting exists so that a load simulation can run against diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs index 0702f98..759649d 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs @@ -1,6 +1,8 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Locking; +using SharpCrafters.Backstage.LicenseServer.Web.Locking; namespace SharpCrafters.Backstage.LicenseServer; @@ -12,12 +14,14 @@ public static class DatabaseRegistration public const string ConnectionStringName = "SharpCrafters_LicenseServerConnectionString"; /// - /// Registers the database context against the engine named by LicenseServer:DatabaseProvider. + /// Registers the database context against the engine named by + /// LicenseServer:DatabaseProvider, together with the lock that serializes the lease + /// requests of that engine. /// /// /// SQL Server is the engine supported in production. SQLite is supported so that the server can - /// be evaluated, and so that the test suite can run the real application against a database held - /// in memory. + /// be evaluated and developed against without a server, and so that the test suite can run + /// against a database held in memory. /// public static IServiceCollection AddLicenseServerDatabase( this IServiceCollection services, @@ -32,17 +36,30 @@ public static IServiceCollection AddLicenseServerDatabase( throw new InvalidOperationException( $"The connection string '{ConnectionStringName}' is not configured." ); } - return provider.ToLowerInvariant() switch + // The lock belongs to the engine and not to the configuration: an administrator cannot select + // it, and there is no implementation that serializes one process only. + switch ( provider.ToLowerInvariant() ) { - "sqlserver" => services.AddDbContext( - options => options.UseSqlServer( connectionString ) ), + case "sqlserver": + services.AddDbContext( options => options.UseSqlServer( connectionString ) ); + services.AddScoped(); - "sqlite" => services.AddDbContext( - options => options.UseSqlite( ResolveSqliteFile( connectionString, environment ) ) ), + break; - _ => throw new InvalidOperationException( - $"Unknown database provider '{provider}'. Use 'SqlServer' or 'Sqlite'." ) - }; + case "sqlite": + services.AddDbContext( + options => options.UseSqlite( ResolveSqliteFile( connectionString, environment ) ) ); + + services.AddScoped(); + + break; + + default: + throw new InvalidOperationException( + $"Unknown database provider '{provider}'. Use 'SqlServer' or 'Sqlite'." ); + } + + return services; } /// diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs index 9687845..8e146a8 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs @@ -8,6 +8,7 @@ using SharpCrafters.Backstage.LicenseServer.Locking; using SharpCrafters.Backstage.LicenseServer.Options; using SharpCrafters.Backstage.LicenseServer.Services; +using SharpCrafters.Common; namespace SharpCrafters.Backstage.LicenseServer.Endpoints; @@ -21,6 +22,11 @@ namespace SharpCrafters.Backstage.LicenseServer.Endpoints; /// public static class LicenseServerEndpoints { + /// + /// The synchronization point that a lease request reaches while it holds the lease lock. + /// + public const string HoldingLeaseLockSyncPoint = "LicenseServerEndpoints.GetLeaseAsync:HoldingLeaseLock"; + public static void MapLicenseServerEndpoints( this WebApplication app ) { app.MapGet( "/Lease.ashx", GetLeaseAsync ).RequireAuthorization( AuthorizationPolicies.LeaseRequest ); @@ -106,6 +112,18 @@ private static async Task GetLeaseAsync( return Error( 503, "Service overloaded." ); } + // A test holds a request here, while it starts a second one, to prove that the second one + // waits for the lock. Two requests that merely run at the same time do not prove it: they + // pass whether the lock works or not. The service is absent in production, and the call then + // costs a null check. See ITestSynchronizationProvider. + ITestSynchronizationProvider? synchronization = + context.RequestServices.GetService(); + + if ( synchronization != null ) + { + await synchronization.SyncPointAsync( HoldingLeaseLockSyncPoint, cancellationToken ); + } + long startTimestamp = timeProvider.GetTimestamp(); Dictionary errors = []; diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs new file mode 100644 index 0000000..51c77cb --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs @@ -0,0 +1,150 @@ +using System.Data; +using System.Data.Common; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Locking; + +namespace SharpCrafters.Backstage.LicenseServer.Web.Locking; + +/// +/// Serializes the lease requests with an application lock of SQL Server, so that the lock covers +/// every process connected to the database. +/// +/// +/// +/// The lock belongs to the session, which means to the connection. The connection is therefore opened +/// here and stays open until the handle is disposed. Without that, Entity Framework would close the +/// connection after the first query and the lock would be released in the middle of the request. The +/// server would keep answering, and it would grant more leases than the capacity of the license. +/// +/// +/// Every process that serves the same database asks for the same resource name, so the lock +/// serializes the requests of a web garden and of several instances as well. +/// +/// +public sealed class SqlServerLeaseLock( LicenseServerDbContext db ) : ILeaseLock +{ + /// + /// The name of the locked resource. SQL Server scopes an application lock to the database, so + /// this name is shared by every process that serves this database, and by nothing else. + /// + public const string ResourceName = "SharpCrafters.Backstage.LicenseServer.Lease"; + + public async ValueTask TryAcquireAsync( + TimeSpan timeout, + CancellationToken cancellationToken = default ) + { + DatabaseFacade database = db.Database; + + await database.OpenConnectionAsync( cancellationToken ); + + try + { + int result = await ExecuteAsync( + database, + "sp_getapplock", + command => + { + AddParameter( command, "@Resource", ResourceName ); + AddParameter( command, "@LockMode", "Exclusive" ); + AddParameter( command, "@LockOwner", "Session" ); + AddParameter( command, "@LockTimeout", (int) timeout.TotalMilliseconds ); + }, + cancellationToken ); + + // 0 and 1 mean granted. -1 means that the timeout elapsed, which the caller answers with + // the status 503. Anything else is a fault of the server or of the arguments. + if ( result == -1 ) + { + await database.CloseConnectionAsync(); + + return null; + } + + if ( result < 0 ) + { + await database.CloseConnectionAsync(); + + throw new InvalidOperationException( + $"sp_getapplock returned {result} for the resource '{ResourceName}'." ); + } + + return new Handle( database ); + } + catch + { + await database.CloseConnectionAsync(); + + throw; + } + } + + private static void AddParameter( DbCommand command, string name, object value ) + { + DbParameter parameter = command.CreateParameter(); + parameter.ParameterName = name; + parameter.Value = value; + command.Parameters.Add( parameter ); + } + + /// + /// Runs one of the two stored procedures and returns its return value, which is how both report + /// their result. + /// + private static async Task ExecuteAsync( + DatabaseFacade database, + string procedure, + Action addParameters, + CancellationToken cancellationToken ) + { + await using DbCommand command = database.GetDbConnection().CreateCommand(); + + command.CommandText = procedure; + command.CommandType = CommandType.StoredProcedure; + + addParameters( command ); + + DbParameter returnValue = command.CreateParameter(); + returnValue.ParameterName = "@Result"; + returnValue.DbType = DbType.Int32; + returnValue.Direction = ParameterDirection.ReturnValue; + command.Parameters.Add( returnValue ); + + await command.ExecuteNonQueryAsync( cancellationToken ); + + return (int) returnValue.Value!; + } + + private sealed class Handle( DatabaseFacade database ) : IAsyncDisposable + { + private int released; + + public async ValueTask DisposeAsync() + { + if ( Interlocked.Exchange( ref this.released, 1 ) != 0 ) + { + return; + } + + try + { + await ExecuteAsync( + database, + "sp_releaseapplock", + command => + { + AddParameter( command, "@Resource", ResourceName ); + AddParameter( command, "@LockOwner", "Session" ); + }, + CancellationToken.None ); + } + finally + { + // Closing the connection releases the lock as well, so the lock is never held by a + // connection that returns to the pool. + await database.CloseConnectionAsync(); + } + } + } +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs new file mode 100644 index 0000000..d4e0ef5 --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs @@ -0,0 +1,116 @@ +using System.Data; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Locking; + +namespace SharpCrafters.Backstage.LicenseServer.Web.Locking; + +/// +/// Serializes the lease requests with a write transaction of SQLite. +/// +/// +/// +/// SQLite has no application lock. A transaction opened as BEGIN IMMEDIATE takes the write +/// lock of the database at once, and SQLite allows one writer at a time, so a second request waits. +/// The transaction covers the whole request, and Entity Framework runs its own statements inside it, +/// so the reads that decide the allocation and the insert that records it are one unit. +/// +/// +/// The wait is bounded by the timeout of the caller. When it elapses, SQLite reports that the +/// database is locked, and the caller answers with the status 503, as it does on SQL Server. +/// +/// +public sealed class SqliteLeaseLock( LicenseServerDbContext db ) : ILeaseLock +{ + public async ValueTask TryAcquireAsync( + TimeSpan timeout, + CancellationToken cancellationToken = default ) + { + DatabaseFacade database = db.Database; + + await database.OpenConnectionAsync( cancellationToken ); + + try + { + var connection = (SqliteConnection) database.GetDbConnection(); + + // Microsoft.Data.Sqlite retries a statement that finds the database locked, and it stops + // after DefaultTimeout, which is thirty seconds. The PRAGMA busy_timeout of SQLite does + // not change that, because the provider passes its own value at every command. The + // previous value is restored with the connection, which the pool hands to another + // request. + int previousTimeout = connection.DefaultTimeout; + connection.DefaultTimeout = Math.Max( 1, (int) timeout.TotalSeconds ); + + SqliteTransaction transaction; + + try + { + // deferred: false is BEGIN IMMEDIATE, which takes the write lock now instead of at the + // first write. Without it, two requests would both read, and one would fail at its + // insert instead of waiting for its turn. + // + // Microsoft.Data.Sqlite offers no asynchronous form of this call, and the wait for the + // write lock therefore blocks this thread. SQLite serves the development loop and an + // evaluation, where one user sends one request at a time. + transaction = connection.BeginTransaction( IsolationLevel.Serializable, deferred: false ); + } + catch ( SqliteException e ) when ( e.SqliteErrorCode is SqliteBusy or SqliteLocked ) + { + connection.DefaultTimeout = previousTimeout; + await database.CloseConnectionAsync(); + + return null; + } + + // The context runs its own statements inside this transaction, so the reads that decide + // the allocation and the insert that records it are one unit. + IDbContextTransaction? contextTransaction = + await database.UseTransactionAsync( transaction, cancellationToken ); + + return new Handle( database, contextTransaction!, connection, previousTimeout ); + } + catch + { + await database.CloseConnectionAsync(); + + throw; + } + } + + private const int SqliteBusy = 5; + private const int SqliteLocked = 6; + + private sealed class Handle( + DatabaseFacade database, + IDbContextTransaction transaction, + SqliteConnection connection, + int previousTimeout ) : IAsyncDisposable + { + private int released; + + public async ValueTask DisposeAsync() + { + if ( Interlocked.Exchange( ref this.released, 1 ) != 0 ) + { + return; + } + + try + { + // The lease was saved inside this transaction, so the commit is what makes it + // visible, and it releases the write lock. + await transaction.CommitAsync(); + } + finally + { + await transaction.DisposeAsync(); + connection.DefaultTimeout = previousTimeout; + await database.CloseConnectionAsync(); + } + } + } +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs index 057e188..535d077 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs @@ -6,7 +6,6 @@ using SharpCrafters.Backstage.LicenseServer.Endpoints; using SharpCrafters.Backstage.LicenseServer.Health; using SharpCrafters.Backstage.LicenseServer.Licensing; -using SharpCrafters.Backstage.LicenseServer.Locking; using SharpCrafters.Backstage.LicenseServer.Options; using SharpCrafters.Backstage.LicenseServer.Services; using SharpCrafters.Backstage.LicenseServer.Time; @@ -73,22 +72,6 @@ return new AcceleratedTimeProvider( TimeProvider.System, (double) options.TimeAcceleration ); } ); -builder.Services.AddSingleton( - services => - { - LicenseServerOptions options = services.GetRequiredService>().Value; - - return options.LeaseLockMode switch - { - LeaseLockMode.InProcess => new InProcessLeaseLock(), - LeaseLockMode.None => new NullLeaseLock(), - LeaseLockMode.SqlApplicationLock => throw new NotSupportedException( - "LeaseLockMode.SqlApplicationLock is not implemented yet. Run a single worker process, or open an " - + "issue at https://github.com/postsharp/SharpCrafters.Backstage.LicenseServer." ), - _ => throw new InvalidOperationException( $"Unknown lease lock mode '{options.LeaseLockMode}'." ) - }; - } ); - string authenticationScheme = builder.Services.AddLicenseServerAuthentication( builder.Configuration ); builder.Services.AddAuthorizationBuilder() diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.json b/src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.json index 5f82180..139a27a 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.json +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.json @@ -17,8 +17,7 @@ "TimeAcceleration": 1, "BuildServers": "", "AdminRoles": [], - "RequireAuthenticatedLeaseRequests": false, - "LeaseLockMode": "InProcess" + "RequireAuthenticatedLeaseRequests": false }, "Smtp": { "Enabled": false, diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs index 677435e..f648c0b 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -17,34 +16,33 @@ using SharpCrafters.Backstage.LicenseServer.Locking; using SharpCrafters.Backstage.LicenseServer.Options; using SharpCrafters.Backstage.LicenseServer.Tests.Fakes; +using SharpCrafters.Common; namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// -/// Hosts the real application in memory. SQLite replaces the SQL Server database, and test doubles -/// replace the license parser, the clock and the e-mail sender. Everything else is the production +/// Hosts the real application in memory, over the database of the test, with test doubles for the +/// license parser, the clock and the e-mail sender. Everything else is the production /// pipeline: the routing, the model binding, the authorization and the endpoints. /// public sealed class LicenseServerApplication : WebApplicationFactory { - private readonly SqliteConnection connection; - - private readonly string connectionString; + private readonly ITestDatabase database; + /// + /// Creates an application over a database of its own, on the engine the run uses. The database is + /// created here, and not at the first request, because a test adds licenses before it sends its + /// first request, and the first request is what starts the host. + /// + /// + /// Creating the database is asynchronous, and a constructor cannot await. The work runs on the + /// thread pool rather than on the synchronization context of the test, where waiting for it would + /// deadlock. The alternative is an asynchronous factory, which every test class would have to + /// call from InitializeAsync. + /// public LicenseServerApplication() { - // A database held in memory with a shared cache, so that the application can open its own - // connections from the connection string, while the database exists only as long as this - // connection stays open. - this.connectionString = $"DataSource=licenseserver-{Guid.NewGuid():N};Mode=Memory;Cache=Shared"; - - this.connection = new SqliteConnection( this.connectionString ); - this.connection.Open(); - - // The schema is created here, because a test adds licenses before it sends its first - // request, and the first request is what starts the host. - using LicenseServerDbContext db = this.CreateDbContext(); - db.Database.EnsureCreated(); + this.database = Task.Run( TestDatabases.CreateAsync ).GetAwaiter().GetResult(); } public FakeLicenseParser LicenseParser { get; } = new(); @@ -52,9 +50,17 @@ public LicenseServerApplication() public InMemoryEmailSender EmailSender { get; } = new(); /// - /// Gets or sets the lock the lease endpoint uses, so that a test can force the overloaded path. + /// Gets or sets a lock that replaces the one of the engine, so that a test can force the path + /// that answers "Service overloaded.". When it stays null, the application uses the lock of its + /// database engine, as it does in production. + /// + public ILeaseLock? LeaseLock { get; set; } + + /// + /// Gets the provider of the synchronization points, which lets a test hold a request at a named + /// point in the code under test. It is registered in every test and enabled by none. /// - public ILeaseLock LeaseLock { get; set; } = new InProcessLeaseLock(); + public TestSynchronizationProvider Synchronization { get; } = new(); /// /// Gets the guard that makes the response body refuse a synchronous write, which a test writing @@ -62,6 +68,12 @@ public LicenseServerApplication() /// public AsyncOnlyResponseBody ResponseBody { get; } = new(); + /// + /// Gets or sets the number of seconds a request waits for the lease lock, which a test that + /// exercises the timeout shortens. + /// + public int MutexTimeoutSeconds { get; set; } = 5; + protected override void ConfigureWebHost( IWebHostBuilder builder ) { builder.UseEnvironment( "Testing" ); @@ -75,9 +87,9 @@ protected override void ConfigureWebHost( IWebHostBuilder builder ) ["LicenseServer:NewLeaseDays"] = "3", ["LicenseServer:MinLeaseDays"] = "1", ["LicenseServer:BuildServers"] = "buildagent", - ["LicenseServer:MutexTimeout"] = "5", - ["LicenseServer:DatabaseProvider"] = "Sqlite", - [$"ConnectionStrings:{DatabaseRegistration.ConnectionStringName}"] = this.connectionString, + ["LicenseServer:MutexTimeout"] = this.MutexTimeoutSeconds.ToString( System.Globalization.CultureInfo.InvariantCulture ), + ["LicenseServer:DatabaseProvider"] = this.database.ProviderName, + [$"ConnectionStrings:{DatabaseRegistration.ConnectionStringName}"] = this.database.ConnectionString, ["Smtp:Enabled"] = "false" }; @@ -89,16 +101,21 @@ protected override void ConfigureWebHost( IWebHostBuilder builder ) builder.ConfigureServices( services => { - // The database is not replaced here. The application selects SQLite from the - // configuration above, through the code path that a customer uses. + // Neither the database nor its lock is replaced here. The application selects both + // from the configuration above, through the code path that a customer uses. services.RemoveAll(); services.AddSingleton( this.LicenseParser ); services.RemoveAll(); services.AddSingleton( this.EmailSender ); - services.RemoveAll(); - services.AddSingleton( _ => this.LeaseLock ); + if ( this.LeaseLock != null ) + { + services.RemoveAll(); + services.AddSingleton( this.LeaseLock ); + } + + services.AddSingleton( this.Synchronization ); // Windows authentication cannot be negotiated against an in-memory host. services.AddAuthentication( TestAuthenticationHandler.SchemeName ) @@ -119,11 +136,14 @@ protected override void ConfigureWebHost( IWebHostBuilder builder ) } ); } - public LicenseServerDbContext CreateDbContext() - => new( - new DbContextOptionsBuilder() - .UseSqlite( this.connectionString ) - .Options ); + public LicenseServerDbContext CreateDbContext() => this.database.CreateContext(); + + /// + /// States that the database of this application must not serve another test. A test that modifies + /// the schema calls it, because a SQL Server run lends the same databases to one test after + /// another. + /// + public void DoNotReuseDatabase() => this.database.DoNotReuse(); /// /// Registers a license both in the database and with the fake parser. @@ -158,7 +178,12 @@ protected override void Dispose( bool disposing ) if ( disposing ) { - this.connection.Dispose(); + this.Synchronization.Dispose(); + + // The database of a SQL Server run returns to its pool here, and a SQLite database in + // memory disappears with its connection. Disposal runs on the thread pool for the same + // reason as the creation. + Task.Run( async () => await this.database.DisposeAsync() ).GetAwaiter().GetResult(); } } } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs index 1646edd..c13e408 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs @@ -10,20 +10,20 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// /// A license server wired up for a test: a real and -/// over a database held in memory, with the license parser, the +/// over the database of the test, with the license parser, the /// clock and the e-mail sender replaced by test doubles. /// public sealed class LicenseServerTestContext : IAsyncDisposable { - private readonly SqliteDatabaseFixture fixture; + private readonly ITestDatabase database; private readonly LicenseServerDbContext db; private LicenseServerTestContext( - SqliteDatabaseFixture fixture, + ITestDatabase database, LicenseServerDbContext db, LicenseServerOptions options ) { - this.fixture = fixture; + this.database = database; this.db = db; this.Options = options; this.LicenseParser = new FakeLicenseParser(); @@ -59,9 +59,8 @@ private LicenseServerTestContext( public LicenseServerDbContext Db => this.db; /// - /// Creates a context over a database that belongs to the calling test. Creating a SQLite database - /// in memory takes less than a millisecond, so the tests do not share one and no test has to - /// reset it. + /// Creates a context over a database that belongs to the calling test. See + /// for the engine the run uses. /// public static async Task CreateAsync( Action? configure = null ) { @@ -77,9 +76,9 @@ public static async Task CreateAsync( Action @@ -88,15 +87,21 @@ public static async Task CreateAsync( Action public LeaseRepository CreateFreshRepository() => new( - this.fixture.CreateContext(), + this.database.CreateContext(), Microsoft.Extensions.Options.Options.Create( this.Options ), this.LicenseParser ); - public LicenseServerDbContext CreateFreshContext() => this.fixture.CreateContext(); + public LicenseServerDbContext CreateFreshContext() => this.database.CreateContext(); + + /// + /// States that the database of this context must not serve another test. A test that modifies the + /// schema calls it, because a SQL Server run lends the same databases to one test after another. + /// + public void DoNotReuseDatabase() => this.database.DoNotReuse(); public async ValueTask DisposeAsync() { await this.db.DisposeAsync(); - await this.fixture.DisposeAsync(); + await this.database.DisposeAsync(); } } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs new file mode 100644 index 0000000..2a63894 --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs @@ -0,0 +1,308 @@ +using System.Collections.Concurrent; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using SharpCrafters.Backstage.LicenseServer.Data; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +/// +/// A database of the SQL Server that the run was given, created from CreateTables.sql. +/// +/// +/// +/// The schema comes from the script that an administrator runs, and not from the EF Core model, so +/// this run also proves that the model and the script agree. +/// +/// +/// Creating a database costs about half a second, and the suite has several hundred tests, so the +/// databases are pooled. A test takes one, the tables are emptied, and the database returns to the +/// pool when the test ends. The pool therefore holds as many databases as the number of tests that +/// run at the same time. +/// +/// +public sealed class SqlServerTestDatabase : ITestDatabase +{ + private readonly SqlServerDatabasePool pool; + private readonly string databaseName; + private int returned; + private volatile bool reusable = true; + + private SqlServerTestDatabase( SqlServerDatabasePool pool, string databaseName, string connectionString ) + { + this.pool = pool; + this.databaseName = databaseName; + this.ConnectionString = connectionString; + } + + public string ProviderName => "SqlServer"; + + public string ConnectionString { get; } + + public static async Task CreateAsync( string serverConnectionString ) + { + SqlServerDatabasePool pool = SqlServerDatabasePool.Get( serverConnectionString ); + string databaseName = await pool.RentAsync(); + + return new SqlServerTestDatabase( pool, databaseName, pool.GetConnectionString( databaseName ) ); + } + + public LicenseServerDbContext CreateContext() + => new( + new DbContextOptionsBuilder() + .UseSqlServer( this.ConnectionString ) + .EnableSensitiveDataLogging() + .Options ); + + /// + /// Marks this database for deletion instead of reuse. The pool empties the tables of a database + /// before it lends it again, and a test that dropped a table would leave the next test without + /// one. + /// + public void DoNotReuse() => this.reusable = false; + + /// + /// Returns the database to the pool, once. A host that implements both + /// and can dispose its database twice, and the pool would then + /// lend the same database to two tests, which clear it under each other. + /// + public async ValueTask DisposeAsync() + { + if ( Interlocked.Exchange( ref this.returned, 1 ) == 0 ) + { + await this.pool.ReturnAsync( this.databaseName, this.reusable ); + } + } +} + +/// +/// The databases of one SQL Server, lent to the tests one at a time. +/// +internal sealed class SqlServerDatabasePool +{ + /// + /// The prefix of every database this pool creates. A run drops the databases that carry it before + /// it creates its own, so a run that was interrupted leaves nothing behind for long. + /// + private const string prefix = "licenseserver_test_"; + + /// + /// The number of databases the pool lends at the same time, which is also the number of tests + /// that touch the server at the same time. A test that finds no free database waits for one. + /// Without this bound, every test of the suite would create a database of its own at once, and + /// the server would refuse the connections. + /// + private const int capacity = 8; + + private static readonly ConcurrentDictionary pools = new( StringComparer.Ordinal ); + + private readonly string serverConnectionString; + private readonly ConcurrentBag available = []; + private readonly SemaphoreSlim lease = new( capacity, capacity ); + private readonly SemaphoreSlim initialization = new( 1, 1 ); + private bool initialized; + + private SqlServerDatabasePool( string serverConnectionString ) + { + SqlConnectionStringBuilder builder = new( serverConnectionString ); + + // Creating a database and connecting to it while the suite runs takes longer than the + // fifteen seconds of the default. + if ( builder.ConnectTimeout < 60 ) + { + builder.ConnectTimeout = 60; + } + + this.serverConnectionString = builder.ToString(); + } + + public static SqlServerDatabasePool Get( string serverConnectionString ) + => pools.GetOrAdd( serverConnectionString, connectionString => new SqlServerDatabasePool( connectionString ) ); + + public string GetConnectionString( string databaseName ) + => new SqlConnectionStringBuilder( this.serverConnectionString ) { InitialCatalog = databaseName }.ToString(); + + public async Task RentAsync() + { + await this.DropLeftoverDatabasesAsync(); + await this.lease.WaitAsync(); + + try + { + if ( this.available.TryTake( out string? databaseName ) ) + { + await this.ClearAsync( databaseName ); + + return databaseName; + } + + databaseName = prefix + Guid.NewGuid().ToString( "N" ); + + await ExecuteAsync( this.serverConnectionString, $"CREATE DATABASE [{databaseName}]" ); + await this.CreateSchemaAsync( databaseName ); + + return databaseName; + } + catch + { + this.lease.Release(); + + throw; + } + } + + /// + /// Takes a database back. A database whose schema a test modified is dropped instead of kept, and + /// the next test that finds the pool empty creates one. + /// + public async ValueTask ReturnAsync( string databaseName, bool reusable ) + { + try + { + if ( reusable ) + { + this.available.Add( databaseName ); + } + else + { + await this.DropAsync( databaseName ); + } + } + finally + { + this.lease.Release(); + } + } + + /// + /// Empties the two tables and restarts the identity of the leases, so that a database that was + /// used by an earlier test looks like a database that was just created. The first lease of the + /// next test therefore always receives the identifier 1. + /// + /// + /// The reseed is conditional. On a table that has received a row since it was created, the next + /// row takes the new value plus the increment, which is 1. On a table that has received no row, + /// the next row takes the new value itself, which is 0, and a test that asserts on the identifier + /// of a lease then fails. The column last_value is null until the first row is inserted, + /// and that case needs no reseed, because the next row already takes the seed of the column. + /// + private async Task ClearAsync( string databaseName ) + => await ExecuteAsync( + this.GetConnectionString( databaseName ), + """ + DELETE FROM [dbo].[Leases]; + DELETE FROM [dbo].[Licenses]; + + IF EXISTS ( + SELECT 1 FROM sys.identity_columns + WHERE object_id = OBJECT_ID('[dbo].[Leases]') AND last_value IS NOT NULL ) + BEGIN + DBCC CHECKIDENT ('[dbo].[Leases]', RESEED, 0) WITH NO_INFOMSGS; + END + """ ); + + private async Task CreateSchemaAsync( string databaseName ) + { + string script = await File.ReadAllTextAsync( LocateCreateTablesScript() ); + + // sqlcmd separates the batches of a script with GO, which is not a statement of Transact-SQL. + foreach ( string batch in script.Split( + "\nGO", + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ) ) + { + if ( batch.Length > 0 ) + { + await ExecuteAsync( this.GetConnectionString( databaseName ), batch ); + } + } + } + + /// + /// Drops the databases that an interrupted run left behind. It runs once per pool, before the + /// first database is created. + /// + private async Task DropLeftoverDatabasesAsync() + { + if ( this.initialized ) + { + return; + } + + await this.initialization.WaitAsync(); + + try + { + if ( this.initialized ) + { + return; + } + + await using SqlConnection connection = new( this.serverConnectionString ); + await connection.OpenAsync(); + + List leftovers = []; + + await using ( SqlCommand query = connection.CreateCommand() ) + { + query.CommandText = $"SELECT name FROM sys.databases WHERE name LIKE '{prefix}%'"; + + await using SqlDataReader reader = await query.ExecuteReaderAsync(); + + while ( await reader.ReadAsync() ) + { + leftovers.Add( reader.GetString( 0 ) ); + } + } + + foreach ( string leftover in leftovers ) + { + await this.DropAsync( leftover ); + } + + this.initialized = true; + } + finally + { + this.initialization.Release(); + } + } + + /// + /// Drops one database. A test leaves its connections open in the pool of the client, so the + /// server closes them before it drops the database. + /// + private async Task DropAsync( string databaseName ) + => await ExecuteAsync( + this.serverConnectionString, + $""" + ALTER DATABASE [{databaseName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + DROP DATABASE [{databaseName}]; + """ ); + + private static async Task ExecuteAsync( string connectionString, string sql ) + { + await using SqlConnection connection = new( connectionString ); + await connection.OpenAsync(); + + await using SqlCommand command = connection.CreateCommand(); + command.CommandText = sql; + + await command.ExecuteNonQueryAsync(); + } + + /// + /// Finds CreateTables.sql next to the test assembly, where the web project copies it. + /// + private static string LocateCreateTablesScript() + { + string path = Path.Combine( AppContext.BaseDirectory, "Database", "CreateTables.sql" ); + + if ( !File.Exists( path ) ) + { + throw new FileNotFoundException( + $"The schema script was not found at '{path}'. The test project copies it from the web project.", + path ); + } + + return path; + } +} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs deleted file mode 100644 index c0bd712..0000000 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using SharpCrafters.Backstage.LicenseServer.Data; - -namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; - -/// -/// A SQLite database held in memory and created from the EF Core model. It behaves like a relational -/// database: it enforces the foreign keys, it runs transactions, and it translates the queries. -/// -/// -/// A SQLite database held in memory exists as long as a connection to it is open, so this fixture -/// holds one connection during its whole lifetime and returns contexts that share that connection. -/// Each context is a separate unit of work with its own change tracker, so a test can distinguish a -/// lease that is pending from a lease that is saved. -/// -public sealed class SqliteDatabaseFixture : IAsyncDisposable -{ - private readonly SqliteConnection connection; - - private SqliteDatabaseFixture( SqliteConnection connection ) - { - this.connection = connection; - } - - public SqliteConnection Connection => this.connection; - - public static async Task CreateAsync() - { - SqliteConnection connection = new( "DataSource=:memory:" ); - await connection.OpenAsync(); - - SqliteDatabaseFixture fixture = new( connection ); - - await using LicenseServerDbContext context = fixture.CreateContext(); - await context.Database.EnsureCreatedAsync(); - - return fixture; - } - - public LicenseServerDbContext CreateContext() - => new( - new DbContextOptionsBuilder() - .UseSqlite( this.connection ) - .EnableSensitiveDataLogging() - .Options ); - - public async ValueTask DisposeAsync() => await this.connection.DisposeAsync(); -} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteTestDatabase.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteTestDatabase.cs new file mode 100644 index 0000000..2cfc369 --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteTestDatabase.cs @@ -0,0 +1,59 @@ +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using SharpCrafters.Backstage.LicenseServer.Data; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +/// +/// A SQLite database held in memory and created from the EF Core model. It behaves like a relational +/// database: it enforces the foreign keys, it runs transactions, and it translates the queries. +/// +/// +/// A database held in memory exists as long as a connection to it is open, so this class holds one +/// connection during its whole lifetime. The cache is shared, so the code under test opens its own +/// connections to the same database, which is what a lock between two connections requires. +/// +public sealed class SqliteTestDatabase : ITestDatabase +{ + private readonly SqliteConnection connection; + + private SqliteTestDatabase( SqliteConnection connection, string connectionString ) + { + this.connection = connection; + this.ConnectionString = connectionString; + } + + public string ProviderName => "Sqlite"; + + public string ConnectionString { get; } + + public static async Task CreateAsync() + { + string connectionString = $"DataSource=licenseserver-{Guid.NewGuid():N};Mode=Memory;Cache=Shared"; + + SqliteConnection connection = new( connectionString ); + await connection.OpenAsync(); + + SqliteTestDatabase database = new( connection, connectionString ); + + await using LicenseServerDbContext context = database.CreateContext(); + await context.Database.EnsureCreatedAsync(); + + return database; + } + + public LicenseServerDbContext CreateContext() + => new( + new DbContextOptionsBuilder() + .UseSqlite( this.ConnectionString ) + .EnableSensitiveDataLogging() + .Options ); + + /// + /// Does nothing. This database belongs to one test and disappears with its connection, so no + /// other test can see the schema that the test modified. + /// + public void DoNotReuse() { } + + public async ValueTask DisposeAsync() => await this.connection.DisposeAsync(); +} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs new file mode 100644 index 0000000..21238ea --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs @@ -0,0 +1,74 @@ +using SharpCrafters.Backstage.LicenseServer.Data; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +/// +/// A database that belongs to one test, on the engine the test run was started with. +/// +public interface ITestDatabase : IAsyncDisposable +{ + /// + /// Gets the name of the provider, as LicenseServer:DatabaseProvider spells it. + /// + string ProviderName { get; } + + /// + /// Gets the connection string of this database, which the tests that host the whole application + /// pass to it as configuration. + /// + string ConnectionString { get; } + + /// + /// Creates a context over this database. Each context is a separate unit of work with its own + /// change tracker, so a test can distinguish a lease that is pending from a lease that is saved. + /// + LicenseServerDbContext CreateContext(); + + /// + /// States that this database must not serve another test, which a test that modifies the schema + /// calls. A SQL Server run lends its databases to one test after another, and a test that drops + /// a table would leave the next test without one. + /// + void DoNotReuse(); +} + +/// +/// Creates the database of a test on the engine the run was started with. +/// +/// +/// +/// The engine is a property of the run and not of a test, so the same tests run against both. SQLite +/// is the default, because it needs no server and keeps the development loop short. The run uses SQL +/// Server when LICENSESERVER_TEST_SQLSERVER holds a connection string, which is how the +/// continuous integration build runs the suite a second time against the engine that customers use. +/// +/// +/// Start that server with docker compose up -d database, which is the same service the +/// container deployment uses. +/// +/// +public static class TestDatabases +{ + /// + /// The name of the environment variable that holds the connection string of the SQL Server used + /// by the tests. The connection string names no database: each test receives one of its own. + /// + public const string SqlServerVariable = "LICENSESERVER_TEST_SQLSERVER"; + + /// + /// Gets the connection string of the SQL Server of the run, or null when the run uses + /// SQLite. + /// + public static string? SqlServerConnectionString { get; } = + Environment.GetEnvironmentVariable( SqlServerVariable ) is { Length: > 0 } value ? value : null; + + /// + /// Gets a value indicating whether this run uses SQL Server. + /// + public static bool UsesSqlServer => SqlServerConnectionString != null; + + public static async Task CreateAsync() + => SqlServerConnectionString == null + ? await SqliteTestDatabase.CreateAsync() + : await SqlServerTestDatabase.CreateAsync( SqlServerConnectionString ); +} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseLockTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseLockTests.cs new file mode 100644 index 0000000..a24c7ed --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseLockTests.cs @@ -0,0 +1,94 @@ +using System.Net; +using Microsoft.EntityFrameworkCore; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Endpoints; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +namespace SharpCrafters.Backstage.LicenseServer.Tests; + +/// +/// The lock that serializes the lease requests, so that two requests cannot both take the last free +/// seat. +/// +/// +/// The lock belongs to the database, so these tests exercise the lock of the engine the run uses: an +/// application lock on SQL Server, and a write transaction on SQLite. They drive the interleaving +/// with a synchronization point instead of starting two requests and hoping that they overlap. Two +/// requests that merely run at the same time prove nothing: they pass whether the lock works or not. +/// +public sealed class LeaseLockTests : IDisposable +{ + private readonly LicenseServerApplication application = new(); + + public void Dispose() => this.application.Dispose(); + + private static string Url( string user, string machine ) + => $"/Lease.ashx?user={user}&machine={machine}&product=Ultimate&version=2027.0.0"; + + /// + /// The second request waits for the first one, sees the seat it took, and is denied. Without the + /// lock it would read the seat count before the first request wrote its lease, and both would be + /// granted. + /// + [Fact] + public async Task SecondRequest_WaitsForTheFirst_AndSeesItsLease() + { + License license = this.application.AddLicense( LicenseBuilder.Default().WithUsers( 1 ).WithGracePercent( 0 ) ); + + this.application.Synchronization.EnableSyncPoint( LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); + + HttpClient client = this.application.CreateClient(); + + Task first = client.GetAsync( Url( "alice", "desktop-1" ) ); + + // The first request now holds the lock. + await this.application.Synchronization.WaitForSyncPointReachedAsync( + LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); + + Task second = client.GetAsync( Url( "bob", "desktop-2" ) ); + + // Releases the first request, and lets the second one through the synchronization point when + // it finally acquires the lock. + this.application.Synchronization.DisableSyncPoint( LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); + + HttpResponseMessage firstResponse = await first; + HttpResponseMessage secondResponse = await second; + + Assert.Equal( HttpStatusCode.OK, firstResponse.StatusCode ); + Assert.Equal( HttpStatusCode.Forbidden, secondResponse.StatusCode ); + + await using LicenseServerDbContext db = this.application.CreateDbContext(); + + Assert.Equal( 1, await db.Leases.CountAsync( l => l.LicenseId == license.LicenseId ) ); + } + + /// + /// A request that waits longer than MutexTimeout for the lock is answered with the status + /// 503, which is what the legacy server answered when its mutex timed out. + /// + [Fact] + public async Task SecondRequest_WhenTheFirstHoldsTheLockTooLong_IsAnsweredWithServiceOverloaded() + { + this.application.MutexTimeoutSeconds = 1; + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + + this.application.Synchronization.EnableSyncPoint( LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); + + HttpClient client = this.application.CreateClient(); + + Task first = client.GetAsync( Url( "alice", "desktop-1" ) ); + + await this.application.Synchronization.WaitForSyncPointReachedAsync( + LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); + + // The first request holds the lock for longer than the second one waits. + HttpResponseMessage secondResponse = await client.GetAsync( Url( "bob", "desktop-2" ) ); + + Assert.Equal( HttpStatusCode.ServiceUnavailable, secondResponse.StatusCode ); + Assert.Equal( "Service overloaded.", await secondResponse.Content.ReadAsStringAsync() ); + + this.application.Synchronization.DisableSyncPoint( LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); + + Assert.Equal( HttpStatusCode.OK, (await first).StatusCode ); + } +} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs index 8b54ece..a583a69 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs @@ -93,6 +93,9 @@ public async Task Health_ExpiredLicense_WarnsAndStaysServed() [Fact] public async Task Health_DatabaseWithoutSchema_FailsAndSaysNothingMore() { + // The database keeps no schema after this test, so it must not serve another one. + this.application.DoNotReuseDatabase(); + using ( LicenseServerDbContext db = this.application.CreateDbContext() ) { await db.Database.ExecuteSqlRawAsync( "DROP TABLE Leases" ); diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs index fbc71d2..f8057e1 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs @@ -145,7 +145,12 @@ public async Task LegacyHmacColumn_IsIgnored() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - await context.Db.Database.ExecuteSqlRawAsync( "ALTER TABLE Leases ADD COLUMN HMAC varchar(100) NULL" ); + // On SQL Server the column is already there, because CreateTables.sql declares it. On SQLite + // the schema comes from the model, which no longer maps it, so the test adds it. + if ( !TestDatabases.UsesSqlServer ) + { + await context.Db.Database.ExecuteSqlRawAsync( "ALTER TABLE Leases ADD COLUMN HMAC varchar(100) NULL" ); + } License license = LicenseBuilder.Default().AddTo( context ); Lease lease = LeaseBuilder.For( license ).AddTo( context ); diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs index 6f5bb81..37909f5 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs @@ -106,10 +106,17 @@ public async Task Seed_Twice_AddsNothingTheSecondTime() TestLicenseAuthority authority = this.CreateAuthority(); TestLicenseSeeder.Seed( context.Db, authority, TimeProvider.System, NullLogger.Instance ); - string[] first = context.Db.Licenses.Select( l => l.LicenseKey ).Order().ToArray(); + + // The keys are sorted after they are read. On SQL Server the column has the type text, which + // Transact-SQL refuses to sort, and the query would fail. This is the rule that + // SchemaCompatibilityTests states for the queries of the server. + string[] first = ReadKeys( context ); Assert.Empty( TestLicenseSeeder.Seed( context.Db, authority, TimeProvider.System, NullLogger.Instance ) ); - Assert.Equal( first, context.Db.Licenses.Select( l => l.LicenseKey ).Order().ToArray() ); + Assert.Equal( first, ReadKeys( context ) ); + + static string[] ReadKeys( LicenseServerTestContext context ) + => context.Db.Licenses.Select( l => l.LicenseKey ).AsEnumerable().Order().ToArray(); } /// diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj index 89f0473..7b33a14 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj @@ -33,4 +33,12 @@ + + + + + From aacc0c82acce88bb619a3c94792760d9777a378a Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Fri, 18 Sep 2026 09:48:36 +0200 Subject: [PATCH 39/44] Support PostgreSQL PostgreSQL joins SQL Server as an engine supported in production, and SQLite stays the engine of the development loop. Set LicenseServer:DatabaseProvider to PostgreSql and run Database/CreateTables.PostgreSql.sql, which is the script an administrator runs, as CreateTables.sql is on SQL Server. The server still never creates the schema and never modifies it. The lock that serializes the lease requests is an advisory lock. The request calls pg_advisory_lock and releases it with pg_advisory_unlock, both of which belong to the session, which is the connection of the request. The wait is bounded by lock_timeout, taken from MutexTimeout. PostgreSQL then cancels the statement with the code 55P03, and the request is answered with the status 503, as it is on SQL Server. Two differences of the engine were found by running the suite rather than by reading. Npgsql refuses a DateTime whose kind is Unspecified, which is what a date built from a year and a month is, and the export builds one from its query string; the value converter of the context now sets the kind in both directions, which writes the same value SQL Server and SQLite have always stored. PostgreSQL folds an identifier that is not quoted to lower case, so the two tests that modify the schema quote the names of the tables. The comparison of a user name and of a machine name ignores the case, as it does on SQL Server, whose default collation ignores it. PostgreSQL offers no such collation, so the schema creates one from the International Components for Unicode, and the model names it. Without it, a user who signed in under two spellings would hold two sets of machines. The test suite runs against PostgreSQL when LICENSESERVER_TEST_POSTGRESQL holds a connection string, the way it already runs against SQL Server. Each test receives a database of its own, created from the script of the engine, and the databases are pooled. TRUNCATE takes the place of the DELETE and the reseed that SQL Server needs. eng/TestDatabase.ps1 replaces eng/TestSqlServer.ps1 and serves both engines. It starts the server the run needs: the one installed in the image, when the continuous integration build runs it, and a container of its own on a developer machine. The container of a test run carries no volume and publishes a port beside the default one, so a license server deployed on the same machine with docker-compose.yml is left alone. On a machine whose Docker engine runs inside the Windows Subsystem for Linux, the script reaches Docker through wsl. The build definition generates a second Linux image, carrying PostgreSQL 17 from the repository of the PostgreSQL project, and a second configuration, Tests on PostgreSQL, beside the one for SQL Server. Verified: 278 tests pass on each of the three engines, and the two images build and run their server. Co-Authored-By: Claude Opus 5 --- .teamcity/settings.kts | 125 ++++++- Directory.Packages.props | 1 + README.md | 54 ++-- docs/configuration.md | 41 ++- docs/docker.md | 12 +- eng/TestDatabase.ps1 | 304 ++++++++++++++++++ eng/TestSqlServer.ps1 | 130 -------- eng/docker/postgresql-build.Dockerfile | 72 +++++ eng/src/Docker/PostgreSqlComponent.cs | 65 ++++ eng/src/Program.cs | 69 ++-- .../Data/LicenseServerDbContext.cs | 54 +++- .../Database/CreateTables.PostgreSql.sql | 56 ++++ .../DatabaseRegistration.cs | 14 +- .../Endpoints/LicenseServerEndpoints.cs | 5 +- .../Locking/PostgreSqlLeaseLock.cs | 137 ++++++++ .../Program.cs | 6 +- ...rafters.Backstage.LicenseServer.Web.csproj | 7 +- .../Infrastructure/PostgreSqlTestDatabase.cs | 286 ++++++++++++++++ .../Infrastructure/TestDatabases.cs | 59 +++- .../OperationsEndpointTests.cs | 6 +- .../SchemaCompatibilityTests.cs | 14 +- ...fters.Backstage.LicenseServer.Tests.csproj | 7 +- 22 files changed, 1287 insertions(+), 237 deletions(-) create mode 100644 eng/TestDatabase.ps1 delete mode 100644 eng/TestSqlServer.ps1 create mode 100644 eng/docker/postgresql-build.Dockerfile create mode 100644 eng/src/Docker/PostgreSqlComponent.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Web/Database/CreateTables.PostgreSql.sql create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlTestDatabase.cs diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index 0bd0763..dc08151 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -17,8 +17,9 @@ project { buildType(PublicDeployment) buildType(VersionBump) buildType(SqlServerTests) + buildType(PostgreSqlTests) - buildTypesOrder = arrayListOf(DebugBuild,ReleaseBuild,PublicBuild,PublicDeployment,VersionBump,SqlServerTests) + buildTypesOrder = arrayListOf(DebugBuild,ReleaseBuild,PublicBuild,PublicDeployment,VersionBump,SqlServerTests,PostgreSqlTests) } @@ -570,7 +571,7 @@ object SqlServerTests : BuildType({ "Exec.Arguments", "", label ="DockerBuild.ps1 Arguments", - description = "Arguments to append to the 'Execute ./eng/TestSqlServer.ps1' build step.", allowEmpty = true) + description = "Arguments to append to the 'Execute ./eng/TestDatabase.ps1' build step.", allowEmpty = true) } vcs { @@ -617,14 +618,130 @@ object SqlServerTests : BuildType({ scriptArgs = "-BuildImage -ImageName backstagelicenseserver-2027.0-sqlservertests -Dockerfile eng/docker/sqlserver-build.Dockerfile " } powerShell { - name = "Execute ./eng/TestSqlServer.ps1" + name = "Execute ./eng/TestDatabase.ps1" id = "Exec" edition = PowerShellStep.Edition.Core scriptMode = file { path = "DockerBuild.ps1" } noProfile = false - scriptArgs = "-Script ./eng/TestSqlServer.ps1 -ImageName backstagelicenseserver-2027.0-sqlservertests -Dockerfile eng/docker/sqlserver-build.Dockerfile -NoBuildImage -Label %system.teamcity.buildType.id%_%build.number% %Exec.Arguments%" + scriptArgs = "-Script ./eng/TestDatabase.ps1 -ImageName backstagelicenseserver-2027.0-sqlservertests -Dockerfile eng/docker/sqlserver-build.Dockerfile -NoBuildImage -Label %system.teamcity.buildType.id%_%build.number% -Engine SqlServer %Exec.Arguments%" + } + powerShell { + name = "Cleanup Docker containers" + id = "DockerCleanup" + executionMode = BuildStep.ExecutionMode.ALWAYS + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "${'$'}label = \"%system.teamcity.buildType.id%_%build.number%\"; ${'$'}ids = docker ps -a -q --filter \"label=postsharp.build=${'$'}label\"; if (${'$'}ids) { docker rm -f ${'$'}ids 2>&1 | Out-Null }" + } + noProfile = false + } + } + + requirements { + equals("teamcity.agent.jvm.os.name", "Linux") + equals("teamcity.agent.jvm.os.arch", "amd64") + } + + features { + swabra { + filesCleanup = Swabra.FilesCleanup.BEFORE_BUILD + lockingProcesses = Swabra.LockingProcessPolicy.KILL + verbose = true + } + gitHubAppBuildScopedToken { + parameterName = "env.GITHUB_TOKEN" + connectionId = "%GITHUB_CONNECTION_POSTSHARP_OPS%" + targetRepositories = "SharpCrafters.Backstage.LicenseServer" + } + } + + dependencies { + snapshot(DebugBuild) { + onDependencyFailure = FailureAction.FAIL_TO_START + } + + artifacts(DebugBuild) { + cleanDestination = true + artifactRules = "+:artifacts/publish/private/**/*=>artifacts/publish/private" + } + snapshot(AbsoluteId("Backstage_Backstage20270_DebugBuild")) { + onDependencyFailure = FailureAction.FAIL_TO_START + } + + artifacts(AbsoluteId("Backstage_Backstage20270_DebugBuild")) { + cleanDestination = true + artifactRules = "+:artifacts/publish/private/**/*=>dependencies/Backstage" + } + } + +}) + +object PostgreSqlTests : BuildType({ + + name = "Tests on PostgreSQL" + + params { + text( + "Exec.Arguments", + "", + label ="DockerBuild.ps1 Arguments", + description = "Arguments to append to the 'Execute ./eng/TestDatabase.ps1' build step.", allowEmpty = true) + } + + vcs { + root(AbsoluteId("Backstage_BackstageLicenseServer20270")) + checkoutMode = CheckoutMode.ON_AGENT + } + + steps { + powerShell { + name = "Clean NuGet cache of produced and dependency packages" + id = "CleanNuGetCache" + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "${'$'}nugetPackages = if ( ${'$'}env:NUGET_PACKAGES ) { ${'$'}env:NUGET_PACKAGES } else { Join-Path ${'$'}HOME '.nuget' 'packages' }; ${'$'}removedDirs = 0; ${'$'}removedFiles = 0; if ( Test-Path -LiteralPath ${'$'}nugetPackages ) { foreach ( ${'$'}pattern in @('metalama.backstage*', 'postsharp.engineering', 'postsharp.engineering.*', 'sharpcrafters.backstage*', 'sharpcrafters.backstage.licenseserver*', 'sharpcrafters.common*') ) { Get-ChildItem -LiteralPath ${'$'}nugetPackages -Directory -Filter ${'$'}pattern -ErrorAction SilentlyContinue | ForEach-Object { ${'$'}files = @( Get-ChildItem -LiteralPath ${'$'}_.FullName -Recurse -File -ErrorAction SilentlyContinue ).Count; Write-Host \"Removing NuGet cache directory: ${'$'}(${'$'}_.FullName) (${'$'}files file(s))\"; Remove-Item -LiteralPath ${'$'}_.FullName -Recurse -Force -ErrorAction SilentlyContinue; if ( -not ( Test-Path -LiteralPath ${'$'}_.FullName ) ) { ${'$'}removedDirs++; ${'$'}removedFiles += ${'$'}files } } } Write-Host \"Removed ${'$'}removedDirs package directory(ies) and ${'$'}removedFiles file(s) from the NuGet cache.\"; } else { Write-Host \"NuGet packages folder not found: ${'$'}nugetPackages\" }" + } + noProfile = false + } + powerShell { + name = "Copy nuget.restored.config to nuget.config" + id = "CopyNuGetConfig" + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "Copy-Item -Path \"artifacts/publish/private/nuget.restored.config\" -Destination \"nuget.config\" -Force;" + } + noProfile = false + } + powerShell { + name = "Create eng/Versions.g.props" + id = "CreateVersionsFile" + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "New-Item -Path \"eng/Versions.g.props\" -ItemType File -Force -Value \"\" | Out-Null;" + } + noProfile = false + } + powerShell { + name = "Prepare Docker image backstagelicenseserver-2027.0-postgresqltests" + id = "PrepareImage" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "DockerBuild.ps1" + } + noProfile = false + scriptArgs = "-BuildImage -ImageName backstagelicenseserver-2027.0-postgresqltests -Dockerfile eng/docker/postgresql-build.Dockerfile " + } + powerShell { + name = "Execute ./eng/TestDatabase.ps1" + id = "Exec" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "DockerBuild.ps1" + } + noProfile = false + scriptArgs = "-Script ./eng/TestDatabase.ps1 -ImageName backstagelicenseserver-2027.0-postgresqltests -Dockerfile eng/docker/postgresql-build.Dockerfile -NoBuildImage -Label %system.teamcity.buildType.id%_%build.number% -Engine PostgreSql %Exec.Arguments%" } powerShell { name = "Cleanup Docker containers" diff --git a/Directory.Packages.props b/Directory.Packages.props index 5c3573d..b40faec 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -37,6 +37,7 @@ + diff --git a/README.md b/README.md index 489914e..2cb3bbf 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ administrator adds to it decide which products it serves. The license server is optional. All commercial licenses are floating licenses, and the license server reports how many of them a team uses. -The license server is an ASP.NET Core application with a SQL Server database. It runs on Windows -behind IIS, and on Linux and macOS in its own process or in a container. +The license server is an ASP.NET Core application with a SQL Server or a PostgreSQL database. It runs +on Windows behind IIS, and on Linux and macOS in its own process or in a container. Respecting the license agreement is the responsibility of the customer. This is the reason why we publish the source code of the license server. The use of license keys @@ -64,12 +64,14 @@ you use the container for anything else than an evaluation. * Windows Server with IIS. * The [ASP.NET Core Hosting Bundle](https://dotnet.microsoft.com/download/dotnet/10.0) for .NET 10. -* SQL Server 2016 or later. +* SQL Server 2016 or later, or PostgreSQL 14 or later. ### Instructions 1. Install the ASP.NET Core Hosting Bundle on the web server, then restart IIS with `iisreset`. -2. Create the database, then run `Database\CreateTables.sql` against it. +2. Create the database, then run `Database\CreateTables.sql` against it. On PostgreSQL, run + `Database\CreateTables.PostgreSql.sql` instead and set `LicenseServer:DatabaseProvider` to + `PostgreSql`. 3. Unpack `SharpCrafters.Backstage.LicenseServer..zip` into the directory of an IIS application. 4. Edit `appsettings.json`: set the connection string, the addresses for notifications and the SMTP @@ -171,29 +173,35 @@ the dependency at it, and build that repository first: ### Running the tests `./Build.ps1 test` runs the suite on SQLite, which needs no server and keeps the loop short. Run the -same tests a second time against SQL Server before you open a pull request. SQL Server is the engine -that customers use, and the two engines differ in the collation, in the column types and in the lock -that serializes the lease requests: +same tests again against each engine that customers deploy before you open a pull request. The +engines differ in the collation, in the column types and in the lock that serializes the lease +requests, so a defect can appear on one of them alone: ``` -$env:MSSQL_SA_PASSWORD = '' -docker compose up -d database -$env:LICENSESERVER_TEST_SQLSERVER = "Server=127.0.0.1,1433;User Id=sa;Password=$env:MSSQL_SA_PASSWORD;TrustServerCertificate=True;Encrypt=False" +./eng/TestDatabase.ps1 -Engine SqlServer +./eng/TestDatabase.ps1 -Engine PostgreSql +``` + +Each run starts its server in a container, waits for it, and runs `Build.ps1 test` against it. Docker +is the only prerequisite. To use a server of your own instead, set the connection string and the +script leaves the server alone: + +``` +$env:LICENSESERVER_TEST_SQLSERVER = "Server=127.0.0.1,1433;User Id=sa;Password=;TrustServerCertificate=True;Encrypt=False" +$env:LICENSESERVER_TEST_POSTGRESQL = "Host=127.0.0.1;Port=5432;Username=postgres;Password=" dotnet test tests\SharpCrafters.Backstage.LicenseServer.Tests ``` -The tests read the connection string from `LICENSESERVER_TEST_SQLSERVER`, and they run on SQLite when -it is not set. The connection string names no database: each test receives a database of its own, -created from `Database\CreateTables.sql`, so this run also proves that the script and the Entity -Framework model agree. The databases are named `licenseserver_test_` followed by a hexadecimal +The tests read those two variables, and they run on SQLite when neither is set. A connection string +names no database: each test receives a database of its own, created from `Database\CreateTables.sql` +or from `Database\CreateTables.PostgreSql.sql`, so each run also proves that the script and the +Entity Framework model agree. The databases are named `licenseserver_test_` followed by a hexadecimal number. They are reused during the run, and the next run drops the ones an interrupted run left -behind. Point the variable at a SQL Server of your own if you prefer, as long as its login may create -a database. +behind, so the login needs the permission to create a database. -The continuous integration build runs the same second run in the configuration `Tests on SQL Server`. -It runs `eng\TestSqlServer.ps1` in an image that carries SQL Server, which `eng\src\Docker\SqlServerComponent.cs` -describes. The script also serves a developer machine: it starts the database service of -`docker-compose.yml` when it finds no other server. +The continuous integration build runs both of them, in the configurations `Tests on SQL Server` and +`Tests on PostgreSQL`. Each one runs `eng\TestDatabase.ps1` in an image that carries its server, +described by `eng\src\Docker\SqlServerComponent.cs` and `eng\src\Docker\PostgreSqlComponent.cs`. ### Running locally @@ -202,8 +210,8 @@ describes. The script also serves a developer machine: it starts the database se dotnet run --project src\SharpCrafters.Backstage.LicenseServer.Web ``` -The development configuration uses a SQLite database, created at the first start, so no SQL Server is -required. Once the server runs, `/Admin/GenerateDemoData` fills the database with simulated activity. +The development configuration uses a SQLite database, created at the first start, so no database +server is required. Once the server runs, `/Admin/GenerateDemoData` fills the database with simulated activity. That page exists only in the Development environment. ### Repository layout @@ -212,7 +220,7 @@ That page exists only in the Development environment. |---|---| | `src\SharpCrafters.Backstage.LicenseServer.Core` | The licensing rules, the database model, and the services they depend on. | | `src\SharpCrafters.Backstage.LicenseServer.Web` | The web application: the pages, the endpoints and the composition root. | -| `tests\SharpCrafters.Backstage.LicenseServer.Tests` | The test suite. It runs on SQLite by default, and on SQL Server when the run is given one. | +| `tests\SharpCrafters.Backstage.LicenseServer.Tests` | The test suite. It runs on SQLite by default, and on SQL Server or PostgreSQL when the run is given one. | | `eng` | The product definition and the version files that PostSharp.Engineering builds from. | To put a server under load, use `LicenseServerLoadSimulator`, in the SharpCrafters.Backstage diff --git a/docs/configuration.md b/docs/configuration.md index 3c68ca3..9200c23 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -50,7 +50,7 @@ The `appsettings.json` of the release package lists most of these settings with | Setting | Default | Meaning | |---|---|---| -| `LicenseServer:DatabaseProvider` | `SqlServer` | The database engine: `SqlServer` or `Sqlite`. | +| `LicenseServer:DatabaseProvider` | `SqlServer` | The database engine: `SqlServer`, `PostgreSql` or `Sqlite`. | | `ConnectionStrings:SharpCrafters_LicenseServerConnectionString` | a local SQL Server | The database to connect to. | The two settings are set together. The connection string is interpreted by the engine named in @@ -65,6 +65,17 @@ The two settings are set together. The connection string is interpreted by the e } ``` +For PostgreSQL: + +```json +{ + "LicenseServer": { "DatabaseProvider": "PostgreSql" }, + "ConnectionStrings": { + "SharpCrafters_LicenseServerConnectionString": "Host=db.example.com;Port=5432;Database=postsharplicenseserver;Username=licenseserver;Password=..." + } +} +``` + For SQLite: ```json @@ -76,12 +87,19 @@ For SQLite: } ``` -SQL Server is the engine supported in production. Create the schema by running -`Database\CreateTables.sql`. The server never creates the schema and never modifies it, so an upgrade -of the server makes no change to the database. +SQL Server and PostgreSQL are the engines supported in production. Create the schema by running +`Database\CreateTables.sql` on SQL Server, or `Database\CreateTables.PostgreSql.sql` on PostgreSQL. +The server never creates the schema and never modifies it, so an upgrade of the server makes no change +to the database. + +The PostgreSQL schema needs PostgreSQL 14 or later, and it creates a collation of its own. The +comparison of a user name and of a machine name ignores the case on SQL Server, whose default +collation ignores it, and PostgreSQL offers no such collation, so the script creates one from the +International Components for Unicode. A server built without them refuses the script. SQLite is supported for tests and for evaluation. The test suite of this repository runs on SQLite by -default, and so does the development configuration of the web project. The database file is created at the +default, and it runs again against SQL Server and against PostgreSQL. The development configuration of +the web project uses SQLite. The database file is created at the first start. A relative path is resolved against the application directory, not against the working directory of the process. Do not use SQLite for a server that serves a team: SQLite accepts one writer at a time, and every lease request writes. @@ -221,10 +239,15 @@ The lock is held by the database and not by the process. Several worker processe therefore serialized against each other, which covers a web garden of Internet Information Services, several containers, and two servers that share one database. -The mechanism depends on the database engine, and there is no setting to choose it. On SQL Server, -the server calls `sp_getapplock` at the beginning of the request and `sp_releaseapplock` at the end -of it. The lock belongs to the session, which is the connection of the request. It requires no -permission beyond the ones the server already needs on its database. +The mechanism depends on the database engine, and there is no setting to choose it. + +On SQL Server, the server calls `sp_getapplock` at the beginning of the request and +`sp_releaseapplock` at the end of it. The lock belongs to the session, which is the connection of the +request. It requires no permission beyond the ones the server already needs on its database. + +On PostgreSQL, the server takes an advisory lock with `pg_advisory_lock` and releases it with +`pg_advisory_unlock`. That lock also belongs to the session. The wait is bounded by `lock_timeout`, +which the server sets on the connection from `MutexTimeout`. On SQLite, the server opens the transaction of the request with `BEGIN IMMEDIATE`, which takes the write lock of the database file at once. SQLite accepts one writer at a time, so the next request diff --git a/docs/docker.md b/docs/docker.md index c532dd8..9ac465a 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -120,7 +120,14 @@ docker run --rm -p 8080:8080 \ Every setting of [configuration.md](configuration.md) can be given as an environment variable, with a double underscore where the name of the setting contains a colon. -For an evaluation without a SQL Server, the server can store its data in a SQLite file, which it +Against an existing PostgreSQL, name the engine as well, because the connection string is interpreted +by the engine that `DatabaseProvider` names: + +``` +docker run --rm -p 8080:8080 -e LicenseServer__DatabaseProvider=PostgreSql -e ConnectionStrings__SharpCrafters_LicenseServerConnectionString="Host=db;Port=5432;Database=postsharplicenseserver;Username=licenseserver;Password=..." -e Authentication__Scheme=None -v licenseserver-data:/app/App_Data backstage-licenseserver +``` + +For an evaluation without a database server, the server can store its data in a SQLite file, which it creates itself: ``` @@ -131,7 +138,8 @@ docker run --rm -p 8080:8080 \ backstage-licenseserver ``` -SQL Server is the engine supported for a real installation. +SQL Server and PostgreSQL are the engines supported for a real installation. Create the schema before +the first start, as [configuration.md](configuration.md) describes: the server never creates it. ## The image diff --git a/eng/TestDatabase.ps1 b/eng/TestDatabase.ps1 new file mode 100644 index 0000000..6e34689 --- /dev/null +++ b/eng/TestDatabase.ps1 @@ -0,0 +1,304 @@ +#Requires -Version 7 + +<# +.SYNOPSIS + Runs the test suite against SQL Server or PostgreSQL. + +.DESCRIPTION + The suite runs on SQLite by default, which needs no server. This script runs the same tests again + against an engine that customers deploy. The engines differ in the collation, in the column types + and in the lock that serializes the lease requests, so each one is run in full. + + The script takes the first server it finds: + + 1. The connection string in LICENSESERVER_TEST_SQLSERVER or LICENSESERVER_TEST_POSTGRESQL, when it + is already set. + 2. A server installed in the image the script runs in, which the continuous integration build + uses. The script starts it and stops it. + 3. A container it starts itself, which is the shortest path on a developer machine. The container + is left running. + + The connection string names no database. Each test receives a database of its own, created from + Database\CreateTables.sql or from Database\CreateTables.PostgreSql.sql. + +.PARAMETER Engine + SqlServer or PostgreSql. + +.PARAMETER Password + The password of the administrative login, which applies to SQL Server and to the PostgreSQL + container. It is read from MSSQL_SA_PASSWORD or POSTGRES_PASSWORD when the parameter is omitted, + and a password is generated when neither is given. + +.PARAMETER StartOnly + Starts the server, prints the connection string, and runs no test. Use it to keep a server for + the runs an editor starts. + +.PARAMETER TimeoutInSeconds + How long to wait for the server to accept a query. + +.PARAMETER BuildArguments + The arguments that are passed on to Build.ps1 test. +#> + +[CmdletBinding()] +param( + [Parameter( Mandatory = $true )] + [ValidateSet( 'SqlServer', 'PostgreSql' )] + [string] $Engine, + + [string] $Password, + + [switch] $StartOnly, + + [int] $TimeoutInSeconds = 180, + + [Parameter( ValueFromRemainingArguments = $true )] + [string[]] $BuildArguments = @() +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repositoryDirectory = Split-Path -Parent $PSScriptRoot + +# The address is written as 127.0.0.1 and not as localhost. On a host that resolves localhost to an +# address of version 6 first, the client reaches nothing, because the port of a container is published +# on version 4. +$address = '127.0.0.1' + +$sqlServerExecutable = '/opt/mssql/bin/sqlservr' +$sqlCmd = '/opt/mssql-tools18/bin/sqlcmd' +$sqlServerContainer = 'licenseserver-test-sqlserver' +$postgreSqlContainer = 'licenseserver-test-postgres' +$postgreSqlData = '/tmp/licenseserver-postgres' + +# A container of this script publishes its port beside the default one, so that a license server +# deployed on this machine with docker-compose.yml keeps the default port for itself. A server +# installed in an image listens on the default port, because it is alone in its container. +$sqlServerContainerPort = 14330 +$postgreSqlContainerPort = 55432 + +# How Docker is reached, which Initialize-Docker resolves at the first call. +$script:dockerCommand = $null + +<# +.SYNOPSIS + Resolves how Docker is reached, and returns the command and the arguments that precede every + Docker argument. +.DESCRIPTION + A Windows machine can run the Docker engine inside a distribution of the Windows Subsystem for + Linux rather than under Docker Desktop. The client of the distribution is then the only one that + reaches a daemon, so the script calls Docker through wsl. A relative path is used with it, because + wsl starts in the translated form of the current directory and would not understand a path that + names a Windows drive. +#> +function Initialize-Docker { + if ( $script:dockerCommand ) { + return + } + + if ( Get-Command docker -ErrorAction SilentlyContinue ) { + & docker version --format '{{.Server.Version}}' 2>&1 | Out-Null + + if ( $LASTEXITCODE -eq 0 ) { + $script:dockerCommand = @( 'docker' ) + + return + } + } + + if ( Get-Command wsl -ErrorAction SilentlyContinue ) { + & wsl -e docker version --format '{{.Server.Version}}' 2>&1 | Out-Null + + if ( $LASTEXITCODE -eq 0 ) { + Write-Host 'Reaching Docker through the Windows Subsystem for Linux.' + $script:dockerCommand = @( 'wsl', '-e', 'docker' ) + + return + } + } + + throw 'No Docker daemon was reached. Start Docker, or set the connection string of a server of your own.' +} + +function Invoke-Docker { + Initialize-Docker + + $command = $script:dockerCommand[0] + $arguments = @( $script:dockerCommand | Select-Object -Skip 1 ) + $args + + & $command @arguments +} + +function Wait-ForServer( [scriptblock] $Query, [int] $Timeout ) { + $deadline = (Get-Date).AddSeconds( $Timeout ) + + while ( (Get-Date) -lt $deadline ) { + & $Query 2>&1 | Out-Null + + if ( $LASTEXITCODE -eq 0 ) { + return + } + + Start-Sleep -Seconds 2 + } + + throw "The server did not accept a query within $Timeout seconds." +} + +function New-Password { + # The server is reachable from this machine alone, and it is deleted with the container, but it + # still refuses a password that does not meet its complexity rules. + return 'Lease-' + [System.Guid]::NewGuid().ToString( 'N' ).Substring( 0, 12 ) + '-1' +} + +$serverProcess = $null +$startedLocalCluster = $false + +# Docker is called with a relative path, so the script works from the directory of the repository. +Push-Location $repositoryDirectory + +try { + if ( $Engine -eq 'SqlServer' ) { + if ( -not $env:LICENSESERVER_TEST_SQLSERVER ) { + if ( -not $Password ) { + $Password = if ( $env:MSSQL_SA_PASSWORD ) { $env:MSSQL_SA_PASSWORD } else { New-Password } + } + + # A server installed in the image listens on the default port; a container of this script + # publishes another one. + $port = 1433 + + if ( Test-Path $sqlServerExecutable ) { + Write-Host 'Starting the SQL Server of this image.' + + $env:ACCEPT_EULA = 'Y' + $env:MSSQL_SA_PASSWORD = $Password + $env:MSSQL_PID = 'Developer' + + # The server refuses to run as root, and the image runs the build as root, so the + # server runs under the account its own package creates. + $serverProcess = Start-Process -FilePath 'runuser' ` + -ArgumentList '-u', 'mssql', '--', $sqlServerExecutable ` + -PassThru -NoNewWindow + + Wait-ForServer { & $sqlCmd -S $address -U sa -P $Password -C -b -Q 'SELECT 1' } $TimeoutInSeconds + } + else { + Write-Host 'Starting a SQL Server container.' + + # The container carries no volume and is replaced at every run. The database service + # of docker-compose.yml is left alone: it does carry a volume, and SQL Server sets the + # password of sa when it creates its files, so a second run with another password + # would be refused by the server it started the first time. + Invoke-Docker rm --force $sqlServerContainer 2>&1 | Out-Null + + Invoke-Docker run --detach --name $sqlServerContainer ` + --env ACCEPT_EULA=Y --env MSSQL_SA_PASSWORD=$Password --env MSSQL_PID=Developer ` + --publish "${sqlServerContainerPort}:1433" mcr.microsoft.com/mssql/server:2022-latest + + if ( $LASTEXITCODE -ne 0 ) { + throw 'The SQL Server container did not start.' + } + + Wait-ForServer { + Invoke-Docker exec $sqlServerContainer ` + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P $Password -C -b -Q 'SELECT 1' + } $TimeoutInSeconds + + $port = $sqlServerContainerPort + } + + $env:LICENSESERVER_TEST_SQLSERVER = + "Server=$address,$port;User Id=sa;Password=$Password;TrustServerCertificate=True;Encrypt=False" + } + + Write-Host "The connection string is in LICENSESERVER_TEST_SQLSERVER." + } + else { + if ( -not $env:LICENSESERVER_TEST_POSTGRESQL ) { + if ( $env:PGBINDIR -and (Test-Path "$env:PGBINDIR/initdb") ) { + Write-Host 'Starting the PostgreSQL server of this image.' + + # The package installs the server but starts no cluster, so the script creates one of + # its own. Both connection methods trust the client: the cluster lives in a container, + # it listens on the loopback address alone, and it is deleted with the container. + New-Item -ItemType Directory -Force -Path $postgreSqlData | Out-Null + & chown postgres $postgreSqlData + + & runuser -u postgres -- "$env:PGBINDIR/initdb" --pgdata=$postgreSqlData ` + --username=postgres --auth-local=trust --auth-host=trust + + if ( $LASTEXITCODE -ne 0 ) { + throw 'The PostgreSQL cluster could not be created.' + } + + $startedLocalCluster = $true + + & runuser -u postgres -- "$env:PGBINDIR/pg_ctl" --pgdata=$postgreSqlData ` + --log=/tmp/postgres.log --options="-c listen_addresses=$address" --wait start + + if ( $LASTEXITCODE -ne 0 ) { + throw 'The PostgreSQL cluster did not start.' + } + + Wait-ForServer { & runuser -u postgres -- "$env:PGBINDIR/pg_isready" -h $address } $TimeoutInSeconds + + $env:LICENSESERVER_TEST_POSTGRESQL = "Host=$address;Port=5432;Username=postgres" + } + else { + Write-Host 'Starting a PostgreSQL container.' + + if ( -not $Password ) { + $Password = if ( $env:POSTGRES_PASSWORD ) { $env:POSTGRES_PASSWORD } else { New-Password } + } + + Invoke-Docker rm --force $postgreSqlContainer 2>&1 | Out-Null + + Invoke-Docker run --detach --name $postgreSqlContainer ` + --env POSTGRES_PASSWORD=$Password ` + --publish "${postgreSqlContainerPort}:5432" postgres:17 + + if ( $LASTEXITCODE -ne 0 ) { + throw 'The PostgreSQL container did not start.' + } + + Wait-ForServer { Invoke-Docker exec $postgreSqlContainer pg_isready -U postgres } $TimeoutInSeconds + + $env:LICENSESERVER_TEST_POSTGRESQL = + "Host=$address;Port=$postgreSqlContainerPort;Username=postgres;Password=$Password" + } + } + + Write-Host "The connection string is in LICENSESERVER_TEST_POSTGRESQL." + } + + if ( $StartOnly ) { + Write-Host 'The server is running, and no test was run.' + + return + } + + Write-Host "Running the tests against $Engine." + + & "$repositoryDirectory/Build.ps1" test @BuildArguments + + if ( $LASTEXITCODE -ne 0 ) { + throw "The tests failed with the exit code $LASTEXITCODE." + } +} +finally { + if ( $serverProcess ) { + Write-Host 'Stopping SQL Server.' + Stop-Process -InputObject $serverProcess -ErrorAction SilentlyContinue + } + + if ( $startedLocalCluster -and -not $StartOnly ) { + Write-Host 'Stopping PostgreSQL.' + + & runuser -u postgres -- "$env:PGBINDIR/pg_ctl" --pgdata=$postgreSqlData ` + --mode=immediate stop 2>&1 | Out-Null + } + + Pop-Location +} diff --git a/eng/TestSqlServer.ps1 b/eng/TestSqlServer.ps1 deleted file mode 100644 index 2088b66..0000000 --- a/eng/TestSqlServer.ps1 +++ /dev/null @@ -1,130 +0,0 @@ -#Requires -Version 7 - -<# -.SYNOPSIS - Runs the test suite against SQL Server. - -.DESCRIPTION - The suite runs on SQLite by default, which needs no server. This script runs the same tests a - second time against SQL Server, the engine that customers use. The two engines differ in the - collation, in the column types and in the lock that serializes the lease requests. - - The script takes the first SQL Server it finds: - - 1. The connection string in LICENSESERVER_TEST_SQLSERVER, when it is already set. - 2. A SQL Server installed in the image the script runs in, which the continuous integration - build uses. The script starts it and stops it. - 3. The database service of docker-compose.yml, which is the shortest path on a developer - machine. The script starts it and leaves it running. - - The connection string names no database. Each test receives a database of its own, created from - Database\CreateTables.sql. - -.PARAMETER Password - The password of the sa login. It is read from MSSQL_SA_PASSWORD when the parameter is omitted, - and a password is generated when neither is given. - -.PARAMETER TimeoutInSeconds - How long to wait for the server to accept a query. - -.PARAMETER BuildArguments - The arguments that are passed on to Build.ps1 test. -#> - -[CmdletBinding()] -param( - [string] $Password = $env:MSSQL_SA_PASSWORD, - [int] $TimeoutInSeconds = 180, - - # Passed on to Build.ps1 test, for instance --configuration Public. - [Parameter( ValueFromRemainingArguments = $true )] - [string[]] $BuildArguments = @() -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -$repositoryDirectory = Split-Path -Parent $PSScriptRoot -$sqlServerExecutable = '/opt/mssql/bin/sqlservr' -$sqlCmd = '/opt/mssql-tools18/bin/sqlcmd' - -function Wait-ForServer( [scriptblock] $Query, [int] $Timeout ) { - $deadline = (Get-Date).AddSeconds( $Timeout ) - - while ( (Get-Date) -lt $deadline ) { - & $Query 2>&1 | Out-Null - - if ( $LASTEXITCODE -eq 0 ) { - return - } - - Start-Sleep -Seconds 2 - } - - throw "SQL Server did not accept a query within $Timeout seconds." -} - -$serverProcess = $null - -try { - if ( $env:LICENSESERVER_TEST_SQLSERVER ) { - Write-Host 'Using the SQL Server named by LICENSESERVER_TEST_SQLSERVER.' - } - else { - if ( -not $Password ) { - # The server is reachable from this machine alone, and it is deleted with the container, - # but it still refuses a password that does not meet its complexity rules. - $Password = 'Lease-' + [System.Guid]::NewGuid().ToString( 'N' ).Substring( 0, 12 ) + '-1' - } - - if ( Test-Path $sqlServerExecutable ) { - Write-Host 'Starting the SQL Server of this image.' - - $env:ACCEPT_EULA = 'Y' - $env:MSSQL_SA_PASSWORD = $Password - $env:MSSQL_PID = 'Developer' - - # The server refuses to run as root, and the image runs the build as root, so the server - # runs under the account its own package creates. - $serverProcess = Start-Process -FilePath 'runuser' ` - -ArgumentList '-u', 'mssql', '--', $sqlServerExecutable ` - -PassThru -NoNewWindow - - Wait-ForServer { & $sqlCmd -S 127.0.0.1 -U sa -P $Password -C -b -Q 'SELECT 1' } $TimeoutInSeconds - } - else { - Write-Host 'Starting the database service of docker-compose.yml.' - - $env:MSSQL_SA_PASSWORD = $Password - - & docker compose --file "$repositoryDirectory/docker-compose.yml" up --detach database - - if ( $LASTEXITCODE -ne 0 ) { - throw 'The database service did not start.' - } - - Wait-ForServer { - & docker compose --file "$repositoryDirectory/docker-compose.yml" exec -T database ` - /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P $Password -C -b -Q 'SELECT 1' - } $TimeoutInSeconds - } - - # The address is written as 127.0.0.1 and not as localhost. On a host that resolves localhost - # to an address of version 6 first, the client reaches nothing, because the port is published - # on version 4. - $env:LICENSESERVER_TEST_SQLSERVER = - "Server=127.0.0.1,1433;User Id=sa;Password=$Password;TrustServerCertificate=True;Encrypt=False" - } - - & "$repositoryDirectory/Build.ps1" test @BuildArguments - - if ( $LASTEXITCODE -ne 0 ) { - throw "The tests failed with the exit code $LASTEXITCODE." - } -} -finally { - if ( $serverProcess ) { - Write-Host 'Stopping SQL Server.' - Stop-Process -InputObject $serverProcess -ErrorAction SilentlyContinue - } -} diff --git a/eng/docker/postgresql-build.Dockerfile b/eng/docker/postgresql-build.Dockerfile new file mode 100644 index 0000000..8731ba9 --- /dev/null +++ b/eng/docker/postgresql-build.Dockerfile @@ -0,0 +1,72 @@ +# This file is auto-generated by PostSharp.Engineering. + +# See the OS_IMAGE_REPOSITORY comment in the Windows branch: the base image is a build-arg so +# that DockerBuild.ps1 can redirect it to a registry-local mirror without changing this file. +ARG OS_IMAGE=ubuntu:22.04 +FROM ${OS_IMAGE} + +# Prepare environment +ENV DEBIAN_FRONTEND=noninteractive +ENV RUNNING_IN_DOCKER=TRUE + +# Set locale for consistent behavior regardless of host locale +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 +ENV DOTNET_CLI_UI_LANGUAGE=en + +# Install the prerequisites shared by the other components +RUN apt-get update && apt-get install -y \ + curl \ + wget \ + ca-certificates \ + libicu70 \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + + + +# Install Git +RUN apt-get update && apt-get install -y git \ + && rm -rf /var/lib/apt/lists/* + +RUN git config --system core.longpaths true && git config --system core.autocrlf false + + +# Install PowerShell 7 +RUN wget -q https://github.com/PowerShell/PowerShell/releases/download/v7.5.2/powershell_7.5.2-1.deb_amd64.deb \ + && dpkg -i powershell_7.5.2-1.deb_amd64.deb \ + && rm powershell_7.5.2-1.deb_amd64.deb + + +# Download .NET Installer +RUN curl -sSL https://dot.net/v1/dotnet-install.sh -o /usr/local/bin/dotnet-install.sh \ + && chmod +x /usr/local/bin/dotnet-install.sh + +ENV DOTNET_ROOT=/usr/share/dotnet +ENV PATH="${DOTNET_ROOT}:${PATH}" + + +# Install .NET Sdk 10.0.102 +RUN /usr/local/bin/dotnet-install.sh --version 10.0.102 --install-dir $DOTNET_ROOT + + +# .NET Dump Tool +RUN dotnet tool install --global dotnet-dump + +ENV PATH="/root/.dotnet/tools:${PATH}" + + +# Install PostgreSQL 17 +RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc -o /etc/apt/trusted.gpg.d/postgresql.asc \ + && echo "deb http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ + && apt-get update \ + && apt-get install -y postgresql-17 \ + && rm -rf /var/lib/apt/lists/* + +ENV PGBINDIR=/usr/lib/postgresql/17/bin +ENV PATH="${PGBINDIR}:${PATH}" + + +# Epilogue +# Configure .NET SDK +ENV DOTNET_NOLOGO=1 diff --git a/eng/src/Docker/PostgreSqlComponent.cs b/eng/src/Docker/PostgreSqlComponent.cs new file mode 100644 index 0000000..baf3302 --- /dev/null +++ b/eng/src/Docker/PostgreSqlComponent.cs @@ -0,0 +1,65 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using PostSharp.Engineering.BuildTools.Docker; +using System; +using System.IO; + +namespace BuildBackstageLicenseServer.Docker; + +/// +/// Installs PostgreSQL into a Linux image, so that the image can run the test suite against the second +/// engine supported in production. eng/TestDatabase.ps1 creates the cluster and starts it. +/// +/// +/// The packages come from the repository of the PostgreSQL project and not from Ubuntu, whose +/// repository carries the version that shipped with the distribution. The tests therefore run against +/// a version a customer can deploy today. +/// +internal sealed class PostgreSqlComponent : ContainerComponent +{ + /// + /// The major version. It is part of the path of the executables, which + /// eng/TestDatabase.ps1 reads from PGBINDIR. + /// + public const string Version = "17"; + + public override string Name => $"Install PostgreSQL {Version}"; + + public override string Key => $"{nameof(PostgreSqlComponent)}:{Version}"; + + /// + /// Gets the kind of this component. The enumeration belongs to PostSharp.Engineering and cannot be + /// extended from here, so this component takes the value of the nearest standard component, which + /// is another external tool installed into the image, and places itself with . + /// + public override ContainerComponentKind Kind => ContainerComponentKind.AzureCli; + + /// + /// Gets the position of this component, which is just before the epilogue. The standard components + /// therefore keep their place, and a change to this one invalidates no layer of theirs. + /// + public override int SortOrder => ((int) ContainerComponentKind.Epilogue * 100) - 50; + + public override void WriteDockerfile( TextWriter writer, ContainerOperatingSystem operatingSystem ) + { + if ( operatingSystem != ContainerOperatingSystem.Linux ) + { + throw new InvalidOperationException( "PostgreSQL is installed into a Linux image only." ); + } + + // jammy is Ubuntu 22.04, the base image of a Linux chain. The server is installed but no + // cluster is started here: a container image runs no service, and the test script creates a + // cluster of its own under /tmp. + writer.WriteLine( + $$""" + RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc -o /etc/apt/trusted.gpg.d/postgresql.asc \ + && echo "deb http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ + && apt-get update \ + && apt-get install -y postgresql-{{Version}} \ + && rm -rf /var/lib/apt/lists/* + + ENV PGBINDIR=/usr/lib/postgresql/{{Version}}/bin + ENV PATH="${PGBINDIR}:${PATH}" + """ ); + } +} diff --git a/eng/src/Program.cs b/eng/src/Program.cs index 024528c..d871922 100644 --- a/eng/src/Program.cs +++ b/eng/src/Program.cs @@ -29,25 +29,21 @@ Components = [new DotNetComponent( dotNetSdkVersion, DotNetComponentKind.Sdk )] }, - // The image of the SQL Server test run. It is a Linux image, because Microsoft publishes no SQL Server for a - // Windows container after the 2019 version, and it carries the server itself rather than starting a second - // container, which a build step running inside a container cannot do without the Docker socket of the agent. + // The image of each database test run. Both are Linux images: Microsoft publishes no SQL Server for a Windows + // container after the 2019 version, and PostgreSQL is deployed on Linux. Each image carries its server rather + // than starting a second container, which a build step running inside a container cannot do without the + // Docker socket of the agent. AdditionalDockerfiles = [ - new AdditionalDockerfile( SqlServerTests.DockerfileName, [] ) - { - Requirements = new ContainerRequirements( ContainerHostKind.Linux ) - { - OperatingSystem = ContainerOperatingSystem.Linux, - Components = [new DotNetComponent( dotNetSdkVersion, DotNetComponentKind.Sdk ), new SqlServerComponent()] - } - } + DatabaseTests.SqlServer.Dockerfile( dotNetSdkVersion ), + DatabaseTests.PostgreSql.Dockerfile( dotNetSdkVersion ) ], - // The second test run. The build itself runs the suite on SQLite, which needs no server; this configuration - // runs the same tests against SQL Server, and it is what proves the lock, the collation and the column types - // of the engine that customers use. See eng/TestSqlServer.ps1 and docs in README.md. - AdditionalCiBuildConfigurations = [SqlServerTests.Configuration], + // The test runs that need a database server. The build itself runs the suite on SQLite, which needs no + // server; these configurations run the same tests against the two engines that customers deploy, and they + // are what proves the lock, the collation and the column types of each one. See eng/TestDatabase.ps1 and the + // section "Running the tests" of README.md. + AdditionalCiBuildConfigurations = [DatabaseTests.SqlServer.Configuration, DatabaseTests.PostgreSql.Configuration], // Built rather than packed: the product ships a deployable archive and no NuGet package, so the Pack // target of every project would be a no-op. @@ -78,22 +74,43 @@ return new EngineeringApp( product ).Run( args ); /// -/// The continuous integration configuration that runs the test suite against SQL Server, in the image that -/// carries the server. +/// The continuous integration configurations that run the test suite against a database server, each one in the +/// image that carries its server. /// -internal static class SqlServerTests +internal static class DatabaseTests { - /// - /// The name of the additional image. PostSharp.Engineering writes the Dockerfile of its build layer to - /// eng/docker/{name}-build.Dockerfile. - /// - public const string DockerfileName = "sqlserver"; + public static DatabaseTestRun SqlServer { get; } = new( "sqlserver", "SqlServer", "SQL Server", new SqlServerComponent() ); + + public static DatabaseTestRun PostgreSql { get; } = new( "postgresql", "PostgreSql", "PostgreSQL", new PostgreSqlComponent() ); +} + +/// +/// One such configuration, with the image it runs in. +/// +/// +/// The name of the image. PostSharp.Engineering writes the Dockerfile of its build layer to +/// eng/docker/{name}-build.Dockerfile. +/// +/// The name of the engine, as the parameter of eng/TestDatabase.ps1 spells it. +/// The name of the engine, as a person writes it. +/// The component that installs the server into the image. +internal sealed class DatabaseTestRun( string name, string engine, string displayName, ContainerComponent server ) +{ + public AdditionalDockerfile Dockerfile( string dotNetSdkVersion ) + => new( name, [] ) + { + Requirements = new ContainerRequirements( ContainerHostKind.Linux ) + { + OperatingSystem = ContainerOperatingSystem.Linux, + Components = [new DotNetComponent( dotNetSdkVersion, DotNetComponentKind.Sdk ), server] + } + }; - public static PowershellAdditionalCiBuildConfiguration Configuration { get; } = - new( "SqlServerTests", "Tests on SQL Server", "./eng/TestSqlServer.ps1", "" ) + public PowershellAdditionalCiBuildConfiguration Configuration + => new( $"{engine}Tests", $"Tests on {displayName}", "./eng/TestDatabase.ps1", $"-Engine {engine}" ) { BuildAgentRequirements = LinuxContainerHost, - Dockerfile = $"eng/docker/{DockerfileName}-build.Dockerfile", + Dockerfile = $"eng/docker/{name}-build.Dockerfile", BuildSnapshotDependency = BuildConfiguration.Debug }; diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs index 836281e..e7f0b55 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs @@ -4,13 +4,15 @@ namespace SharpCrafters.Backstage.LicenseServer.Data; /// -/// The license server database. The provider is chosen by the hosting application: SQL Server in -/// production, SQLite in tests. +/// The license server database. The hosting application chooses the provider: SQL Server or +/// PostgreSQL in production, SQLite for an evaluation and for the tests. /// /// /// The model maps exactly onto the schema that CreateTables.sql creates, so that an existing /// deployment is upgraded without any work on the database. The project contains no EF migration. -/// SchemaCompatibilityTests verifies the compatibility of the model with the schema. +/// SchemaCompatibilityTests verifies the compatibility of the model with the schema, and a run +/// against SQL Server or PostgreSQL creates every test database from the script of that engine, which +/// verifies the same thing against a live server. /// public class LicenseServerDbContext( DbContextOptions options ) : DbContext( options ) { @@ -36,14 +38,26 @@ protected override void OnModelCreating( ModelBuilder modelBuilder ) bool isSqlServer = this.Database.ProviderName == "Microsoft.EntityFrameworkCore.SqlServer"; - // SQL Server returns DateTimeKind.Unspecified. Lease.Write serializes the timestamps as UTC, - // and XmlConvert treats an Unspecified value as a local time, which shifted every exported - // timestamp by the offset of the server. The converter sets the kind when a row is read. + // Every timestamp of this database is UTC, and the converter states it in both directions. + // + // On the way out: SQL Server returns DateTimeKind.Unspecified. Lease.Write serializes the + // timestamps as UTC, and XmlConvert treats an Unspecified value as a local time, which shifted + // every exported timestamp by the offset of the server. + // + // On the way in: PostgreSQL stores these columns as 'timestamp with time zone', and Npgsql + // refuses a value whose kind is Unspecified. A date built from a year and a month, which is + // what the export takes from its query string, is such a value. The kind is set and not + // converted, so the value written is the value the caller gave, which is what SQL Server and + // SQLite have always stored. ValueConverter toUtc = - new( v => v, v => DateTime.SpecifyKind( v, DateTimeKind.Utc ) ); + new( + v => DateTime.SpecifyKind( v, DateTimeKind.Utc ), + v => DateTime.SpecifyKind( v, DateTimeKind.Utc ) ); ValueConverter toUtcNullable = - new( v => v, v => v.HasValue ? DateTime.SpecifyKind( v.Value, DateTimeKind.Utc ) : null ); + new( + v => v.HasValue ? DateTime.SpecifyKind( v.Value, DateTimeKind.Utc ) : null, + v => v.HasValue ? DateTime.SpecifyKind( v.Value, DateTimeKind.Utc ) : null ); foreach ( var property in modelBuilder.Model.GetEntityTypes().SelectMany( e => e.GetProperties() ) ) { @@ -75,11 +89,25 @@ protected override void OnModelCreating( ModelBuilder modelBuilder ) } else { - // The default collation of SQL Server ignores the case, and the default collation of - // SQLite does not. Without this collation, grouping the leases by user name would behave - // differently in the tests and in production. - modelBuilder.Entity().Property( x => x.UserName ).UseCollation( "NOCASE" ); - modelBuilder.Entity().Property( x => x.Machine ).UseCollation( "NOCASE" ); + // The default collation of SQL Server ignores the case. The default collation of SQLite + // and of PostgreSQL does not. Without a collation that ignores the case, grouping the + // leases by user name would behave differently from one engine to the next, and a user + // who signed in under two spellings would hold two sets of machines. + // + // SQLite carries NOCASE. PostgreSQL carries no collation that ignores the case, so the + // schema creates one. + string collation = this.Database.ProviderName == "Npgsql.EntityFrameworkCore.PostgreSQL" + ? CaseInsensitiveCollation + : "NOCASE"; + + modelBuilder.Entity().Property( x => x.UserName ).UseCollation( collation ); + modelBuilder.Entity().Property( x => x.Machine ).UseCollation( collation ); } } + + /// + /// The name of the PostgreSQL collation that ignores the case. CreateTables.PostgreSql.sql + /// creates it, and so does a test database, because PostgreSQL defines no such collation itself. + /// + public const string CaseInsensitiveCollation = "license_server_ci"; } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Database/CreateTables.PostgreSql.sql b/src/SharpCrafters.Backstage.LicenseServer.Web/Database/CreateTables.PostgreSql.sql new file mode 100644 index 0000000..f9e28e3 --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Database/CreateTables.PostgreSql.sql @@ -0,0 +1,56 @@ +-- The schema of the license server on PostgreSQL. Run it once, against an empty database, before you +-- start the server for the first time. The server never creates the schema and never modifies it, so +-- an upgrade of the server makes no change to the database. +-- +-- The names of the tables and of the columns are quoted, so they keep their capitals. The queries of +-- the server quote them the same way. +-- +-- This schema has no HMAC column. The SQL Server schema has one, because installations created before +-- 2026 have it and the column is left in place there. Nothing writes it. + +-- The comparison of a user name and of a machine name ignores the case, as it does on SQL Server, +-- whose default collation ignores it. A collation that is not deterministic is what makes a +-- comparison and a grouping ignore the case. It refuses the pattern operators, and no query of this +-- server applies one to these two columns. +CREATE COLLATION IF NOT EXISTS "license_server_ci" ( + provider = icu, locale = 'und-u-ks-level2', deterministic = false ); + +CREATE TABLE "Licenses" ( + -- The identifier comes from the license key and is not generated by the database. + "LicenseId" integer NOT NULL, + "LicenseKey" text NOT NULL, + "ProductCode" character varying(50) NOT NULL, + "Priority" integer NOT NULL, + "CreatedOn" timestamp with time zone NOT NULL, + "GraceStartTime" timestamp with time zone, + "GraceLastWarningTime" timestamp with time zone, + CONSTRAINT "PK_Licenses" PRIMARY KEY ("LicenseId") +); + +CREATE TABLE "Leases" ( + "LeaseId" integer GENERATED BY DEFAULT AS IDENTITY, + -- The lease this one replaces. Prolonging a lease and cancelling one both insert a row, so the + -- table is the audit log and a row is never updated and never deleted. + "OverwrittenLeaseId" integer, + "LicenseId" integer NOT NULL, + "StartTime" timestamp with time zone NOT NULL, + "EndTime" timestamp with time zone NOT NULL, + "UserName" character varying(200) COLLATE "license_server_ci" NOT NULL, + "Machine" character varying(200) COLLATE "license_server_ci" NOT NULL, + "AuthenticatedUser" character varying(200) NOT NULL, + "Grace" boolean NOT NULL, + CONSTRAINT "PK_Leases" PRIMARY KEY ("LeaseId"), + -- The deletion of a license must not cascade through the chain of replaced leases, so both keys + -- restrict instead of cascading. The page that deletes a license deletes the leases itself, the + -- most recent one first. + CONSTRAINT "FK_Leases_Leases" FOREIGN KEY ("OverwrittenLeaseId") REFERENCES "Leases" ("LeaseId") ON DELETE RESTRICT, + CONSTRAINT "FK_Leases_Licenses" FOREIGN KEY ("LicenseId") REFERENCES "Licenses" ("LicenseId") ON DELETE RESTRICT +); + +-- The index a lease request uses: it reads the leases that are open at the current time. +CREATE INDEX "IX_Leases_EndTime" ON "Leases" ("EndTime"); + +CREATE INDEX "IX_Leases_LicenseId" ON "Leases" ("LicenseId"); + +-- The index of the anti-join that finds the leases nothing has replaced. +CREATE INDEX "IX_Leases_OverwrittenLeaseId" ON "Leases" ("OverwrittenLeaseId"); diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs index 759649d..823924b 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs @@ -19,9 +19,9 @@ public static class DatabaseRegistration /// requests of that engine. /// /// - /// SQL Server is the engine supported in production. SQLite is supported so that the server can - /// be evaluated and developed against without a server, and so that the test suite can run - /// against a database held in memory. + /// SQL Server and PostgreSQL are the engines supported in production. SQLite is supported so that + /// the server can be evaluated and developed against without a database server, and so that the + /// test suite can run against a database held in memory. /// public static IServiceCollection AddLicenseServerDatabase( this IServiceCollection services, @@ -46,6 +46,12 @@ public static IServiceCollection AddLicenseServerDatabase( break; + case "postgresql": + services.AddDbContext( options => options.UseNpgsql( connectionString ) ); + services.AddScoped(); + + break; + case "sqlite": services.AddDbContext( options => options.UseSqlite( ResolveSqliteFile( connectionString, environment ) ) ); @@ -56,7 +62,7 @@ public static IServiceCollection AddLicenseServerDatabase( default: throw new InvalidOperationException( - $"Unknown database provider '{provider}'. Use 'SqlServer' or 'Sqlite'." ); + $"Unknown database provider '{provider}'. Use 'SqlServer', 'PostgreSql' or 'Sqlite'." ); } return services; diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs index 8e146a8..e33f267 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs @@ -205,8 +205,9 @@ private static async Task ExportAsync( $"The range of months is missing or invalid. Years must be between {firstYear} and {lastYear}." ); } - DateTime fromTime = new( fy.Value, fm.Value, 1 ); - DateTime toTime = new DateTime( ty.Value, tm.Value, 1 ).AddMonths( 1 ); + // The months are read as UTC, which is the time zone of every timestamp of the database. + DateTime fromTime = new( fy.Value, fm.Value, 1, 0, 0, 0, DateTimeKind.Utc ); + DateTime toTime = new DateTime( ty.Value, tm.Value, 1, 0, 0, 0, DateTimeKind.Utc ).AddMonths( 1 ); // The range of months is resolved to a range of lease identifiers, and every lease in that // range is exported. The legacy server selected the rows in the same way, and customers diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs new file mode 100644 index 0000000..7609f10 --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs @@ -0,0 +1,137 @@ +using System.Data.Common; +using System.Globalization; +using System.Security.Cryptography; +using System.Text; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Locking; + +namespace SharpCrafters.Backstage.LicenseServer.Web.Locking; + +/// +/// Serializes the lease requests with an advisory lock of PostgreSQL, so that the lock covers every +/// process connected to the database. +/// +/// +/// +/// The lock belongs to the session, which means to the connection. The connection is therefore opened +/// here and stays open until the handle is disposed. Without that, Entity Framework would close the +/// connection after the first query and the lock would be released in the middle of the request. The +/// server would keep answering, and it would grant more leases than the capacity of the license. +/// +/// +/// The wait is bounded by lock_timeout. When it elapses, PostgreSQL cancels the statement with +/// the code 55P03, and the caller answers with the status 503, as it does on SQL Server. +/// +/// +public sealed class PostgreSqlLeaseLock( LicenseServerDbContext db ) : ILeaseLock +{ + /// + /// The name of the locked resource, which is the name the SQL Server lock uses. The two engines + /// never share a database, so the two locks never meet. + /// + private const string resourceName = "SharpCrafters.Backstage.LicenseServer.Lease"; + + /// + /// The code PostgreSQL reports when lock_timeout elapses, which is lock_not_available. + /// + private const string lockNotAvailable = "55P03"; + + /// + /// The key of the advisory lock. PostgreSQL identifies an advisory lock by a number and not by a + /// name, so the number is derived from . An advisory lock is scoped to + /// the database, so every process that serves this database asks for this key, and nothing else + /// does. + /// + public static readonly long ResourceKey = + BitConverter.ToInt64( SHA256.HashData( Encoding.UTF8.GetBytes( resourceName ) ) ); + + public async ValueTask TryAcquireAsync( + TimeSpan timeout, + CancellationToken cancellationToken = default ) + { + DatabaseFacade database = db.Database; + + await database.OpenConnectionAsync( cancellationToken ); + + try + { + // SET takes no parameter, so the value is written into the statement. It is a number + // computed here and never a value that a request carries. + int milliseconds = Math.Max( 0, (int) timeout.TotalMilliseconds ); + + await ExecuteAsync( + database, + $"SET lock_timeout = {milliseconds.ToString( CultureInfo.InvariantCulture )}", + cancellationToken ); + + try + { + await ExecuteAsync( database, $"SELECT pg_advisory_lock({ResourceKey})", cancellationToken ); + } + catch ( DbException e ) when ( e.SqlState == lockNotAvailable ) + { + await ResetTimeoutAsync( database ); + await database.CloseConnectionAsync(); + + return null; + } + + return new Handle( database ); + } + catch + { + await database.CloseConnectionAsync(); + + throw; + } + } + + private static async Task ExecuteAsync( + DatabaseFacade database, + string sql, + CancellationToken cancellationToken ) + { + await using DbCommand command = database.GetDbConnection().CreateCommand(); + command.CommandText = sql; + + await command.ExecuteNonQueryAsync( cancellationToken ); + } + + /// + /// Restores the timeout of the session. The connection returns to the pool of the client, which + /// hands it to another request. + /// + private static async Task ResetTimeoutAsync( DatabaseFacade database ) + => await ExecuteAsync( database, "SET lock_timeout = DEFAULT", CancellationToken.None ); + + private sealed class Handle( DatabaseFacade database ) : IAsyncDisposable + { + private int released; + + public async ValueTask DisposeAsync() + { + if ( Interlocked.Exchange( ref this.released, 1 ) != 0 ) + { + return; + } + + try + { + await ExecuteAsync( + database, + $"SELECT pg_advisory_unlock({ResourceKey})", + CancellationToken.None ); + + await ResetTimeoutAsync( database ); + } + finally + { + // Closing the connection releases the lock as well, so the lock is never held by a + // connection that returns to the pool. + await database.CloseConnectionAsync(); + } + } + } +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs index 535d077..8a60d09 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs @@ -136,9 +136,9 @@ app.MapOperationsEndpoints(); app.MapLegacyUrlRedirects(); -// On SQL Server, Database/CreateTables.sql creates the schema. That script defines the schema, and -// an administrator runs it. A SQLite database is created here, because it is used for evaluation and -// for tests, where no administrator runs a script. +// On SQL Server and on PostgreSQL, the script of that engine under Database/ creates the schema. The +// script defines the schema, and an administrator runs it. A SQLite database is created here, because +// it is used for evaluation and for tests, where no administrator runs a script. if ( string.Equals( app.Configuration["LicenseServer:DatabaseProvider"], "Sqlite", diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj b/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj index cb09c5e..42ebef5 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj @@ -11,7 +11,8 @@ - + + @@ -21,8 +22,10 @@ - + + + + From c1413f7a0e3578a683f820aab45898bdc3056f6d Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Fri, 18 Sep 2026 09:58:03 +0200 Subject: [PATCH 40/44] Answer the review The audit log names the user and the machine. It used to write a hash of each name, which nothing could read, and the file is exported to be read: the administrator of the server uses it to learn which user holds a seat on which machine. The exported file therefore contains personal data, and the export page, the protocol document and the upgrade notes say so. StringHash had no other caller and is removed. A Metalama license names the lowest version of Metalama that can read it, in MinMetalamaVersion, which the signature algorithm of the key decides. That minimum is independent of the minimum PostSharp version, and a Metalama version number is lower than the PostSharp version number of the same year, so comparing a Metalama client against the PostSharp minimum refused leases it should grant. The minimum that applies is now the one of the family of the licensed product, the message names that family, and two tests cover the rule. ILeaseSerializer is removed. It had one implementation, no test replaced it, and the endpoint takes the class. ILicenseParser stays: CachingLicenseParser decorates it and every test that hosts the application replaces it with FakeLicenseParser. LicenseServerOptions is now the one place that reads the administrative roles. The policy read the configuration section a second time, key by key, so the property looked unused. The section is bound once, before the policies are built, and the two policies read that instance. The messages of LeaseService are interpolated strings. Their text is unchanged. The documents answer the rest of the review. The configuration document opens with the purpose of the server: compliance with the license agreement is the responsibility of the customer, the server measures usage inside the network of the customer, it is independent of the license audit, and a customer who uses it is eligible for a waiver of that audit. It also says that only the described deployments are supported, that an interruption affects a user who holds no lease yet, that the settings of the Testing section are for the developers of this product, and what the Development environment is and how it is detected. The container document says that its deployment is for development and testing, because the database it starts is Developer Edition. The protocol document says that clients of several versions use one server at the same time, with compatibility down to the versions released before PostSharp 5, and that the audit log has no auditor as its reader. The cancellation page says that the machine of the user keeps its lease until that lease expires. eng/.gitignore is removed. It held one entry, for a file the build system no longer writes. Co-Authored-By: Claude Opus 5 --- .gitignore | 3 +- README.md | 16 ++-- docs/configuration.md | 44 ++++++++++- docs/docker.md | 6 ++ docs/protocol.md | 39 ++++++---- eng/.gitignore | 1 - .../Data/Lease.Audit.cs | 19 +++-- .../Licensing/BackstageLicenseParser.cs | 12 ++- .../Licensing/ILeaseSerializer.cs | 10 --- .../Licensing/LeaseSerializer.cs | 2 +- .../Licensing/LicenseInfo.cs | 28 +++++++ .../Security/StringHash.cs | 60 -------------- .../Services/LeaseService.cs | 78 ++++++++----------- .../Endpoints/LicenseServerEndpoints.cs | 2 +- .../Pages/Admin/Cancel.cshtml | 8 +- .../Pages/Admin/Export.cshtml | 5 +- .../Program.cs | 18 ++--- .../AuditLogExportTests.cs | 7 +- .../Infrastructure/TestData.cs | 23 ++++++ .../LeaseAuditLineTests.cs | 23 +++--- .../LicenseValidationTests.cs | 43 ++++++++++ 21 files changed, 267 insertions(+), 180 deletions(-) delete mode 100644 eng/.gitignore delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILeaseSerializer.cs delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Security/StringHash.cs diff --git a/.gitignore b/.gitignore index 7d50f6b..d2afef1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,8 @@ obj *.db *.db-shm *.db-wal -# The audit signing key is generated on first start and is a secret. The pattern is not anchored, +# The directory the server writes its own files to. It holds the key pair of the test licensing +# authority, which a development server signs its own license keys with. The pattern is not anchored, # because the directory is created next to whichever project is being run. App_Data/ diff --git a/README.md b/README.md index 2cb3bbf..fb8be57 100644 --- a/README.md +++ b/README.md @@ -130,11 +130,17 @@ points in the interpretation of a license key. `PostSharpUltimate` where it used to be stored as `Ultimate`. The existing rows are not modified, and a request that names either spelling finds both, so there is nothing to migrate. -One more change concerns the exported audit log. A line now has seven fields instead of eight. The -eighth field contained a signature of the line, and that signature could not be verified: the server -generated a random key at every call, so no two lines were signed with the same key. The server no -longer writes that field, and it no longer writes the `HMAC` column of the `Leases` table. The column -is left in place, and the values already written are left as they are. +Two changes concern the exported audit log. + +* A line now has seven fields instead of eight. The eighth field contained a signature of the line, + and that signature could not be verified: the server generated a random key at every call, so no + two lines were signed with the same key. The server no longer writes that field, and it no longer + writes the `HMAC` column of the `Leases` table. The column is left in place, and the values already + written are left as they are. +* The sixth field and the seventh field contain the machine name and the user name, where they used + to contain a hash of each. The log is read by the administrator of the server, who needs to know + which user and which machine hold a seat. The exported file therefore contains personal data. + Handle it as you handle the database, which has always contained the same names. ## Building from source diff --git a/docs/configuration.md b/docs/configuration.md index 9200c23..dc81791 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,5 +1,25 @@ # Configuring the license server +## Why this server exists + +Respecting the license agreement is the responsibility of the customer. The license server is a tool +that helps a customer to measure how many licenses are used, inside the network of the customer. It +sends nothing to PostSharp Technologies. + +The source code is published, so the server can be read, built and modified. A modified server, and +an unmodified one, can both be operated in a way that does not comply with the license agreement. +What the server reports is not, by itself, a proof of compliance. + +The license server is independent of the license audit that the license agreement and the privacy +policy describe. The audit reports the use of the product to PostSharp Technologies. The license +server reports it to the administrator of the customer. + +A customer who uses the license server is eligible for a waiver of the license audit. The waiver is +what guarantees that no data about the use of the product reaches PostSharp Technologies. Ask the +[sales team](https://www.postsharp.net/support) for it. + +## Where the settings are read from + The server reads its settings from `appsettings.json`, in the application directory. Every setting can also be given as an environment variable. In the name of the variable, a colon becomes a double underscore: the connection string is `ConnectionStrings__SharpCrafters_LicenseServerConnectionString`. @@ -239,6 +259,9 @@ The lock is held by the database and not by the process. Several worker processe therefore serialized against each other, which covers a web garden of Internet Information Services, several containers, and two servers that share one database. +The deployments described here are the deployments that are supported today. If you need another one, +for instance one database per site, ask the [support team](https://www.postsharp.net/support). + The mechanism depends on the database engine, and there is no setting to choose it. On SQL Server, the server calls `sp_getapplock` at the beginning of the request and @@ -259,8 +282,10 @@ The `Leases` table is the audit log. The server never updates a lease and never prolonging a lease and cancelling a lease both insert a new row. Export the log from the page [Audit log](protocol.md#exporting-the-audit-log-get-adminexportashx), which writes one line per lease. -The audit log has no setting. Protect it as you protect the database: with the permissions of the -database and with your backups. +The audit log has no setting. It names the user and the machine of every lease, so it contains +personal data. Protect it as you protect the database, which contains the same names: with the +permissions of the database and with your backups. The log is for the organization that runs the +server, and it is not sent to PostSharp Technologies. ## Storage @@ -293,8 +318,10 @@ version number is readable by anyone who can reach the server. The operating requirements of this server are low. One developer sends about one request per day, because the client stores its lease and renews it after `NewLeaseDays` minus `MinLeaseDays` days. A -client that cannot reach the server keeps the lease it holds, so an interruption of a few hours -affects nobody. Monitor the server to learn that it needs attention, and not to fail over. +client that cannot reach the server keeps the lease it holds. An interruption of a few hours +therefore affects only a user or a machine that holds no lease yet, which means a new user, a new +machine, or one whose lease has expired. Monitor the server to learn that it needs attention, and +not to fail over. `/health` reports the result of each check in its body. It has three states: @@ -342,6 +369,9 @@ reports `Healthy` can therefore still deny an individual request. See ## Testing +The settings of this section exist for the developers of the license server itself, and for an +evaluation of it. A customer who serves license keys needs none of them. + | Setting | Default | Meaning | |---|---|---| | `LicenseServer:TimeAcceleration` | 1 | How much faster than real time the clock of the server runs. | @@ -351,6 +381,12 @@ reports `Healthy` can therefore still deny an individual request. See The server refuses to start when one of the last two settings is set outside the Development environment. +The environment is the one of ASP.NET Core. It is read from the variable `ASPNETCORE_ENVIRONMENT`, +and the server runs in the environment `Production` when that variable is not set. The value +`Development` is set by `Properties/launchSettings.json` when the project is started from an editor +or with `dotnet run`, and by nothing else. A published server therefore runs as `Production`, unless +an administrator sets the variable. + Keep `TimeAcceleration` at 1. Another value exists so that a licensing scenario that lasts several days can be replayed against a test server in a few minutes. The server writes a warning to the log when the value is not 1. diff --git a/docs/docker.md b/docs/docker.md index 9ac465a..52c5561 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -1,5 +1,11 @@ # Running the license server in a container +This deployment is provided for development and for testing, and it is not recommended for +production. The database it starts is SQL Server 2022 Developer Edition, whose license does not cover +a production installation. For a production installation, run the server against a SQL Server or a +PostgreSQL that your organization licenses and operates, as +[configuration.md](configuration.md) describes. + `docker-compose.yml` starts a license server: the application, a SQL Server database, and a job that creates the schema from `Database/CreateTables.sql` and then stops. diff --git a/docs/protocol.md b/docs/protocol.md index b08b2b2..ab569f4 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -1,8 +1,13 @@ # The license server protocol A client asks this server for a lease: the permission to use a license key during a limited period. -The protocol has not changed since PostSharp 5, and every deployed client uses it. The shape of each -request and of each response is therefore a contract that this server cannot change. +Every client of PostSharp and of Metalama uses this protocol, and clients of several versions use one +server at the same time. The protocol is compatible with all of them, including the versions released +before PostSharp 5. + +The shape of each request and of each response is therefore a contract. A later version of the +protocol may add an argument or a part, and a client that sends neither is served as it was before. +This server cannot change what is already there. This document describes what the server accepts and what it answers. It is written for the developer who maintains a client, diagnoses a deployment, or modifies this server. @@ -261,9 +266,12 @@ dated in the future. Delete the database as well as restarting the process betwe ## Exporting the audit log: `GET /Admin/Export.ashx` -The export is not part of the client protocol. An administrator gives the exported file to an -auditor. Its format is a contract of its own, because customers archive these files and compare them -across years. +The export is not part of the client protocol. The administrator of the server exports the log to +understand how the licenses are used: which user held which seat, on which machine, and when. Its +format is a contract of its own, because customers archive these files and compare them across years. + +The exported file contains personal data. It is meant for the organization that runs the server, and +it is not meant to be sent to PostSharp Technologies. ### Request @@ -284,7 +292,7 @@ rows. One line per lease, with seven fields separated by `;`: ``` -40;;900001;2026-09-17T11:02:14.1234567Z;2026-09-20T11:02:14.1234567Z;d97556dbab6becaa;93cf1e71b44530bd +40;;900001;2026-09-17T11:02:14.1234567Z;2026-09-20T11:02:14.1234567Z;desktop-1;alice ``` | Field | Meaning | @@ -294,16 +302,15 @@ One line per lease, with seven fields separated by `;`: | 3 | The identifier of the license. | | 4 | The instant the lease began, in UTC. | | 5 | The instant the lease ended, in UTC. | -| 6 | The hash of the machine name. | -| 7 | The hash of the user name. | +| 6 | The machine name. | +| 7 | The user name. | + +The two names are written as the server recorded them, which is in lower case, because the endpoint +converts them before it stores them. -Names appear only as hashes, so the file can be shared without disclosing who works where. The hash -is the hexadecimal representation, in lower case, of an unkeyed 64-bit hash of the name. The name is -trimmed and converted to lower case first. The algorithm is MD5, truncated to its first eight bytes -and read as a little-endian signed integer. MD5 is not used for its cryptographic properties, which -are irrelevant to an anonymizing hash. It is used because the values must be equal to the values -PostSharp has produced since 2013, and because the license audit of Backstage hashes the same names -in the same way. +A version earlier than 2027.0 wrote the two names as hashes. The log is read by the administrator of +the server, who needs to know which user and which machine hold a seat, and a hash answers neither +question. A version earlier than 2027.0 wrote an eighth field, which contained a signature of the line. That signature could not be verified: the server generated a random key at every call, so no two lines @@ -327,4 +334,4 @@ breaks a client that is already deployed. | The four part names of a lease. | A client matches the parts by name and ignores a part it does not know. | | The status codes, and the body of a 403. | A client displays the body of a 403 to the user and treats every other status than 200 as a failure. | | The trailing hexadecimal suffix of a machine name is removed before a build server is matched. | The list of build servers in an existing configuration names the machines without it. | -| The order of the first seven fields of an audit line, and the hash of the names. | Customers archive exported files and compare them across years. | +| The order of the seven fields of an audit line. | Customers archive exported files and compare them across years. | diff --git a/eng/.gitignore b/eng/.gitignore deleted file mode 100644 index 02690fd..0000000 --- a/eng/.gitignore +++ /dev/null @@ -1 +0,0 @@ -Dependencies.props diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs index 5bd7215..a2eaa3b 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs @@ -1,5 +1,4 @@ using System.Xml; -using SharpCrafters.Backstage.LicenseServer.Security; namespace SharpCrafters.Backstage.LicenseServer; @@ -7,14 +6,24 @@ public partial class Lease { /// /// Writes this lease as a line of the audit log. The fields are separated by semicolons, and the - /// machine name and the user name appear only as hashes, so that the log can be shared without - /// disclosing who works where. + /// machine name and the user name are written as they are stored. /// /// + /// + /// The log is read by the administrator of the server, who needs to know which user and which + /// machine hold a seat. A version earlier than 2027.0 wrote the two names as hashes. Nothing + /// could read such a log, and the file was exported to be read. + /// + /// + /// The file therefore contains personal data. It is meant for the organization that runs the + /// server, and not for PostSharp Technologies. + /// + /// /// This format is a serialization contract. The timestamps are written as UTC. The /// values of the entity receive the UTC kind when they are read from the /// database. Without that kind, would treat them as local times and /// shift them. + /// /// public void Write( TextWriter textWriter ) { @@ -30,9 +39,9 @@ public void Write( TextWriter textWriter ) textWriter.Write( ';' ); textWriter.Write( XmlConvert.ToString( this.EndTime, XmlDateTimeSerializationMode.RoundtripKind ) ); textWriter.Write( ';' ); - textWriter.Write( StringHash.ComputeStringHash64( this.Machine ).ToString( "x" ) ); + textWriter.Write( this.Machine ); textWriter.Write( ';' ); - textWriter.Write( StringHash.ComputeStringHash64( this.UserName ).ToString( "x" ) ); + textWriter.Write( this.UserName ); } /// diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs index 7b0b609..3d3b8f5 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs @@ -81,6 +81,11 @@ public sealed class BackstageLicenseParser( ILicensingAuthorityProvider? authori ValidTo = properties.ValidTo, SubscriptionEndDate = properties.SubscriptionEndDate, MinPostSharpVersion = properties.MinPostSharpVersion, + MinMetalamaVersion = properties.MinMetalamaVersion, + + // The enumeration of the products names the family first, and Backstage offers no other + // way to read it. + IsMetalamaProduct = properties.Product.ToString().StartsWith( "Metalama", StringComparison.Ordinal ), GraceDays = data.GraceDays, GracePercent = data.GracePercent ?? defaultGracePercent, IsLicenseServerEligible = properties.LicenseServerEligible, @@ -94,9 +99,10 @@ public sealed class BackstageLicenseParser( ILicensingAuthorityProvider? authori /// /// /// After its identifier and a hyphen, a license key contains only Base32 characters, so it - /// contains no whitespace. Backstage has no equivalent method. It trims a license string and - /// does nothing else, because its keys come from a command line or from a configuration file, - /// and not from a web form. + /// contains no whitespace. Backstage cleans a key in License.CleanLicenseKey, which is + /// private, so this server cannot call it. That method also keeps letters, digits and hyphens + /// alone and converts the result to upper case, which changes a key rather than only trimming + /// what a web form added. /// public string CleanLicenseString( string licenseKey ) => new( licenseKey.Where( c => !char.IsWhiteSpace( c ) ).ToArray() ); diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILeaseSerializer.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILeaseSerializer.cs deleted file mode 100644 index 4cb9972..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILeaseSerializer.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SharpCrafters.Backstage.LicenseServer.Licensing; - -/// -/// Produces the body of the response of the lease endpoint. This format is the contract with the -/// PostSharp client, so an interface isolates it and the tests verify it. -/// -public interface ILeaseSerializer -{ - string Serialize( string licenseKey, DateTime startTime, DateTime endTime, DateTime renewTime ); -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs index 9ad76dc..b1c1dff 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs @@ -21,7 +21,7 @@ namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// cannot rename one. /// /// -public sealed class LeaseSerializer : ILeaseSerializer +public sealed class LeaseSerializer { public string Serialize( string licenseKey, DateTime startTime, DateTime endTime, DateTime renewTime ) => $"License: {licenseKey}" diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs index 65a7563..c5e9740 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs @@ -38,6 +38,34 @@ public sealed record LicenseInfo /// public required Version MinPostSharpVersion { get; init; } + /// + /// Gets the lowest version of Metalama that can read this license, or null when every version of + /// Metalama can read it. The signature algorithm of the license key decides the value: a key + /// signed with an elliptic curve is read by no version released before that algorithm. + /// + public Version? MinMetalamaVersion { get; init; } + + /// + /// Gets a value indicating whether is a Metalama product. A Metalama client + /// reads and a PostSharp client reads + /// , and the two are independent of each other. + /// + public bool IsMetalamaProduct { get; init; } + + /// + /// Gets the lowest version of the client that can read this license, which is the minimum of the + /// family of . + /// + public Version MinClientVersion + => this.IsMetalamaProduct + ? this.MinMetalamaVersion ?? new Version( 0, 0, 0 ) + : this.MinPostSharpVersion; + + /// + /// Gets the name of the product family, which the messages that name a version use. + /// + public string ClientName => this.IsMetalamaProduct ? "Metalama" : "PostSharp"; + /// /// Gets the number of days during which the license may be used above its capacity before the /// server denies a request. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/StringHash.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/StringHash.cs deleted file mode 100644 index e6a5cff..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/StringHash.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Security.Cryptography; -using System.Text; - -namespace SharpCrafters.Backstage.LicenseServer.Security; - -/// -/// The unkeyed 64-bit hash that anonymizes a user name or a machine name in the audit log. -/// -/// -/// -/// This is the algorithm of CryptoUtilities.ComputeStringHash64 in the PostSharp SDK and of -/// HashUtilities.ComputeStringHash64 in SharpCrafters.Backstage. It is reproduced here and -/// not called, because the method of Backstage is internal to that package. -/// -/// -/// The values are part of the format of the audit log. Customers archive the exported files and -/// compare them across years, and the license audit of Backstage hashes the same names in the same -/// way, so that one person is counted once whatever the products they use. A change to the algorithm -/// would break both. LeaseAuditLineTests verifies the values. -/// -/// -/// MD5 is not used for its cryptographic properties, which are irrelevant to an unkeyed anonymizing -/// hash. It is used because the values must be equal to the values PostSharp has produced since -/// 2013. -/// -/// -public static class StringHash -{ - /// - /// Computes the hash of a string, or 0 when it is null. - /// - public static long ComputeStringHash64( string? value ) - { - if ( value == null ) - { - return 0; - } - - // The name is normalized first, so that the same person produces the same value whatever the - // case and the whitespace of the name that the client sent. - byte[] bytes = Encoding.UTF8.GetBytes( value.Trim().ToLowerInvariant().Normalize() ); - -#pragma warning disable CA5350, CA5351 // MD5 is required to reproduce the values of PostSharp. - byte[] hash = MD5.HashData( bytes ); -#pragma warning restore CA5350, CA5351 - - // The first eight bytes, read as a little-endian signed integer. The bytes are combined - // explicitly and not reinterpreted, so that the value does not depend on the byte order of - // the platform. - long hash64 = 0; - - for ( int i = 7; i >= 0; i-- ) - { - hash64 = (hash64 << 8) | hash[i]; - } - - // A string that is not null never produces the value that represents null. - return hash64 == 0 ? -1 : hash64; - } -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs index f775ad5..9cacacf 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs @@ -90,28 +90,26 @@ public LeaseService( if ( parsedLicense.MinPostSharpVersion > this.serverVersion.LicensingLibraryVersion ) { - errors[license.LicenseId] = string.Format( - "The license #{0} requires a higher version of the licensing library on the License Server. Please upgrade the License Server to >= {1}.{2}.{3}", - license.LicenseId, - parsedLicense.MinPostSharpVersion.Major, - parsedLicense.MinPostSharpVersion.Minor, - parsedLicense.MinPostSharpVersion.Build ); + errors[license.LicenseId] = + $"The license #{license.LicenseId} requires a higher version of the licensing library on the License Server. " + + $"Please upgrade the License Server to >= {parsedLicense.MinPostSharpVersion.Major}." + + $"{parsedLicense.MinPostSharpVersion.Minor}.{parsedLicense.MinPostSharpVersion.Build}"; return null; } - if ( parsedLicense.MinPostSharpVersion > version ) + // A PostSharp license names the lowest version of PostSharp that can read it, and a Metalama + // license names the lowest version of Metalama. The two are independent, so the minimum that + // applies is the one of the family of the licensed product. + Version minClientVersion = parsedLicense.MinClientVersion; + + if ( minClientVersion > version ) { - errors[license.LicenseId] = string.Format( - "The license #{0} of type {1} requires PostSharp version >= {2}.{3}.{4} but the requested version is {5}.{6}.{7}.", - license.LicenseId, - parsedLicense.LicenseType, - parsedLicense.MinPostSharpVersion.Major, - parsedLicense.MinPostSharpVersion.Minor, - parsedLicense.MinPostSharpVersion.Build, - version.Major, - version.Minor, - version.Build ); + errors[license.LicenseId] = + $"The license #{license.LicenseId} of type {parsedLicense.LicenseType} requires " + + $"{parsedLicense.ClientName} version >= {minClientVersion.Major}.{minClientVersion.Minor}." + + $"{minClientVersion.Build} but the requested version is " + + $"{version.Major}.{version.Minor}.{version.Build}."; return null; } @@ -129,19 +127,12 @@ public LeaseService( { // The version number was introduced in the license server protocol in PostSharp 5. errors[license.LicenseId] = version.Major >= 5 - ? string.Format( - "The maintenance subscription of license #{0} ends on {1:d} but the requested version {2}.{3}.{4} has been built on {5:d}.", - license.LicenseId, - parsedLicense.SubscriptionEndDate, - version.Major, - version.Minor, - version.Build, - buildDate ) - : string.Format( - "The maintenance subscription of license #{0} ends on {1:d} but the requested version has been built on {2:d}.", - license.LicenseId, - parsedLicense.SubscriptionEndDate, - buildDate ); + ? $"The maintenance subscription of license #{license.LicenseId} ends on " + + $"{parsedLicense.SubscriptionEndDate:d} but the requested version " + + $"{version.Major}.{version.Minor}.{version.Build} has been built on {buildDate:d}." + : $"The maintenance subscription of license #{license.LicenseId} ends on " + + $"{parsedLicense.SubscriptionEndDate:d} but the requested version has been built on " + + $"{buildDate:d}."; return null; } @@ -387,16 +378,13 @@ public LeaseService( if ( license.GraceLastWarningTime.GetValueOrDefault( DateTime.MinValue ) .AddDays( this.settings.GracePeriodWarningDays ) < now ) { - string body = string.Format( - "The license #{0} has a capacity of {1} concurrent user(s), but {2} users are currently using the product {3}. " - + "The grace period has started on {4} and will end on {5}. After this date, additional leases will be denied." - + "Please contact PostSharp Technologies to acquire additional licenses.", - license.LicenseId, - licenseState.Maximum, - licenseState.Usage + 1, - licenseState.ParsedLicense.Product, - license.GraceStartTime, - graceEnd ); + string body = + $"The license #{license.LicenseId} has a capacity of {licenseState.Maximum} concurrent user(s), " + + $"but {licenseState.Usage + 1} users are currently using the product " + + $"{licenseState.ParsedLicense.Product}. " + + $"The grace period has started on {license.GraceStartTime} and will end on {graceEnd}. " + + "After this date, additional leases will be denied." + + "Please contact PostSharp Technologies to acquire additional licenses."; await this.SendEmailAsync( this.settings.GracePeriodWarningEmailTo, @@ -429,13 +417,9 @@ await this.SendEmailAsync( this.settings.DeniedRequestEmailTo, null, "ERROR: license request denied", - string.Format( - "No license with free capacity was found to satisfy the lease request for the product {0} from " - + "the user '{1}' (authentication: '{2}'), machine '{3}'. " + string.Join( ". ", errors.Values ), - productCode, - userName, - authenticatedUserName, - machine ), + $"No license with free capacity was found to satisfy the lease request for the product " + + $"{productCode} from the user '{userName}' (authentication: '{authenticatedUserName}'), " + + $"machine '{machine}'. " + string.Join( ". ", errors.Values ), cancellationToken ); return null; diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs index e33f267..b309aed 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs @@ -38,7 +38,7 @@ private static async Task GetLeaseAsync( HttpContext context, LeaseService leaseService, ILeaseRepository repository, - ILeaseSerializer leaseSerializer, + LeaseSerializer leaseSerializer, ILeaseLock leaseLock, IOptions options, TimeProvider timeProvider, diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml index f37d0ab..7c59ff5 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml @@ -16,7 +16,13 @@

- The seat is released immediately. The lease itself is kept in the audit log. + The seat is released on this server immediately, and the lease is kept in the audit log. +

+ +

+ The machine of the user keeps the lease it already holds until that lease expires, because a + client is never told that a lease was cancelled. Cancelling frees the seat for somebody else; it + does not stop the product on that machine.

diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml index b667680..53cdddf 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml @@ -10,8 +10,9 @@

- The export is a text file with one line per lease. User and machine names appear only as hashes, - so the file can be shared without disclosing who works where. + The export is a text file with one line per lease, with the user name and the machine name as + they were recorded. The file contains personal data. It is meant for the administrator of this + server, who uses it to understand how the licenses are used.

diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs index 8a60d09..5bac850 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs @@ -44,7 +44,7 @@ .AddCheck( "database" ) .AddCheck( "licenses" ); -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton( services => services.GetRequiredService>().Value.Enabled @@ -74,14 +74,18 @@ string authenticationScheme = builder.Services.AddLicenseServerAuthentication( builder.Configuration ); +// A policy is built before the options are available from the container, so the section is bound +// here. It is bound as a whole, and not read key by key, so that the policy and the rest of the +// application read the same properties of the same type. +LicenseServerOptions startupOptions = + builder.Configuration.GetSection( LicenseServerOptions.SectionName ).Get() ?? new LicenseServerOptions(); + builder.Services.AddAuthorizationBuilder() .AddPolicy( AuthorizationPolicies.Admin, policy => { - string[] roles = builder.Configuration - .GetSection( $"{LicenseServerOptions.SectionName}:AdminRoles" ) - .Get() ?? []; + string[] roles = startupOptions.AdminRoles; // When no role is configured, the administrative pages are open, as they were in the // legacy Web.config. A restrictive default would lock administrators out of their own @@ -99,11 +103,7 @@ AuthorizationPolicies.LeaseRequest, policy => { - bool requireAuthentication = builder.Configuration.GetValue( - $"{LicenseServerOptions.SectionName}:RequireAuthenticatedLeaseRequests", - false ); - - if ( requireAuthentication ) + if ( startupOptions.RequireAuthenticatedLeaseRequests ) { policy.RequireAuthenticatedUser(); } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs index 1df7599..737782c 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs @@ -47,14 +47,15 @@ public async Task Export_WithMoreLeasesThanTheWriterBuffers_StreamsThemAll() foreach ( string line in lines ) { - // The identifier, the overwritten lease, the license, the two instants and the two - // hashed names. + // The identifier, the overwritten lease, the license, the two instants, the machine and + // the user. string[] fields = line.TrimEnd( '\r' ).Split( ';' ); Assert.Equal( 7, fields.Length ); } - Assert.DoesNotContain( "alice", string.Join( "", lines ), StringComparison.OrdinalIgnoreCase ); + // The log is read by the administrator of the server, so it names the user. + Assert.Contains( "alice", string.Join( "", lines ), StringComparison.Ordinal ); } /// diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs index f31e10d..7d6f42d 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs @@ -35,6 +35,8 @@ public sealed class LicenseBuilder private DateTime? validTo; private DateTime? subscriptionEndDate; private Version minPostSharpVersion = new( 1, 0, 0 ); + private Version? minMetalamaVersion; + private bool isMetalamaProduct; private int graceDays = 30; private int gracePercent = 20; private bool isLicenseServerEligible = true; @@ -106,6 +108,25 @@ public LicenseBuilder WithMinPostSharpVersion( Version value ) return this; } + /// + /// Makes this a license of a Metalama product, whose client reads the minimum Metalama version + /// and not the minimum PostSharp version. + /// + public LicenseBuilder AsMetalamaProduct( string productCode = "MetalamaProfessional" ) + { + this.isMetalamaProduct = true; + this.product = productCode; + + return this; + } + + public LicenseBuilder WithMinMetalamaVersion( Version? value ) + { + this.minMetalamaVersion = value; + + return this; + } + public LicenseBuilder WithGraceDays( int value ) { this.graceDays = value; @@ -144,6 +165,8 @@ public LicenseInfo BuildInfo() ValidTo = this.validTo, SubscriptionEndDate = this.subscriptionEndDate, MinPostSharpVersion = this.minPostSharpVersion, + MinMetalamaVersion = this.minMetalamaVersion, + IsMetalamaProduct = this.isMetalamaProduct, GraceDays = this.graceDays, GracePercent = this.gracePercent, IsLicenseServerEligible = this.isLicenseServerEligible, diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs index c73e7fb..defe04a 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs @@ -12,10 +12,6 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// public sealed class LeaseAuditLineTests { - // The hashes that CryptoUtilities.ComputeStringHash64 produces. They anonymize the names. - private const string aliceHash = "f5cb4b18b2e28463"; - private const string desktop1Hash = "da7251349d0ffa49"; - private static Lease CreateLease() => new() { @@ -33,7 +29,7 @@ private static Lease CreateLease() public void Write_ProducesTheExpectedLine() { Assert.Equal( - $"42;41;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;{desktop1Hash};{aliceHash}", + "42;41;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;desktop-1;alice", CreateLease().ToAuditLine() ); } @@ -44,17 +40,22 @@ public void Write_NoOverwrittenLease_LeavesTheFieldEmpty() lease.OverwrittenLeaseId = null; Assert.Equal( - $"42;;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;{desktop1Hash};{aliceHash}", + "42;;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;desktop-1;alice", lease.ToAuditLine() ); } + /// + /// The administrator of the server reads the log to learn which user and which machine hold a + /// seat, so both names are written as they were recorded. A version earlier than 2027.0 wrote + /// them as hashes, which nothing could read. + /// [Fact] - public void Write_NeverDisclosesTheUserOrMachineName() + public void Write_NamesTheUserAndTheMachine() { - string line = CreateLease().ToAuditLine(); + string[] fields = CreateLease().ToAuditLine().Split( ';' ); - Assert.DoesNotContain( "alice", line, StringComparison.OrdinalIgnoreCase ); - Assert.DoesNotContain( "desktop", line, StringComparison.OrdinalIgnoreCase ); + Assert.Equal( "desktop-1", fields[5] ); + Assert.Equal( "alice", fields[6] ); } /// @@ -104,7 +105,7 @@ public void Write_UsesInvariantFormatting() CultureInfo.CurrentCulture = new CultureInfo( "de-DE" ); Assert.Equal( - $"42;41;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;{desktop1Hash};{aliceHash}", + "42;41;7;2026-01-05T09:00:00Z;2026-01-08T09:00:00Z;desktop-1;alice", CreateLease().ToAuditLine() ); } finally diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs index 10ef327..ffdd009 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs @@ -87,6 +87,49 @@ public async Task ClientIsOlderThanTheLicenseRequires_SaysSo() Assert.Contains( "the requested version is 6.5.4", errors[1], StringComparison.Ordinal ); } + /// + /// A Metalama license names the lowest version of Metalama that can read it, which the signature + /// algorithm of the key decides. That minimum is independent of the minimum PostSharp version. + /// + [Fact] + public async Task MetalamaClientIsOlderThanTheLicenseRequires_SaysSo() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + License license = LicenseBuilder.Default() + .AsMetalamaProduct() + .WithMinMetalamaVersion( new Version( 2026, 1, 0 ) ) + .AddTo( context ); + + var (lease, errors) = await RequestAsync( context, license, new Version( 2025, 2, 3 ) ); + + Assert.Null( lease ); + Assert.Contains( "requires Metalama version >= 2026.1.0", errors[1], StringComparison.Ordinal ); + Assert.Contains( "the requested version is 2025.2.3", errors[1], StringComparison.Ordinal ); + } + + /// + /// A Metalama client is not refused by the minimum PostSharp version of the same license. The two + /// minimums belong to two product families, and a Metalama version number is lower than the + /// PostSharp version numbers of the same years. + /// + [Fact] + public async Task MetalamaLicense_IgnoresTheMinimumPostSharpVersion() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + + License license = LicenseBuilder.Default() + .AsMetalamaProduct() + .WithMinPostSharpVersion( new Version( 2024, 0, 0 ) ) + .WithMinMetalamaVersion( null ) + .AddTo( context ); + + var (lease, errors) = await RequestAsync( context, license, new Version( 2023, 4, 0 ) ); + + Assert.NotNull( lease ); + Assert.Empty( errors ); + } + [Fact] public async Task LicenseNotEligibleForALicenseServer_SaysSo() { From cbb896425b5bfdc2af71c97cce68ffe065c63cb8 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Fri, 18 Sep 2026 10:23:13 +0200 Subject: [PATCH 41/44] Write explicit constructors, and wire the shared code style A class no longer uses a primary constructor. Thirty of them did, in the two projects of the product, in the tests and in the build definition. Each one now declares its fields, assigns them in a constructor, and reads them as this.field. A record keeps its primary constructor. eng/style carries the shared code style of PostSharp.Engineering: .editorconfig, CommonStyle.DotSettings for the JetBrains tools, and stylecop.json. The .editorconfig at the root of the repository is a symbolic link to eng/style/.editorconfig, which is what `Build.ps1 codestyle pull` creates, so a clone needs core.symlinks. README.md says this, and says which step of Rider no script performs. The style raised six warnings, and the build has none again. LeaseSerializer holds no state and becomes a static class, so the endpoint calls it directly and the container registers nothing; that is the end of the interface the review asked to remove. Two members of the tests become static, one call names its StringComparison, one names its IFormatProvider, and the two database pools carry a justification for CA1001: a pool lives in a static dictionary as long as the run, and its semaphores are released with the process. TestDatabase.ps1 chooses the Docker daemon by the operating system it reports, and no longer by whether a client answers. Docker Desktop in the Windows container mode answers and then refuses a Linux image, with "no matching manifest for windows", which is what happened on the machine this was written on. Verified: 280 tests pass on SQLite, on SQL Server and on PostgreSQL. Co-Authored-By: Claude Opus 5 --- .editorconfig | 1 + README.md | 11 + eng/TestDatabase.ps1 | 49 +- eng/src/Program.cs | 23 +- eng/style/.editorconfig | 586 ++++++++++++++++++ eng/style/CommonStyle.DotSettings | 346 +++++++++++ eng/style/LICENSE | 21 + eng/style/README.md | 44 ++ eng/style/stylecop.json | 8 + .../Data/LeaseRepository.cs | 41 +- .../Data/LicenseServerDbContext.cs | 4 +- .../Email/SmtpEmailSender.cs | 14 +- .../Licensing/BackstageLicenseParser.cs | 10 +- .../Licensing/CachingLicenseParser.cs | 12 +- .../Licensing/LeaseSerializer.cs | 4 +- .../Services/LeaseService.cs | 28 +- .../Services/LicenseAvailabilityService.cs | 27 +- .../AuthenticationRegistration.cs | 10 +- .../Endpoints/LicenseServerEndpoints.cs | 3 +- .../Health/DatabaseHealthCheck.cs | 15 +- .../Health/LicenseHealthCheck.cs | 23 +- .../Locking/PostgreSqlLeaseLock.cs | 25 +- .../Locking/SqlServerLeaseLock.cs | 23 +- .../Locking/SqliteLeaseLock.cs | 43 +- .../Pages/Admin/AddLicense.cshtml.cs | 35 +- .../Pages/Admin/Cancel.cshtml.cs | 21 +- .../Pages/Admin/Details.cshtml.cs | 49 +- .../Pages/Admin/Export.cshtml.cs | 11 +- .../Pages/Admin/GenerateDemoData.cshtml.cs | 37 +- .../Pages/Graph.cshtml.cs | 27 +- .../Pages/Index.cshtml.cs | 27 +- .../Program.cs | 1 - .../AuditLogExportTests.cs | 8 +- .../Fakes/FixedServerVersion.cs | 9 +- .../Infrastructure/AsyncOnlyResponseBody.cs | 17 +- .../LicenseServerApplication.cs | 10 +- .../Infrastructure/PostgreSqlTestDatabase.cs | 6 + .../Infrastructure/SqlServerTestDatabase.cs | 6 + .../Infrastructure/TestLicenseKeys.cs | 19 +- .../LeaseSerializerTests.cs | 12 +- .../SeedTestLicensesTests.cs | 12 +- .../TestLicensingAuthorityTests.cs | 12 +- 42 files changed, 1482 insertions(+), 208 deletions(-) create mode 120000 .editorconfig create mode 100644 eng/style/.editorconfig create mode 100644 eng/style/CommonStyle.DotSettings create mode 100644 eng/style/LICENSE create mode 100644 eng/style/README.md create mode 100644 eng/style/stylecop.json diff --git a/.editorconfig b/.editorconfig new file mode 120000 index 0000000..11790e1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1 @@ +eng/style/.editorconfig \ No newline at end of file diff --git a/README.md b/README.md index fb8be57..17fb9e4 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,17 @@ the dependency at it, and build that repository first: ./Build.ps1 dependencies set local Backstage --path ``` +### The code style + +`eng/style` holds the shared code style of PostSharp.Engineering: `.editorconfig`, +`CommonStyle.DotSettings` for the JetBrains tools, and `stylecop.json`. The `.editorconfig` at the +root of the repository is a symbolic link to `eng/style/.editorconfig`, so a clone needs +`core.symlinks = true`. Refresh the style with `./Build.ps1 codestyle pull`. + +In Rider, open Settings, choose "Manage Layers", select the team-shared layer, click the plus icon, +choose "Open Settings File", and select `eng/style/CommonStyle.DotSettings`. This step is what makes +`./Build.ps1 codestyle format` reformat the code the way the team writes it. + ### Running the tests `./Build.ps1 test` runs the suite on SQLite, which needs no server and keeps the loop short. Run the diff --git a/eng/TestDatabase.ps1 b/eng/TestDatabase.ps1 index 6e34689..3706f30 100644 --- a/eng/TestDatabase.ps1 +++ b/eng/TestDatabase.ps1 @@ -83,42 +83,49 @@ $script:dockerCommand = $null <# .SYNOPSIS - Resolves how Docker is reached, and returns the command and the arguments that precede every - Docker argument. + Resolves how Docker is reached, and stores the command and the arguments that precede every Docker + argument. .DESCRIPTION - A Windows machine can run the Docker engine inside a distribution of the Windows Subsystem for - Linux rather than under Docker Desktop. The client of the distribution is then the only one that - reaches a daemon, so the script calls Docker through wsl. A relative path is used with it, because - wsl starts in the translated form of the current directory and would not understand a path that - names a Windows drive. + The servers run in Linux containers, so the daemon has to be one that runs Linux containers. A + Windows machine can answer on the client and still refuse them: Docker Desktop in the Windows + container mode reports a server, and pulling a Linux image then fails with "no matching manifest + for windows". The engine can also live inside a distribution of the Windows Subsystem for Linux + rather than under Docker Desktop, and the client of the distribution is then the only one that + reaches it. The daemon is therefore chosen by the operating system it reports, and not by whether + a client answers. + + Docker is called with a relative path when it is called through wsl, because wsl starts in the + translated form of the current directory and would not understand a path that names a Windows + drive. #> function Initialize-Docker { if ( $script:dockerCommand ) { return } - if ( Get-Command docker -ErrorAction SilentlyContinue ) { - & docker version --format '{{.Server.Version}}' 2>&1 | Out-Null + function Test-LinuxDaemon( [string[]] $Command ) { + $name = $Command[0] + $arguments = @( $Command | Select-Object -Skip 1 ) + @( 'version', '--format', '{{.Server.Os}}' ) - if ( $LASTEXITCODE -eq 0 ) { - $script:dockerCommand = @( 'docker' ) + $operatingSystem = & $name @arguments 2>&1 - return - } + return $LASTEXITCODE -eq 0 -and "$operatingSystem".Trim() -eq 'linux' } - if ( Get-Command wsl -ErrorAction SilentlyContinue ) { - & wsl -e docker version --format '{{.Server.Version}}' 2>&1 | Out-Null + if ( (Get-Command docker -ErrorAction SilentlyContinue) -and (Test-LinuxDaemon @( 'docker' )) ) { + $script:dockerCommand = @( 'docker' ) - if ( $LASTEXITCODE -eq 0 ) { - Write-Host 'Reaching Docker through the Windows Subsystem for Linux.' - $script:dockerCommand = @( 'wsl', '-e', 'docker' ) + return + } - return - } + if ( (Get-Command wsl -ErrorAction SilentlyContinue) -and (Test-LinuxDaemon @( 'wsl', '-e', 'docker' )) ) { + Write-Host 'Reaching Docker through the Windows Subsystem for Linux.' + $script:dockerCommand = @( 'wsl', '-e', 'docker' ) + + return } - throw 'No Docker daemon was reached. Start Docker, or set the connection string of a server of your own.' + throw 'No Docker daemon that runs Linux containers was reached. Start one, or set the connection string of a server of your own.' } function Invoke-Docker { diff --git a/eng/src/Program.cs b/eng/src/Program.cs index d871922..f28b9a5 100644 --- a/eng/src/Program.cs +++ b/eng/src/Program.cs @@ -94,23 +94,36 @@ internal static class DatabaseTests /// The name of the engine, as the parameter of eng/TestDatabase.ps1 spells it. /// The name of the engine, as a person writes it. /// The component that installs the server into the image. -internal sealed class DatabaseTestRun( string name, string engine, string displayName, ContainerComponent server ) +internal sealed class DatabaseTestRun { + private readonly string name; + private readonly string engine; + private readonly string displayName; + private readonly ContainerComponent server; + + public DatabaseTestRun( string name, string engine, string displayName, ContainerComponent server ) + { + this.name = name; + this.engine = engine; + this.displayName = displayName; + this.server = server; + } + public AdditionalDockerfile Dockerfile( string dotNetSdkVersion ) - => new( name, [] ) + => new( this.name, [] ) { Requirements = new ContainerRequirements( ContainerHostKind.Linux ) { OperatingSystem = ContainerOperatingSystem.Linux, - Components = [new DotNetComponent( dotNetSdkVersion, DotNetComponentKind.Sdk ), server] + Components = [new DotNetComponent( dotNetSdkVersion, DotNetComponentKind.Sdk ), this.server] } }; public PowershellAdditionalCiBuildConfiguration Configuration - => new( $"{engine}Tests", $"Tests on {displayName}", "./eng/TestDatabase.ps1", $"-Engine {engine}" ) + => new( $"{this.engine}Tests", $"Tests on {this.displayName}", "./eng/TestDatabase.ps1", $"-Engine {this.engine}" ) { BuildAgentRequirements = LinuxContainerHost, - Dockerfile = $"eng/docker/{name}-build.Dockerfile", + Dockerfile = $"eng/docker/{this.name}-build.Dockerfile", BuildSnapshotDependency = BuildConfiguration.Debug }; diff --git a/eng/style/.editorconfig b/eng/style/.editorconfig new file mode 100644 index 0000000..0fe1fa3 --- /dev/null +++ b/eng/style/.editorconfig @@ -0,0 +1,586 @@ +root = true + + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# File header +file_header_template = Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false + +# this. and Me. preferences +dotnet_style_qualification_for_event = true:warning +dotnet_style_qualification_for_field = true:warning +dotnet_style_qualification_for_method = true:warning +dotnet_style_qualification_for_property = true:warning + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning + +# Expression-level preferences +csharp_style_deconstructed_variable_declaration = true:none +dotnet_style_coalesce_expression = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_null_propagation = true:warning +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_compound_assignment = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:none +dotnet_style_prefer_conditional_expression_over_return = true:none +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = false:warning +dotnet_style_prefer_simplified_boolean_expressions = true:warning +dotnet_style_prefer_simplified_interpolation = true:warning + +# Field preferences +dotnet_style_readonly_field = true:warning + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = warning + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = true:warning +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning + +# Expression-bodied members +csharp_style_expression_bodied_accessors = when_on_single_line:warning +csharp_style_expression_bodied_constructors = false:warning +csharp_style_expression_bodied_indexers = when_on_single_line:warning +csharp_style_expression_bodied_lambdas = true:warning +csharp_style_expression_bodied_local_functions = false:none +csharp_style_expression_bodied_methods = when_on_single_line:hint +csharp_style_expression_bodied_operators = when_on_single_line:warning +csharp_style_expression_bodied_properties = when_on_single_line:warning + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_pattern_matching = false:none +csharp_style_prefer_switch_expression = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:warning + +# Modifier preferences +csharp_prefer_static_local_function = true:warning +csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:warning + +# Code-block preferences +csharp_prefer_braces = true:warning +csharp_prefer_simple_using_statement = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:warning +csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_inlined_variable_declaration = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_throw_expression = true:warning +csharp_style_unused_value_assignment_preference = discard_variable:warning +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:warning + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# ElasticSpace preferences +csharp_space_after_cast = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = true +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = true +csharp_space_between_parentheses = control_flow_statements +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = error +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = error +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.public_or_protected_field_should_be_pascal_case.severity = warning +dotnet_naming_rule.public_or_protected_field_should_be_pascal_case.symbols = public_or_protected_field +dotnet_naming_rule.public_or_protected_field_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_field_should_be_begins_with_underscore.severity = warning +dotnet_naming_rule.private_field_should_be_begins_with_underscore.symbols = private_field +dotnet_naming_rule.private_field_should_be_begins_with_underscore.style = begins_with_underscore + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.public_or_protected_field.applicable_kinds = field +dotnet_naming_symbols.public_or_protected_field.applicable_accessibilities = public, protected +dotnet_naming_symbols.public_or_protected_field.required_modifiers = + +dotnet_naming_symbols.private_field.applicable_kinds = field +dotnet_naming_symbols.private_field.applicable_accessibilities = private, private_protected +dotnet_naming_symbols.private_field.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.begins_with_underscore.required_prefix = _ +dotnet_naming_style.begins_with_underscore.required_suffix = +dotnet_naming_style.begins_with_underscore.word_separator = +dotnet_naming_style.begins_with_underscore.capitalization = camel_case + +# IDE0021: Use expression body for constructors +dotnet_diagnostic.IDE0021.severity = suggestion + +# IDE0019: Use pattern matching +dotnet_diagnostic.IDE0019.severity = suggestion + +# IDE0021: Use expression body for constructors +dotnet_diagnostic.IDE0021.severity = warning + +# Default severity for analyzer diagnostics with category 'MicrosoftCodeAnalysisReleaseTracking' +# https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md +dotnet_analyzer_diagnostic.category-MicrosoftCodeAnalysisReleaseTracking.severity = none + + +# IDE0044: Add readonly modifier +dotnet_diagnostic.IDE0044.severity = warning + +# IDE0079: Remove unnecessary suppression +dotnet_diagnostic.IDE0079.severity = warning + +# SA1009: Closing parenthesis should be spaced correctly +dotnet_diagnostic.SA1009.severity = none + +# SA1309: Field names should not begin with underscore +dotnet_diagnostic.SA1309.severity = none + +# SA1633: File should have header +dotnet_diagnostic.SA1633.severity = none + +# SA1636: File header should be correct +dotnet_diagnostic.SA1636.severity = none + + +# SA1200: Using directives should be placed within namespace +dotnet_diagnostic.SA1200.severity = none + +# SA1204: Static members should appear before non-static members +dotnet_diagnostic.SA1204.severity = none + +# SA1208: Using directive should appear before... +dotnet_diagnostic.SA1208.severity = none + +# SA1501: Statement should not be on a single line +dotnet_diagnostic.SA1501.severity = none + + +# SA1502: Element should not be on a single line +dotnet_diagnostic.SA1502.severity = none + + +# SA1505: Opening braces should not be followed by blank line +dotnet_diagnostic.SA1505.severity = none + +# SA1600: Elements should be documented +dotnet_diagnostic.SA1600.severity = none + +# SA1008: Opening parenthesis should be spaced correctly +dotnet_diagnostic.SA1008.severity = none + +# SA1128: Put constructor initializers on their own line +dotnet_diagnostic.SA1128.severity = none + +# SA1202: Elements should be ordered by access +dotnet_diagnostic.SA1202.severity = none + +# SA1614: Element parameter documentation should have text +dotnet_diagnostic.SA1614.severity = none + +# SA1615: Element return value should be documented +dotnet_diagnostic.SA1615.severity = none + +# SA1616: Element return value documentation should have text +dotnet_diagnostic.SA1616.severity = none + +# SA1201: Elements should appear in the correct order +dotnet_diagnostic.SA1201.severity = none + +# SA1601: Partial elements should be documented +dotnet_diagnostic.SA1601.severity = none + +# SA1003: Symbols should be spaced correctly +dotnet_diagnostic.SA1003.severity = none + +# SA1622: Generic type parameter documentation should have text +dotnet_diagnostic.SA1622.severity = none + +# SA1512: Single-line comments should not be followed by blank line +dotnet_diagnostic.SA1512.severity = none + +# SA1611: Element parameters should be documented +dotnet_diagnostic.SA1611.severity = none + +# SA1010: Opening square brackets should be spaced correctly +dotnet_diagnostic.SA1010.severity = none + +# SA1011: Closing square brackets should be spaced correctly +dotnet_diagnostic.SA1011.severity = none + +# SA1118: Parameter should not span multiple lines +dotnet_diagnostic.SA1118.severity = suggestion + +# SA1413: Use trailing comma in multi-line initializers +dotnet_diagnostic.SA1413.severity = none + +# SA1122: Use string.Empty for empty strings +dotnet_diagnostic.SA1122.severity = none + +# CA1310: Specify StringComparison for correctness +dotnet_diagnostic.CA1310.severity = warning + +# CA1725: Parameter names should match base declaration +dotnet_diagnostic.CA1725.severity = warning + +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = warning + +# CA1822: Member can be marked as static +dotnet_diagnostic.CA1822.severity = warning + +# CA1829: Use the Count property instead of Enumerable.Count() +dotnet_diagnostic.CA1829.severity = warning + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = warning + +# CA2215: Dispose methods should call base class dispose +dotnet_diagnostic.CA2215.severity = warning + +# CA5350: Do Not Use Weak Cryptographic Algorithms +dotnet_diagnostic.CA5350.severity = warning + +# CA5351: Do Not Use Broken Cryptographic Algorithms +dotnet_diagnostic.CA5351.severity = warning + +# CA5369: Use XmlReader For Deserialize +dotnet_diagnostic.CA5369.severity = warning + +# CA5370: Use XmlReader For Validating Reader +dotnet_diagnostic.CA5370.severity = warning + +# CA5371: Use XmlReader For Schema Read +dotnet_diagnostic.CA5371.severity = warning + +# CA5372: Use XmlReader For XPathDocument +dotnet_diagnostic.CA5372.severity = warning + +# CA5373: Do not use obsolete key derivation function +dotnet_diagnostic.CA5373.severity = warning + +# CA5374: Do Not Use XslTransform +dotnet_diagnostic.CA5374.severity = warning + +# CA5379: Do Not Use Weak Key Derivation Function Algorithm +dotnet_diagnostic.CA5379.severity = warning + +# CA5384: Do Not Use Digital Signature Algorithm (DSA) +dotnet_diagnostic.CA5384.severity = warning + +# CA5385: Use Rivest�Shamir�Adleman (RSA) Algorithm With Sufficient Key Size +dotnet_diagnostic.CA5385.severity = warning + +# CA5397: Do not use deprecated SslProtocols values +dotnet_diagnostic.CA5397.severity = warning + +# CA1001: Types that own disposable fields should be disposable +dotnet_diagnostic.CA1001.severity = warning + +# CA1309: Use ordinal string comparison +dotnet_diagnostic.CA1309.severity = warning +dotnet_diagnostic.CA1307.severity = warning +dotnet_diagnostic.CA1305.severity = warning +dotnet_diagnostic.CA1304.severity = warning + +# IDE0004: Remove Unnecessary Cast +dotnet_diagnostic.IDE0004.severity = warning + +# IDE0005: Using directive is unnecessary. +dotnet_diagnostic.IDE0005.severity = warning + +# IDE0007: Use implicit type +dotnet_diagnostic.IDE0007.severity = warning + +# IDE0007: Use explicit type +dotnet_diagnostic.IDE0008.severity = none + +# IDE0009: Member access should be qualified. +dotnet_diagnostic.IDE0009.severity = warning + +# IDE0011: Add braces +dotnet_diagnostic.IDE0011.severity = warning + +# IDE0016: Use 'throw' expression +dotnet_diagnostic.IDE0016.severity = warning + +# IDE0017: Simplify object initialization +dotnet_diagnostic.IDE0017.severity = suggestion + +# IDE0018: Inline variable declaration +dotnet_diagnostic.IDE0018.severity = warning + +# IDE0020: Use pattern matching +dotnet_diagnostic.IDE0020.severity = suggestion + +# IDE0023: Use expression body for operators +dotnet_diagnostic.IDE0023.severity = none + +# IDE0024: Use expression body for operators +dotnet_diagnostic.IDE0024.severity = none + +# IDE0025: Use expression body for properties +dotnet_diagnostic.IDE0025.severity = warning + +# IDE0026: Use expression body for indexers +dotnet_diagnostic.IDE0026.severity = warning + +# IDE0027: Use expression body for accessors +dotnet_diagnostic.IDE0027.severity = warning + +# IDE0028: Simplify collection initialization +dotnet_diagnostic.IDE0028.severity = warning + +# IDE0031: Use null propagation +dotnet_diagnostic.IDE0031.severity = warning + +# IDE0032: Use auto property +dotnet_diagnostic.IDE0032.severity = warning + +# IDE0030: Use coalesce expression +dotnet_diagnostic.IDE0030.severity = warning + +# IDE0029: Use coalesce expression +dotnet_diagnostic.IDE0029.severity = warning + +# IDE0034: Simplify 'default' expression +dotnet_diagnostic.IDE0034.severity = warning + +# IDE0036: Order modifiers +dotnet_diagnostic.IDE0036.severity = warning + +# IDE0039: Use local function +dotnet_diagnostic.IDE0039.severity = warning + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = warning + +# IDE0041: Use 'is null' check +dotnet_diagnostic.IDE0041.severity = warning + +# IDE0042: Deconstruct variable declaration +dotnet_diagnostic.IDE0042.severity = none + +# IDE0047: Remove unnecessary parentheses +dotnet_diagnostic.IDE0047.severity = warning + +# IDE0048: Add parentheses for clarity +dotnet_diagnostic.IDE0048.severity = warning + +# IDE0050: Convert to tuple +dotnet_diagnostic.IDE0050.severity = warning + +# IDE0051: Private member is unused +dotnet_diagnostic.IDE0051.severity = warning + +# IDE0054: Use compound assignment +dotnet_diagnostic.IDE0054.severity = warning + +# IDE0057: Use range operator +dotnet_diagnostic.IDE0057.severity = suggestion + +# IDE0058: Expression value is never used +dotnet_diagnostic.IDE0058.severity = none + +# IDE0059: Unnecessary assignment of a value +dotnet_diagnostic.IDE0059.severity = warning + +# IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = suggestion + +# IDE0061: Use expression body for local functions +dotnet_diagnostic.IDE0061.severity = none + +# IDE0062: Make local function 'static' +dotnet_diagnostic.IDE0062.severity = warning + +# IDE0065: Misplaced using directive +dotnet_diagnostic.IDE0065.severity = warning + +# IDE0071: Simplify interpolation +dotnet_diagnostic.IDE0071.severity = warning + +# IDE0072: Add missing cases +dotnet_diagnostic.IDE0072.severity = suggestion + +# IDE0073: File header +dotnet_diagnostic.IDE0073.severity = warning + +# IDE0075: Simplify conditional expression +dotnet_diagnostic.IDE0075.severity = warning + +# IDE0080: Remove unnecessary suppression operator +dotnet_diagnostic.IDE0080.severity = warning + +# IDE0082: 'typeof' can be converted to 'nameof' +dotnet_diagnostic.IDE0082.severity = warning + +# IDE0083: Use pattern matching +dotnet_diagnostic.IDE0083.severity = suggestion + +# IDE0090: 'new' expression can be simplified +dotnet_diagnostic.IDE0083.severity = warning + + +# IDE1005: Delegate invocation can be simplified. +dotnet_diagnostic.IDE1005.severity = warning + +# IDE1006: Naming Styles +dotnet_diagnostic.IDE1006.severity = warning + +# SA1602: Enumeration items should be documented +dotnet_diagnostic.SA1602.severity = suggestion + +# SA1618: Generic type parameters should be documented +dotnet_diagnostic.SA1618.severity = suggestion + +# SA1021: Negative signs should be spaced correctly +dotnet_diagnostic.SA1021.severity = none + +# SA1402: File may only contain a single type +dotnet_diagnostic.SA1402.severity = warning + +# SA1604: Element documentation should have summary +dotnet_diagnostic.SA1604.severity = none + +# SA1620: Generic type parameter documentation should match type parameters +dotnet_diagnostic.SA1620.severity = none + + +# SA1028: Code should not contain trailing whitespace +dotnet_diagnostic.SA1028.severity = none + +# SA1024: Deference symbol '*' should not be followed by a space. +dotnet_diagnostic.SA1023.severity = none +dotnet_diagnostic.IDE0052.severity = warning +dotnet_diagnostic.IDE0043.severity = warning +dotnet_diagnostic.CA1507.severity = warning +dotnet_diagnostic.CA1825.severity = warning + +dotnet_diagnostic.CA1018.severity = warning +dotnet_diagnostic.CA1821.severity = warning +dotnet_diagnostic.CA1827.severity = warning +dotnet_diagnostic.CA1836.severity = warning +dotnet_diagnostic.CA2011.severity = warning + +# RS1024 Compare (Roslyn) symbols correctly. There is a bug in the analyzer and it has dozens of false positive. +dotnet_diagnostic.RS1024.severity = none \ No newline at end of file diff --git a/eng/style/CommonStyle.DotSettings b/eng/style/CommonStyle.DotSettings new file mode 100644 index 0000000..dddc862 --- /dev/null +++ b/eng/style/CommonStyle.DotSettings @@ -0,0 +1,346 @@ + + AutomaticProperty + True + ExplicitlyExcluded + ExplicitlyExcluded + *.g.cs + WARNING + HINT + WARNING + WARNING + DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + WARNING + DO_NOT_SHOW + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + SUGGESTION + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + SUGGESTION + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + True + False + ShowAndRun + SUGGESTION + <?xml version="1.0" encoding="utf-16"?><Profile name="Add file header"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="False" RemoveRedundantParentheses="False" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" ArrangeCodeBodyStyle="False" ArrangeVarStyle="False" ArrangeTrailingCommas="False" ArrangeObjectCreation="False" ArrangeDefaultValue="False" /><HtmlReformatCode>True</HtmlReformatCode><CppReformatCode>True</CppReformatCode><ShaderLabReformatCode>True</ShaderLabReformatCode><VBReformatCode>True</VBReformatCode><CSOptimizeUsings><OptimizeUsings>False</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + &lt;option name="myName" value="Add file header" /&gt; +&lt;/profile&gt;</IDEA_SETTINGS></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="Custom"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" /><CSOptimizeUsings><OptimizeUsings>False</OptimizeUsings></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSShortenReferences>True</CSShortenReferences><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + &lt;option name="myName" value="Custom" /&gt; + &lt;inspection_tool class="ES6ShorthandObjectProperty" enabled="false" level="INFORMATION" enabled_by_default="false" /&gt; + &lt;inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="false" level="INFORMATION" enabled_by_default="false" /&gt; + &lt;inspection_tool class="JSPrimitiveTypeWrapperUsage" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="JSRemoveUnnecessaryParentheses" enabled="false" level="INFORMATION" enabled_by_default="false" /&gt; + &lt;inspection_tool class="JSUnnecessarySemicolon" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="TypeScriptExplicitMemberType" enabled="false" level="INFORMATION" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryContinueJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryLabelJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryLabelOnBreakStatementJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryLabelOnContinueStatementJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryReturnJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; +&lt;/profile&gt;</IDEA_SETTINGS><CppCodeStyleCleanupDescriptor /><CSUpdateFileHeader>True</CSUpdateFileHeader><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><RIDER_SETTINGS>&lt;profile&gt; + &lt;Language id="CSS"&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="EditorConfig"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTML"&gt; + &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTTP Request"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Handlebars"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Ini"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JSON"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Jade"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JavaScript"&gt; + &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Markdown"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Properties"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="RELAX-NG"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="SQL"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="XML"&gt; + &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="yaml"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; +&lt;/profile&gt;</RIDER_SETTINGS></Profile> + Built-in: Reformat & Apply Syntax Style + False + Required + Required + Required + Required + True + True + False + True + True + True + True + True + True + True + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + TOGETHER_SAME_LINE + True + 1 + 1 + False + False + False + False + False + True + True + NEVER + NEVER + NEVER + False + NEVER + True + True + True + True + True + True + True + True + CHOP_IF_LONG + CHOP_IF_LONG + True + True + True + CHOP_IF_LONG + 160 + CHOP_ALWAYS + CHOP_IF_LONG + False + False + True + Skip + Skip + True + True + True + False + True + True + False + False + True + False + True + True + MS + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/eng/style/LICENSE b/eng/style/LICENSE new file mode 100644 index 0000000..fe7a0a4 --- /dev/null +++ b/eng/style/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 SharpCrafters s.r.o. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/eng/style/README.md b/eng/style/README.md new file mode 100644 index 0000000..c4b66c3 --- /dev/null +++ b/eng/style/README.md @@ -0,0 +1,44 @@ +# PostSharp Engineering: Code Style Features + +Make sure you have read and understood [PostSharp Engineering](../README.md) before reading this doc. + +## Table of contents + +- [PostSharp Engineering: Code Style Features](#postsharp-engineering-code-style-features) + - [Table of contents](#table-of-contents) + - [Introduction](#introduction) + - [Installation](#installation) + - [Configuration](#configuration) + - [Code style cleanup](#code-style-cleanup) + +## Introduction + +This directory contains centralized code-style configuration and scripts. + +## Installation + +1. Copy the `PostSharp.Engineering.CodeStyle` repo to your own repo into `eng/style` using `PostSharp.Engineering.BuildTools`, with the command: + + ``` + .\Build.ps1 codestyle pull + ``` + + This tool will create a symlink for `.editorconfig`. + +2. Enable symlinks for your repo (edit `.git/config`). + +3. For each solution, in Rider, open Settings, choose "Manage Layers", select the team-shared layer, click on the `+` icon and then on "Open Settings File", then choose `eng/style/CommonStyle.DotSettings`. + This step is required for code formatting using `Build.ps1 reformat`, even if you are otherwise not using Rider. + +## Configuration + +The code quality configuration is configured in the following files: + +- `.editorconfig` +- `CommonStyle.DotSettings` (used by JetBrains tools) +- `stylecop.json` + +## Code style cleanup + +1. Commit all your changes. You cannot reformat a repo with uncommitted changes. +2. Do `.\Build.ps1 reformat` from the repo root (see `PostSharp.Engineering`). \ No newline at end of file diff --git a/eng/style/stylecop.json b/eng/style/stylecop.json new file mode 100644 index 0000000..2b28d72 --- /dev/null +++ b/eng/style/stylecop.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "SharpCrafters s.r.o." + } + } +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs index abc8856..ba6afdc 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs @@ -6,18 +6,27 @@ namespace SharpCrafters.Backstage.LicenseServer.Data; /// -public sealed class LeaseRepository( - LicenseServerDbContext db, - IOptions options, - ILicenseParser licenseParser ) : ILeaseRepository +public sealed class LeaseRepository : ILeaseRepository { - private readonly LicenseServerOptions settings = options.Value; + private readonly LicenseServerDbContext db; + private readonly ILicenseParser licenseParser; + private readonly LicenseServerOptions settings; + + public LeaseRepository( + LicenseServerDbContext db, + IOptions options, + ILicenseParser licenseParser ) + { + this.db = db; + this.licenseParser = licenseParser; + this.settings = options.Value; + } - public IQueryable OpenLeases => db.OpenLeases; + public IQueryable OpenLeases => this.db.OpenLeases; - public IQueryable Leases => db.Leases; + public IQueryable Leases => this.db.Leases; - public IQueryable Licenses => db.Licenses; + public IQueryable Licenses => this.db.Licenses; public Lease? CreateLease( License license, @@ -48,7 +57,7 @@ public sealed class LeaseRepository( return null; } - db.Leases.Add( lease ); + this.db.Leases.Add( lease ); return lease; } @@ -74,7 +83,7 @@ public sealed class LeaseRepository( return null; } - db.Leases.Add( newLease ); + this.db.Leases.Add( newLease ); return newLease; } @@ -99,7 +108,7 @@ public void CancelLease( Lease lease, string authenticatedUserName, DateTime tim // instant, and the rule that a lease must end after the current instant would reject that. this.FixLease( overwrite, time, false ); - db.Leases.Add( overwrite ); + this.db.Leases.Add( overwrite ); } /// @@ -108,7 +117,7 @@ public void CancelLease( Lease lease, string authenticatedUserName, DateTime tim /// false when no time is left to grant. private bool FixLease( Lease lease, DateTime time, bool fixEndTime = true ) { - LicenseInfo? parsedLicense = licenseParser.TryParse( lease.License.LicenseKey ); + LicenseInfo? parsedLicense = this.licenseParser.TryParse( lease.License.LicenseKey ); if ( parsedLicense == null ) { @@ -161,7 +170,7 @@ public int GetActiveSeats( int licenseId, DateTime dateTime ) // It counts distinct machines and not leases. A user can hold two leases on one machine, // which happens when the clock of the server moves backwards. Counting the leases would // charge that user for a machine they do not work on. - List machinesPerUser = db.OpenLeases + List machinesPerUser = this.db.OpenLeases .Where( l => l.LicenseId == licenseId && l.StartTime <= dateTime && l.EndTime > dateTime ) .GroupBy( l => l.UserName ) .Select( g => g.Select( l => l.Machine ).Distinct().Count() ) @@ -175,7 +184,7 @@ public IEnumerable GetLeaseCountingPoints( DateTime startTime, DateTime endTime ) { - List leases = db.OpenLeases + List leases = this.db.OpenLeases .Where( l => l.LicenseId == licenseId && l.StartTime <= endTime && l.EndTime > startTime ) .AsNoTracking() .ToList(); @@ -254,7 +263,7 @@ public IEnumerable GetLeaseCountingPoints( /// Saves the unit of work. /// public Task SaveChangesAsync( CancellationToken cancellationToken = default ) - => db.SaveChangesAsync( cancellationToken ); + => this.db.SaveChangesAsync( cancellationToken ); - public int SaveChanges() => db.SaveChanges(); + public int SaveChanges() => this.db.SaveChanges(); } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs index e7f0b55..6b5b0cc 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs @@ -14,8 +14,10 @@ namespace SharpCrafters.Backstage.LicenseServer.Data; /// against SQL Server or PostgreSQL creates every test database from the script of that engine, which /// verifies the same thing against a live server. /// -public class LicenseServerDbContext( DbContextOptions options ) : DbContext( options ) +public class LicenseServerDbContext : DbContext { + public LicenseServerDbContext( DbContextOptions options ) : base( options ) { } + public DbSet Licenses => this.Set(); public DbSet Leases => this.Set(); diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs index 62dd9ca..faf6c52 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs @@ -15,12 +15,16 @@ namespace SharpCrafters.Backstage.LicenseServer.Email; /// result, so every failure was silent. This implementation writes a failure to the log, and it /// still raises no exception, because an SMTP server that fails must not deny a license. /// -public sealed class SmtpEmailSender( - IOptions options, - ILogger logger ) : IEmailSender +public sealed class SmtpEmailSender : IEmailSender { - private readonly SmtpOptions options = options.Value; - private readonly ILogger logger = logger; + private readonly SmtpOptions options; + private readonly ILogger logger; + + public SmtpEmailSender( IOptions options, ILogger logger ) + { + this.options = options.Value; + this.logger = logger; + } public async Task SendAsync( EmailMessage message, CancellationToken cancellationToken = default ) { diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs index 3d3b8f5..bf3913f 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs @@ -13,7 +13,7 @@ namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// The authorities whose signature the parser accepts. The default authorities are the production /// ones. A test signs with an authority of its own. /// -public sealed class BackstageLicenseParser( ILicensingAuthorityProvider? authorities = null ) : ILicenseParser +public sealed class BackstageLicenseParser : ILicenseParser { /// /// The percentage of additional seats allowed during the grace period, when the license key @@ -23,8 +23,12 @@ public sealed class BackstageLicenseParser( ILicensingAuthorityProvider? authori /// private const int defaultGracePercent = 30; - private readonly ILicensingAuthorityProvider authorities = - authorities ?? new ProductionLicensingAuthorityProvider(); + private readonly ILicensingAuthorityProvider authorities; + + public BackstageLicenseParser( ILicensingAuthorityProvider? authorities = null ) + { + this.authorities = authorities ?? new ProductionLicensingAuthorityProvider(); + } public LicenseInfo? TryParse( string licenseKey ) { diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs index 0a79926..2715516 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs @@ -11,14 +11,20 @@ namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// ParsedLicenseManager returned before it added the entry to the dictionary, so it parsed an /// invalid key at every call. /// -public sealed class CachingLicenseParser( ILicenseParser inner ) : ILicenseParser +public sealed class CachingLicenseParser : ILicenseParser { private readonly ConcurrentDictionary cache = new( StringComparer.Ordinal ); + private readonly ILicenseParser inner; + + public CachingLicenseParser( ILicenseParser inner ) + { + this.inner = inner; + } public LicenseInfo? TryParse( string licenseKey ) => string.IsNullOrWhiteSpace( licenseKey ) ? null - : this.cache.GetOrAdd( licenseKey, inner.TryParse ); + : this.cache.GetOrAdd( licenseKey, this.inner.TryParse ); - public string CleanLicenseString( string licenseKey ) => inner.CleanLicenseString( licenseKey ); + public string CleanLicenseString( string licenseKey ) => this.inner.CleanLicenseString( licenseKey ); } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs index b1c1dff..6c98e76 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs @@ -21,9 +21,9 @@ namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// cannot rename one. /// /// -public sealed class LeaseSerializer +public static class LeaseSerializer { - public string Serialize( string licenseKey, DateTime startTime, DateTime endTime, DateTime renewTime ) + public static string Serialize( string licenseKey, DateTime startTime, DateTime endTime, DateTime renewTime ) => $"License: {licenseKey}" + $"; StartTime: {ToUtcString( startTime )}" + $"; EndTime: {ToUtcString( endTime )}" diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs index 9cacacf..69830ee 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs @@ -464,33 +464,45 @@ private async Task SendEmailAsync( /// The capacity and the current usage of a license. The usage is computed on demand, because the /// first pass serves most requests and does not need it. /// - private sealed class LicenseState( - DateTime time, - ILeaseRepository repository, - License license, - LicenseInfo parsedLicense ) + private sealed class LicenseState { + private readonly DateTime time; + private readonly ILeaseRepository repository; + private readonly License license; + private readonly LicenseInfo parsedLicense; private int usage = -1; + public LicenseState( + DateTime time, + ILeaseRepository repository, + License license, + LicenseInfo parsedLicense ) + { + this.time = time; + this.repository = repository; + this.license = license; + this.parsedLicense = parsedLicense; + } + public int Usage { get { if ( this.usage == -1 ) { - this.usage = repository.GetActiveSeats( license.LicenseId, time ); + this.usage = this.repository.GetActiveSeats( this.license.LicenseId, this.time ); } return this.usage; } } - public int? Maximum => parsedLicense.UserNumber; + public int? Maximum => this.parsedLicense.UserNumber; [SuppressMessage( "ReSharper", "UnusedMember.Local", Justification = "Part of the state's contract." )] public bool InExcess => this.Maximum.HasValue && this.Maximum.Value < this.Usage; - public LicenseInfo ParsedLicense => parsedLicense; + public LicenseInfo ParsedLicense => this.parsedLicense; } } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs index 826c7fe..17b5614 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs @@ -22,16 +22,27 @@ namespace SharpCrafters.Backstage.LicenseServer.Services; /// of a license that no client uses, and the period would elapse while the server is idle. /// /// -public sealed class LicenseAvailabilityService( - ILeaseRepository repository, - ILicenseParser licenseParser, - ILicenseServerVersion serverVersion ) +public sealed class LicenseAvailabilityService { + private readonly ILeaseRepository repository; + private readonly ILicenseParser licenseParser; + private readonly ILicenseServerVersion serverVersion; + + public LicenseAvailabilityService( + ILeaseRepository repository, + ILicenseParser licenseParser, + ILicenseServerVersion serverVersion ) + { + this.repository = repository; + this.licenseParser = licenseParser; + this.serverVersion = serverVersion; + } + public async Task GetAvailabilityAsync( DateTime now, CancellationToken cancellationToken = default ) { - License[] licenses = await repository.Licenses + License[] licenses = await this.repository.Licenses .AsNoTracking() .ToArrayAsync( cancellationToken ); @@ -50,11 +61,11 @@ public async Task GetAvailabilityAsync( continue; } - LicenseInfo? parsedLicense = licenseParser.TryParse( license.LicenseKey ); + LicenseInfo? parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); if ( parsedLicense == null || !parsedLicense.IsLicenseServerEligible - || parsedLicense.MinPostSharpVersion > serverVersion.LicensingLibraryVersion ) + || parsedLicense.MinPostSharpVersion > this.serverVersion.LicensingLibraryVersion ) { invalid++; @@ -76,7 +87,7 @@ public async Task GetAvailabilityAsync( continue; } - int usage = repository.GetActiveSeats( license.LicenseId, now ); + int usage = this.repository.GetActiveSeats( license.LicenseId, now ); if ( usage < parsedLicense.UserNumber.Value ) { diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs index cb618ae..a3f7e5d 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs @@ -109,11 +109,13 @@ private static string ResolveScheme( IConfiguration configuration ) /// /// Authenticates no caller, so that the server serves every request anonymously. /// -public sealed class AnonymousAuthenticationHandler( - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder ) : AuthenticationHandler( options, logger, encoder ) +public sealed class AnonymousAuthenticationHandler : AuthenticationHandler { + public AnonymousAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder ) : base( options, logger, encoder ) { } + public const string SchemeName = "None"; protected override Task HandleAuthenticateAsync() diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs index b309aed..5fee15d 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs @@ -38,7 +38,6 @@ private static async Task GetLeaseAsync( HttpContext context, LeaseService leaseService, ILeaseRepository repository, - LeaseSerializer leaseSerializer, ILeaseLock leaseLock, IOptions options, TimeProvider timeProvider, @@ -160,7 +159,7 @@ private static async Task GetLeaseAsync( } return Results.Text( - leaseSerializer.Serialize( + LeaseSerializer.Serialize( grantedLease.LicenseKey, grantedLease.StartTime, grantedLease.EndTime, diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/DatabaseHealthCheck.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/DatabaseHealthCheck.cs index 3c1df63..6ab64b7 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/DatabaseHealthCheck.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/DatabaseHealthCheck.cs @@ -12,22 +12,31 @@ namespace SharpCrafters.Backstage.LicenseServer.Health; /// own schema on SQL Server, so a database that accepts connections but has no schema is a /// deployment error. A query detects it; opening a connection does not. /// -public sealed class DatabaseHealthCheck( LicenseServerDbContext db, IHostEnvironment environment ) : IHealthCheck +public sealed class DatabaseHealthCheck : IHealthCheck { + private readonly LicenseServerDbContext db; + private readonly IHostEnvironment environment; + + public DatabaseHealthCheck( LicenseServerDbContext db, IHostEnvironment environment ) + { + this.db = db; + this.environment = environment; + } + public async Task CheckHealthAsync( HealthCheckContext context, CancellationToken cancellationToken = default ) { try { - await db.Licenses.AsNoTracking().Select( l => l.LicenseId ).FirstOrDefaultAsync( cancellationToken ); + await this.db.Licenses.AsNoTracking().Select( l => l.LicenseId ).FirstOrDefaultAsync( cancellationToken ); return HealthCheckResult.Healthy( "The database answers." ); } catch ( Exception e ) when ( e is not OperationCanceledException ) { return HealthCheckResult.Unhealthy( - HealthDescriptions.ForException( "The database cannot be queried.", e, environment ), + HealthDescriptions.ForException( "The database cannot be queried.", e, this.environment ), e ); } } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs index f31eee3..f12c0f4 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs @@ -18,11 +18,22 @@ namespace SharpCrafters.Backstage.LicenseServer.Health; /// license. Only the process and the database can make the probe fail. /// /// -public sealed class LicenseHealthCheck( - LicenseAvailabilityService availability, - TimeProvider timeProvider, - IHostEnvironment environment ) : IHealthCheck +public sealed class LicenseHealthCheck : IHealthCheck { + private readonly LicenseAvailabilityService availability; + private readonly TimeProvider timeProvider; + private readonly IHostEnvironment environment; + + public LicenseHealthCheck( + LicenseAvailabilityService availability, + TimeProvider timeProvider, + IHostEnvironment environment ) + { + this.availability = availability; + this.timeProvider = timeProvider; + this.environment = environment; + } + public async Task CheckHealthAsync( HealthCheckContext context, CancellationToken cancellationToken = default ) @@ -31,7 +42,7 @@ public async Task CheckHealthAsync( try { - result = await availability.GetAvailabilityAsync( timeProvider.GetUtcNow().UtcDateTime, cancellationToken ); + result = await this.availability.GetAvailabilityAsync( this.timeProvider.GetUtcNow().UtcDateTime, cancellationToken ); } catch ( Exception e ) when ( e is not OperationCanceledException ) { @@ -41,7 +52,7 @@ public async Task CheckHealthAsync( // uncaught exception as the description of the failure, and an anonymous endpoint must // not return the message of a database exception. return HealthCheckResult.Degraded( - HealthDescriptions.ForException( "The license state cannot be read.", e, environment ), + HealthDescriptions.ForException( "The license state cannot be read.", e, this.environment ), e ); } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs index 7609f10..170668c 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs @@ -25,8 +25,10 @@ namespace SharpCrafters.Backstage.LicenseServer.Web.Locking; /// the code 55P03, and the caller answers with the status 503, as it does on SQL Server. /// /// -public sealed class PostgreSqlLeaseLock( LicenseServerDbContext db ) : ILeaseLock +public sealed class PostgreSqlLeaseLock : ILeaseLock { + private readonly LicenseServerDbContext db; + /// /// The name of the locked resource, which is the name the SQL Server lock uses. The two engines /// never share a database, so the two locks never meet. @@ -46,12 +48,17 @@ public sealed class PostgreSqlLeaseLock( LicenseServerDbContext db ) : ILeaseLoc /// public static readonly long ResourceKey = BitConverter.ToInt64( SHA256.HashData( Encoding.UTF8.GetBytes( resourceName ) ) ); + public PostgreSqlLeaseLock( LicenseServerDbContext db ) + { + this.db = db; + } + public async ValueTask TryAcquireAsync( TimeSpan timeout, CancellationToken cancellationToken = default ) { - DatabaseFacade database = db.Database; + DatabaseFacade database = this.db.Database; await database.OpenConnectionAsync( cancellationToken ); @@ -106,10 +113,16 @@ private static async Task ExecuteAsync( private static async Task ResetTimeoutAsync( DatabaseFacade database ) => await ExecuteAsync( database, "SET lock_timeout = DEFAULT", CancellationToken.None ); - private sealed class Handle( DatabaseFacade database ) : IAsyncDisposable + private sealed class Handle : IAsyncDisposable { + private readonly DatabaseFacade database; private int released; + public Handle( DatabaseFacade database ) + { + this.database = database; + } + public async ValueTask DisposeAsync() { if ( Interlocked.Exchange( ref this.released, 1 ) != 0 ) @@ -120,17 +133,17 @@ public async ValueTask DisposeAsync() try { await ExecuteAsync( - database, + this.database, $"SELECT pg_advisory_unlock({ResourceKey})", CancellationToken.None ); - await ResetTimeoutAsync( database ); + await ResetTimeoutAsync( this.database ); } finally { // Closing the connection releases the lock as well, so the lock is never held by a // connection that returns to the pool. - await database.CloseConnectionAsync(); + await this.database.CloseConnectionAsync(); } } } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs index 51c77cb..e5a875d 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs @@ -23,19 +23,26 @@ namespace SharpCrafters.Backstage.LicenseServer.Web.Locking; /// serializes the requests of a web garden and of several instances as well. /// /// -public sealed class SqlServerLeaseLock( LicenseServerDbContext db ) : ILeaseLock +public sealed class SqlServerLeaseLock : ILeaseLock { + private readonly LicenseServerDbContext db; + /// /// The name of the locked resource. SQL Server scopes an application lock to the database, so /// this name is shared by every process that serves this database, and by nothing else. /// public const string ResourceName = "SharpCrafters.Backstage.LicenseServer.Lease"; + public SqlServerLeaseLock( LicenseServerDbContext db ) + { + this.db = db; + } + public async ValueTask TryAcquireAsync( TimeSpan timeout, CancellationToken cancellationToken = default ) { - DatabaseFacade database = db.Database; + DatabaseFacade database = this.db.Database; await database.OpenConnectionAsync( cancellationToken ); @@ -116,10 +123,16 @@ private static async Task ExecuteAsync( return (int) returnValue.Value!; } - private sealed class Handle( DatabaseFacade database ) : IAsyncDisposable + private sealed class Handle : IAsyncDisposable { + private readonly DatabaseFacade database; private int released; + public Handle( DatabaseFacade database ) + { + this.database = database; + } + public async ValueTask DisposeAsync() { if ( Interlocked.Exchange( ref this.released, 1 ) != 0 ) @@ -130,7 +143,7 @@ public async ValueTask DisposeAsync() try { await ExecuteAsync( - database, + this.database, "sp_releaseapplock", command => { @@ -143,7 +156,7 @@ await ExecuteAsync( { // Closing the connection releases the lock as well, so the lock is never held by a // connection that returns to the pool. - await database.CloseConnectionAsync(); + await this.database.CloseConnectionAsync(); } } } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs index d4e0ef5..d72fb28 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs @@ -23,13 +23,20 @@ namespace SharpCrafters.Backstage.LicenseServer.Web.Locking; /// database is locked, and the caller answers with the status 503, as it does on SQL Server. /// /// -public sealed class SqliteLeaseLock( LicenseServerDbContext db ) : ILeaseLock +public sealed class SqliteLeaseLock : ILeaseLock { + private readonly LicenseServerDbContext db; + + public SqliteLeaseLock( LicenseServerDbContext db ) + { + this.db = db; + } + public async ValueTask TryAcquireAsync( TimeSpan timeout, CancellationToken cancellationToken = default ) { - DatabaseFacade database = db.Database; + DatabaseFacade database = this.db.Database; await database.OpenConnectionAsync( cancellationToken ); @@ -84,14 +91,26 @@ public sealed class SqliteLeaseLock( LicenseServerDbContext db ) : ILeaseLock private const int SqliteBusy = 5; private const int SqliteLocked = 6; - private sealed class Handle( - DatabaseFacade database, - IDbContextTransaction transaction, - SqliteConnection connection, - int previousTimeout ) : IAsyncDisposable + private sealed class Handle : IAsyncDisposable { + private readonly DatabaseFacade database; + private readonly IDbContextTransaction transaction; + private readonly SqliteConnection connection; + private readonly int previousTimeout; private int released; + public Handle( + DatabaseFacade database, + IDbContextTransaction transaction, + SqliteConnection connection, + int previousTimeout ) + { + this.database = database; + this.transaction = transaction; + this.connection = connection; + this.previousTimeout = previousTimeout; + } + public async ValueTask DisposeAsync() { if ( Interlocked.Exchange( ref this.released, 1 ) != 0 ) @@ -101,15 +120,15 @@ public async ValueTask DisposeAsync() try { - // The lease was saved inside this transaction, so the commit is what makes it + // The lease was saved inside this this.transaction, so the commit is what makes it // visible, and it releases the write lock. - await transaction.CommitAsync(); + await this.transaction.CommitAsync(); } finally { - await transaction.DisposeAsync(); - connection.DefaultTimeout = previousTimeout; - await database.CloseConnectionAsync(); + await this.transaction.DisposeAsync(); + this.connection.DefaultTimeout = this.previousTimeout; + await this.database.CloseConnectionAsync(); } } } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs index 4ce91ac..f05cb1d 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs @@ -10,12 +10,25 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; /// /// Registers a license key so that the server can serve leases against it. /// -public sealed class AddLicenseModel( - ILeaseRepository repository, - LicenseServerDbContext db, - ILicenseParser licenseParser, - TimeProvider timeProvider ) : PageModel +public sealed class AddLicenseModel : PageModel { + private readonly ILeaseRepository repository; + private readonly LicenseServerDbContext db; + private readonly ILicenseParser licenseParser; + private readonly TimeProvider timeProvider; + + public AddLicenseModel( + ILeaseRepository repository, + LicenseServerDbContext db, + ILicenseParser licenseParser, + TimeProvider timeProvider ) + { + this.repository = repository; + this.db = db; + this.licenseParser = licenseParser; + this.timeProvider = timeProvider; + } + [BindProperty] [Required( ErrorMessage = "Paste the license key." )] [Display( Name = "License key" )] @@ -30,7 +43,7 @@ public async Task OnPostAsync( CancellationToken cancellationToke return this.Page(); } - LicenseInfo? parsedLicense = licenseParser.TryParse( this.LicenseKey ); + LicenseInfo? parsedLicense = this.licenseParser.TryParse( this.LicenseKey ); if ( parsedLicense == null ) { @@ -49,23 +62,23 @@ public async Task OnPostAsync( CancellationToken cancellationToke return this.Page(); } - if ( await repository.Licenses.AnyAsync( l => l.LicenseId == parsedLicense.LicenseId, cancellationToken ) ) + if ( await this.repository.Licenses.AnyAsync( l => l.LicenseId == parsedLicense.LicenseId, cancellationToken ) ) { this.ModelState.AddModelError( nameof(this.LicenseKey), "The given license has been added already." ); return this.Page(); } - db.Licenses.Add( + this.db.Licenses.Add( new License { LicenseId = parsedLicense.LicenseId, - LicenseKey = licenseParser.CleanLicenseString( this.LicenseKey ), - CreatedOn = timeProvider.GetUtcNow().UtcDateTime, + LicenseKey = this.licenseParser.CleanLicenseString( this.LicenseKey ), + CreatedOn = this.timeProvider.GetUtcNow().UtcDateTime, ProductCode = parsedLicense.Product } ); - await db.SaveChangesAsync( cancellationToken ); + await this.db.SaveChangesAsync( cancellationToken ); return this.RedirectToPage( "/Index" ); } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs index 04ea45a..8604bed 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs @@ -8,8 +8,17 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; /// /// Ends a lease before its end time, so that another user can take the seat. /// -public sealed class CancelModel( ILeaseRepository repository, TimeProvider timeProvider ) : PageModel +public sealed class CancelModel : PageModel { + private readonly ILeaseRepository repository; + private readonly TimeProvider timeProvider; + + public CancelModel( ILeaseRepository repository, TimeProvider timeProvider ) + { + this.repository = repository; + this.timeProvider = timeProvider; + } + [BindProperty( SupportsGet = true )] public int Id { get; set; } @@ -17,7 +26,7 @@ public sealed class CancelModel( ILeaseRepository repository, TimeProvider timeP public async Task OnGetAsync( CancellationToken cancellationToken ) { - this.Lease = await repository.OpenLeases + this.Lease = await this.repository.OpenLeases .Include( l => l.License ) .AsNoTracking() .SingleOrDefaultAsync( l => l.LeaseId == this.Id, cancellationToken ); @@ -27,7 +36,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken public async Task OnPostAsync( CancellationToken cancellationToken ) { - Lease? lease = await repository.OpenLeases + Lease? lease = await this.repository.OpenLeases .Include( l => l.License ) .SingleOrDefaultAsync( l => l.LeaseId == this.Id, cancellationToken ); @@ -36,12 +45,12 @@ public async Task OnPostAsync( CancellationToken cancellationToke return this.NotFound(); } - repository.CancelLease( + this.repository.CancelLease( lease, this.User.Identity?.Name ?? string.Empty, - timeProvider.GetUtcNow().UtcDateTime ); + this.timeProvider.GetUtcNow().UtcDateTime ); - await repository.SaveChangesAsync( cancellationToken ); + await this.repository.SaveChangesAsync( cancellationToken ); return this.RedirectToPage( "/Admin/Details", new { id = lease.LicenseId } ); } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs index eb0d2a0..f8ad089 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs @@ -10,12 +10,25 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; /// /// The leases currently held against one license, and the actions an administrator can take on it. /// -public sealed class DetailsModel( - ILeaseRepository repository, - LicenseServerDbContext db, - IOptions options, - TimeProvider timeProvider ) : PageModel +public sealed class DetailsModel : PageModel { + private readonly ILeaseRepository repository; + private readonly LicenseServerDbContext db; + private readonly IOptions options; + private readonly TimeProvider timeProvider; + + public DetailsModel( + ILeaseRepository repository, + LicenseServerDbContext db, + IOptions options, + TimeProvider timeProvider ) + { + this.repository = repository; + this.db = db; + this.options = options; + this.timeProvider = timeProvider; + } + [BindProperty( SupportsGet = true )] public int Id { get; set; } @@ -27,7 +40,7 @@ public sealed class DetailsModel( /// Gets the number of machines that one seat covers, so that the page states the rule with the /// value configured on this server and not with the default value. ///
- public int MachinesPerSeat => options.Value.MachinesPerUser; + public int MachinesPerSeat => this.options.Value.MachinesPerUser; /// /// Gets with its noun, so that a server configured with one @@ -40,7 +53,7 @@ public string MachinesPerSeatText public async Task OnGetAsync( CancellationToken cancellationToken ) { - License? license = await repository.Licenses + License? license = await this.repository.Licenses .AsNoTracking() .SingleOrDefaultAsync( l => l.LicenseId == this.Id, cancellationToken ); @@ -49,15 +62,15 @@ public async Task OnGetAsync( CancellationToken cancellationToken return this.NotFound(); } - DateTime now = timeProvider.GetUtcNow().UtcDateTime; + DateTime now = this.timeProvider.GetUtcNow().UtcDateTime; - this.Leases = await repository.OpenLeases + this.Leases = await this.repository.OpenLeases .Where( l => l.LicenseId == this.Id && l.StartTime <= now && l.EndTime >= now ) .OrderBy( l => l.StartTime ) .AsNoTracking() .ToListAsync( cancellationToken ); - this.Seats = repository.GetActiveSeats( this.Id, now ); + this.Seats = this.repository.GetActiveSeats( this.Id, now ); this.IsDisabled = license.Priority < 0; return this.Page(); @@ -71,7 +84,7 @@ public Task OnPostDisableAsync( CancellationToken cancellationTok private async Task SetPriorityAsync( int priority, CancellationToken cancellationToken ) { - License? license = await db.Licenses.SingleOrDefaultAsync( l => l.LicenseId == this.Id, cancellationToken ); + License? license = await this.db.Licenses.SingleOrDefaultAsync( l => l.LicenseId == this.Id, cancellationToken ); if ( license == null ) { @@ -79,34 +92,34 @@ private async Task SetPriorityAsync( int priority, CancellationTo } license.Priority = priority; - await db.SaveChangesAsync( cancellationToken ); + await this.db.SaveChangesAsync( cancellationToken ); return this.RedirectToPage( "/Index" ); } public async Task OnPostDeleteAsync( CancellationToken cancellationToken ) { - if ( !await db.Licenses.AnyAsync( l => l.LicenseId == this.Id, cancellationToken ) ) + if ( !await this.db.Licenses.AnyAsync( l => l.LicenseId == this.Id, cancellationToken ) ) { return this.NotFound(); } - await using var transaction = await db.Database.BeginTransactionAsync( cancellationToken ); + await using var transaction = await this.db.Database.BeginTransactionAsync( cancellationToken ); // The leases reference each other through the chain of replacements, so they are deleted // before the license, and the most recent ones before the ones they replaced. - List leases = await db.Leases + List leases = await this.db.Leases .Where( l => l.LicenseId == this.Id ) .OrderByDescending( l => l.LeaseId ) .ToListAsync( cancellationToken ); foreach ( Lease lease in leases ) { - db.Leases.Remove( lease ); - await db.SaveChangesAsync( cancellationToken ); + this.db.Leases.Remove( lease ); + await this.db.SaveChangesAsync( cancellationToken ); } - await db.Licenses.Where( l => l.LicenseId == this.Id ).ExecuteDeleteAsync( cancellationToken ); + await this.db.Licenses.Where( l => l.LicenseId == this.Id ).ExecuteDeleteAsync( cancellationToken ); await transaction.CommitAsync( cancellationToken ); return this.RedirectToPage( "/Index" ); diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs index fb8fece..a7ed185 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs @@ -8,8 +8,15 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; /// /// Chooses the range of months to export from the lease audit log. /// -public sealed class ExportModel( TimeProvider timeProvider ) : PageModel +public sealed class ExportModel : PageModel { + private readonly TimeProvider timeProvider; + + public ExportModel( TimeProvider timeProvider ) + { + this.timeProvider = timeProvider; + } + [BindProperty] [Range( 2010, 2100, ErrorMessage = "The year must be between 2010 and 2100." )] [Display( Name = "From year" )] @@ -38,7 +45,7 @@ public sealed class ExportModel( TimeProvider timeProvider ) : PageModel public void OnGet() { - DateTime now = timeProvider.GetUtcNow().UtcDateTime; + DateTime now = this.timeProvider.GetUtcNow().UtcDateTime; this.FromYear = now.Year; this.ToYear = now.Year; diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs index 29da86d..10233e9 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs @@ -14,13 +14,28 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; /// This page exists only in the Development environment. The legacy version of this page was /// reachable in production, on a deployment whose administrative pages were open by default. /// -public sealed class GenerateDemoDataModel( - ILeaseRepository repository, - LicenseServerDbContext db, - LeaseService leaseService, - IHostEnvironment environment, - TimeProvider timeProvider ) : PageModel +public sealed class GenerateDemoDataModel : PageModel { + private readonly ILeaseRepository repository; + private readonly LicenseServerDbContext db; + private readonly LeaseService leaseService; + private readonly IHostEnvironment environment; + private readonly TimeProvider timeProvider; + + public GenerateDemoDataModel( + ILeaseRepository repository, + LicenseServerDbContext db, + LeaseService leaseService, + IHostEnvironment environment, + TimeProvider timeProvider ) + { + this.repository = repository; + this.db = db; + this.leaseService = leaseService; + this.environment = environment; + this.timeProvider = timeProvider; + } + private static readonly string[] firstNames = [ "David", "Jimmy", "Carroll", "Keith", "Marsha", "Mike", "Julio", "Salvatore", "Herbert", "Gary", @@ -39,7 +54,7 @@ public sealed class GenerateDemoDataModel( [Microsoft.AspNetCore.Mvc.ModelBinding.BindNever] public string? Message { get; private set; } - public bool IsAvailable => environment.IsDevelopment(); + public bool IsAvailable => this.environment.IsDevelopment(); public int UserCount { get; set; } = 20; @@ -57,7 +72,7 @@ public async Task OnPostAsync( int userCount, int days, Cancellat this.UserCount = Math.Clamp( userCount, 1, 200 ); this.Days = Math.Clamp( days, 1, 365 ); - License[] licenses = await repository.Licenses + License[] licenses = await this.repository.Licenses .Where( l => l.Priority >= 0 ) .OrderBy( l => l.Priority ) .ToArrayAsync( cancellationToken ); @@ -87,7 +102,7 @@ public async Task OnPostAsync( int userCount, int days, Cancellat } ) .ToArray(); - DateTime now = timeProvider.GetUtcNow().UtcDateTime; + DateTime now = this.timeProvider.GetUtcNow().UtcDateTime; DateTime start = now.Date.AddDays( -this.Days ); int granted = 0; @@ -109,7 +124,7 @@ public async Task OnPostAsync( int userCount, int days, Cancellat string machine = machines[random.Next( machines.Length )]; DateTime time = date.AddHours( 8 + (random.NextDouble() * 9) ); - Lease? lease = await leaseService.GetLeaseAsync( + Lease? lease = await this.leaseService.GetLeaseAsync( new Version( 2025, 1, 0 ), null, machine, @@ -129,7 +144,7 @@ public async Task OnPostAsync( int userCount, int days, Cancellat // The generator saves once per simulated day, and not once per lease. Otherwise the // change tracker would grow during the whole run, and each save would be slower than the // previous one. - await db.SaveChangesAsync( cancellationToken ); + await this.db.SaveChangesAsync( cancellationToken ); } this.Message = diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs index a6dde95..55c305f 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs @@ -17,11 +17,22 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages; /// the capacity, so the line and the two limits above it use the same unit, and the chart agrees /// with the column "In use" of the license list. /// -public sealed class GraphModel( - ILeaseRepository repository, - ILicenseParser licenseParser, - TimeProvider timeProvider ) : PageModel +public sealed class GraphModel : PageModel { + private readonly ILeaseRepository repository; + private readonly ILicenseParser licenseParser; + private readonly TimeProvider timeProvider; + + public GraphModel( + ILeaseRepository repository, + ILicenseParser licenseParser, + TimeProvider timeProvider ) + { + this.repository = repository; + this.licenseParser = licenseParser; + this.timeProvider = timeProvider; + } + /// /// The windows that the page offers. The list is closed, so that an arbitrary value cannot /// produce an unbounded query. @@ -43,7 +54,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken return this.BadRequest( $"The window must be one of {string.Join( ", ", AllowedWindows )} days." ); } - License? license = await repository.Licenses + License? license = await this.repository.Licenses .AsNoTracking() .SingleOrDefaultAsync( l => l.LicenseId == this.Id, cancellationToken ); @@ -52,14 +63,14 @@ public async Task OnGetAsync( CancellationToken cancellationToken return this.NotFound(); } - DateTime endDate = timeProvider.GetUtcNow().UtcDateTime.Date.AddDays( 1 ); + DateTime endDate = this.timeProvider.GetUtcNow().UtcDateTime.Date.AddDays( 1 ); DateTime startDate = endDate.AddDays( -this.Days ); int? maximum = null; int? graceMaximum = null; int axisMaximum = 0; - LicenseInfo? parsedLicense = licenseParser.TryParse( license.LicenseKey ); + LicenseInfo? parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); if ( parsedLicense?.UserNumber != null ) { @@ -72,7 +83,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken axisMaximum = graceMaximum.Value; } - var dailyUsage = repository.GetLeaseCountingPoints( this.Id, startDate, endDate ) + var dailyUsage = this.repository.GetLeaseCountingPoints( this.Id, startDate, endDate ) .GroupBy( point => point.Time.Date ) .Select( day => new diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs index f97896e..aab761b 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs @@ -8,27 +8,38 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages; /// /// The home page. It lists every registered license with the part of its capacity that is in use. /// -public sealed class IndexModel( - ILeaseRepository repository, - ILicenseParser licenseParser, - TimeProvider timeProvider ) : PageModel +public sealed class IndexModel : PageModel { + private readonly ILeaseRepository repository; + private readonly ILicenseParser licenseParser; + private readonly TimeProvider timeProvider; + + public IndexModel( + ILeaseRepository repository, + ILicenseParser licenseParser, + TimeProvider timeProvider ) + { + this.repository = repository; + this.licenseParser = licenseParser; + this.timeProvider = timeProvider; + } + public IReadOnlyList Licenses { get; private set; } = []; public async Task OnGetAsync( CancellationToken cancellationToken ) { - License[] licenses = await repository.Licenses + License[] licenses = await this.repository.Licenses .OrderBy( l => l.Priority ) .ThenByDescending( l => l.LicenseId ) .AsNoTracking() .ToArrayAsync( cancellationToken ); - DateTime now = timeProvider.GetUtcNow().UtcDateTime; + DateTime now = this.timeProvider.GetUtcNow().UtcDateTime; List summaries = []; foreach ( License license in licenses ) { - LicenseInfo? parsedLicense = licenseParser.TryParse( license.LicenseKey ); + LicenseInfo? parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); summaries.Add( parsedLicense == null @@ -44,7 +55,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken ) LicenseType = parsedLicense.LicenseType, ProductCode = parsedLicense.Product, MaxUsers = parsedLicense.UserNumber, - CurrentUsers = repository.GetActiveSeats( license.LicenseId, now ), + CurrentUsers = this.repository.GetActiveSeats( license.LicenseId, now ), GraceStartTime = license.GraceStartTime, Status = license.Priority >= 0 ? "Active" : "Disabled", MaintenanceEndDate = parsedLicense.SubscriptionEndDate diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs index 5bac850..4410718 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs @@ -44,7 +44,6 @@ .AddCheck( "database" ) .AddCheck( "licenses" ); -builder.Services.AddSingleton(); builder.Services.AddSingleton( services => services.GetRequiredService>().Value.Enabled diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs index 737782c..6f41e54 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs @@ -36,7 +36,7 @@ public async Task Export_WithMoreLeasesThanTheWriterBuffers_StreamsThemAll() // writes synchronously passes here and fails against a real server. this.application.ResponseBody.IsEnabled = true; - HttpResponseMessage response = await client.GetAsync( this.ExportUrl( 1, 12 ) ); + HttpResponseMessage response = await client.GetAsync( ExportUrl( 1, 12 ) ); Assert.Equal( HttpStatusCode.OK, response.StatusCode ); @@ -70,7 +70,7 @@ public async Task Export_WithLeases_IsOfferedAsAFile() HttpClient client = this.application.CreateClient(); this.application.ResponseBody.IsEnabled = true; - HttpResponseMessage response = await client.GetAsync( this.ExportUrl( 3, 4 ) ); + HttpResponseMessage response = await client.GetAsync( ExportUrl( 3, 4 ) ); Assert.Equal( HttpStatusCode.OK, response.StatusCode ); @@ -89,7 +89,7 @@ public async Task Export_WithNoLease_IsEmpty() HttpClient client = this.application.CreateClient(); this.application.ResponseBody.IsEnabled = true; - HttpResponseMessage response = await client.GetAsync( this.ExportUrl( 1, 12 ) ); + HttpResponseMessage response = await client.GetAsync( ExportUrl( 1, 12 ) ); Assert.Equal( HttpStatusCode.OK, response.StatusCode ); Assert.Equal( "", await response.Content.ReadAsStringAsync() ); @@ -122,7 +122,7 @@ private void SeedLeases( License license, int count ) db.SaveChanges(); } - private string ExportUrl( int fromMonth, int toMonth ) + private static string ExportUrl( int fromMonth, int toMonth ) { int year = DateTime.UtcNow.Year; diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs index 5b6337a..bff38ec 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs @@ -6,9 +6,14 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// Reports a fixed version of the licensing library, so that a test can reach the branch in which /// the license server is older than the license key requires. /// -public sealed class FixedServerVersion( Version version ) : ILicenseServerVersion +public sealed class FixedServerVersion : ILicenseServerVersion { public FixedServerVersion() : this( new Version( 2025, 1, 5 ) ) { } - public Version LicensingLibraryVersion { get; } = version; + public FixedServerVersion( Version version ) + { + this.LicensingLibraryVersion = version; + } + + public Version LicensingLibraryVersion { get; } } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs index 63c665c..e9e028a 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs @@ -53,8 +53,15 @@ public Action Configure( Action next ) /// Forwards every asynchronous write, and raises an exception at every synchronous write, with /// the message that Kestrel uses. /// - private sealed class AsyncOnlyStream( Stream inner ) : Stream + private sealed class AsyncOnlyStream : Stream { + private readonly Stream inner; + + public AsyncOnlyStream( Stream inner ) + { + this.inner = inner; + } + private const string message = "Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead."; @@ -62,7 +69,7 @@ private sealed class AsyncOnlyStream( Stream inner ) : Stream public override bool CanSeek => false; - public override bool CanWrite => inner.CanWrite; + public override bool CanWrite => this.inner.CanWrite; public override long Length => throw new NotSupportedException(); @@ -81,12 +88,12 @@ public override long Position public override void Flush() => throw new InvalidOperationException( message ); public override Task WriteAsync( byte[] buffer, int offset, int count, CancellationToken cancellationToken ) - => inner.WriteAsync( buffer, offset, count, cancellationToken ); + => this.inner.WriteAsync( buffer, offset, count, cancellationToken ); public override ValueTask WriteAsync( ReadOnlyMemory buffer, CancellationToken cancellationToken = default ) - => inner.WriteAsync( buffer, cancellationToken ); + => this.inner.WriteAsync( buffer, cancellationToken ); - public override Task FlushAsync( CancellationToken cancellationToken ) => inner.FlushAsync( cancellationToken ); + public override Task FlushAsync( CancellationToken cancellationToken ) => this.inner.FlushAsync( cancellationToken ); public override int Read( byte[] buffer, int offset, int count ) => throw new NotSupportedException(); diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs index f648c0b..738ff79 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs @@ -192,11 +192,13 @@ protected override void Dispose( bool disposing ) /// Authenticates every request as the same Windows-style identity, so the tests exercise the /// authenticated path without a domain controller. ///
-public sealed class TestAuthenticationHandler( - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder ) : AuthenticationHandler( options, logger, encoder ) +public sealed class TestAuthenticationHandler : AuthenticationHandler { + public TestAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder ) : base( options, logger, encoder ) { } + public const string SchemeName = "Test"; /// diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlTestDatabase.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlTestDatabase.cs index a646236..a6cf290 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlTestDatabase.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlTestDatabase.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore; using Npgsql; using SharpCrafters.Backstage.LicenseServer.Data; @@ -74,6 +75,11 @@ public async ValueTask DisposeAsync() /// /// The databases of one PostgreSQL server, lent to the tests one at a time. /// +[SuppressMessage( + "Microsoft.Design", + "CA1001", + Justification = "A pool lives as long as the run, in a static dictionary, and nothing disposes it. " + + "Its two semaphores are released with the process." )] internal sealed class PostgreSqlDatabasePool { /// diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs index 2a63894..72d7567 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Data; @@ -77,6 +78,11 @@ public async ValueTask DisposeAsync() /// /// The databases of one SQL Server, lent to the tests one at a time. /// +[SuppressMessage( + "Microsoft.Design", + "CA1001", + Justification = "A pool lives as long as the run, in a static dictionary, and nothing disposes it. " + + "Its two semaphores are released with the process." )] internal sealed class SqlServerDatabasePool { /// diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs index a6a645f..d4decba 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs @@ -128,13 +128,22 @@ private static LicensingAuthority CreateStandaloneAuthority( byte keyId ) /// authority from the XML representation of a key, and the key of the test authority is generated /// in the process instead of being written in the code. /// - private sealed class TestAuthorityProvider( LicensingAuthority authority, byte keyId ) : ILicensingAuthorityProvider + private sealed class TestAuthorityProvider : ILicensingAuthorityProvider { - public IEnumerable KeyIds => [keyId]; + private readonly LicensingAuthority authority; + private readonly byte keyId; + + public TestAuthorityProvider( LicensingAuthority authority, byte keyId ) + { + this.authority = authority; + this.keyId = keyId; + } + + public IEnumerable KeyIds => [this.keyId]; public LicensingAuthority GetAuthority( byte id ) - => id == keyId - ? authority - : throw new KeyNotFoundException( $"There is no test licensing authority key of identifier {id}." ); + => id == this.keyId + ? this.authority + : throw new KeyNotFoundException( $"There is no test licensing this.authority key of identifier {id}." ); } } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs index efb0062..270f52a 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs @@ -12,8 +12,6 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// public sealed class LeaseSerializerTests { - private static readonly LeaseSerializer serializer = new(); - private static readonly DateTime start = new( 2026, 1, 5, 9, 0, 0, DateTimeKind.Utc ); [Fact] @@ -23,7 +21,7 @@ public void Serialize_ProducesTheExpectedBody() + "; StartTime: 2026-01-05T09:00:00Z" + "; EndTime: 2026-01-08T09:00:00Z" + "; RenewTime: 2026-01-07T09:00:00Z", - serializer.Serialize( "1-ABCDEF", start, start.AddDays( 3 ), start.AddDays( 2 ) ) ); + LeaseSerializer.Serialize( "1-ABCDEF", start, start.AddDays( 3 ), start.AddDays( 2 ) ) ); /// /// An instant read from a datetime column carries no kind. It is a UTC instant, and the @@ -35,8 +33,8 @@ public void Serialize_TreatsAnUntaggedTimeAsUtc() DateTime unspecified = new( 2026, 1, 5, 9, 0, 0, DateTimeKind.Unspecified ); Assert.Equal( - serializer.Serialize( "1-ABCDEF", start, start, start ), - serializer.Serialize( "1-ABCDEF", unspecified, unspecified, unspecified ) ); + LeaseSerializer.Serialize( "1-ABCDEF", start, start, start ), + LeaseSerializer.Serialize( "1-ABCDEF", unspecified, unspecified, unspecified ) ); } /// @@ -47,9 +45,9 @@ public void Serialize_TreatsAnUntaggedTimeAsUtc() [Fact] public void Serialize_ProducesFourPartsTheClientCanSplit() { - string[] parts = serializer.Serialize( "1-ABCDEF", start, start, start ).Split( ';' ); + string[] parts = LeaseSerializer.Serialize( "1-ABCDEF", start, start, start ).Split( ';' ); Assert.Equal( 4, parts.Length ); - Assert.Equal( ["License", "StartTime", "EndTime", "RenewTime"], parts.Select( p => p[..p.IndexOf( ':' )].Trim() ) ); + Assert.Equal( ["License", "StartTime", "EndTime", "RenewTime"], parts.Select( p => p[..p.IndexOf( ':', StringComparison.Ordinal )].Trim() ) ); } } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs index 37909f5..3b080e8 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs @@ -156,13 +156,19 @@ public void NoTestSettings_OutsideDevelopment_Starts() Assert.Null( services.BuildServiceProvider().GetService() ); } - private sealed class StubEnvironment( string environmentName, string contentRootPath = "." ) : IHostEnvironment + private sealed class StubEnvironment : IHostEnvironment { - public string EnvironmentName { get; set; } = environmentName; + public StubEnvironment( string environmentName, string contentRootPath = "." ) + { + this.EnvironmentName = environmentName; + this.ContentRootPath = contentRootPath; + } + + public string EnvironmentName { get; set; } public string ApplicationName { get; set; } = "Tests"; - public string ContentRootPath { get; set; } = contentRootPath; + public string ContentRootPath { get; set; } public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = new Microsoft.Extensions.FileProviders.NullFileProvider(); diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs index 496a06e..1148207 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Security.Cryptography; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -105,7 +106,7 @@ private static ILicenseParser BuildParser( string environmentName, params (int K for ( int i = 0; i < authorities.Length; i++ ) { - settings[$"LicenseServer:TestLicensingAuthorities:{i}:KeyId"] = authorities[i].KeyId.ToString(); + settings[$"LicenseServer:TestLicensingAuthorities:{i}:KeyId"] = authorities[i].KeyId.ToString( CultureInfo.InvariantCulture ); settings[$"LicenseServer:TestLicensingAuthorities:{i}:PublicKey"] = authorities[i].PublicKey; } @@ -140,9 +141,14 @@ string ToXml( bool includePrivateValue ) return (ToXml( false ), licenseKey); } - private sealed class StubEnvironment( string environmentName ) : IHostEnvironment + private sealed class StubEnvironment : IHostEnvironment { - public string EnvironmentName { get; set; } = environmentName; + public StubEnvironment( string environmentName ) + { + this.EnvironmentName = environmentName; + } + + public string EnvironmentName { get; set; } public string ApplicationName { get; set; } = "Tests"; From d40cad2764a259b630084bad5f64121fdd018acf Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Fri, 18 Sep 2026 10:36:47 +0200 Subject: [PATCH 42/44] Reformat the code with the shared style, and add the license The JetBrains tools reformatted every file against eng/style/CommonStyle.DotSettings, the profile named Custom. They wrote the copyright header of the company, replaced an explicit type with var, removed the newline at the end of a file, reflowed the Razor markup, and spaced the nested parentheses. Nothing else changed: the build has no warning, and 280 tests pass on SQLite, on SQL Server and on PostgreSQL. The header the tools write names LICENSE.md, and this repository had no license file, although the README states that the server is published under the MIT License. LICENSE.md now carries that license, in the form the other repositories of the company use, and the README links to it. SharpCrafters.Backstage.LicenseServer.slnx.DotSettings carries the team-shared settings layer of the solution, which names eng/style/CommonStyle.DotSettings. Rider reads it, so the manual step of "Manage Layers" is no longer needed. `Build.ps1 codestyle format` does not work here, and the README says so and gives the command that does. That command relies on the settings layer of the solution to find the cleanup profile, and the JetBrains tools do not read that layer for a solution in the slnx format: they answer "Unable to find the code cleanup profile with 'Custom' name", with either name for the settings file and with or without the absolute path in it. Passing --settings=eng/style/CommonStyle.DotSettings works, with the same layers disabled as the build system disables, so the result is the one the command would have produced. dotnet-tools.json is ignored. The format command writes it to pin the version of the JetBrains tools on the machine that runs them. Co-Authored-By: Claude Opus 5 --- .gitignore | 4 + LICENSE.md | 18 ++ README.md | 24 ++- ...s.Backstage.LicenseServer.slnx.DotSettings | 5 + .../Data/ILeaseRepository.cs | 4 +- .../Data/Lease.Audit.cs | 4 +- .../Data/Lease.Entity.cs | 4 +- .../Data/LeaseConfiguration.cs | 4 +- .../Data/LeaseCountingPoint.cs | 4 +- .../Data/LeaseCountingPointKind.cs | 4 +- .../Data/LeaseRepository.cs | 36 ++-- .../Data/License.cs | 4 +- .../Data/LicenseConfiguration.cs | 4 +- .../Data/LicenseServerDbContext.cs | 11 +- .../Data/SeatCounter.cs | 4 +- .../Email/EmailMessage.cs | 4 +- .../Email/IEmailSender.cs | 4 +- .../Email/NullEmailSender.cs | 7 +- .../Email/SmtpEmailSender.cs | 9 +- .../Licensing/BackstageLicenseParser.cs | 13 +- .../Licensing/BackstageServerVersion.cs | 4 +- .../Licensing/CachingLicenseParser.cs | 4 +- .../CompositeLicensingAuthorityProvider.cs | 10 +- .../Licensing/ILicenseParser.cs | 4 +- .../Licensing/ILicenseServerVersion.cs | 4 +- .../Licensing/LeaseSerializer.cs | 7 +- .../Licensing/LicenseInfo.cs | 4 +- .../Licensing/LicenseServerProductCatalog.cs | 10 +- .../Licensing/ProductCodes.cs | 8 +- .../Licensing/TestLicenseAuthority.cs | 13 +- .../Licensing/TestLicenseSeeder.cs | 12 +- .../Locking/ILeaseLock.cs | 4 +- .../Options/LicenseServerOptions.cs | 4 +- .../Options/LicenseServerOptionsValidator.cs | 4 +- .../Options/SmtpOptions.cs | 4 +- .../Options/TestLicensingAuthority.cs | 4 +- .../Services/LeaseService.cs | 69 +++---- .../Services/LicenseAvailabilityService.cs | 26 +-- .../Services/LicenseCapacity.cs | 7 +- .../Time/AcceleratedTimeProvider.cs | 7 +- .../AuthenticationRegistration.cs | 16 +- .../DatabaseRegistration.cs | 14 +- .../Endpoints/LegacyUrlRedirects.cs | 24 +-- .../Endpoints/LicenseServerEndpoints.cs | 38 ++-- .../Endpoints/OperationsEndpoints.cs | 29 +-- .../Health/DatabaseHealthCheck.cs | 4 +- .../Health/HealthDescriptions.cs | 4 +- .../Health/LicenseHealthCheck.cs | 4 +- .../LicensingRegistration.cs | 22 +-- .../Locking/PostgreSqlLeaseLock.cs | 12 +- .../Locking/SqlServerLeaseLock.cs | 19 +- .../Locking/SqliteLeaseLock.cs | 10 +- .../Pages/Admin/AddLicense.cshtml | 12 +- .../Pages/Admin/AddLicense.cshtml.cs | 6 +- .../Pages/Admin/Cancel.cshtml | 6 +- .../Pages/Admin/Cancel.cshtml.cs | 6 +- .../Pages/Admin/Details.cshtml | 56 +++--- .../Pages/Admin/Details.cshtml.cs | 23 ++- .../Pages/Admin/Export.cshtml | 22 ++- .../Pages/Admin/Export.cshtml.cs | 13 +- .../Pages/Admin/GenerateDemoData.cshtml | 16 +- .../Pages/Admin/GenerateDemoData.cshtml.cs | 50 ++--- .../Pages/Error.cshtml | 6 +- .../Pages/Error.cshtml.cs | 4 +- .../Pages/Graph.cshtml | 4 +- .../Pages/Graph.cshtml.cs | 53 +++--- .../Pages/Index.cshtml | 68 +++---- .../Pages/Index.cshtml.cs | 19 +- .../Pages/Shared/_Layout.cshtml | 20 +- .../Program.cs | 70 ++++--- .../AuditLogExportTests.cs | 34 ++-- .../BackstageLicenseParserTests.cs | 35 ++-- .../BuildServerDetectionTests.cs | 19 +- .../CancelLeaseTests.cs | 40 ++-- .../ConfigurationTests.cs | 24 ++- .../EmailSenderTests.cs | 9 +- .../Fakes/FakeLicenseParser.cs | 7 +- .../Fakes/FixedServerVersion.cs | 4 +- .../Fakes/InMemoryEmailSender.cs | 4 +- .../Fakes/NeverAcquiringLeaseLock.cs | 4 +- .../GetActiveSeatsTests.cs | 58 +++--- .../Infrastructure/AsyncOnlyResponseBody.cs | 45 ++--- .../LicenseServerApplication.cs | 85 ++++----- .../LicenseServerTestContext.cs | 6 +- .../Infrastructure/PostgreSqlTestDatabase.cs | 22 ++- .../Infrastructure/SqlServerTestDatabase.cs | 24 +-- .../Infrastructure/SqliteTestDatabase.cs | 8 +- .../Infrastructure/TestData.cs | 6 +- .../Infrastructure/TestDatabases.cs | 7 +- .../Infrastructure/TestLicenseKeys.cs | 33 ++-- .../LeaseAllocationTests.cs | 142 ++++++++------ .../LeaseAuditLineTests.cs | 22 ++- .../LeaseCountingPointsTests.cs | 180 +++++++++++------- .../LeaseEndpointTests.cs | 86 +++++---- .../LeaseLockTests.cs | 35 ++-- .../LeaseSerializerTests.cs | 6 +- .../LicenseAvailabilityTests.cs | 67 +++---- .../LicenseValidationTests.cs | 60 +++--- .../OpenLeasesTests.cs | 42 ++-- .../OperationsEndpointTests.cs | 39 ++-- .../PageTests.cs | 107 +++++------ .../ProductCodesTests.cs | 15 +- .../ReviewRegressionTests.cs | 40 ++-- .../SchemaCompatibilityTests.cs | 39 ++-- .../SeatCountingTests.cs | 17 +- .../SeedTestLicensesTests.cs | 44 +++-- .../TestLicensingAuthorityTests.cs | 50 ++--- 107 files changed, 1358 insertions(+), 1137 deletions(-) create mode 100644 LICENSE.md create mode 100644 SharpCrafters.Backstage.LicenseServer.slnx.DotSettings diff --git a/.gitignore b/.gitignore index d2afef1..c366586 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,7 @@ nuget.config /source-dependencies/ /eng/tools/ /.config/dotnet-tools.json + +# The local tool manifest that `Build.ps1 codestyle format` writes when it installs the JetBrains +# command line tools. It pins a version per machine and is not part of the product. +/dotnet-tools.json diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..6886398 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,18 @@ + + +# MIT License + +Copyright (c) 2026 SharpCrafters s.r.o. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 17fb9e4..39e4a5b 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ your organization, ask the PostSharp sales team for a license key that waives it ## License -The license server is published under the MIT License. PostSharp itself is a commercial product with -a proprietary license. +The license server is published under the MIT License, which [LICENSE.md](LICENSE.md) states in full. +PostSharp itself is a commercial product with a proprietary license. ## Download @@ -183,9 +183,23 @@ the dependency at it, and build that repository first: root of the repository is a symbolic link to `eng/style/.editorconfig`, so a clone needs `core.symlinks = true`. Refresh the style with `./Build.ps1 codestyle pull`. -In Rider, open Settings, choose "Manage Layers", select the team-shared layer, click the plus icon, -choose "Open Settings File", and select `eng/style/CommonStyle.DotSettings`. This step is what makes -`./Build.ps1 codestyle format` reformat the code the way the team writes it. +`SharpCrafters.Backstage.LicenseServer.slnx.DotSettings` carries the team-shared layer of the +solution, which names `eng/style/CommonStyle.DotSettings`. In Rider that layer is what +"Manage Layers" edits, so no manual step is left there. + +Reformat the code with the command below. `./Build.ps1 codestyle format` does the same thing, but it +fails against this repository: it relies on the settings layer of the solution to find the cleanup +profile, and the JetBrains command line tools do not read that layer for a solution in the `.slnx` +format. They report "Unable to find the code cleanup profile with 'Custom' name". Naming the settings +file works: + +``` +dotnet jb cleanupcode --profile:Custom --settings=eng/style/CommonStyle.DotSettings ` + --disable-settings-layers:"GlobalAll;GlobalPerProduct;SolutionPersonal;ProjectPersonal" ` + SharpCrafters.Backstage.LicenseServer.slnx +``` + +Commit your work before you reformat, so that the reformatting is a commit of its own. ### Running the tests diff --git a/SharpCrafters.Backstage.LicenseServer.slnx.DotSettings b/SharpCrafters.Backstage.LicenseServer.slnx.DotSettings new file mode 100644 index 0000000..5dc3523 --- /dev/null +++ b/SharpCrafters.Backstage.LicenseServer.slnx.DotSettings @@ -0,0 +1,5 @@ + + eng\style\CommonStyle.DotSettings + True + True + 1 \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs index 5548124..d4a02ae 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Data; /// @@ -58,4 +60,4 @@ public interface ILeaseRepository Task SaveChangesAsync( CancellationToken cancellationToken = default ); int SaveChanges(); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs index a2eaa3b..275e3c8 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Xml; namespace SharpCrafters.Backstage.LicenseServer; @@ -54,4 +56,4 @@ public string ToAuditLine() return writer.ToString(); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs index 281c7a6..26d3f1c 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer; /// @@ -45,4 +47,4 @@ public partial class Lease public Lease? OverwritesLease { get; set; } public ICollection OverwrittenByLease { get; set; } = new List(); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs index f649745..5608eb3 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -34,4 +36,4 @@ public void Configure( EntityTypeBuilder builder ) builder.HasIndex( x => x.EndTime ).HasDatabaseName( "IX_Leases_EndTime" ); builder.HasIndex( x => x.OverwrittenLeaseId ).HasDatabaseName( "IX_Leases_OverwrittenLeaseId" ); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs index 4ab9213..3af9042 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer; /// @@ -22,4 +24,4 @@ public sealed class LeaseCountingPoint /// the same quantity against the capacity. /// public int SeatCount { get; set; } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPointKind.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPointKind.cs index 89a180f..338416f 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPointKind.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPointKind.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer; /// @@ -15,4 +17,4 @@ public enum LeaseCountingPointKind // A close is processed before an open. Close = 1, Open = 2 -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs index ba6afdc..3526939 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using SharpCrafters.Backstage.LicenseServer.Licensing; @@ -43,7 +45,6 @@ public LeaseRepository( // Assigned next to the navigation property, and not left to the fixup of EF Core, so that // the lease is complete before it is tracked and signed. LicenseId = license.LicenseId, - AuthenticatedUser = authenticatedUserName, EndTime = time.AddDays( this.settings.NewLeaseDays ), Machine = machine, @@ -117,7 +118,7 @@ public void CancelLease( Lease lease, string authenticatedUserName, DateTime tim /// false when no time is left to grant. private bool FixLease( Lease lease, DateTime time, bool fixEndTime = true ) { - LicenseInfo? parsedLicense = this.licenseParser.TryParse( lease.License.LicenseKey ); + var parsedLicense = this.licenseParser.TryParse( lease.License.LicenseKey ); if ( parsedLicense == null ) { @@ -138,7 +139,7 @@ private bool FixLease( Lease lease, DateTime time, bool fixEndTime = true ) if ( lease.Grace ) { - DateTime graceEnd = lease.License.GraceStartTime!.Value.AddDays( parsedLicense.GraceDays ); + var graceEnd = lease.License.GraceStartTime!.Value.AddDays( parsedLicense.GraceDays ); if ( lease.EndTime > graceEnd ) { @@ -170,7 +171,7 @@ public int GetActiveSeats( int licenseId, DateTime dateTime ) // It counts distinct machines and not leases. A user can hold two leases on one machine, // which happens when the clock of the server moves backwards. Counting the leases would // charge that user for a machine they do not work on. - List machinesPerUser = this.db.OpenLeases + var machinesPerUser = this.db.OpenLeases .Where( l => l.LicenseId == licenseId && l.StartTime <= dateTime && l.EndTime > dateTime ) .GroupBy( l => l.UserName ) .Select( g => g.Select( l => l.Machine ).Distinct().Count() ) @@ -184,7 +185,7 @@ public IEnumerable GetLeaseCountingPoints( DateTime startTime, DateTime endTime ) { - List leases = this.db.OpenLeases + var leases = this.db.OpenLeases .Where( l => l.LicenseId == licenseId && l.StartTime <= endTime && l.EndTime > startTime ) .AsNoTracking() .ToList(); @@ -197,11 +198,9 @@ public IEnumerable GetLeaseCountingPoints( // Ordering in memory gives a stable sort, so the timeline is reproducible. Close sorts // before Open at the same instant; see LeaseCountingPointKind. - List allRecords = leases + var allRecords = leases .Select( l => new LeaseCountingPoint { Time = l.StartTime, Kind = LeaseCountingPointKind.Open, Lease = l } ) - .Concat( - leases.Select( - l => new LeaseCountingPoint { Time = l.EndTime, Kind = LeaseCountingPointKind.Close, Lease = l } ) ) + .Concat( leases.Select( l => new LeaseCountingPoint { Time = l.EndTime, Kind = LeaseCountingPointKind.Close, Lease = l } ) ) .OrderBy( p => p.Time ) .ThenBy( p => p.Kind ) .ThenBy( p => p.Lease.LeaseId ) @@ -214,24 +213,24 @@ public IEnumerable GetLeaseCountingPoints( // the machine at the first close and found nothing to remove at the second. Dictionary> currentUsers = new( StringComparer.OrdinalIgnoreCase ); - int seatCount = 0; + var seatCount = 0; - foreach ( LeaseCountingPoint record in allRecords ) + foreach ( var record in allRecords ) { - if ( !currentUsers.TryGetValue( record.Lease.UserName, out Dictionary? machines ) ) + if ( !currentUsers.TryGetValue( record.Lease.UserName, out var machines ) ) { machines = new Dictionary( StringComparer.OrdinalIgnoreCase ); currentUsers.Add( record.Lease.UserName, machines ); } - int seatsBefore = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); - string machine = record.Lease.Machine; + var seatsBefore = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); + var machine = record.Lease.Machine; if ( record.Kind == LeaseCountingPointKind.Open ) { machines[machine] = machines.GetValueOrDefault( machine ) + 1; } - else if ( machines.TryGetValue( machine, out int openLeases ) ) + else if ( machines.TryGetValue( machine, out var openLeases ) ) { // The machine leaves the list when its last lease closes. if ( openLeases > 1 ) @@ -250,7 +249,7 @@ public IEnumerable GetLeaseCountingPoints( // because the page that draws the timeline is a report: an administrator who looks at // usage must not receive an error. - int seatsAfter = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); + var seatsAfter = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); seatCount += seatsAfter - seatsBefore; record.SeatCount = seatCount; @@ -262,8 +261,7 @@ public IEnumerable GetLeaseCountingPoints( /// /// Saves the unit of work. /// - public Task SaveChangesAsync( CancellationToken cancellationToken = default ) - => this.db.SaveChangesAsync( cancellationToken ); + public Task SaveChangesAsync( CancellationToken cancellationToken = default ) => this.db.SaveChangesAsync( cancellationToken ); public int SaveChanges() => this.db.SaveChanges(); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/License.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/License.cs index 31a722e..e0acd37 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/License.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/License.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer; /// @@ -36,4 +38,4 @@ public class License public DateTime? GraceLastWarningTime { get; set; } public ICollection Leases { get; set; } = new List(); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseConfiguration.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseConfiguration.cs index 0f82593..e0f8b80 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseConfiguration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseConfiguration.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -25,4 +27,4 @@ public void Configure( EntityTypeBuilder builder ) builder.Property( x => x.Priority ).IsRequired(); builder.Property( x => x.CreatedOn ).IsRequired(); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs index 6b5b0cc..fed5893 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -31,14 +33,13 @@ public LicenseServerDbContext( DbContextOptions options /// returned a lease twice when two leases had overwritten it. No unique constraint prevents /// that. /// - public IQueryable OpenLeases - => this.Leases.Where( l => !this.Leases.Any( o => o.OverwrittenLeaseId == l.LeaseId ) ); + public IQueryable OpenLeases => this.Leases.Where( l => !this.Leases.Any( o => o.OverwrittenLeaseId == l.LeaseId ) ); protected override void OnModelCreating( ModelBuilder modelBuilder ) { modelBuilder.ApplyConfigurationsFromAssembly( typeof(LicenseServerDbContext).Assembly ); - bool isSqlServer = this.Database.ProviderName == "Microsoft.EntityFrameworkCore.SqlServer"; + var isSqlServer = this.Database.ProviderName == "Microsoft.EntityFrameworkCore.SqlServer"; // Every timestamp of this database is UTC, and the converter states it in both directions. // @@ -98,7 +99,7 @@ protected override void OnModelCreating( ModelBuilder modelBuilder ) // // SQLite carries NOCASE. PostgreSQL carries no collation that ignores the case, so the // schema creates one. - string collation = this.Database.ProviderName == "Npgsql.EntityFrameworkCore.PostgreSQL" + var collation = this.Database.ProviderName == "Npgsql.EntityFrameworkCore.PostgreSQL" ? CaseInsensitiveCollation : "NOCASE"; @@ -112,4 +113,4 @@ protected override void OnModelCreating( ModelBuilder modelBuilder ) /// creates it, and so does a test database, because PostgreSQL defines no such collation itself. /// public const string CaseInsensitiveCollation = "license_server_ci"; -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs index 6ed8bb9..d448918 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Data; /// @@ -37,4 +39,4 @@ public static int CountSeats( IEnumerable machinesPerUser, int machinesPerU return (int) machinesPerUser.Sum( count => Math.Ceiling( count / (double) machinesPerUserLimit ) ); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/EmailMessage.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/EmailMessage.cs index 21180aa..e22db95 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/EmailMessage.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/EmailMessage.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Email; /// @@ -7,4 +9,4 @@ public sealed record EmailMessage( string To, string? Cc, string Subject, - string Body ); + string Body ); \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/IEmailSender.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/IEmailSender.cs index b1bd778..1e14356 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/IEmailSender.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/IEmailSender.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Email; /// @@ -7,4 +9,4 @@ namespace SharpCrafters.Backstage.LicenseServer.Email; public interface IEmailSender { Task SendAsync( EmailMessage message, CancellationToken cancellationToken = default ); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/NullEmailSender.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/NullEmailSender.cs index c64f572..42bf57c 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/NullEmailSender.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/NullEmailSender.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Email; /// @@ -6,6 +8,5 @@ namespace SharpCrafters.Backstage.LicenseServer.Email; /// public sealed class NullEmailSender : IEmailSender { - public Task SendAsync( EmailMessage message, CancellationToken cancellationToken = default ) - => Task.CompletedTask; -} + public Task SendAsync( EmailMessage message, CancellationToken cancellationToken = default ) => Task.CompletedTask; +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs index faf6c52..873f604 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using MailKit.Net.Smtp; using MailKit.Security; using Microsoft.Extensions.Logging; @@ -46,9 +48,9 @@ public SmtpEmailSender( IOptions options, ILogger if ( !string.IsNullOrEmpty( message.Cc ) ) { - foreach ( string address in message.Cc.Split( [',', ';', ' '], StringSplitOptions.RemoveEmptyEntries ) ) + foreach ( var address in message.Cc.Split( [',', ';', ' '], StringSplitOptions.RemoveEmptyEntries ) ) { - string trimmed = address.Trim( ' ', '\n', '\r', '\t' ); + var trimmed = address.Trim( ' ', '\n', '\r', '\t' ); if ( trimmed.Length > 0 ) { @@ -82,5 +84,4 @@ await client.ConnectAsync( this.logger.LogError( e, "Cannot send the notification email '{Subject}' to {To}.", message.Subject, message.To ); } } - -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs index bf3913f..88ddde5 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.Licensing; using SharpCrafters.Backstage.Licensing.Licenses; using SharpCrafters.Backstage.Licensing.Registration; @@ -37,7 +39,7 @@ public BackstageLicenseParser( ILicensingAuthorityProvider? authorities = null ) return null; } - if ( !LicenseKeyData.TryDeserialize( licenseKey, out LicenseKeyData? data, out _ ) ) + if ( !LicenseKeyData.TryDeserialize( licenseKey, out var data, out _ ) ) { return null; } @@ -57,7 +59,7 @@ public BackstageLicenseParser( ILicensingAuthorityProvider? authorities = null ) // works around postsharp-ops/SharpCrafters.Backstage#2. It can be removed when // TryVerifySignature returns false for an unknown identifier. if ( data.RequiresSignature() - && (data.SignatureKeyId == null || !this.authorities.KeyIds.Contains( data.SignatureKeyId.Value )) ) + && ( data.SignatureKeyId == null || !this.authorities.KeyIds.Contains( data.SignatureKeyId.Value ) ) ) { return null; } @@ -72,7 +74,7 @@ public BackstageLicenseParser( ILicensingAuthorityProvider? authorities = null ) // PostSharp version of a key that is older than MinPostSharpVersion, and the normalization // of the products that were renamed. Reading the fields directly would duplicate these // rules. - LicenseRegistrationProperties properties = data.ToLicenseRegistrationProperties( + var properties = data.ToLicenseRegistrationProperties( LicenseServerProductCatalog.Instance, licenseKey ); @@ -108,6 +110,5 @@ public BackstageLicenseParser( ILicensingAuthorityProvider? authorities = null ) /// alone and converts the result to upper case, which changes a key rather than only trimming /// what a web form added. /// - public string CleanLicenseString( string licenseKey ) - => new( licenseKey.Where( c => !char.IsWhiteSpace( c ) ).ToArray() ); -} + public string CleanLicenseString( string licenseKey ) => new( licenseKey.Where( c => !char.IsWhiteSpace( c ) ).ToArray() ); +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageServerVersion.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageServerVersion.cs index b3c185c..5c29150 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageServerVersion.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageServerVersion.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.Licensing.Licenses; namespace SharpCrafters.Backstage.LicenseServer.Licensing; @@ -14,4 +16,4 @@ public sealed class BackstageServerVersion : ILicenseServerVersion { public Version LicensingLibraryVersion { get; } = typeof(LicenseKeyData).Assembly.GetName().Version ?? new Version( 0, 0 ); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs index 2715516..67eb472 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Collections.Concurrent; namespace SharpCrafters.Backstage.LicenseServer.Licensing; @@ -27,4 +29,4 @@ public CachingLicenseParser( ILicenseParser inner ) : this.cache.GetOrAdd( licenseKey, this.inner.TryParse ); public string CleanLicenseString( string licenseKey ) => this.inner.CleanLicenseString( licenseKey ); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CompositeLicensingAuthorityProvider.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CompositeLicensingAuthorityProvider.cs index 6c61140..a36b43f 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CompositeLicensingAuthorityProvider.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CompositeLicensingAuthorityProvider.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.Licensing.Licenses; namespace SharpCrafters.Backstage.LicenseServer.Licensing; @@ -19,9 +21,9 @@ public CompositeLicensingAuthorityProvider( params ILicensingAuthorityProvider[] { ArgumentNullException.ThrowIfNull( providers ); - foreach ( ILicensingAuthorityProvider provider in providers ) + foreach ( var provider in providers ) { - foreach ( byte keyId in provider.KeyIds ) + foreach ( var keyId in provider.KeyIds ) { if ( !this.providers.TryAdd( keyId, provider ) ) { @@ -36,7 +38,7 @@ public CompositeLicensingAuthorityProvider( params ILicensingAuthorityProvider[] public IEnumerable KeyIds => this.providers.Keys; public LicensingAuthority GetAuthority( byte keyId ) - => this.providers.TryGetValue( keyId, out ILicensingAuthorityProvider? provider ) + => this.providers.TryGetValue( keyId, out var provider ) ? provider.GetAuthority( keyId ) : throw new KeyNotFoundException( $"There is no licensing authority key of identifier {keyId}." ); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseParser.cs index 0992f68..b0d2b41 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseParser.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseParser.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// @@ -16,4 +18,4 @@ public interface ILicenseParser /// form. /// string CleanLicenseString( string licenseKey ); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs index af5f328..5e24bbd 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// @@ -7,4 +9,4 @@ namespace SharpCrafters.Backstage.LicenseServer.Licensing; public interface ILicenseServerVersion { Version LicensingLibraryVersion { get; } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs index 6c98e76..f5c3315 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Xml; namespace SharpCrafters.Backstage.LicenseServer.Licensing; @@ -37,6 +39,5 @@ public static string Serialize( string licenseKey, DateTime startTime, DateTime /// so both modes give the same result for them. This mode also gives the correct result for a /// caller that passes an instant without a kind. /// - private static string ToUtcString( DateTime value ) - => XmlConvert.ToString( value, XmlDateTimeSerializationMode.Utc ); -} + private static string ToUtcString( DateTime value ) => XmlConvert.ToString( value, XmlDateTimeSerializationMode.Utc ); +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs index c5e9740..f349584 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// @@ -85,4 +87,4 @@ public Version MinClientVersion public string? LicenseTypeName { get; init; } public string? ProductName { get; init; } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseServerProductCatalog.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseServerProductCatalog.cs index 9318b3b..a7caa0a 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseServerProductCatalog.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseServerProductCatalog.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Collections.Immutable; using SharpCrafters.Backstage.Licensing; @@ -41,8 +43,7 @@ private LicenseServerProductCatalog() { } public override bool IsProductOfFamily( LicenseProduct product ) => true; /// - public override bool IsFreeProduct( LicenseProduct product ) - => product is LicenseProduct.MetalamaCommunity or LicenseProduct.PostSharpEssentials; + public override bool IsFreeProduct( LicenseProduct product ) => product is LicenseProduct.MetalamaCommunity or LicenseProduct.PostSharpEssentials; /// /// The server stores every license key in the same table. The registration of a client, which @@ -51,8 +52,7 @@ public override bool IsFreeProduct( LicenseProduct product ) public override bool RequiresVersionSpecificRegistration( LicenseProduct product ) => false; /// - public override ImmutableArray GetProductsCoexistingWith( LicenseProduct product ) - => throw new NotSupportedException( notAClient ); + public override ImmutableArray GetProductsCoexistingWith( LicenseProduct product ) => throw new NotSupportedException( notAClient ); /// public override string PremiumEditionDisplayName => throw new NotSupportedException( notAClient ); @@ -65,4 +65,4 @@ public override ImmutableArray GetProductsCoexistingWith( Licens /// public override LicenseProduct? LegacyFreeProduct => throw new NotSupportedException( notAClient ); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ProductCodes.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ProductCodes.cs index ac6fe46..2f3351e 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ProductCodes.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ProductCodes.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Collections.Immutable; using SharpCrafters.Backstage.Licensing; @@ -59,16 +61,16 @@ public static class ProductCodes /// public static IReadOnlyList Matching( string productCode ) { - if ( legacyNames.TryGetValue( productCode, out string? legacy ) ) + if ( legacyNames.TryGetValue( productCode, out var legacy ) ) { return [productCode, legacy]; } - if ( currentNames.TryGetValue( productCode, out string? current ) ) + if ( currentNames.TryGetValue( productCode, out var current ) ) { return [productCode, current]; } return [productCode]; } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseAuthority.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseAuthority.cs index 72c7360..f1c16a6 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseAuthority.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseAuthority.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Security.Cryptography; using Microsoft.Extensions.Logging; using SharpCrafters.Backstage.Licensing; @@ -41,7 +43,7 @@ public sealed class TestLicenseAuthority private TestLicenseAuthority( ECParameters parameters ) { this.signingAuthority = CreateAuthority( ToXml( parameters, true ) ); - this.Authority = new ExplicitLicensingAuthorityProvider( (KeyId, ToXml( parameters, false )) ); + this.Authority = new ExplicitLicensingAuthorityProvider( ( KeyId, ToXml( parameters, false ) ) ); } /// @@ -53,7 +55,7 @@ public static TestLicenseAuthority LoadOrCreate( string keyFilePath, ILogger log ArgumentException.ThrowIfNullOrWhiteSpace( keyFilePath ); ArgumentNullException.ThrowIfNull( logger ); - using ECDsa key = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); + using var key = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); if ( File.Exists( keyFilePath ) ) { @@ -101,13 +103,12 @@ public string CreateLicenseKey( return builder.SignAndSerialize( this.signingAuthority ); } - private static LicensingAuthority CreateAuthority( string keyXml ) - => new ExplicitLicensingAuthorityProvider( (KeyId, keyXml) ).GetAuthority( KeyId ); + private static LicensingAuthority CreateAuthority( string keyXml ) => new ExplicitLicensingAuthorityProvider( ( KeyId, keyXml ) ).GetAuthority( KeyId ); private static string ToXml( ECParameters parameters, bool includePrivateValue ) => "nistP256" + $"{Convert.ToBase64String( parameters.Q.X! )}" + $"{Convert.ToBase64String( parameters.Q.Y! )}" - + (includePrivateValue ? $"{Convert.ToBase64String( parameters.D! )}" : string.Empty) + + ( includePrivateValue ? $"{Convert.ToBase64String( parameters.D! )}" : string.Empty ) + ""; -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseSeeder.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseSeeder.cs index 2c4b709..5be4c33 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseSeeder.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseSeeder.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using SharpCrafters.Backstage.LicenseServer.Data; @@ -18,8 +20,8 @@ public static class TestLicenseSeeder /// private static readonly (int LicenseId, LicenseProduct Product, short Users)[] licenses = [ - (900001, LicenseProduct.MetalamaProfessional, 25), - (900002, LicenseProduct.PostSharpUltimate, 25) + ( 900001, LicenseProduct.MetalamaProfessional, 25 ), + ( 900002, LicenseProduct.PostSharpUltimate, 25 ) ]; /// @@ -44,11 +46,11 @@ public static IReadOnlyList Seed( ArgumentNullException.ThrowIfNull( timeProvider ); ArgumentNullException.ThrowIfNull( logger ); - DateTime now = timeProvider.GetUtcNow().UtcDateTime; + var now = timeProvider.GetUtcNow().UtcDateTime; HashSet existing = [.. db.Licenses.Select( l => l.LicenseId )]; List added = []; - foreach ( (int licenseId, LicenseProduct product, short users) in licenses ) + foreach ( var (licenseId, product, users) in licenses ) { if ( existing.Contains( licenseId ) ) { @@ -86,4 +88,4 @@ public static IReadOnlyList Seed( return added; } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/ILeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/ILeaseLock.cs index 92e04ac..cce6a8d 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/ILeaseLock.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/ILeaseLock.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Locking; /// @@ -15,4 +17,4 @@ public interface ILeaseLock /// The caller then answers with the status 503. /// ValueTask TryAcquireAsync( TimeSpan timeout, CancellationToken cancellationToken = default ); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs index bd9a9ce..0042db5 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.ComponentModel.DataAnnotations; namespace SharpCrafters.Backstage.LicenseServer.Options; @@ -121,4 +123,4 @@ public sealed class LicenseServerOptions public string DataDirectory { get; set; } = "App_Data"; public TimeSpan MutexTimeoutSpan => TimeSpan.FromSeconds( this.MutexTimeout ); -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs index b1be691..945bf16 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.Extensions.Options; namespace SharpCrafters.Backstage.LicenseServer.Options; @@ -28,4 +30,4 @@ public ValidateOptionsResult Validate( string? name, LicenseServerOptions option return failures.Count == 0 ? ValidateOptionsResult.Success : ValidateOptionsResult.Fail( failures ); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/SmtpOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/SmtpOptions.cs index 134385b..d500f51 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/SmtpOptions.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/SmtpOptions.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Options; /// @@ -28,4 +30,4 @@ public sealed class SmtpOptions public string? UserName { get; set; } public string? Password { get; set; } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/TestLicensingAuthority.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/TestLicensingAuthority.cs index 21ee035..4744a68 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/TestLicensingAuthority.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/TestLicensingAuthority.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Options; /// @@ -24,4 +26,4 @@ public sealed class TestLicensingAuthority /// for a finite field DSA one. /// public string PublicKey { get; set; } = ""; -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs index 69830ee..597a1cd 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using Microsoft.EntityFrameworkCore; @@ -45,7 +47,7 @@ public LeaseService( if ( !string.IsNullOrWhiteSpace( this.settings.BuildServers ) ) { - foreach ( string buildServer in this.settings.BuildServers.Split( [';', ',', ' '] ) ) + foreach ( var buildServer in this.settings.BuildServers.Split( [';', ',', ' '] ) ) { if ( !string.IsNullOrWhiteSpace( buildServer ) ) { @@ -74,12 +76,12 @@ public LeaseService( Dictionary cache, Dictionary errors ) { - if ( cache.TryGetValue( license.LicenseId, out LicenseState? licenseState ) ) + if ( cache.TryGetValue( license.LicenseId, out var licenseState ) ) { return licenseState; } - LicenseInfo? parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); + var parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); if ( parsedLicense == null ) { @@ -101,7 +103,7 @@ public LeaseService( // A PostSharp license names the lowest version of PostSharp that can read it, and a Metalama // license names the lowest version of Metalama. The two are independent, so the minimum that // applies is the one of the family of the licensed product. - Version minClientVersion = parsedLicense.MinClientVersion; + var minClientVersion = parsedLicense.MinClientVersion; if ( minClientVersion > version ) { @@ -122,8 +124,8 @@ public LeaseService( return null; } - if ( !(buildDate == null || parsedLicense.SubscriptionEndDate == null - || buildDate <= parsedLicense.SubscriptionEndDate) ) + if ( !( buildDate == null || parsedLicense.SubscriptionEndDate == null + || buildDate <= parsedLicense.SubscriptionEndDate ) ) { // The version number was introduced in the license server protocol in PostSharp 5. errors[license.LicenseId] = version.Major >= 5 @@ -159,12 +161,12 @@ public LeaseService( { // A client that names no product is served from any pool. Every PostSharp client relies on // this behaviour, because none of them sends the argument. - IReadOnlyList productCodes = string.IsNullOrEmpty( productCode ) + var productCodes = string.IsNullOrEmpty( productCode ) ? [] : ProductCodes.Matching( productCode ); - License[] licenses = await this.repository.Licenses - .Where( license => (productCodes.Count == 0 || productCodes.Contains( license.ProductCode )) + var licenses = await this.repository.Licenses + .Where( license => ( productCodes.Count == 0 || productCodes.Contains( license.ProductCode ) ) && license.Priority >= 0 ) .OrderBy( license => license.Priority ) .ToArrayAsync( cancellationToken ); @@ -175,9 +177,9 @@ public LeaseService( // A build agent is exempt from consuming a seat. It is not exempt from the rules that // decide which licenses may be served, so the same validation runs. - foreach ( License candidate in licenses ) + foreach ( var candidate in licenses ) { - LicenseState? state = + var state = this.GetLicenseState( candidate, version, buildDate, now, buildServerStates, errors ); if ( state == null ) @@ -185,7 +187,7 @@ public LeaseService( continue; } - DateTime endTime = now.AddDays( this.settings.NewLeaseDays ); + var endTime = now.AddDays( this.settings.NewLeaseDays ); if ( state.ParsedLicense.ValidTo.HasValue && state.ParsedLicense.ValidTo < endTime ) { @@ -210,7 +212,7 @@ public LeaseService( // No license could be served without a lease. Continue with the normal allocation. } - Lease? lease = await this.GetLeaseAsync( + var lease = await this.GetLeaseAsync( version, buildDate, machine, @@ -252,9 +254,9 @@ public LeaseService( Dictionary licenseStates = []; // First pass: a lease this user already holds on this machine, reused or prolonged. - foreach ( License license in licenses ) + foreach ( var license in licenses ) { - LicenseState? licenseState = + var licenseState = this.GetLicenseState( license, version, buildDate, now, licenseStates, errors ); if ( licenseState == null ) @@ -262,9 +264,9 @@ public LeaseService( continue; } - int licenseId = license.LicenseId; + var licenseId = license.LicenseId; - Lease[] currentLeases = await this.repository.OpenLeases + var currentLeases = await this.repository.OpenLeases .Where( l => l.LicenseId == licenseId && l.StartTime <= now && l.EndTime > now && l.UserName == userName ) .Include( l => l.License ) .OrderBy( l => l.StartTime ) @@ -272,7 +274,7 @@ public LeaseService( Dictionary machines = new( StringComparer.OrdinalIgnoreCase ); - foreach ( Lease candidateLease in currentLeases ) + foreach ( var candidateLease in currentLeases ) { machines[candidateLease.Machine] = candidateLease.Machine; @@ -290,7 +292,7 @@ public LeaseService( // A lease can always be prolonged, because a lease starts at the current instant and // therefore already covers it. The license period or the grace period can still end // first. - Lease? prolonged = this.repository.ProlongLease( candidateLease, authenticatedUserName, now ); + var prolonged = this.repository.ProlongLease( candidateLease, authenticatedUserName, now ); if ( prolonged == null ) { @@ -304,7 +306,7 @@ public LeaseService( // the user works on fewer machines than MachinesPerUser. if ( machines.Count % this.settings.MachinesPerUser != 0 ) { - Lease? lease = this.repository.CreateLease( + var lease = this.repository.CreateLease( license, userName, machine, @@ -320,9 +322,9 @@ public LeaseService( } // Second pass: a new lease against spare capacity. - foreach ( License license in licenses ) + foreach ( var license in licenses ) { - LicenseState? licenseState = + var licenseState = this.GetLicenseState( license, version, buildDate, now, licenseStates, errors ); if ( licenseState == null ) @@ -331,14 +333,14 @@ public LeaseService( } if ( licenseState.Maximum.HasValue && licenseState.Maximum.Value <= licenseState.Usage - && (!licenseState.ParsedLicense.ValidTo.HasValue - || now < licenseState.ParsedLicense.ValidTo) ) + && ( !licenseState.ParsedLicense.ValidTo.HasValue + || now < licenseState.ParsedLicense.ValidTo ) ) { // This license is full. continue; } - Lease? lease = this.repository.CreateLease( license, userName, machine, authenticatedUserName, now, false ); + var lease = this.repository.CreateLease( license, userName, machine, authenticatedUserName, now, false ); if ( lease != null ) { @@ -347,9 +349,9 @@ public LeaseService( } // Third pass: the grace period. - foreach ( License license in licenses ) + foreach ( var license in licenses ) { - LicenseState? licenseState = + var licenseState = this.GetLicenseState( license, version, buildDate, now, licenseStates, errors ); if ( licenseState == null ) @@ -367,18 +369,18 @@ public LeaseService( license.GraceStartTime ??= now; - int graceLimit = LicenseCapacity.GetGraceLimit( + var graceLimit = LicenseCapacity.GetGraceLimit( licenseState.Maximum.Value, licenseState.ParsedLicense.GracePercent ); - DateTime graceEnd = license.GraceStartTime.Value.AddDays( licenseState.ParsedLicense.GraceDays ); + var graceEnd = license.GraceStartTime.Value.AddDays( licenseState.ParsedLicense.GraceDays ); if ( license.GraceStartTime <= now && graceEnd > now && licenseState.Usage < graceLimit ) { if ( license.GraceLastWarningTime.GetValueOrDefault( DateTime.MinValue ) .AddDays( this.settings.GracePeriodWarningDays ) < now ) { - string body = + var body = $"The license #{license.LicenseId} has a capacity of {licenseState.Maximum} concurrent user(s), " + $"but {licenseState.Usage + 1} users are currently using the product " + $"{licenseState.ParsedLicense.Product}. " @@ -398,7 +400,7 @@ await this.SendEmailAsync( license.GraceLastWarningTime = now; } - Lease? lease = this.repository.CreateLease( + var lease = this.repository.CreateLease( license, userName, machine, @@ -450,7 +452,8 @@ private async Task SendEmailAsync( try { - await this.emailSender.SendAsync( new EmailMessage( to.Trim( ' ', '\n', '\r', '\t' ), cc, subject, body ), + await this.emailSender.SendAsync( + new EmailMessage( to.Trim( ' ', '\n', '\r', '\t' ), cc, subject, body ), cancellationToken ); } catch ( Exception e ) @@ -509,4 +512,4 @@ public int Usage /// /// The lease granted to a client: which license key to use, and for how long. /// -public sealed record GrantedLease( string LicenseKey, DateTime StartTime, DateTime EndTime, DateTime RenewTime ); +public sealed record GrantedLease( string LicenseKey, DateTime StartTime, DateTime EndTime, DateTime RenewTime ); \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs index 17b5614..5928c45 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Data; using SharpCrafters.Backstage.LicenseServer.Licensing; @@ -42,17 +44,17 @@ public async Task GetAvailabilityAsync( DateTime now, CancellationToken cancellationToken = default ) { - License[] licenses = await this.repository.Licenses + var licenses = await this.repository.Licenses .AsNoTracking() .ToArrayAsync( cancellationToken ); - int available = 0; - int disabled = 0; - int invalid = 0; - int expired = 0; - int exhausted = 0; + var available = 0; + var disabled = 0; + var invalid = 0; + var expired = 0; + var exhausted = 0; - foreach ( License license in licenses ) + foreach ( var license in licenses ) { if ( license.Priority < 0 ) { @@ -61,7 +63,7 @@ public async Task GetAvailabilityAsync( continue; } - LicenseInfo? parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); + var parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); if ( parsedLicense == null || !parsedLicense.IsLicenseServerEligible @@ -87,7 +89,7 @@ public async Task GetAvailabilityAsync( continue; } - int usage = this.repository.GetActiveSeats( license.LicenseId, now ); + var usage = this.repository.GetActiveSeats( license.LicenseId, now ); if ( usage < parsedLicense.UserNumber.Value ) { @@ -99,8 +101,8 @@ public async Task GetAvailabilityAsync( // Above the capacity, only the grace period remains. It is limited by a number of seats // and by a number of days. A license whose grace period has not started yet starts it at // the next request, so it counts as available. - int graceLimit = LicenseCapacity.GetGraceLimit( parsedLicense.UserNumber.Value, parsedLicense.GracePercent ); - DateTime graceEnd = (license.GraceStartTime ?? now).AddDays( parsedLicense.GraceDays ); + var graceLimit = LicenseCapacity.GetGraceLimit( parsedLicense.UserNumber.Value, parsedLicense.GracePercent ); + var graceEnd = ( license.GraceStartTime ?? now ).AddDays( parsedLicense.GraceDays ); if ( usage < graceLimit && graceEnd > now ) { @@ -191,4 +193,4 @@ void Add( int count, string reason ) } } } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseCapacity.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseCapacity.cs index 8c6387a..97eb3d9 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseCapacity.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseCapacity.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Services; /// @@ -10,6 +12,5 @@ public static class LicenseCapacity /// Returns the number of seats a license covers while its grace period runs, which is its /// capacity raised by the percentage the license key carries and rounded up. /// - public static int GetGraceLimit( int maximum, int gracePercent ) - => (int) Math.Ceiling( maximum * (100.0 + gracePercent) / 100.0 ); -} + public static int GetGraceLimit( int maximum, int gracePercent ) => (int) Math.Ceiling( maximum * ( 100.0 + gracePercent ) / 100.0 ); +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Time/AcceleratedTimeProvider.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Time/AcceleratedTimeProvider.cs index 908c8ec..14dc350 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Time/AcceleratedTimeProvider.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Time/AcceleratedTimeProvider.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Time; /// @@ -28,12 +30,11 @@ public AcceleratedTimeProvider( TimeProvider inner, double acceleration ) public double Acceleration => this.acceleration; - public override DateTimeOffset GetUtcNow() - => this.origin + ((this.inner.GetUtcNow() - this.origin) * this.acceleration); + public override DateTimeOffset GetUtcNow() => this.origin + ( ( this.inner.GetUtcNow() - this.origin ) * this.acceleration ); public override long GetTimestamp() => this.inner.GetTimestamp(); public override long TimestampFrequency => this.inner.TimestampFrequency; public override TimeZoneInfo LocalTimeZone => this.inner.LocalTimeZone; -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs index a3f7e5d..f7d6bd3 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Runtime.InteropServices; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Authentication; @@ -38,7 +40,7 @@ public static string AddLicenseServerAuthentication( this IServiceCollection services, IConfiguration configuration ) { - string scheme = ResolveScheme( configuration ); + var scheme = ResolveScheme( configuration ); switch ( scheme ) { @@ -61,8 +63,7 @@ public static string AddLicenseServerAuthentication( break; default: - throw new InvalidOperationException( - $"Unknown authentication scheme '{scheme}'. Use '{IisIntegrated}', '{Negotiate}' or '{None}'." ); + throw new InvalidOperationException( $"Unknown authentication scheme '{scheme}'. Use '{IisIntegrated}', '{Negotiate}' or '{None}'." ); } return scheme; @@ -79,13 +80,13 @@ public static string AddLicenseServerAuthentication( /// private static string ResolveScheme( IConfiguration configuration ) { - string? configured = configuration["Authentication:Scheme"]; + var configured = configuration["Authentication:Scheme"]; if ( !string.IsNullOrWhiteSpace( configured ) ) { // The comparison ignores the case, so that "negotiate" in the environment of a container // is accepted. - foreach ( string known in new[] { IisIntegrated, Negotiate, None } ) + foreach ( var known in new[] { IisIntegrated, Negotiate, None } ) { if ( string.Equals( configured, known, StringComparison.OrdinalIgnoreCase ) ) { @@ -118,6 +119,5 @@ public AnonymousAuthenticationHandler( public const string SchemeName = "None"; - protected override Task HandleAuthenticateAsync() - => Task.FromResult( AuthenticateResult.NoResult() ); -} + protected override Task HandleAuthenticateAsync() => Task.FromResult( AuthenticateResult.NoResult() ); +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs index 823924b..f141d1b 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Data; @@ -28,8 +30,8 @@ public static IServiceCollection AddLicenseServerDatabase( IConfiguration configuration, IHostEnvironment environment ) { - string provider = configuration["LicenseServer:DatabaseProvider"] ?? "SqlServer"; - string? connectionString = configuration.GetConnectionString( ConnectionStringName ); + var provider = configuration["LicenseServer:DatabaseProvider"] ?? "SqlServer"; + var connectionString = configuration.GetConnectionString( ConnectionStringName ); if ( string.IsNullOrWhiteSpace( connectionString ) ) { @@ -53,16 +55,14 @@ public static IServiceCollection AddLicenseServerDatabase( break; case "sqlite": - services.AddDbContext( - options => options.UseSqlite( ResolveSqliteFile( connectionString, environment ) ) ); + services.AddDbContext( options => options.UseSqlite( ResolveSqliteFile( connectionString, environment ) ) ); services.AddScoped(); break; default: - throw new InvalidOperationException( - $"Unknown database provider '{provider}'. Use 'SqlServer', 'PostgreSql' or 'Sqlite'." ); + throw new InvalidOperationException( $"Unknown database provider '{provider}'. Use 'SqlServer', 'PostgreSql' or 'Sqlite'." ); } return services; @@ -89,4 +89,4 @@ private static string ResolveSqliteFile( string connectionString, IHostEnvironme return builder.ToString(); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs index 6501531..7dce8bc 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Endpoints; /// @@ -8,13 +10,13 @@ public static class LegacyUrlRedirects { private static readonly (string Legacy, string Current)[] redirects = [ - ("/Default.aspx", "/"), - ("/Graph.aspx", "/Graph"), - ("/Admin/AddLicense.aspx", "/Admin/AddLicense"), - ("/Admin/Cancel.aspx", "/Admin/Cancel"), - ("/Admin/Details.aspx", "/Admin/Details"), - ("/Admin/Export.aspx", "/Admin/Export"), - ("/Admin/GenerateDemoData.aspx", "/Admin/GenerateDemoData") + ( "/Default.aspx", "/" ), + ( "/Graph.aspx", "/Graph" ), + ( "/Admin/AddLicense.aspx", "/Admin/AddLicense" ), + ( "/Admin/Cancel.aspx", "/Admin/Cancel" ), + ( "/Admin/Details.aspx", "/Admin/Details" ), + ( "/Admin/Export.aspx", "/Admin/Export" ), + ( "/Admin/GenerateDemoData.aspx", "/Admin/GenerateDemoData" ) ]; /// @@ -34,16 +36,16 @@ public static string BuildRedirectLocation( PathString pathBase, string target, } // The home page is the path base itself, and not the path base followed by a slash. - string path = target == "/" ? pathBase.Value! : pathBase.Value + target; + var path = target == "/" ? pathBase.Value! : pathBase.Value + target; return path + queryString; } public static void MapLegacyUrlRedirects( this WebApplication app ) { - foreach ( (string legacy, string current) in redirects ) + foreach ( var (legacy, current) in redirects ) { - string target = current; + var target = current; app.MapGet( legacy, @@ -52,4 +54,4 @@ public static void MapLegacyUrlRedirects( this WebApplication app ) true ) ); } } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs index 5fee15d..dbd6535 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Diagnostics; using System.Globalization; using System.Xml; @@ -54,7 +56,7 @@ private static async Task GetLeaseAsync( { version = new Version( 4, 9, 9 ); } - else if ( !Version.TryParse( versionString, out Version? parsedVersion ) ) + else if ( !Version.TryParse( versionString, out var parsedVersion ) ) { return Error( 400, "Cannot parse the argument: version." ); } @@ -72,7 +74,7 @@ private static async Task GetLeaseAsync( buildDateString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, - out DateTime parsedBuildDate ) ) + out var parsedBuildDate ) ) { return Error( 400, "Cannot parse the argument: buildDate." ); } @@ -100,9 +102,9 @@ private static async Task GetLeaseAsync( // An anonymous request has no name in ASP.NET Core, where WebForms returned an empty string. // The column does not accept null. - string authenticatedUserName = context.User.Identity?.Name ?? string.Empty; + var authenticatedUserName = context.User.Identity?.Name ?? string.Empty; - await using IAsyncDisposable? handle = await leaseLock.TryAcquireAsync( + await using var handle = await leaseLock.TryAcquireAsync( options.Value.MutexTimeoutSpan, cancellationToken ); @@ -115,7 +117,7 @@ private static async Task GetLeaseAsync( // waits for the lock. Two requests that merely run at the same time do not prove it: they // pass whether the lock works or not. The service is absent in production, and the call then // costs a null check. See ITestSynchronizationProvider. - ITestSynchronizationProvider? synchronization = + var synchronization = context.RequestServices.GetService(); if ( synchronization != null ) @@ -123,11 +125,11 @@ private static async Task GetLeaseAsync( await synchronization.SyncPointAsync( HoldingLeaseLockSyncPoint, cancellationToken ); } - long startTimestamp = timeProvider.GetTimestamp(); + var startTimestamp = timeProvider.GetTimestamp(); Dictionary errors = []; - GrantedLease? grantedLease = await leaseService.GetLicenseLeaseAsync( + var grantedLease = await leaseService.GetLicenseLeaseAsync( productCode, version, buildDate, @@ -145,7 +147,7 @@ private static async Task GetLeaseAsync( await repository.SaveChangesAsync( cancellationToken ); - TimeSpan elapsed = timeProvider.GetElapsedTime( startTimestamp ); + var elapsed = timeProvider.GetElapsedTime( startTimestamp ); if ( elapsed > TimeSpan.FromSeconds( 1 ) ) { @@ -200,13 +202,12 @@ private static async Task ExportAsync( || fm is null or < 1 or > 12 || tm is null or < 1 or > 12 ) { - return Results.BadRequest( - $"The range of months is missing or invalid. Years must be between {firstYear} and {lastYear}." ); + return Results.BadRequest( $"The range of months is missing or invalid. Years must be between {firstYear} and {lastYear}." ); } // The months are read as UTC, which is the time zone of every timestamp of the database. DateTime fromTime = new( fy.Value, fm.Value, 1, 0, 0, 0, DateTimeKind.Utc ); - DateTime toTime = new DateTime( ty.Value, tm.Value, 1, 0, 0, 0, DateTimeKind.Utc ).AddMonths( 1 ); + var toTime = new DateTime( ty.Value, tm.Value, 1, 0, 0, 0, DateTimeKind.Utc ).AddMonths( 1 ); // The range of months is resolved to a range of lease identifiers, and every lease in that // range is exported. The legacy server selected the rows in the same way, and customers @@ -218,7 +219,7 @@ private static async Task ExportAsync( .Select( g => new { MinLeaseId = g.Min( l => l.LeaseId ), MaxLeaseId = g.Max( l => l.LeaseId ) } ) .SingleOrDefaultAsync( cancellationToken ); - string fileName = + var fileName = $"PostSharp_LicenseLog_{fy.Value}-{fm.Value}_{ty.Value}-{tm.Value}.txt"; context.Response.Headers.ContentDisposition = $"attachment; filename={fileName}"; @@ -228,8 +229,8 @@ private static async Task ExportAsync( return Results.Text( string.Empty, "text/plain" ); } - int minLeaseId = bounds.MinLeaseId; - int maxLeaseId = bounds.MaxLeaseId; + var minLeaseId = bounds.MinLeaseId; + var maxLeaseId = bounds.MaxLeaseId; // The rows are written to the response as they arrive. An audit log that covers years of // activity is too large to assemble in memory, and the download starts before the server has @@ -239,13 +240,13 @@ private static async Task ExportAsync( { await using StreamWriter writer = new( stream ); - IAsyncEnumerable leases = repository.Leases + var leases = repository.Leases .Where( l => l.LeaseId >= minLeaseId && l.LeaseId <= maxLeaseId ) .OrderBy( l => l.LeaseId ) .AsNoTracking() .AsAsyncEnumerable(); - await foreach ( Lease lease in leases.WithCancellation( cancellationToken ) ) + await foreach ( var lease in leases.WithCancellation( cancellationToken ) ) { // The line is built in memory and written asynchronously. Writing the fields // directly to the writer makes the writer flush synchronously when its buffer is @@ -257,8 +258,7 @@ private static async Task ExportAsync( "text/plain" ); } - private static IResult Error( int statusCode, string description ) - => Results.Text( description, "text/plain", statusCode: statusCode ); + private static IResult Error( int statusCode, string description ) => Results.Text( description, "text/plain", statusCode: statusCode ); /// /// Removes the control characters from a value of the request, so that the value cannot forge a @@ -272,4 +272,4 @@ private static string Sanitize( string value ) return cleaned.Length <= maximumLength ? cleaned : cleaned[..maximumLength]; } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/OperationsEndpoints.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/OperationsEndpoints.cs index f49a023..a5cd205 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/OperationsEndpoints.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/OperationsEndpoints.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Reflection; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -55,13 +57,7 @@ private static Task WriteHealthAsync( HttpContext context, HealthReport report ) { status = report.Status.ToString(), checks = report.Entries - .Select( - entry => new - { - name = entry.Key, - status = entry.Value.Status.ToString(), - description = entry.Value.Description - } ) + .Select( entry => new { name = entry.Key, status = entry.Value.Status.ToString(), description = entry.Value.Description } ) .ToArray() } ); @@ -71,13 +67,7 @@ private static Task WriteHealthAsync( HttpContext context, HealthReport report ) /// requires a recent library. /// private static IResult GetVersion( ILicenseServerVersion version ) - => Results.Json( - new - { - product = ProductName, - version = ProductVersion, - licensingLibrary = version.LicensingLibraryVersion.ToString() - } ); + => Results.Json( new { product = ProductName, version = ProductVersion, licensingLibrary = version.LicensingLibraryVersion.ToString() } ); private static string ProductName { get; } = typeof(OperationsEndpoints).Assembly.GetName().Name ?? "SharpCrafters.Backstage.LicenseServer"; @@ -90,10 +80,11 @@ private static IResult GetVersion( ILicenseServerVersion version ) private static string ReadProductVersion() { - Assembly assembly = typeof(OperationsEndpoints).Assembly; + var assembly = typeof(OperationsEndpoints).Assembly; - string? informationalVersion = assembly - .GetCustomAttribute()? + var informationalVersion = assembly + .GetCustomAttribute() + ? .InformationalVersion; if ( string.IsNullOrEmpty( informationalVersion ) ) @@ -101,8 +92,8 @@ private static string ReadProductVersion() return assembly.GetName().Version?.ToString() ?? "unknown"; } - int metadata = informationalVersion.IndexOf( '+', StringComparison.Ordinal ); + var metadata = informationalVersion.IndexOf( '+', StringComparison.Ordinal ); return metadata < 0 ? informationalVersion : informationalVersion[..metadata]; } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/DatabaseHealthCheck.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/DatabaseHealthCheck.cs index 6ab64b7..e6b60df 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/DatabaseHealthCheck.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/DatabaseHealthCheck.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Diagnostics.HealthChecks; using SharpCrafters.Backstage.LicenseServer.Data; @@ -40,4 +42,4 @@ public async Task CheckHealthAsync( e ); } } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/HealthDescriptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/HealthDescriptions.cs index d461a0a..8d6ba8c 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/HealthDescriptions.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/HealthDescriptions.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + namespace SharpCrafters.Backstage.LicenseServer.Health; /// @@ -14,4 +16,4 @@ public static string ForException( string summary, Exception exception, IHostEnv => environment.IsDevelopment() ? $"{summary} {exception.Message}" : $"{summary} The reason is in the log of the server."; -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs index f12c0f4..c79f793 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Health/LicenseHealthCheck.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.Extensions.Diagnostics.HealthChecks; using SharpCrafters.Backstage.LicenseServer.Services; @@ -70,4 +72,4 @@ public async Task CheckHealthAsync( ? HealthCheckResult.Healthy( result.Describe(), data ) : HealthCheckResult.Degraded( result.Describe(), data: data ); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs index cf1cfaa..656f12c 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.Extensions.Logging.Abstractions; using SharpCrafters.Backstage.LicenseServer.Licensing; using SharpCrafters.Backstage.LicenseServer.Options; @@ -29,12 +31,12 @@ public static IReadOnlyList AddLicenseServerLicensing( IConfiguration configuration, IHostEnvironment environment ) { - IConfigurationSection section = configuration.GetSection( LicenseServerOptions.SectionName ); + var section = configuration.GetSection( LicenseServerOptions.SectionName ); - TestLicensingAuthority[] testAuthorities = + var testAuthorities = section.GetSection( "TestLicensingAuthorities" ).Get() ?? []; - bool seedTestLicenses = section.GetValue( "SeedTestLicenses", false ); + var seedTestLicenses = section.GetValue( "SeedTestLicenses", false ); // The server refuses to start instead of ignoring the setting or applying it. Ignoring it // would let an administrator believe that the server accepts those license keys. Applying it @@ -50,12 +52,11 @@ public static IReadOnlyList AddLicenseServerLicensing( if ( testAuthorities.Length > 0 ) { - var explicitAuthorities = new ExplicitLicensingAuthorityProvider( - testAuthorities.Select( a => ((int) a.KeyId, a.PublicKey) ).ToArray() ); + var explicitAuthorities = new ExplicitLicensingAuthorityProvider( testAuthorities.Select( a => ( (int) a.KeyId, a.PublicKey ) ).ToArray() ); // A key is parsed at its first use, which would be during a lease request. Requesting // each authority here turns a malformed key into a failure at startup. - foreach ( TestLicensingAuthority authority in testAuthorities ) + foreach ( var authority in testAuthorities ) { explicitAuthorities.GetAuthority( authority.KeyId ); } @@ -66,7 +67,7 @@ public static IReadOnlyList AddLicenseServerLicensing( if ( seedTestLicenses ) { - TestLicenseAuthority ownAuthority = TestLicenseAuthority.LoadOrCreate( + var ownAuthority = TestLicenseAuthority.LoadOrCreate( Path.Combine( ResolveDataDirectory( section, environment ), "test-authority.key" ), NullLogger.Instance ); @@ -76,8 +77,7 @@ public static IReadOnlyList AddLicenseServerLicensing( testKeyIds.Add( TestLicenseAuthority.KeyId ); } - services.AddSingleton( - _ => new CachingLicenseParser( new BackstageLicenseParser( authorities ) ) ); + services.AddSingleton( _ => new CachingLicenseParser( new BackstageLicenseParser( authorities ) ) ); return testKeyIds; @@ -100,10 +100,10 @@ void Refuse( bool condition, string what ) /// public static string ResolveDataDirectory( IConfigurationSection section, IHostEnvironment environment ) { - string configured = section.GetValue( "DataDirectory", "App_Data" ) ?? "App_Data"; + var configured = section.GetValue( "DataDirectory", "App_Data" ) ?? "App_Data"; return Path.IsPathRooted( configured ) ? configured : Path.Combine( environment.ContentRootPath, configured ); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs index 170668c..695d390 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/PostgreSqlLeaseLock.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Data.Common; using System.Globalization; using System.Security.Cryptography; @@ -48,17 +50,17 @@ public sealed class PostgreSqlLeaseLock : ILeaseLock /// public static readonly long ResourceKey = BitConverter.ToInt64( SHA256.HashData( Encoding.UTF8.GetBytes( resourceName ) ) ); + public PostgreSqlLeaseLock( LicenseServerDbContext db ) { this.db = db; } - public async ValueTask TryAcquireAsync( TimeSpan timeout, CancellationToken cancellationToken = default ) { - DatabaseFacade database = this.db.Database; + var database = this.db.Database; await database.OpenConnectionAsync( cancellationToken ); @@ -66,7 +68,7 @@ public PostgreSqlLeaseLock( LicenseServerDbContext db ) { // SET takes no parameter, so the value is written into the statement. It is a number // computed here and never a value that a request carries. - int milliseconds = Math.Max( 0, (int) timeout.TotalMilliseconds ); + var milliseconds = Math.Max( 0, (int) timeout.TotalMilliseconds ); await ExecuteAsync( database, @@ -100,7 +102,7 @@ private static async Task ExecuteAsync( string sql, CancellationToken cancellationToken ) { - await using DbCommand command = database.GetDbConnection().CreateCommand(); + await using var command = database.GetDbConnection().CreateCommand(); command.CommandText = sql; await command.ExecuteNonQueryAsync( cancellationToken ); @@ -147,4 +149,4 @@ await ExecuteAsync( } } } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs index e5a875d..757b982 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqlServerLeaseLock.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Data; using System.Data.Common; using Microsoft.EntityFrameworkCore; @@ -32,23 +34,23 @@ public sealed class SqlServerLeaseLock : ILeaseLock /// this name is shared by every process that serves this database, and by nothing else. /// public const string ResourceName = "SharpCrafters.Backstage.LicenseServer.Lease"; + public SqlServerLeaseLock( LicenseServerDbContext db ) { this.db = db; } - public async ValueTask TryAcquireAsync( TimeSpan timeout, CancellationToken cancellationToken = default ) { - DatabaseFacade database = this.db.Database; + var database = this.db.Database; await database.OpenConnectionAsync( cancellationToken ); try { - int result = await ExecuteAsync( + var result = await ExecuteAsync( database, "sp_getapplock", command => @@ -73,8 +75,7 @@ public SqlServerLeaseLock( LicenseServerDbContext db ) { await database.CloseConnectionAsync(); - throw new InvalidOperationException( - $"sp_getapplock returned {result} for the resource '{ResourceName}'." ); + throw new InvalidOperationException( $"sp_getapplock returned {result} for the resource '{ResourceName}'." ); } return new Handle( database ); @@ -89,7 +90,7 @@ public SqlServerLeaseLock( LicenseServerDbContext db ) private static void AddParameter( DbCommand command, string name, object value ) { - DbParameter parameter = command.CreateParameter(); + var parameter = command.CreateParameter(); parameter.ParameterName = name; parameter.Value = value; command.Parameters.Add( parameter ); @@ -105,14 +106,14 @@ private static async Task ExecuteAsync( Action addParameters, CancellationToken cancellationToken ) { - await using DbCommand command = database.GetDbConnection().CreateCommand(); + await using var command = database.GetDbConnection().CreateCommand(); command.CommandText = procedure; command.CommandType = CommandType.StoredProcedure; addParameters( command ); - DbParameter returnValue = command.CreateParameter(); + var returnValue = command.CreateParameter(); returnValue.ParameterName = "@Result"; returnValue.DbType = DbType.Int32; returnValue.Direction = ParameterDirection.ReturnValue; @@ -160,4 +161,4 @@ await ExecuteAsync( } } } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs index d72fb28..9e509c3 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Locking/SqliteLeaseLock.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Data; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; @@ -36,7 +38,7 @@ public SqliteLeaseLock( LicenseServerDbContext db ) TimeSpan timeout, CancellationToken cancellationToken = default ) { - DatabaseFacade database = this.db.Database; + var database = this.db.Database; await database.OpenConnectionAsync( cancellationToken ); @@ -49,7 +51,7 @@ public SqliteLeaseLock( LicenseServerDbContext db ) // not change that, because the provider passes its own value at every command. The // previous value is restored with the connection, which the pool hands to another // request. - int previousTimeout = connection.DefaultTimeout; + var previousTimeout = connection.DefaultTimeout; connection.DefaultTimeout = Math.Max( 1, (int) timeout.TotalSeconds ); SqliteTransaction transaction; @@ -75,7 +77,7 @@ public SqliteLeaseLock( LicenseServerDbContext db ) // The context runs its own statements inside this transaction, so the reads that decide // the allocation and the insert that records it are one unit. - IDbContextTransaction? contextTransaction = + var contextTransaction = await database.UseTransactionAsync( transaction, cancellationToken ); return new Handle( database, contextTransaction!, connection, previousTimeout ); @@ -132,4 +134,4 @@ public async ValueTask DisposeAsync() } } } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml index 31b262a..644347f 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml @@ -1,12 +1,14 @@ @page -@model SharpCrafters.Backstage.LicenseServer.Pages.Admin.AddLicenseModel +@model AddLicenseModel @{ ViewData["Title"] = "Add a license"; }

Paste the license key sent to you by PostSharp Technologies.

@@ -15,9 +17,9 @@

-
- - +
+ +

diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs index f05cb1d..4b2b6ed 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; @@ -43,7 +45,7 @@ public async Task OnPostAsync( CancellationToken cancellationToke return this.Page(); } - LicenseInfo? parsedLicense = this.licenseParser.TryParse( this.LicenseKey ); + var parsedLicense = this.licenseParser.TryParse( this.LicenseKey ); if ( parsedLicense == null ) { @@ -82,4 +84,4 @@ public async Task OnPostAsync( CancellationToken cancellationToke return this.RedirectToPage( "/Index" ); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml index 7c59ff5..db3dc25 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml @@ -1,12 +1,14 @@ @page -@model SharpCrafters.Backstage.LicenseServer.Pages.Admin.CancelModel +@model CancelModel @{ ViewData["Title"] = "Cancel a lease"; }

diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs index 8604bed..a998c57 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; @@ -36,7 +38,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken public async Task OnPostAsync( CancellationToken cancellationToken ) { - Lease? lease = await this.repository.OpenLeases + var lease = await this.repository.OpenLeases .Include( l => l.License ) .SingleOrDefaultAsync( l => l.LeaseId == this.Id, cancellationToken ); @@ -54,4 +56,4 @@ public async Task OnPostAsync( CancellationToken cancellationToke return this.RedirectToPage( "/Admin/Details", new { id = lease.LicenseId } ); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml index 96495e4..c5fb9da 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml @@ -1,5 +1,5 @@ @page -@model SharpCrafters.Backstage.LicenseServer.Pages.Admin.DetailsModel +@model DetailsModel @{ ViewData["Title"] = $"License {Model.Id}"; } @@ -15,7 +15,7 @@

Manage -@if ( Model.IsDisabled ) +@if (Model.IsDisabled) {

@@ -70,7 +70,7 @@ The capacity of a license is a number of seats.

-@if ( Model.Leases.Count == 0 ) +@if (Model.Leases.Count == 0) {

No lease is currently held against this license.

} @@ -78,31 +78,33 @@ else {
- - - - - - - - - - - - - @foreach ( var lease in Model.Leases ) - { + - - - - - - - + + + + + + + - } - + + + @foreach (var lease in Model.Leases) + { + + + + + + + + + + } +
LeaseUserMachineStartEndGrace
@lease.LeaseId@lease.UserName@lease.Machine@lease.StartTime.ToString( "g" )@lease.EndTime.ToString( "g" )@(lease.Grace ? "Yes" : "")CancelLeaseUserMachineStartEndGrace
@lease.LeaseId@lease.UserName@lease.Machine@lease.StartTime.ToString( "g" )@lease.EndTime.ToString( "g" )@( lease.Grace ? "Yes" : "" ) + Cancel +
} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs index f8ad089..d5cbf5b 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; @@ -46,14 +48,13 @@ public DetailsModel( /// Gets with its noun, so that a server configured with one /// machine per seat displays "1 machine" and not "1 machines". ///
- public string MachinesPerSeatText - => this.MachinesPerSeat == 1 ? "1 machine" : $"{this.MachinesPerSeat} machines"; + public string MachinesPerSeatText => this.MachinesPerSeat == 1 ? "1 machine" : $"{this.MachinesPerSeat} machines"; public bool IsDisabled { get; private set; } public async Task OnGetAsync( CancellationToken cancellationToken ) { - License? license = await this.repository.Licenses + var license = await this.repository.Licenses .AsNoTracking() .SingleOrDefaultAsync( l => l.LicenseId == this.Id, cancellationToken ); @@ -62,7 +63,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken return this.NotFound(); } - DateTime now = this.timeProvider.GetUtcNow().UtcDateTime; + var now = this.timeProvider.GetUtcNow().UtcDateTime; this.Leases = await this.repository.OpenLeases .Where( l => l.LicenseId == this.Id && l.StartTime <= now && l.EndTime >= now ) @@ -76,15 +77,13 @@ public async Task OnGetAsync( CancellationToken cancellationToken return this.Page(); } - public Task OnPostEnableAsync( CancellationToken cancellationToken ) - => this.SetPriorityAsync( 0, cancellationToken ); + public Task OnPostEnableAsync( CancellationToken cancellationToken ) => this.SetPriorityAsync( 0, cancellationToken ); - public Task OnPostDisableAsync( CancellationToken cancellationToken ) - => this.SetPriorityAsync( -1, cancellationToken ); + public Task OnPostDisableAsync( CancellationToken cancellationToken ) => this.SetPriorityAsync( -1, cancellationToken ); private async Task SetPriorityAsync( int priority, CancellationToken cancellationToken ) { - License? license = await this.db.Licenses.SingleOrDefaultAsync( l => l.LicenseId == this.Id, cancellationToken ); + var license = await this.db.Licenses.SingleOrDefaultAsync( l => l.LicenseId == this.Id, cancellationToken ); if ( license == null ) { @@ -108,12 +107,12 @@ public async Task OnPostDeleteAsync( CancellationToken cancellati // The leases reference each other through the chain of replacements, so they are deleted // before the license, and the most recent ones before the ones they replaced. - List leases = await this.db.Leases + var leases = await this.db.Leases .Where( l => l.LicenseId == this.Id ) .OrderByDescending( l => l.LeaseId ) .ToListAsync( cancellationToken ); - foreach ( Lease lease in leases ) + foreach ( var lease in leases ) { this.db.Leases.Remove( lease ); await this.db.SaveChangesAsync( cancellationToken ); @@ -124,4 +123,4 @@ public async Task OnPostDeleteAsync( CancellationToken cancellati return this.RedirectToPage( "/Index" ); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml index 53cdddf..0c5eda6 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml @@ -1,12 +1,14 @@ @page -@model SharpCrafters.Backstage.LicenseServer.Pages.Admin.ExportModel +@model ExportModel @{ ViewData["Title"] = "Export the audit log"; }

@@ -19,17 +21,17 @@

- - - - + + + +

- - - - + + + +

diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs index a7ed185..fe2ef18 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs @@ -1,7 +1,10 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.RazorPages; +using System.Globalization; namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; @@ -39,13 +42,13 @@ public ExportModel( TimeProvider timeProvider ) public static SelectList Months { get; } = new( Enumerable.Range( 1, 12 ) - .Select( m => new { Value = m, Text = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName( m ) } ), + .Select( m => new { Value = m, Text = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName( m ) } ), "Value", "Text" ); public void OnGet() { - DateTime now = this.timeProvider.GetUtcNow().UtcDateTime; + var now = this.timeProvider.GetUtcNow().UtcDateTime; this.FromYear = now.Year; this.ToYear = now.Year; @@ -69,8 +72,6 @@ public IActionResult OnPost() // Url.Content resolves the path, so that the link works when the server is installed as an // application below the root of an IIS site. - return this.Redirect( - this.Url.Content( - $"~/Admin/Export.ashx?fy={this.FromYear}&fm={this.FromMonth}&ty={this.ToYear}&tm={this.ToMonth}" ) ); + return this.Redirect( this.Url.Content( $"~/Admin/Export.ashx?fy={this.FromYear}&fm={this.FromMonth}&ty={this.ToYear}&tm={this.ToMonth}" ) ); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml index ab24e62..e0563ae 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml @@ -1,12 +1,14 @@ @page -@model SharpCrafters.Backstage.LicenseServer.Pages.Admin.GenerateDemoDataModel +@model GenerateDemoDataModel @{ ViewData["Title"] = "Generate demo data"; }

@@ -14,19 +16,21 @@ something to show. This page exists only in a development environment.

-@if ( Model.Message != null ) +@if (Model.Message != null) { -

@Model.Message

+

+ @Model.Message +

}

- +

- +

diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs index 10233e9..37e443b 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs @@ -1,4 +1,7 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Data; @@ -51,7 +54,7 @@ public GenerateDemoDataModel( ]; [BindProperty] - [Microsoft.AspNetCore.Mvc.ModelBinding.BindNever] + [BindNever] public string? Message { get; private set; } public bool IsAvailable => this.environment.IsDevelopment(); @@ -72,7 +75,7 @@ public async Task OnPostAsync( int userCount, int days, Cancellat this.UserCount = Math.Clamp( userCount, 1, 200 ); this.Days = Math.Clamp( days, 1, 365 ); - License[] licenses = await this.repository.Licenses + var licenses = await this.repository.Licenses .Where( l => l.Priority >= 0 ) .OrderBy( l => l.Priority ) .ToArrayAsync( cancellationToken ); @@ -88,43 +91,42 @@ public async Task OnPostAsync( int userCount, int days, Cancellat Random random = new( 20260105 ); (string User, string[] Machines)[] users = Enumerable.Range( 0, this.UserCount ) - .Select( - i => - { - string user = - $"{firstNames[i % firstNames.Length]}.{lastNames[(i * 7) % lastNames.Length]}".ToLowerInvariant(); + .Select( i => + { + var user = + $"{firstNames[i % firstNames.Length]}.{lastNames[( i * 7 ) % lastNames.Length]}".ToLowerInvariant(); - string[] machines = random.Next( 3 ) == 0 - ? [$"{user}-desktop", $"{user}-laptop"] - : [$"{user}-desktop"]; + string[] machines = random.Next( 3 ) == 0 + ? [$"{user}-desktop", $"{user}-laptop"] + : [$"{user}-desktop"]; - return (user, machines); - } ) + return ( user, machines ); + } ) .ToArray(); - DateTime now = this.timeProvider.GetUtcNow().UtcDateTime; - DateTime start = now.Date.AddDays( -this.Days ); - int granted = 0; + var now = this.timeProvider.GetUtcNow().UtcDateTime; + var start = now.Date.AddDays( -this.Days ); + var granted = 0; - for ( int day = 0; day < this.Days; day++ ) + for ( var day = 0; day < this.Days; day++ ) { - DateTime date = start.AddDays( day ); + var date = start.AddDays( day ); - bool isWeekend = date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday; + var isWeekend = date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday; - foreach ( (string user, string[] machines) in users ) + foreach ( var (user, machines) in users ) { // Most developers do not work at the weekend, and a developer does not build every // day. - if ( random.NextDouble() > (isWeekend ? 0.1 : 0.85) ) + if ( random.NextDouble() > ( isWeekend ? 0.1 : 0.85 ) ) { continue; } - string machine = machines[random.Next( machines.Length )]; - DateTime time = date.AddHours( 8 + (random.NextDouble() * 9) ); + var machine = machines[random.Next( machines.Length )]; + var time = date.AddHours( 8 + ( random.NextDouble() * 9 ) ); - Lease? lease = await this.leaseService.GetLeaseAsync( + var lease = await this.leaseService.GetLeaseAsync( new Version( 2025, 1, 0 ), null, machine, @@ -153,4 +155,4 @@ public async Task OnPostAsync( int userCount, int days, Cancellat return this.Page(); } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml index f3e66ee..c6666a8 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml @@ -8,11 +8,13 @@

An unexpected error occurred while handling your request.

-@if ( Model.RequestId != null ) +@if (Model.RequestId != null) {

Request identifier: @Model.RequestId

}

The details are in the server log.

-

Back to the licenses

+

+ Back to the licenses +

diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml.cs index 8b315dd..fdca4f7 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Diagnostics; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; @@ -13,4 +15,4 @@ public sealed class ErrorModel : PageModel public string? RequestId { get; private set; } public void OnGet() => this.RequestId = Activity.Current?.Id ?? this.HttpContext.TraceIdentifier; -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml index 4414611..7940346 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml @@ -12,9 +12,9 @@

License @Model.Id: @Model.Days-day usage history

- @foreach ( int window in GraphModel.AllowedWindows ) + @foreach (var window in GraphModel.AllowedWindows) { - if ( window == Model.Days ) + if (window == Model.Days) { @window days } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs index 55c305f..e770d58 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Globalization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; @@ -54,7 +56,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken return this.BadRequest( $"The window must be one of {string.Join( ", ", AllowedWindows )} days." ); } - License? license = await this.repository.Licenses + var license = await this.repository.Licenses .AsNoTracking() .SingleOrDefaultAsync( l => l.LicenseId == this.Id, cancellationToken ); @@ -63,14 +65,14 @@ public async Task OnGetAsync( CancellationToken cancellationToken return this.NotFound(); } - DateTime endDate = this.timeProvider.GetUtcNow().UtcDateTime.Date.AddDays( 1 ); - DateTime startDate = endDate.AddDays( -this.Days ); + var endDate = this.timeProvider.GetUtcNow().UtcDateTime.Date.AddDays( 1 ); + var startDate = endDate.AddDays( -this.Days ); int? maximum = null; int? graceMaximum = null; - int axisMaximum = 0; + var axisMaximum = 0; - LicenseInfo? parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); + var parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); if ( parsedLicense?.UserNumber != null ) { @@ -79,32 +81,31 @@ public async Task OnGetAsync( CancellationToken cancellationToken // The arithmetic of the allocator, which rounds up. An integer division would round // down, and a license of one seat with a grace period of 20 per cent would then be drawn // with a limit of one seat, while the server grants two. - graceMaximum = (int) Math.Ceiling( maximum.Value * (100.0 + parsedLicense.GracePercent) / 100.0 ); + graceMaximum = (int) Math.Ceiling( maximum.Value * ( 100.0 + parsedLicense.GracePercent ) / 100.0 ); axisMaximum = graceMaximum.Value; } var dailyUsage = this.repository.GetLeaseCountingPoints( this.Id, startDate, endDate ) .GroupBy( point => point.Time.Date ) - .Select( - day => new - { - Date = day.Key, - Peak = day.Max( point => point.SeatCount ), - - // The timeline is ordered, and the grouping preserves that order inside a group, - // so the last point of a day carries the count that the next day starts from. - AtEndOfDay = day.Last().SeatCount - } ) + .Select( day => new + { + Date = day.Key, + Peak = day.Max( point => point.SeatCount ), + + // The timeline is ordered, and the grouping preserves that order inside a group, + // so the last point of a day carries the count that the next day starts from. + AtEndOfDay = day.Last().SeatCount + } ) .ToList(); - string[] labels = new string[this.Days]; - int[] values = new int[this.Days]; - bool[] hasValue = new bool[this.Days]; - int?[] endOfDayValues = new int?[this.Days]; + var labels = new string[this.Days]; + var values = new int[this.Days]; + var hasValue = new bool[this.Days]; + var endOfDayValues = new int?[this.Days]; foreach ( var point in dailyUsage ) { - int day = (int) Math.Floor( point.Date.Subtract( startDate ).TotalDays ); + var day = (int) Math.Floor( point.Date.Subtract( startDate ).TotalDays ); if ( point.Peak > axisMaximum ) { @@ -112,7 +113,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken } // A lease that started before the window contributes to its first day. - int index = day < 0 ? 0 : day; + var index = day < 0 ? 0 : day; if ( index < this.Days ) { @@ -123,11 +124,11 @@ public async Task OnGetAsync( CancellationToken cancellationToken } // A day without lease activity keeps the count of the end of the previous day. - int lastValue = 0; + var lastValue = 0; - for ( int i = 0; i < this.Days; i++ ) + for ( var i = 0; i < this.Days; i++ ) { - DateTime date = startDate.AddDays( i ); + var date = startDate.AddDays( i ); labels[i] = date.ToString( "yyyy-MM-dd", CultureInfo.InvariantCulture ); @@ -178,4 +179,4 @@ public sealed class UsageChart public int AxisMaximum { get; init; } } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml index 9251398..c1abdca 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml @@ -8,7 +8,7 @@

Licenses

-@if ( Model.Licenses.Count == 0 ) +@if (Model.Licenses.Count == 0) {

@@ -21,41 +21,41 @@ else {

- - - - - - - - - - - - - - - @foreach ( var license in Model.Licenses ) - { + - - - - - - - - - + + + + + + + + + - } - + + + @foreach (var license in Model.Licenses) + { + + + + + + + + + + + + } +
LicenseTypeProductSeatsIn useGrace startedMaintenance endsStatus
@license.LicenseId@license.LicenseType@license.ProductCode@(license.MaxUsers?.ToString() ?? "Unlimited")@license.CurrentUsers@license.GraceStartTime?.ToString( "d" )@license.MaintenanceEndDate?.ToString( "d" ) - @license.Status - - Details - · - Usage - LicenseTypeProductSeatsIn useGrace startedMaintenance endsStatus
@license.LicenseId@license.LicenseType@license.ProductCode@( license.MaxUsers?.ToString() ?? "Unlimited" )@license.CurrentUsers@license.GraceStartTime?.ToString( "d" )@license.MaintenanceEndDate?.ToString( "d" ) + @license.Status + + Details + · + Usage +
} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs index aab761b..4748724 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Data; @@ -28,27 +30,22 @@ public IndexModel( public async Task OnGetAsync( CancellationToken cancellationToken ) { - License[] licenses = await this.repository.Licenses + var licenses = await this.repository.Licenses .OrderBy( l => l.Priority ) .ThenByDescending( l => l.LicenseId ) .AsNoTracking() .ToArrayAsync( cancellationToken ); - DateTime now = this.timeProvider.GetUtcNow().UtcDateTime; + var now = this.timeProvider.GetUtcNow().UtcDateTime; List summaries = []; - foreach ( License license in licenses ) + foreach ( var license in licenses ) { - LicenseInfo? parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); + var parsedLicense = this.licenseParser.TryParse( license.LicenseKey ); summaries.Add( parsedLicense == null - ? new LicenseSummary - { - LicenseId = license.LicenseId, - LicenseType = "INVALID", - Status = "Invalid" - } + ? new LicenseSummary { LicenseId = license.LicenseId, LicenseType = "INVALID", Status = "Invalid" } : new LicenseSummary { LicenseId = license.LicenseId, @@ -93,4 +90,4 @@ public string StatusModifier : this.Status == "Active" ? "active" : "disabled"; } -} +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Shared/_Layout.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Shared/_Layout.cshtml index ff20401..b2732e2 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Shared/_Layout.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Shared/_Layout.cshtml @@ -1,23 +1,23 @@ - - + + @ViewData["Title"] - PostSharp License Server - - - - - - - + + + + + + + @await RenderSectionAsync( "Head", required: false )
License Server
private void SeedLeases( License license, int count ) { - using LicenseServerDbContext db = this.application.CreateDbContext(); + using var db = this.application.CreateDbContext(); - DateTime start = DateTime.UtcNow.Date.AddDays( -1 ); + var start = DateTime.UtcNow.Date.AddDays( -1 ); - for ( int i = 0; i < count; i++ ) + for ( var i = 0; i < count; i++ ) { db.Leases.Add( new Lease @@ -124,8 +126,8 @@ private void SeedLeases( License license, int count ) private static string ExportUrl( int fromMonth, int toMonth ) { - int year = DateTime.UtcNow.Year; + var year = DateTime.UtcNow.Year; return $"/Admin/Export.ashx?fy={year}&fm={fromMonth}&ty={year}&tm={toMonth}"; } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs index 0c9eb98..aa6e9ec 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Licensing; using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; using SharpCrafters.Backstage.Licensing; @@ -17,14 +19,14 @@ public sealed class BackstageLicenseParserTests [Fact] public void SignedKey_IsParsedIntoTheFactsTheServerNeeds() { - LicenseKeyDataBuilder builder = TestLicenseKeys.Builder( licenseId: 4242 ); + var builder = TestLicenseKeys.Builder( licenseId: 4242 ); builder.UserNumber = 25; builder.ValidTo = new DateTime( 2030, 6, 30, 0, 0, 0, DateTimeKind.Utc ); builder.SubscriptionEndDate = new DateTime( 2029, 6, 30, 0, 0, 0, DateTimeKind.Utc ); builder.GraceDays = 7; builder.GracePercent = 15; - LicenseInfo? license = parser.TryParse( builder.Sign() ); + var license = parser.TryParse( builder.Sign() ); Assert.NotNull( license ); Assert.Equal( 4242, license.LicenseId ); @@ -51,7 +53,7 @@ public void SignedKey_IsParsedIntoTheFactsTheServerNeeds() [Fact] public void KeyIssuedByTheBackstageTestProvider_IsParsed() { - LicenseInfo? license = parser.TryParse( TestLicenseKeys.Keys.PostSharpUltimate ); + var license = parser.TryParse( TestLicenseKeys.Keys.PostSharpUltimate ); Assert.NotNull( license ); Assert.Equal( "PostSharpUltimate", license.Product ); @@ -63,8 +65,7 @@ public void KeyIssuedByTheBackstageTestProvider_IsParsed() /// serve a key signed with a key that the authority does not hold. ///
[Fact] - public void KeySignedWithAForgedKey_IsRejected() - => Assert.Null( parser.TryParse( TestLicenseKeys.Builder().SignWithAForgedKey() ) ); + public void KeySignedWithAForgedKey_IsRejected() => Assert.Null( parser.TryParse( TestLicenseKeys.Builder().SignWithAForgedKey() ) ); /// /// A key whose signature names an authority that was never issued is an invalid key, and not an @@ -76,8 +77,7 @@ public void KeySignedByAnUnknownAuthority_IsRejectedWithoutThrowing() => Assert.Null( parser.TryParse( TestLicenseKeys.Builder().SignWithAnUnknownAuthority() ) ); [Fact] - public void UnsignedKeyOfATypeThatRequiresASignature_IsRejected() - => Assert.Null( parser.TryParse( TestLicenseKeys.Builder().Unsigned() ) ); + public void UnsignedKeyOfATypeThatRequiresASignature_IsRejected() => Assert.Null( parser.TryParse( TestLicenseKeys.Builder().Unsigned() ) ); /// /// An evaluation key carries no signature by design, and the server may serve it. @@ -85,9 +85,9 @@ public void UnsignedKeyOfATypeThatRequiresASignature_IsRejected() [Fact] public void UnsignedKeyOfATypeThatRequiresNoSignature_IsParsed() { - string key = TestLicenseKeys.Builder( licenseType: LicenseType.Evaluation ).Unsigned(); + var key = TestLicenseKeys.Builder( licenseType: LicenseType.Evaluation ).Unsigned(); - LicenseInfo? license = parser.TryParse( key ); + var license = parser.TryParse( key ); Assert.NotNull( license ); Assert.Equal( nameof(LicenseType.Evaluation), license.LicenseType ); @@ -108,7 +108,7 @@ public void UnsignedKeyOfATypeThatRequiresNoSignature_IsParsed() [Fact] public void KeyWithoutAGracePercentage_GetsThirtyPercent() { - LicenseKeyDataBuilder builder = TestLicenseKeys.Builder(); + var builder = TestLicenseKeys.Builder(); builder.GracePercent = null; Assert.Equal( 30, parser.TryParse( builder.Sign() )!.GracePercent ); @@ -127,7 +127,7 @@ public void KeyWithoutAGracePercentage_GetsThirtyPercent() [Fact] public void KeyWithoutAGracePeriod_GetsThirtyDays() { - LicenseKeyDataBuilder builder = TestLicenseKeys.Builder(); + var builder = TestLicenseKeys.Builder(); Assert.Equal( 30, parser.TryParse( builder.Sign() )!.GraceDays ); } @@ -141,7 +141,7 @@ public void KeyWithoutAGracePeriod_GetsThirtyDays() [Fact] public void MinPostSharpVersion_IsDerivedWhenTheKeyDoesNotDeclareIt() { - LicenseInfo? license = parser.TryParse( TestLicenseKeys.Builder().Sign() ); + var license = parser.TryParse( TestLicenseKeys.Builder().Sign() ); Assert.NotNull( license ); Assert.Equal( new Version( 5, 0, 22 ), license.MinPostSharpVersion ); @@ -155,8 +155,7 @@ public void MinPostSharpVersion_IsDerivedWhenTheKeyDoesNotDeclareIt() [Fact] public void Product_IsStoredUnderItsBackstageName() { - LicenseInfo? license = parser.TryParse( - TestLicenseKeys.Builder( product: LicenseProduct.PostSharpFramework ).Sign() ); + var license = parser.TryParse( TestLicenseKeys.Builder( product: LicenseProduct.PostSharpFramework ).Sign() ); Assert.Equal( "PostSharpFramework", license!.Product ); } @@ -167,7 +166,7 @@ public void Product_IsStoredUnderItsBackstageName() [Fact] public void KeyThatIsNotEligibleForALicenseServer_SaysSo() { - LicenseKeyDataBuilder builder = TestLicenseKeys.Builder(); + var builder = TestLicenseKeys.Builder(); builder.LicenseServerEligible = false; Assert.False( parser.TryParse( builder.Sign() )!.IsLicenseServerEligible ); @@ -187,10 +186,10 @@ public void CleanLicenseString_RemovesEveryKindOfWhitespace( string pasted, stri [Fact] public void PastedKey_ParsesAfterCleaning() { - string key = TestLicenseKeys.Builder().Sign(); - string pasted = key[..10] + " \r\n " + key[10..]; + var key = TestLicenseKeys.Builder().Sign(); + var pasted = key[..10] + " \r\n " + key[10..]; Assert.Null( parser.TryParse( pasted ) ); Assert.NotNull( parser.TryParse( parser.CleanLicenseString( pasted ) ) ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs index 03accda..bfa16e8 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using SharpCrafters.Backstage.LicenseServer.Data; @@ -38,14 +40,14 @@ public void IsBuildServer_KnownAgent_ReturnsTrue( string machine, bool expected => Assert.Equal( expected, CreateService( "server" ).IsBuildServer( machine ) ); [Theory] + // Only a hexadecimal suffix is stripped, so "-xyz" stays part of the name. [InlineData( "server-xyz" )] [InlineData( "myserver" )] [InlineData( "server2" )] [InlineData( "desktop-1a2b" )] [InlineData( "" )] - public void IsBuildServer_OtherMachine_ReturnsFalse( string machine ) - => Assert.False( CreateService( "server" ).IsBuildServer( machine ) ); + public void IsBuildServer_OtherMachine_ReturnsFalse( string machine ) => Assert.False( CreateService( "server" ).IsBuildServer( machine ) ); [Theory] [InlineData( "build1;build2" )] @@ -54,7 +56,7 @@ public void IsBuildServer_OtherMachine_ReturnsFalse( string machine ) [InlineData( " build1 ; build2 " )] public void IsBuildServer_AcceptsEverySeparatorAndTrimsWhitespace( string buildServers ) { - LeaseService service = CreateService( buildServers ); + var service = CreateService( buildServers ); Assert.True( service.IsBuildServer( "build1" ) ); Assert.True( service.IsBuildServer( "build2-ff01" ) ); @@ -89,11 +91,9 @@ private sealed class StubRepository : ILeaseRepository bool grace ) => throw new NotSupportedException(); - public Lease? ProlongLease( Lease oldLease, string authenticatedUserName, DateTime time ) - => throw new NotSupportedException(); + public Lease? ProlongLease( Lease oldLease, string authenticatedUserName, DateTime time ) => throw new NotSupportedException(); - public void CancelLease( Lease lease, string authenticatedUserName, DateTime time ) - => throw new NotSupportedException(); + public void CancelLease( Lease lease, string authenticatedUserName, DateTime time ) => throw new NotSupportedException(); public int GetActiveSeats( int licenseId, DateTime dateTime ) => throw new NotSupportedException(); @@ -103,9 +103,8 @@ public IEnumerable GetLeaseCountingPoints( DateTime endTime ) => throw new NotSupportedException(); - public Task SaveChangesAsync( CancellationToken cancellationToken = default ) - => throw new NotSupportedException(); + public Task SaveChangesAsync( CancellationToken cancellationToken = default ) => throw new NotSupportedException(); public int SaveChanges() => throw new NotSupportedException(); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs index 2acd75c..797a8b8 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -12,14 +14,14 @@ public sealed class CancelLeaseTests [Fact] public async Task CancelLease_InsertsAReplacementEndingNow() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease original = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var original = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); context.Repository.CancelLease( original, "DOMAIN\\admin", TestClock.Days( 1 ) ); await context.Repository.SaveChangesAsync(); - Lease replacement = await context.Db.Leases.SingleAsync( l => l.LeaseId != original.LeaseId ); + var replacement = await context.Db.Leases.SingleAsync( l => l.LeaseId != original.LeaseId ); Assert.Equal( original.LeaseId, replacement.OverwrittenLeaseId ); Assert.Equal( TestClock.Days( 1 ), replacement.EndTime ); @@ -32,9 +34,9 @@ public async Task CancelLease_InsertsAReplacementEndingNow() [Fact] public async Task CancelLease_KeepsTheOriginalRow() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease original = LeaseBuilder.For( license ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var original = LeaseBuilder.For( license ).AddTo( context ); context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); await context.Repository.SaveChangesAsync(); @@ -47,9 +49,9 @@ public async Task CancelLease_KeepsTheOriginalRow() [Fact] public async Task CancelLease_ReleasesTheSeat() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease original = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var original = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); Assert.Equal( 1, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 2 ) ) ); @@ -67,28 +69,28 @@ public async Task CancelLease_ReleasesTheSeat() [Fact] public async Task CancelLease_IsNotRejectedForEndingImmediately() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithValidTo( TestClock.Days( 2 ) ).AddTo( context ); - Lease original = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 2 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithValidTo( TestClock.Days( 2 ) ).AddTo( context ); + var original = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 2 ).AddTo( context ); context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); await context.Repository.SaveChangesAsync(); - Lease replacement = await context.Db.Leases.SingleAsync( l => l.LeaseId != original.LeaseId ); + var replacement = await context.Db.Leases.SingleAsync( l => l.LeaseId != original.LeaseId ); Assert.Equal( TestClock.Days( 1 ), replacement.EndTime ); } [Fact] public async Task CancelLease_ThenRequestAgain_GrantsAFreshLease() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease original = LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var original = LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); await context.Repository.SaveChangesAsync(); - Lease? lease = await context.LeaseService.GetLeaseAsync( + var lease = await context.LeaseService.GetLeaseAsync( new Version( 2025, 1, 0 ), null, "desktop-1", @@ -102,4 +104,4 @@ public async Task CancelLease_ThenRequestAgain_GrantsAFreshLease() Assert.NotEqual( original.LeaseId, lease.LeaseId ); Assert.Equal( TestClock.Days( 2 ), lease.StartTime ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs index 16aa2e5..50ce624 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.Options; using Microsoft.Extensions.Time.Testing; @@ -34,20 +36,18 @@ private static ValidateOptionsResult Validate( Action conf [InlineData( 5, 3 )] public void MinLeaseDaysNotBelowNewLeaseDays_IsRejected( int minLeaseDays, int newLeaseDays ) { - ValidateOptionsResult result = Validate( - o => - { - o.MinLeaseDays = minLeaseDays; - o.NewLeaseDays = newLeaseDays; - } ); + var result = Validate( o => + { + o.MinLeaseDays = minLeaseDays; + o.NewLeaseDays = newLeaseDays; + } ); Assert.True( result.Failed ); Assert.Contains( result.Failures!, f => f.Contains( "MinLeaseDays", StringComparison.Ordinal ) ); } [Fact] - public void NegativeTimeAcceleration_IsRejected() - => Assert.True( Validate( o => o.TimeAcceleration = -1 ).Failed ); + public void NegativeTimeAcceleration_IsRejected() => Assert.True( Validate( o => o.TimeAcceleration = -1 ).Failed ); [Theory] [InlineData( 0 )] @@ -57,8 +57,7 @@ public void MachinesPerUserBelowOne_FailsDataAnnotations( int value ) LicenseServerOptions options = new() { MachinesPerUser = value }; List results = []; - Assert.False( - Validator.TryValidateObject( options, new ValidationContext( options ), results, true ) ); + Assert.False( Validator.TryValidateObject( options, new ValidationContext( options ), results, true ) ); } [Fact] @@ -89,8 +88,7 @@ public void AcceleratedTimeProvider_StartsAtTheCurrentTime() [InlineData( 0 )] [InlineData( -2 )] public void AcceleratedTimeProvider_RejectsNonPositiveAcceleration( double acceleration ) - => Assert.Throws( - () => new AcceleratedTimeProvider( TimeProvider.System, acceleration ) ); + => Assert.Throws( () => new AcceleratedTimeProvider( TimeProvider.System, acceleration ) ); [Fact] public void CachingLicenseParser_ParsesEachKeyOnlyOnce() @@ -130,4 +128,4 @@ public void CachingLicenseParser_BlankKey_IsRejectedWithoutParsing( string key ) Assert.Null( new CachingLicenseParser( inner ).TryParse( key ) ); Assert.Equal( 0, inner.ParseCount ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/EmailSenderTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/EmailSenderTests.cs index bfe70b1..2ba25bf 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/EmailSenderTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/EmailSenderTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Email; using SharpCrafters.Backstage.LicenseServer.Tests.Fakes; @@ -16,7 +18,7 @@ public async Task InMemorySender_RecordsWhatItWasAskedToSend() await sender.SendAsync( new EmailMessage( "admin@example.com", null, "Subject", "Body" ) ); - EmailMessage message = Assert.Single( sender.Sent ); + var message = Assert.Single( sender.Sent ); Assert.Equal( "admin@example.com", message.To ); Assert.Equal( "Subject", message.Subject ); Assert.Equal( "Body", message.Body ); @@ -53,8 +55,7 @@ public async Task InMemorySender_CanSimulateABrokenServer() { InMemoryEmailSender sender = new() { ThrowOnSend = new InvalidOperationException( "SMTP is down" ) }; - await Assert.ThrowsAsync( - () => sender.SendAsync( new EmailMessage( "a@example.com", null, "s", "b" ) ) ); + await Assert.ThrowsAsync( () => sender.SendAsync( new EmailMessage( "a@example.com", null, "s", "b" ) ) ); Assert.Empty( sender.Sent ); } @@ -79,4 +80,4 @@ public async Task NullSender_DiscardsEverything() // Must not throw: this is what the demo-data generator and disabled-SMTP deployments use. await sender.SendAsync( new EmailMessage( "a@example.com", "b@example.com", "s", "b" ) ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs index 5c0a718..6f60392 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Licensing; namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; @@ -32,6 +34,5 @@ public sealed class FakeLicenseParser : ILicenseParser /// /// Reproduces the real implementation, which removes the whitespace of a pasted key. /// - public string CleanLicenseString( string licenseKey ) - => new( licenseKey.Where( c => !char.IsWhiteSpace( c ) ).ToArray() ); -} + public string CleanLicenseString( string licenseKey ) => new( licenseKey.Where( c => !char.IsWhiteSpace( c ) ).ToArray() ); +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs index bff38ec..a585544 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Licensing; namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; @@ -16,4 +18,4 @@ public FixedServerVersion( Version version ) } public Version LicensingLibraryVersion { get; } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs index 2400862..1e81217 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Collections.Concurrent; using SharpCrafters.Backstage.LicenseServer.Email; @@ -43,4 +45,4 @@ public sealed class InMemoryEmailSender : IEmailSender /// public IReadOnlyList WithSubject( string substring ) => this.Sent.Where( m => m.Subject.Contains( substring, StringComparison.OrdinalIgnoreCase ) ).ToArray(); -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs index 0498ba6..e0267f9 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Locking; namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; @@ -11,4 +13,4 @@ public sealed class NeverAcquiringLeaseLock : ILeaseLock TimeSpan timeout, CancellationToken cancellationToken = default ) => ValueTask.FromResult( null ); -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveSeatsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveSeatsTests.cs index 57cf387..f1c0383 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveSeatsTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveSeatsTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; namespace SharpCrafters.Backstage.LicenseServer.Tests; @@ -10,8 +12,8 @@ public sealed class GetActiveSeatsTests [Fact] public async Task GetActiveSeats_NoLeases_ReturnsZero() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); Assert.Equal( 0, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); } @@ -19,8 +21,8 @@ public async Task GetActiveSeats_NoLeases_ReturnsZero() [Fact] public async Task GetActiveSeats_OneUserOneMachine_ReturnsOne() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); Assert.Equal( 1, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); @@ -29,8 +31,8 @@ public async Task GetActiveSeats_OneUserOneMachine_ReturnsOne() [Fact] public async Task GetActiveSeats_OneUserTwoMachines_StillReturnsOne() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).AddTo( context ); @@ -40,10 +42,10 @@ public async Task GetActiveSeats_OneUserTwoMachines_StillReturnsOne() [Fact] public async Task GetActiveSeats_OneUserThreeMachines_ReturnsTwo() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); - foreach ( string machine in new[] { "desktop-1", "laptop-1", "desktop-2" } ) + foreach ( var machine in new[] { "desktop-1", "laptop-1", "desktop-2" } ) { LeaseBuilder.For( license ).User( "alice" ).Machine( machine ).AddTo( context ); } @@ -54,8 +56,8 @@ public async Task GetActiveSeats_OneUserThreeMachines_ReturnsTwo() [Fact] public async Task GetActiveSeats_TwoUsers_ReturnsTwo() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).AddTo( context ); LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); @@ -65,8 +67,8 @@ public async Task GetActiveSeats_TwoUsers_ReturnsTwo() [Fact] public async Task GetActiveSeats_LeaseStartingExactlyNow_IsCounted() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); Assert.Equal( 1, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Origin ) ); @@ -75,8 +77,8 @@ public async Task GetActiveSeats_LeaseStartingExactlyNow_IsCounted() [Fact] public async Task GetActiveSeats_LeaseEndingExactlyNow_IsNotCounted() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); Assert.Equal( 0, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 3 ) ) ); @@ -85,9 +87,9 @@ public async Task GetActiveSeats_LeaseEndingExactlyNow_IsNotCounted() [Fact] public async Task GetActiveSeats_OtherLicense_IsNotCounted() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License first = LicenseBuilder.Default().WithLicenseId( 1 ).AddTo( context ); - License second = LicenseBuilder.Default().WithLicenseId( 2 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var first = LicenseBuilder.Default().WithLicenseId( 1 ).AddTo( context ); + var second = LicenseBuilder.Default().WithLicenseId( 2 ).AddTo( context ); LeaseBuilder.For( second ).AddTo( context ); @@ -98,9 +100,9 @@ public async Task GetActiveSeats_OtherLicense_IsNotCounted() [Fact] public async Task GetActiveSeats_ReplacedLease_IsNotCounted() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease original = LeaseBuilder.For( license ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var original = LeaseBuilder.For( license ).AddTo( context ); context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); await context.Repository.SaveChangesAsync(); @@ -115,8 +117,8 @@ public async Task GetActiveSeats_ReplacedLease_IsNotCounted() [Fact] public async Task GetActiveSeats_UserNameCasingDiffers_CountsAsOneUser() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); LeaseBuilder.For( license ).User( "ALICE" ).Machine( "laptop-1" ).AddTo( context ); @@ -126,10 +128,10 @@ public async Task GetActiveSeats_UserNameCasingDiffers_CountsAsOneUser() [Fact] public async Task GetActiveSeats_HonoursMachinesPerUser() { - await using LicenseServerTestContext context = + await using var context = await LicenseServerTestContext.CreateAsync( o => o.MachinesPerUser = 1 ); - License license = LicenseBuilder.Default().AddTo( context ); + var license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).AddTo( context ); @@ -150,8 +152,8 @@ public async Task GetActiveSeats_HonoursMachinesPerUser() [Fact] public async Task GetActiveSeats_TwoLeasesOnOneMachine_CountAsOneMachine() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); @@ -161,4 +163,4 @@ public async Task GetActiveSeats_TwoLeasesOnOneMachine_CountAsOneMachine() // two. Assert.Equal( 1, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs index e9e028a..7f5df6d 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -23,28 +25,27 @@ public sealed class AsyncOnlyResponseBody : IStartupFilter public Action Configure( Action next ) => builder => { - builder.Use( - async ( context, nextMiddleware ) => + builder.Use( async ( context, nextMiddleware ) => + { + if ( !this.IsEnabled ) + { + await nextMiddleware( context ); + + return; + } + + var original = context.Response.Body; + context.Response.Body = new AsyncOnlyStream( original ); + + try + { + await nextMiddleware( context ); + } + finally { - if ( !this.IsEnabled ) - { - await nextMiddleware( context ); - - return; - } - - Stream original = context.Response.Body; - context.Response.Body = new AsyncOnlyStream( original ); - - try - { - await nextMiddleware( context ); - } - finally - { - context.Response.Body = original; - } - } ); + context.Response.Body = original; + } + } ); next( builder ); }; @@ -101,4 +102,4 @@ public override Task WriteAsync( byte[] buffer, int offset, int count, Cancellat public override void SetLength( long value ) => throw new NotSupportedException(); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs index 738ff79..b5f6a59 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Security.Claims; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Authentication; @@ -17,6 +19,7 @@ using SharpCrafters.Backstage.LicenseServer.Options; using SharpCrafters.Backstage.LicenseServer.Tests.Fakes; using SharpCrafters.Common; +using System.Globalization; namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -87,53 +90,51 @@ protected override void ConfigureWebHost( IWebHostBuilder builder ) ["LicenseServer:NewLeaseDays"] = "3", ["LicenseServer:MinLeaseDays"] = "1", ["LicenseServer:BuildServers"] = "buildagent", - ["LicenseServer:MutexTimeout"] = this.MutexTimeoutSeconds.ToString( System.Globalization.CultureInfo.InvariantCulture ), + ["LicenseServer:MutexTimeout"] = this.MutexTimeoutSeconds.ToString( CultureInfo.InvariantCulture ), ["LicenseServer:DatabaseProvider"] = this.database.ProviderName, [$"ConnectionStrings:{DatabaseRegistration.ConnectionStringName}"] = this.database.ConnectionString, ["Smtp:Enabled"] = "false" }; - foreach ( (string key, string? value) in settings ) + foreach ( var (key, value) in settings ) { builder.UseSetting( key, value ); } - builder.ConfigureServices( - services => + builder.ConfigureServices( services => + { + // Neither the database nor its lock is replaced here. The application selects both + // from the configuration above, through the code path that a customer uses. + services.RemoveAll(); + services.AddSingleton( this.LicenseParser ); + + services.RemoveAll(); + services.AddSingleton( this.EmailSender ); + + if ( this.LeaseLock != null ) { - // Neither the database nor its lock is replaced here. The application selects both - // from the configuration above, through the code path that a customer uses. - services.RemoveAll(); - services.AddSingleton( this.LicenseParser ); - - services.RemoveAll(); - services.AddSingleton( this.EmailSender ); - - if ( this.LeaseLock != null ) - { - services.RemoveAll(); - services.AddSingleton( this.LeaseLock ); - } - - services.AddSingleton( this.Synchronization ); - - // Windows authentication cannot be negotiated against an in-memory host. - services.AddAuthentication( TestAuthenticationHandler.SchemeName ) - .AddScheme( - TestAuthenticationHandler.SchemeName, - _ => { } ); - - services.PostConfigure( - options => - { - options.DefaultAuthenticateScheme = TestAuthenticationHandler.SchemeName; - options.DefaultChallengeScheme = TestAuthenticationHandler.SchemeName; - } ); - - services.AddSingleton( this.ResponseBody ); - - services.AddLogging( logging => logging.SetMinimumLevel( LogLevel.Warning ) ); + services.RemoveAll(); + services.AddSingleton( this.LeaseLock ); + } + + services.AddSingleton( this.Synchronization ); + + // Windows authentication cannot be negotiated against an in-memory host. + services.AddAuthentication( TestAuthenticationHandler.SchemeName ) + .AddScheme( + TestAuthenticationHandler.SchemeName, + _ => { } ); + + services.PostConfigure( options => + { + options.DefaultAuthenticateScheme = TestAuthenticationHandler.SchemeName; + options.DefaultChallengeScheme = TestAuthenticationHandler.SchemeName; } ); + + services.AddSingleton( this.ResponseBody ); + + services.AddLogging( logging => logging.SetMinimumLevel( LogLevel.Warning ) ); + } ); } public LicenseServerDbContext CreateDbContext() => this.database.CreateContext(); @@ -150,12 +151,12 @@ protected override void ConfigureWebHost( IWebHostBuilder builder ) /// public License AddLicense( LicenseBuilder builder ) { - LicenseInfo info = builder.BuildInfo(); - string key = $"FAKE-KEY-{info.LicenseId}"; + var info = builder.BuildInfo(); + var key = $"FAKE-KEY-{info.LicenseId}"; this.LicenseParser.Register( key, info ); - using LicenseServerDbContext db = this.CreateDbContext(); + using var db = this.CreateDbContext(); License license = new() { @@ -217,8 +218,6 @@ protected override Task HandleAuthenticateAsync() [new Claim( ClaimTypes.Name, "DOMAIN\\tester" )], SchemeName ); - return Task.FromResult( - AuthenticateResult.Success( - new AuthenticationTicket( new ClaimsPrincipal( identity ), SchemeName ) ) ); + return Task.FromResult( AuthenticateResult.Success( new AuthenticationTicket( new ClaimsPrincipal( identity ), SchemeName ) ) ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs index c13e408..f4cb07f 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using SharpCrafters.Backstage.LicenseServer.Data; @@ -76,7 +78,7 @@ public static async Task CreateAsync( Action CreateAsync( string serverConnectionString ) { - PostgreSqlDatabasePool pool = PostgreSqlDatabasePool.Get( serverConnectionString ); - string databaseName = await pool.RentAsync(); + var pool = PostgreSqlDatabasePool.Get( serverConnectionString ); + var databaseName = await pool.RentAsync(); return new PostgreSqlTestDatabase( pool, databaseName, pool.GetConnectionString( databaseName ) ); } @@ -129,7 +131,7 @@ public async Task RentAsync() try { - if ( this.available.TryTake( out string? databaseName ) ) + if ( this.available.TryTake( out var databaseName ) ) { await this.ClearAsync( databaseName ); @@ -186,7 +188,7 @@ private async Task ClearAsync( string databaseName ) private async Task CreateSchemaAsync( string databaseName ) { - string script = await File.ReadAllTextAsync( LocateCreateTablesScript() ); + var script = await File.ReadAllTextAsync( LocateCreateTablesScript() ); // PostgreSQL has no batch separator: the whole script is one command. await ExecuteAsync( this.GetConnectionString( databaseName ), script ); @@ -218,10 +220,10 @@ private async Task DropLeftoverDatabasesAsync() { await connection.OpenAsync(); - await using NpgsqlCommand query = connection.CreateCommand(); + await using var query = connection.CreateCommand(); query.CommandText = $"SELECT datname FROM pg_database WHERE datname LIKE '{prefix}%'"; - await using NpgsqlDataReader reader = await query.ExecuteReaderAsync(); + await using var reader = await query.ExecuteReaderAsync(); while ( await reader.ReadAsync() ) { @@ -229,7 +231,7 @@ private async Task DropLeftoverDatabasesAsync() } } - foreach ( string leftover in leftovers ) + foreach ( var leftover in leftovers ) { await this.DropAsync( leftover ); } @@ -266,7 +268,7 @@ private static async Task ExecuteAsync( string connectionString, string sql ) await using NpgsqlConnection connection = new( connectionString ); await connection.OpenAsync(); - await using NpgsqlCommand command = connection.CreateCommand(); + await using var command = connection.CreateCommand(); command.CommandText = sql; await command.ExecuteNonQueryAsync(); @@ -278,7 +280,7 @@ private static async Task ExecuteAsync( string connectionString, string sql ) ///
private static string LocateCreateTablesScript() { - string path = Path.Combine( AppContext.BaseDirectory, "Database", "CreateTables.PostgreSql.sql" ); + var path = Path.Combine( AppContext.BaseDirectory, "Database", "CreateTables.PostgreSql.sql" ); if ( !File.Exists( path ) ) { @@ -289,4 +291,4 @@ private static string LocateCreateTablesScript() return path; } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs index 72d7567..4197657 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using Microsoft.Data.SqlClient; @@ -41,8 +43,8 @@ private SqlServerTestDatabase( SqlServerDatabasePool pool, string databaseName, public static async Task CreateAsync( string serverConnectionString ) { - SqlServerDatabasePool pool = SqlServerDatabasePool.Get( serverConnectionString ); - string databaseName = await pool.RentAsync(); + var pool = SqlServerDatabasePool.Get( serverConnectionString ); + var databaseName = await pool.RentAsync(); return new SqlServerTestDatabase( pool, databaseName, pool.GetConnectionString( databaseName ) ); } @@ -134,7 +136,7 @@ public async Task RentAsync() try { - if ( this.available.TryTake( out string? databaseName ) ) + if ( this.available.TryTake( out var databaseName ) ) { await this.ClearAsync( databaseName ); @@ -208,10 +210,10 @@ SELECT 1 FROM sys.identity_columns private async Task CreateSchemaAsync( string databaseName ) { - string script = await File.ReadAllTextAsync( LocateCreateTablesScript() ); + var script = await File.ReadAllTextAsync( LocateCreateTablesScript() ); // sqlcmd separates the batches of a script with GO, which is not a statement of Transact-SQL. - foreach ( string batch in script.Split( + foreach ( var batch in script.Split( "\nGO", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ) ) { @@ -247,11 +249,11 @@ private async Task DropLeftoverDatabasesAsync() List leftovers = []; - await using ( SqlCommand query = connection.CreateCommand() ) + await using ( var query = connection.CreateCommand() ) { query.CommandText = $"SELECT name FROM sys.databases WHERE name LIKE '{prefix}%'"; - await using SqlDataReader reader = await query.ExecuteReaderAsync(); + await using var reader = await query.ExecuteReaderAsync(); while ( await reader.ReadAsync() ) { @@ -259,7 +261,7 @@ private async Task DropLeftoverDatabasesAsync() } } - foreach ( string leftover in leftovers ) + foreach ( var leftover in leftovers ) { await this.DropAsync( leftover ); } @@ -289,7 +291,7 @@ private static async Task ExecuteAsync( string connectionString, string sql ) await using SqlConnection connection = new( connectionString ); await connection.OpenAsync(); - await using SqlCommand command = connection.CreateCommand(); + await using var command = connection.CreateCommand(); command.CommandText = sql; await command.ExecuteNonQueryAsync(); @@ -300,7 +302,7 @@ private static async Task ExecuteAsync( string connectionString, string sql ) ///
private static string LocateCreateTablesScript() { - string path = Path.Combine( AppContext.BaseDirectory, "Database", "CreateTables.sql" ); + var path = Path.Combine( AppContext.BaseDirectory, "Database", "CreateTables.sql" ); if ( !File.Exists( path ) ) { @@ -311,4 +313,4 @@ private static string LocateCreateTablesScript() return path; } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteTestDatabase.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteTestDatabase.cs index 2cfc369..f0e056a 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteTestDatabase.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteTestDatabase.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Data; @@ -29,14 +31,14 @@ private SqliteTestDatabase( SqliteConnection connection, string connectionString public static async Task CreateAsync() { - string connectionString = $"DataSource=licenseserver-{Guid.NewGuid():N};Mode=Memory;Cache=Shared"; + var connectionString = $"DataSource=licenseserver-{Guid.NewGuid():N};Mode=Memory;Cache=Shared"; SqliteConnection connection = new( connectionString ); await connection.OpenAsync(); SqliteTestDatabase database = new( connection, connectionString ); - await using LicenseServerDbContext context = database.CreateContext(); + await using var context = database.CreateContext(); await context.Database.EnsureCreatedAsync(); return database; @@ -56,4 +58,4 @@ public LicenseServerDbContext CreateContext() public void DoNotReuse() { } public async ValueTask DisposeAsync() => await this.connection.DisposeAsync(); -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs index 7d6f42d..dc35aee 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Licensing; namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -179,7 +181,7 @@ public LicenseInfo BuildInfo() ///
public License AddTo( LicenseServerTestContext context ) { - string key = $"FAKE-KEY-{this.licenseId}"; + var key = $"FAKE-KEY-{this.licenseId}"; License license = new() { @@ -280,4 +282,4 @@ public Lease AddTo( LicenseServerTestContext context ) return lease; } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs index 88aed81..a494a85 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Data; namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -104,6 +106,5 @@ public static async Task CreateAsync() return await SqliteTestDatabase.CreateAsync(); } - private static string? Read( string variable ) - => Environment.GetEnvironmentVariable( variable ) is { Length: > 0 } value ? value : null; -} + private static string? Read( string variable ) => Environment.GetEnvironmentVariable( variable ) is { Length: > 0 } value ? value : null; +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs index d4decba..3a583bd 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Security.Cryptography; using SharpCrafters.Backstage.Licensing; using SharpCrafters.Backstage.Licensing.Licenses; @@ -48,10 +50,10 @@ public static class TestLicenseKeys static TestLicenseKeys() { - string probe = new LicenseKeyDataBuilder { LicenseId = 1, LicenseType = LicenseType.Business } + var probe = new LicenseKeyDataBuilder { LicenseId = 1, LicenseType = LicenseType.Business } .SignAndSerialize( provider.Authority ); - LicenseKeyData.TryDeserialize( probe, out LicenseKeyData? data, out _ ); + LicenseKeyData.TryDeserialize( probe, out var data, out _ ); authorityKeyId = data!.SignatureKeyId!.Value; Authority = new TestAuthorityProvider( provider.Authority, authorityKeyId ); @@ -77,8 +79,7 @@ public static LicenseKeyDataBuilder Builder( /// /// Signs and serializes a license key with the test authority. /// - public static string Sign( this LicenseKeyDataBuilder builder ) - => builder.SignAndSerialize( provider.Authority ); + public static string Sign( this LicenseKeyDataBuilder builder ) => builder.SignAndSerialize( provider.Authority ); /// /// Serializes a license key without signing it. Only the types of license that require no @@ -91,8 +92,7 @@ public static string Sign( this LicenseKeyDataBuilder builder ) /// result is a forgery: the parser reads the identifier, finds the real key, and the signature /// does not verify against it. /// - public static string SignWithAForgedKey( this LicenseKeyDataBuilder builder ) - => builder.SignAndSerialize( CreateStandaloneAuthority( authorityKeyId ) ); + public static string SignWithAForgedKey( this LicenseKeyDataBuilder builder ) => builder.SignAndSerialize( CreateStandaloneAuthority( authorityKeyId ) ); /// /// Signs a license key with an authority that the parser does not know, so that the identifier of @@ -102,21 +102,20 @@ public static string SignWithAForgedKey( this LicenseKeyDataBuilder builder ) /// The identifier differs from the identifiers of the production keys and from the identifiers of /// the test keys of Backstage. /// - public static string SignWithAnUnknownAuthority( this LicenseKeyDataBuilder builder ) - => builder.SignAndSerialize( CreateStandaloneAuthority( 200 ) ); + public static string SignWithAnUnknownAuthority( this LicenseKeyDataBuilder builder ) => builder.SignAndSerialize( CreateStandaloneAuthority( 200 ) ); private static LicensingAuthority CreateStandaloneAuthority( byte keyId ) { - using ECDsa key = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); - ECParameters parameters = key.ExportParameters( true ); + using var key = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); + var parameters = key.ExportParameters( true ); - string xml = "nistP256" - + $"{Convert.ToBase64String( parameters.Q.X! )}" - + $"{Convert.ToBase64String( parameters.Q.Y! )}" - + $"{Convert.ToBase64String( parameters.D! )}" - + ""; + var xml = "nistP256" + + $"{Convert.ToBase64String( parameters.Q.X! )}" + + $"{Convert.ToBase64String( parameters.Q.Y! )}" + + $"{Convert.ToBase64String( parameters.D! )}" + + ""; - return new ExplicitLicensingAuthorityProvider( (keyId, xml) ).GetAuthority( keyId ); + return new ExplicitLicensingAuthorityProvider( ( keyId, xml ) ).GetAuthority( keyId ); } /// @@ -146,4 +145,4 @@ public LicensingAuthority GetAuthority( byte id ) ? this.authority : throw new KeyNotFoundException( $"There is no test licensing this.authority key of identifier {id}." ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs index 52af5e1..b9351c6 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -32,10 +34,10 @@ public sealed class LeaseAllocationTests [Fact] public async Task GetLease_NoExistingLease_GrantsANewOne() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 5 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithUsers( 5 ).AddTo( context ); - Lease? lease = await RequestAsync( context, [license] ); + var lease = await RequestAsync( context, [license] ); Assert.NotNull( lease ); Assert.Equal( "alice", lease.UserName ); @@ -48,11 +50,11 @@ public async Task GetLease_NoExistingLease_GrantsANewOne() [Fact] public async Task GetLease_GoodExistingLease_IsReusedWithoutInsertingARow() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease existing = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var existing = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); - Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); + var lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); Assert.Equal( existing.LeaseId, lease!.LeaseId ); Assert.DoesNotContain( context.Db.ChangeTracker.Entries(), e => e.State == EntityState.Added ); @@ -61,12 +63,12 @@ public async Task GetLease_GoodExistingLease_IsReusedWithoutInsertingARow() [Fact] public async Task GetLease_LeaseNearingExpiry_IsProlonged() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease existing = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var existing = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); // Within MinLeaseDays of the end, so the client is told to renew. - Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 2.5 ) ); + var lease = await RequestAsync( context, [license], now: TestClock.Days( 2.5 ) ); Assert.NotNull( lease ); Assert.NotEqual( existing.LeaseId, lease.LeaseId ); @@ -78,11 +80,11 @@ public async Task GetLease_LeaseNearingExpiry_IsProlonged() [Fact] public async Task GetLease_SecondMachineForTheSameUser_DoesNotConsumeASeat() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); - Lease? lease = await RequestAsync( context, [license], machine: "laptop-1", now: TestClock.Days( 1 ) ); + var lease = await RequestAsync( context, [license], machine: "laptop-1", now: TestClock.Days( 1 ) ); await context.Repository.SaveChangesAsync(); Assert.NotNull( lease ); @@ -92,13 +94,13 @@ public async Task GetLease_SecondMachineForTheSameUser_DoesNotConsumeASeat() [Fact] public async Task GetLease_ThirdMachineOnAFullLicense_FallsBackToGrace() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).AddTo( context ); // A third machine rounds up to a second seat, which this one-seat license does not have. - Lease? lease = await RequestAsync( context, [license], machine: "desktop-2", now: TestClock.Days( 1 ) ); + var lease = await RequestAsync( context, [license], machine: "desktop-2", now: TestClock.Days( 1 ) ); Assert.NotNull( lease ); Assert.True( lease.Grace ); @@ -107,11 +109,11 @@ public async Task GetLease_ThirdMachineOnAFullLicense_FallsBackToGrace() [Fact] public async Task GetLease_CapacityAvailable_DoesNotUseGrace() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 5 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithUsers( 5 ).AddTo( context ); LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); - Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); + var lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); Assert.False( lease!.Grace ); Assert.Null( license.GraceStartTime ); @@ -120,11 +122,11 @@ public async Task GetLease_CapacityAvailable_DoesNotUseGrace() [Fact] public async Task GetLease_CapacityExhausted_StartsTheGracePeriodAndWarns() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); - Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); + var lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); Assert.NotNull( lease ); Assert.True( lease.Grace ); @@ -138,18 +140,21 @@ public async Task GetLease_CapacityExhausted_StartsTheGracePeriodAndWarns() [Fact] public async Task GetLease_WithinGraceLimit_IsGranted() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); // 10 seats, 20 percent grace, so up to 12 are tolerated. - License license = LicenseBuilder.Default().WithUsers( 10 ).WithGracePercent( 20 ) - .WithGraceStartTime( TestClock.Origin ).AddTo( context ); + var license = LicenseBuilder.Default() + .WithUsers( 10 ) + .WithGracePercent( 20 ) + .WithGraceStartTime( TestClock.Origin ) + .AddTo( context ); - for ( int i = 0; i < 11; i++ ) + for ( var i = 0; i < 11; i++ ) { LeaseBuilder.For( license ).User( $"user{i}" ).AddTo( context ); } - Lease? lease = await RequestAsync( context, [license], user: "newcomer", now: TestClock.Days( 1 ) ); + var lease = await RequestAsync( context, [license], user: "newcomer", now: TestClock.Days( 1 ) ); Assert.NotNull( lease ); Assert.True( lease.Grace ); @@ -158,16 +163,20 @@ public async Task GetLease_WithinGraceLimit_IsGranted() [Fact] public async Task GetLease_AtTheGraceLimit_IsDenied() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 10 ).WithGracePercent( 20 ) - .WithGraceStartTime( TestClock.Origin ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + + var license = LicenseBuilder.Default() + .WithUsers( 10 ) + .WithGracePercent( 20 ) + .WithGraceStartTime( TestClock.Origin ) + .AddTo( context ); - for ( int i = 0; i < 12; i++ ) + for ( var i = 0; i < 12; i++ ) { LeaseBuilder.For( license ).User( $"user{i}" ).AddTo( context ); } - Lease? lease = await RequestAsync( context, [license], user: "newcomer", now: TestClock.Days( 1 ) ); + var lease = await RequestAsync( context, [license], user: "newcomer", now: TestClock.Days( 1 ) ); Assert.Null( lease ); } @@ -175,13 +184,17 @@ public async Task GetLease_AtTheGraceLimit_IsDenied() [Fact] public async Task GetLease_GracePeriodOver_IsDenied() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 1 ).WithGraceDays( 30 ) - .WithGraceStartTime( TestClock.Origin ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + + var license = LicenseBuilder.Default() + .WithUsers( 1 ) + .WithGraceDays( 30 ) + .WithGraceStartTime( TestClock.Origin ) + .AddTo( context ); LeaseBuilder.For( license ).User( "bob" ).From( TestClock.Days( 40 ) ).Lasting( 3 ).AddTo( context ); - Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 41 ) ); + var lease = await RequestAsync( context, [license], now: TestClock.Days( 41 ) ); Assert.Null( lease ); } @@ -189,13 +202,17 @@ public async Task GetLease_GracePeriodOver_IsDenied() [Fact] public async Task GetLease_GraceLease_EndsWhenTheGracePeriodEnds() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 1 ).WithGraceDays( 30 ) - .WithGraceStartTime( TestClock.Origin ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + + var license = LicenseBuilder.Default() + .WithUsers( 1 ) + .WithGraceDays( 30 ) + .WithGraceStartTime( TestClock.Origin ) + .AddTo( context ); LeaseBuilder.For( license ).User( "bob" ).From( TestClock.Days( 28 ) ).Lasting( 5 ).AddTo( context ); - Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 29 ) ); + var lease = await RequestAsync( context, [license], now: TestClock.Days( 29 ) ); // A three-day lease would run past the end of the grace period, so it is cut short. Assert.NotNull( lease ); @@ -205,13 +222,18 @@ public async Task GetLease_GraceLease_EndsWhenTheGracePeriodEnds() [Fact] public async Task GetLease_DeniedRequest_NotifiesTheAdministrator() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 1 ).WithGracePercent( 0 ) - .WithGraceStartTime( TestClock.Origin.AddDays( -100 ) ).WithGraceDays( 1 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + + var license = LicenseBuilder.Default() + .WithUsers( 1 ) + .WithGracePercent( 0 ) + .WithGraceStartTime( TestClock.Origin.AddDays( -100 ) ) + .WithGraceDays( 1 ) + .AddTo( context ); LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); - Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); + var lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); Assert.Null( lease ); var denial = Assert.Single( context.EmailSender.WithSubject( "denied" ) ); @@ -222,10 +244,10 @@ public async Task GetLease_DeniedRequest_NotifiesTheAdministrator() [Fact] public async Task GetLease_LeaseEndClampedByLicenseExpiry() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithValidTo( TestClock.Days( 1 ) ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithValidTo( TestClock.Days( 1 ) ).AddTo( context ); - Lease? lease = await RequestAsync( context, [license] ); + var lease = await RequestAsync( context, [license] ); Assert.NotNull( lease ); Assert.Equal( TestClock.Days( 1 ), lease.EndTime ); @@ -234,8 +256,8 @@ public async Task GetLease_LeaseEndClampedByLicenseExpiry() [Fact] public async Task GetLease_LicenseAlreadyExpired_IsDenied() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithValidTo( TestClock.Days( -1 ) ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithValidTo( TestClock.Days( -1 ) ).AddTo( context ); Assert.Null( await RequestAsync( context, [license] ) ); } @@ -243,11 +265,11 @@ public async Task GetLease_LicenseAlreadyExpired_IsDenied() [Fact] public async Task GetLease_TriesLicensesInPriorityOrder() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License low = LicenseBuilder.Default().WithLicenseId( 1 ).WithPriority( 10 ).AddTo( context ); - License high = LicenseBuilder.Default().WithLicenseId( 2 ).WithPriority( 0 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var low = LicenseBuilder.Default().WithLicenseId( 1 ).WithPriority( 10 ).AddTo( context ); + var high = LicenseBuilder.Default().WithLicenseId( 2 ).WithPriority( 0 ).AddTo( context ); - Lease? lease = await RequestAsync( context, [high, low] ); + var lease = await RequestAsync( context, [high, low] ); Assert.Equal( high.LicenseId, lease!.LicenseId ); } @@ -259,13 +281,13 @@ public async Task GetLease_TriesLicensesInPriorityOrder() [Fact] public async Task GetLease_WarningEmailFails_StillRecordsThatItWasAttempted() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); context.EmailSender.ThrowOnSend = new InvalidOperationException( "SMTP is down" ); - License license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); + var license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); - Lease? lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); + var lease = await RequestAsync( context, [license], now: TestClock.Days( 1 ) ); Assert.NotNull( lease ); Assert.Equal( TestClock.Days( 1 ), license.GraceLastWarningTime ); @@ -274,10 +296,10 @@ public async Task GetLease_WarningEmailFails_StillRecordsThatItWasAttempted() [Fact] public async Task GetLease_WarningIsNotRepeatedWithinTheConfiguredInterval() { - await using LicenseServerTestContext context = + await using var context = await LicenseServerTestContext.CreateAsync( o => o.GracePeriodWarningDays = 7 ); - License license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); + var license = LicenseBuilder.Default().WithUsers( 1 ).AddTo( context ); LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); await RequestAsync( context, [license], user: "alice", now: TestClock.Days( 1 ) ); @@ -286,4 +308,4 @@ public async Task GetLease_WarningIsNotRepeatedWithinTheConfiguredInterval() Assert.Single( context.EmailSender.WithSubject( "WARNING" ) ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs index defe04a..d3302ae 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using System.Globalization; using SharpCrafters.Backstage.LicenseServer.Data; @@ -36,7 +38,7 @@ public void Write_ProducesTheExpectedLine() [Fact] public void Write_NoOverwrittenLease_LeavesTheFieldEmpty() { - Lease lease = CreateLease(); + var lease = CreateLease(); lease.OverwrittenLeaseId = null; Assert.Equal( @@ -52,7 +54,7 @@ public void Write_NoOverwrittenLease_LeavesTheFieldEmpty() [Fact] public void Write_NamesTheUserAndTheMachine() { - string[] fields = CreateLease().ToAuditLine().Split( ';' ); + var fields = CreateLease().ToAuditLine().Split( ';' ); Assert.Equal( "desktop-1", fields[5] ); Assert.Equal( "alice", fields[6] ); @@ -70,7 +72,7 @@ public void Write_NamesTheUserAndTheMachine() [Fact] public void Write_EmitsAbsoluteTimestamps() { - string[] fields = CreateLease().ToAuditLine().Split( ';' ); + var fields = CreateLease().ToAuditLine().Split( ';' ); Assert.EndsWith( "Z", fields[3], StringComparison.Ordinal ); Assert.EndsWith( "Z", fields[4], StringComparison.Ordinal ); @@ -83,12 +85,12 @@ public void Write_EmitsAbsoluteTimestamps() [Fact] public async Task Write_AfterReload_StillEmitsUtc() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease saved = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var saved = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); - await using LicenseServerDbContext reader = context.CreateFreshContext(); - Lease reloaded = await reader.Leases.SingleAsync( l => l.LeaseId == saved.LeaseId ); + await using var reader = context.CreateFreshContext(); + var reloaded = await reader.Leases.SingleAsync( l => l.LeaseId == saved.LeaseId ); Assert.Equal( DateTimeKind.Utc, reloaded.StartTime.Kind ); Assert.Equal( DateTimeKind.Utc, reloaded.EndTime.Kind ); @@ -98,7 +100,7 @@ public async Task Write_AfterReload_StillEmitsUtc() [Fact] public void Write_UsesInvariantFormatting() { - CultureInfo original = CultureInfo.CurrentCulture; + var original = CultureInfo.CurrentCulture; try { @@ -113,4 +115,4 @@ public void Write_UsesInvariantFormatting() CultureInfo.CurrentCulture = original; } } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs index 6789eaa..033b371 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; namespace SharpCrafters.Backstage.LicenseServer.Tests; @@ -16,11 +18,11 @@ private static List Timeline( LicenseServerTestContext conte [Fact] public async Task GetLeaseCountingPoints_OneLease_OpensThenCloses() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); - List points = Timeline( context, license ); + var points = Timeline( context, license ); Assert.Equal( 2, points.Count ); Assert.Equal( LeaseCountingPointKind.Open, points[0].Kind ); @@ -32,16 +34,20 @@ public async Task GetLeaseCountingPoints_OneLease_OpensThenCloses() [Fact] public async Task GetLeaseCountingPoints_ThreeUsersOneMachineEach_AreThreeSeats() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); - foreach ( string user in new[] { "alice", "bob", "carol" } ) + foreach ( var user in new[] { "alice", "bob", "carol" } ) { - LeaseBuilder.For( license ).User( user ).Machine( $"desktop-{user}" ) - .From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + LeaseBuilder.For( license ) + .User( user ) + .Machine( $"desktop-{user}" ) + .From( TestClock.Origin ) + .Lasting( 3 ) + .AddTo( context ); } - List points = Timeline( context, license ); + var points = Timeline( context, license ); Assert.Equal( 3, points.Max( p => p.SeatCount ) ); Assert.Equal( 0, points[^1].SeatCount ); @@ -54,16 +60,24 @@ public async Task GetLeaseCountingPoints_ThreeUsersOneMachineEach_AreThreeSeats( [Fact] public async Task GetLeaseCountingPoints_UserKeepingOneMachine_StaysCounted() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - - LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) - .From( TestClock.Origin ).Lasting( 1 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + + LeaseBuilder.For( license ) + .User( "alice" ) + .Machine( "desktop-1" ) + .From( TestClock.Origin ) + .Lasting( 1 ) + .AddTo( context ); - LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ) - .From( TestClock.Origin ).Lasting( 5 ).AddTo( context ); + LeaseBuilder.For( license ) + .User( "alice" ) + .Machine( "laptop-1" ) + .From( TestClock.Origin ) + .Lasting( 5 ) + .AddTo( context ); - List points = Timeline( context, license ); + var points = Timeline( context, license ); // The first lease closes on day one and the second on day five. The seat is held throughout // and released only at the last point. @@ -74,15 +88,24 @@ public async Task GetLeaseCountingPoints_UserKeepingOneMachine_StaysCounted() [Fact] public async Task GetLeaseCountingPoints_OneUserTwoMachines_NeverExceedsOneSeat() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).From( TestClock.Origin ).Lasting( 3 ) + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + + LeaseBuilder.For( license ) + .User( "alice" ) + .Machine( "desktop-1" ) + .From( TestClock.Origin ) + .Lasting( 3 ) .AddTo( context ); - LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).From( TestClock.Days( 1 ) ).Lasting( 3 ) + LeaseBuilder.For( license ) + .User( "alice" ) + .Machine( "laptop-1" ) + .From( TestClock.Days( 1 ) ) + .Lasting( 3 ) .AddTo( context ); - List points = Timeline( context, license ); + var points = Timeline( context, license ); Assert.Equal( 1, points.Max( p => p.SeatCount ) ); } @@ -90,16 +113,20 @@ public async Task GetLeaseCountingPoints_OneUserTwoMachines_NeverExceedsOneSeat( [Fact] public async Task GetLeaseCountingPoints_OneUserThreeMachines_ReachesTwoSeats() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); - foreach ( string machine in new[] { "desktop-1", "laptop-1", "desktop-2" } ) + foreach ( var machine in new[] { "desktop-1", "laptop-1", "desktop-2" } ) { - LeaseBuilder.For( license ).User( "alice" ).Machine( machine ).From( TestClock.Origin ).Lasting( 3 ) + LeaseBuilder.For( license ) + .User( "alice" ) + .Machine( machine ) + .From( TestClock.Origin ) + .Lasting( 3 ) .AddTo( context ); } - List points = Timeline( context, license ); + var points = Timeline( context, license ); // One user on three machines is two seats: one seat covers two machines, and the third takes // a second seat. @@ -114,23 +141,31 @@ public async Task GetLeaseCountingPoints_OneUserThreeMachines_ReachesTwoSeats() [Fact] public async Task GetLeaseCountingPoints_CloseIsProcessedBeforeOpenAtTheSameInstant() { - await using LicenseServerTestContext context = + await using var context = await LicenseServerTestContext.CreateAsync( o => o.MachinesPerUser = 1 ); - License license = LicenseBuilder.Default().AddTo( context ); + var license = LicenseBuilder.Default().AddTo( context ); - LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) - .From( TestClock.Origin ).To( TestClock.Days( 1 ) ).AddTo( context ); + LeaseBuilder.For( license ) + .User( "alice" ) + .Machine( "desktop-1" ) + .From( TestClock.Origin ) + .To( TestClock.Days( 1 ) ) + .AddTo( context ); - LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) - .From( TestClock.Days( 1 ) ).To( TestClock.Days( 2 ) ).AddTo( context ); + LeaseBuilder.For( license ) + .User( "alice" ) + .Machine( "desktop-1" ) + .From( TestClock.Days( 1 ) ) + .To( TestClock.Days( 2 ) ) + .AddTo( context ); - List points = Timeline( context, license ); + var points = Timeline( context, license ); // With one machine per seat, a transient double-count would show up as 2. Assert.Equal( 1, points.Max( p => p.SeatCount ) ); - LeaseCountingPoint[] atHandover = points.Where( p => p.Time == TestClock.Days( 1 ) ).ToArray(); + var atHandover = points.Where( p => p.Time == TestClock.Days( 1 ) ).ToArray(); Assert.Equal( 2, atHandover.Length ); Assert.Equal( LeaseCountingPointKind.Close, atHandover[0].Kind ); Assert.Equal( LeaseCountingPointKind.Open, atHandover[1].Kind ); @@ -146,15 +181,15 @@ public void LeaseCountingPointKind_OrdersCloseBeforeOpen() [Fact] public async Task GetLeaseCountingPoints_IsOrderedByTime() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); - for ( int i = 3; i >= 0; i-- ) + for ( var i = 3; i >= 0; i-- ) { LeaseBuilder.For( license ).User( $"user{i}" ).From( TestClock.Days( i ) ).Lasting( 1 ).AddTo( context ); } - List points = Timeline( context, license ); + var points = Timeline( context, license ); Assert.Equal( points.Select( p => p.Time ).Order(), points.Select( p => p.Time ) ); } @@ -162,18 +197,19 @@ public async Task GetLeaseCountingPoints_IsOrderedByTime() [Fact] public async Task GetLeaseCountingPoints_IsDeterministic() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); // Several leases starting and ending at the same instants, so ties are everywhere. - for ( int i = 0; i < 5; i++ ) + for ( var i = 0; i < 5; i++ ) { LeaseBuilder.For( license ).User( $"user{i}" ).From( TestClock.Origin ).Lasting( 1 ).AddTo( context ); } - string First() => string.Join( - "|", - Timeline( context, license ).Select( p => $"{p.Time:O}/{p.Kind}/{p.Lease.LeaseId}/{p.SeatCount}" ) ); + string First() + => string.Join( + "|", + Timeline( context, license ).Select( p => $"{p.Time:O}/{p.Kind}/{p.Lease.LeaseId}/{p.SeatCount}" ) ); Assert.Equal( First(), First() ); } @@ -181,9 +217,9 @@ string First() => string.Join( [Fact] public async Task GetLeaseCountingPoints_ExcludesReplacedLeases() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease original = LeaseBuilder.For( license ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var original = LeaseBuilder.For( license ).AddTo( context ); context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); await context.Repository.SaveChangesAsync(); @@ -194,14 +230,14 @@ public async Task GetLeaseCountingPoints_ExcludesReplacedLeases() [Fact] public async Task GetLeaseCountingPoints_ExcludesLeasesOutsideTheWindow() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).User( "past" ).From( TestClock.Days( -30 ) ).Lasting( 1 ).AddTo( context ); LeaseBuilder.For( license ).User( "inside" ).From( TestClock.Origin ).Lasting( 1 ).AddTo( context ); LeaseBuilder.For( license ).User( "future" ).From( TestClock.Days( 30 ) ).Lasting( 1 ).AddTo( context ); - List points = context.Repository + var points = context.Repository .GetLeaseCountingPoints( license.LicenseId, TestClock.Days( -1 ), TestClock.Days( 1 ) ) .ToList(); @@ -224,16 +260,24 @@ public async Task GetLeaseCountingPoints_ExcludesLeasesOutsideTheWindow() [Fact] public async Task GetLeaseCountingPoints_TwoOpenLeasesOnOneMachine_CountAsOneSeat() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - - LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) - .From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + + LeaseBuilder.For( license ) + .User( "alice" ) + .Machine( "desktop-1" ) + .From( TestClock.Origin ) + .Lasting( 3 ) + .AddTo( context ); - LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) - .From( TestClock.Days( 1 ) ).Lasting( 3 ).AddTo( context ); + LeaseBuilder.For( license ) + .User( "alice" ) + .Machine( "desktop-1" ) + .From( TestClock.Days( 1 ) ) + .Lasting( 3 ) + .AddTo( context ); - List points = Timeline( context, license ); + var points = Timeline( context, license ); Assert.Equal( 4, points.Count ); Assert.Equal( 1, points.Max( p => p.SeatCount ) ); @@ -243,19 +287,23 @@ public async Task GetLeaseCountingPoints_TwoOpenLeasesOnOneMachine_CountAsOneSea [Fact] public async Task GetLeaseCountingPoints_ReturnsToZeroAfterEveryLeaseEnds() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); - for ( int i = 0; i < 6; i++ ) + for ( var i = 0; i < 6; i++ ) { - LeaseBuilder.For( license ).User( $"user{i}" ).Machine( $"machine-{i}" ) - .From( TestClock.Days( i * 0.5 ) ).Lasting( 2 ).AddTo( context ); + LeaseBuilder.For( license ) + .User( $"user{i}" ) + .Machine( $"machine-{i}" ) + .From( TestClock.Days( i * 0.5 ) ) + .Lasting( 2 ) + .AddTo( context ); } - List points = Timeline( context, license ); + var points = Timeline( context, license ); Assert.NotEmpty( points ); Assert.Equal( 0, points[^1].SeatCount ); Assert.All( points, p => Assert.True( p.SeatCount >= 0 ) ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs index 323b11a..bc2da0d 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Net; using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Data; @@ -26,9 +28,13 @@ private static string Url( List arguments = []; if ( user != null ) { arguments.Add( $"user={user}" ); } + if ( machine != null ) { arguments.Add( $"machine={machine}" ); } + if ( product != null ) { arguments.Add( $"product={product}" ); } + if ( version != null ) { arguments.Add( $"version={version}" ); } + if ( buildDate != null ) { arguments.Add( $"buildDate={buildDate}" ); } return "/Lease.ashx?" + string.Join( "&", arguments ); @@ -38,10 +44,10 @@ private static string Url( public async Task Lease_Succeeds_ReturnsTheSerializedLease() { this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( Url() ); - string body = await response.Content.ReadAsStringAsync(); + var response = await client.GetAsync( Url() ); + var body = await response.Content.ReadAsStringAsync(); Assert.Equal( HttpStatusCode.OK, response.StatusCode ); Assert.Equal( "text/plain", response.Content.Headers.ContentType?.MediaType ); @@ -55,12 +61,12 @@ public async Task Lease_Succeeds_ReturnsTheSerializedLease() public async Task Lease_Succeeds_PersistsExactlyOneLease() { this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); await client.GetAsync( Url() ); - await using LicenseServerDbContext db = this.application.CreateDbContext(); - Lease lease = await db.Leases.SingleAsync(); + await using var db = this.application.CreateDbContext(); + var lease = await db.Leases.SingleAsync(); Assert.Equal( "alice", lease.UserName ); Assert.Equal( "desktop-1", lease.Machine ); @@ -71,12 +77,12 @@ public async Task Lease_Succeeds_PersistsExactlyOneLease() public async Task Lease_MixedCaseUserAndMachine_ArePersistedInLowerCase() { this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); await client.GetAsync( Url( user: "Alice", machine: "DESKTOP-1" ) ); - await using LicenseServerDbContext db = this.application.CreateDbContext(); - Lease lease = await db.Leases.SingleAsync(); + await using var db = this.application.CreateDbContext(); + var lease = await db.Leases.SingleAsync(); Assert.Equal( "alice", lease.UserName ); Assert.Equal( "desktop-1", lease.Machine ); @@ -90,15 +96,15 @@ public async Task Lease_MixedCaseUserAndMachine_ArePersistedInLowerCase() public async Task Lease_AnonymousRequest_IsServedAndRecorded() { this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); client.DefaultRequestHeaders.Add( TestAuthenticationHandler.AnonymousHeader, "1" ); - HttpResponseMessage response = await client.GetAsync( Url() ); + var response = await client.GetAsync( Url() ); Assert.Equal( HttpStatusCode.OK, response.StatusCode ); - await using LicenseServerDbContext db = this.application.CreateDbContext(); - Lease lease = await db.Leases.SingleAsync(); + await using var db = this.application.CreateDbContext(); + var lease = await db.Leases.SingleAsync(); Assert.Equal( string.Empty, lease.AuthenticatedUser ); } @@ -107,9 +113,9 @@ public async Task Lease_AnonymousRequest_IsServedAndRecorded() [InlineData( "alice", null, "Missing query string argument: machine." )] public async Task Lease_MissingArgument_Returns400( string? user, string? machine, string expected ) { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( Url( user, machine ) ); + var response = await client.GetAsync( Url( user, machine ) ); Assert.Equal( HttpStatusCode.BadRequest, response.StatusCode ); Assert.Equal( expected, await response.Content.ReadAsStringAsync() ); @@ -118,9 +124,9 @@ public async Task Lease_MissingArgument_Returns400( string? user, string? machin [Fact] public async Task Lease_UnparseableVersion_Returns400() { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( Url( version: "not-a-version" ) ); + var response = await client.GetAsync( Url( version: "not-a-version" ) ); Assert.Equal( HttpStatusCode.BadRequest, response.StatusCode ); Assert.Equal( "Cannot parse the argument: version.", await response.Content.ReadAsStringAsync() ); @@ -129,9 +135,9 @@ public async Task Lease_UnparseableVersion_Returns400() [Fact] public async Task Lease_UnparseableBuildDate_Returns400() { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( Url( buildDate: "yesterday" ) ); + var response = await client.GetAsync( Url( buildDate: "yesterday" ) ); Assert.Equal( HttpStatusCode.BadRequest, response.StatusCode ); Assert.Equal( "Cannot parse the argument: buildDate.", await response.Content.ReadAsStringAsync() ); @@ -143,13 +149,12 @@ public async Task Lease_UnparseableBuildDate_Returns400() [Fact] public async Task Lease_NoVersion_IsTreatedAsPostSharp499() { - this.application.AddLicense( - LicenseBuilder.Default().WithUsers( 5 ).WithMinPostSharpVersion( new Version( 5, 0, 0 ) ) ); + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ).WithMinPostSharpVersion( new Version( 5, 0, 0 ) ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( Url( version: null ) ); - string body = await response.Content.ReadAsStringAsync(); + var response = await client.GetAsync( Url( version: null ) ); + var body = await response.Content.ReadAsStringAsync(); Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); Assert.Contains( "the requested version is 4.9.9", body, StringComparison.Ordinal ); @@ -159,10 +164,10 @@ public async Task Lease_NoVersion_IsTreatedAsPostSharp499() public async Task Lease_NoCapacity_Returns403WithTheReason() { this.application.AddLicense( LicenseBuilder.Default().NotLicenseServerEligible() ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( Url() ); - string body = await response.Content.ReadAsStringAsync(); + var response = await client.GetAsync( Url() ); + var body = await response.Content.ReadAsStringAsync(); Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); Assert.StartsWith( "No license with free capacity. ", body, StringComparison.Ordinal ); @@ -172,9 +177,9 @@ public async Task Lease_NoCapacity_Returns403WithTheReason() [Fact] public async Task Lease_NoLicenseAtAll_Returns403() { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( Url() ); + var response = await client.GetAsync( Url() ); Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); } @@ -185,9 +190,9 @@ public async Task Lease_LockTimesOut_Returns503() this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); this.application.LeaseLock = new NeverAcquiringLeaseLock(); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( Url() ); + var response = await client.GetAsync( Url() ); Assert.Equal( HttpStatusCode.ServiceUnavailable, response.StatusCode ); Assert.Equal( "Service overloaded.", await response.Content.ReadAsStringAsync() ); @@ -201,13 +206,13 @@ public async Task Lease_LockTimesOut_Returns503() public async Task Lease_BuildAgent_IsServedWithoutConsumingASeat() { this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( Url( machine: "buildagent-1f2e" ) ); + var response = await client.GetAsync( Url( machine: "buildagent-1f2e" ) ); Assert.Equal( HttpStatusCode.OK, response.StatusCode ); - await using LicenseServerDbContext db = this.application.CreateDbContext(); + await using var db = this.application.CreateDbContext(); Assert.Equal( 0, await db.Leases.CountAsync() ); } @@ -215,12 +220,12 @@ public async Task Lease_BuildAgent_IsServedWithoutConsumingASeat() public async Task Lease_RequestedTwiceForTheSameMachine_ReusesTheLease() { this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); await client.GetAsync( Url() ); await client.GetAsync( Url() ); - await using LicenseServerDbContext db = this.application.CreateDbContext(); + await using var db = this.application.CreateDbContext(); Assert.Equal( 1, await db.Leases.CountAsync() ); } @@ -231,14 +236,13 @@ public async Task Lease_RequestedTwiceForTheSameMachine_ReusesTheLease() public async Task Lease_ConcurrentRequestsForTheSameMachine_GrantOneLease() { this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage[] responses = await Task.WhenAll( - Enumerable.Range( 0, 8 ).Select( _ => client.GetAsync( Url() ) ) ); + var responses = await Task.WhenAll( Enumerable.Range( 0, 8 ).Select( _ => client.GetAsync( Url() ) ) ); Assert.All( responses, r => Assert.Equal( HttpStatusCode.OK, r.StatusCode ) ); - await using LicenseServerDbContext db = this.application.CreateDbContext(); + await using var db = this.application.CreateDbContext(); Assert.Equal( 1, await db.Leases.CountAsync() ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseLockTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseLockTests.cs index a24c7ed..4af5bc1 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseLockTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseLockTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Net; using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Data; @@ -22,8 +24,7 @@ public sealed class LeaseLockTests : IDisposable public void Dispose() => this.application.Dispose(); - private static string Url( string user, string machine ) - => $"/Lease.ashx?user={user}&machine={machine}&product=Ultimate&version=2027.0.0"; + private static string Url( string user, string machine ) => $"/Lease.ashx?user={user}&machine={machine}&product=Ultimate&version=2027.0.0"; /// /// The second request waits for the first one, sees the seat it took, and is denied. Without the @@ -33,31 +34,30 @@ private static string Url( string user, string machine ) [Fact] public async Task SecondRequest_WaitsForTheFirst_AndSeesItsLease() { - License license = this.application.AddLicense( LicenseBuilder.Default().WithUsers( 1 ).WithGracePercent( 0 ) ); + var license = this.application.AddLicense( LicenseBuilder.Default().WithUsers( 1 ).WithGracePercent( 0 ) ); this.application.Synchronization.EnableSyncPoint( LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - Task first = client.GetAsync( Url( "alice", "desktop-1" ) ); + var first = client.GetAsync( Url( "alice", "desktop-1" ) ); // The first request now holds the lock. - await this.application.Synchronization.WaitForSyncPointReachedAsync( - LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); + await this.application.Synchronization.WaitForSyncPointReachedAsync( LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); - Task second = client.GetAsync( Url( "bob", "desktop-2" ) ); + var second = client.GetAsync( Url( "bob", "desktop-2" ) ); // Releases the first request, and lets the second one through the synchronization point when // it finally acquires the lock. this.application.Synchronization.DisableSyncPoint( LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); - HttpResponseMessage firstResponse = await first; - HttpResponseMessage secondResponse = await second; + var firstResponse = await first; + var secondResponse = await second; Assert.Equal( HttpStatusCode.OK, firstResponse.StatusCode ); Assert.Equal( HttpStatusCode.Forbidden, secondResponse.StatusCode ); - await using LicenseServerDbContext db = this.application.CreateDbContext(); + await using var db = this.application.CreateDbContext(); Assert.Equal( 1, await db.Leases.CountAsync( l => l.LicenseId == license.LicenseId ) ); } @@ -74,21 +74,20 @@ public async Task SecondRequest_WhenTheFirstHoldsTheLockTooLong_IsAnsweredWithSe this.application.Synchronization.EnableSyncPoint( LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - Task first = client.GetAsync( Url( "alice", "desktop-1" ) ); + var first = client.GetAsync( Url( "alice", "desktop-1" ) ); - await this.application.Synchronization.WaitForSyncPointReachedAsync( - LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); + await this.application.Synchronization.WaitForSyncPointReachedAsync( LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); // The first request holds the lock for longer than the second one waits. - HttpResponseMessage secondResponse = await client.GetAsync( Url( "bob", "desktop-2" ) ); + var secondResponse = await client.GetAsync( Url( "bob", "desktop-2" ) ); Assert.Equal( HttpStatusCode.ServiceUnavailable, secondResponse.StatusCode ); Assert.Equal( "Service overloaded.", await secondResponse.Content.ReadAsStringAsync() ); this.application.Synchronization.DisableSyncPoint( LicenseServerEndpoints.HoldingLeaseLockSyncPoint ); - Assert.Equal( HttpStatusCode.OK, (await first).StatusCode ); + Assert.Equal( HttpStatusCode.OK, ( await first ).StatusCode ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs index 270f52a..973a28e 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Licensing; namespace SharpCrafters.Backstage.LicenseServer.Tests; @@ -45,9 +47,9 @@ public void Serialize_TreatsAnUntaggedTimeAsUtc() [Fact] public void Serialize_ProducesFourPartsTheClientCanSplit() { - string[] parts = LeaseSerializer.Serialize( "1-ABCDEF", start, start, start ).Split( ';' ); + var parts = LeaseSerializer.Serialize( "1-ABCDEF", start, start, start ).Split( ';' ); Assert.Equal( 4, parts.Length ); Assert.Equal( ["License", "StartTime", "EndTime", "RenewTime"], parts.Select( p => p[..p.IndexOf( ':', StringComparison.Ordinal )].Trim() ) ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseAvailabilityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseAvailabilityTests.cs index dce53a9..cb4fe59 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseAvailabilityTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseAvailabilityTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Data; using SharpCrafters.Backstage.LicenseServer.Services; using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -27,7 +29,7 @@ private static async Task GetAvailabilityAsync( LicenseServ /// private static void Occupy( LicenseServerTestContext context, License license, int seats ) { - for ( int i = 0; i < seats; i++ ) + for ( var i = 0; i < seats; i++ ) { LeaseBuilder.For( license ).User( $"user{i}" ).Machine( $"machine{i}" ).AddTo( context ); } @@ -36,9 +38,9 @@ private static void Occupy( LicenseServerTestContext context, License license, i [Fact] public async Task Availability_NoLicense_CannotServe() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - LicenseAvailability availability = await GetAvailabilityAsync( context ); + var availability = await GetAvailabilityAsync( context ); Assert.False( availability.CanServeLease ); Assert.Equal( 0, availability.Total ); @@ -48,11 +50,11 @@ public async Task Availability_NoLicense_CannotServe() [Fact] public async Task Availability_FreeCapacity_CanServe() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 5 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithUsers( 5 ).AddTo( context ); Occupy( context, license, 3 ); - LicenseAvailability availability = await GetAvailabilityAsync( context ); + var availability = await GetAvailabilityAsync( context ); Assert.True( availability.CanServeLease ); Assert.Equal( 1, availability.Available ); @@ -61,19 +63,19 @@ public async Task Availability_FreeCapacity_CanServe() [Fact] public async Task Availability_NoSeatLimit_CanServe() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); LicenseBuilder.Default().WithUsers( null ).AddTo( context ); - Assert.True( (await GetAvailabilityAsync( context )).CanServeLease ); + Assert.True( ( await GetAvailabilityAsync( context ) ).CanServeLease ); } [Fact] public async Task Availability_Disabled_CannotServe() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); LicenseBuilder.Default().WithUsers( 5 ).WithPriority( -1 ).AddTo( context ); - LicenseAvailability availability = await GetAvailabilityAsync( context ); + var availability = await GetAvailabilityAsync( context ); Assert.False( availability.CanServeLease ); Assert.Equal( 1, availability.Disabled ); @@ -83,10 +85,10 @@ public async Task Availability_Disabled_CannotServe() [Fact] public async Task Availability_Expired_CannotServe() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); LicenseBuilder.Default().WithUsers( 5 ).WithValidTo( TestClock.Days( 0.5 ) ).AddTo( context ); - LicenseAvailability availability = await GetAvailabilityAsync( context ); + var availability = await GetAvailabilityAsync( context ); Assert.False( availability.CanServeLease ); Assert.Equal( 1, availability.Expired ); @@ -99,14 +101,13 @@ public async Task Availability_Expired_CannotServe() [Fact] public async Task Availability_UnparsableKey_CannotServe() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - context.Db.Licenses.Add( - new License { LicenseId = 1, LicenseKey = "NOT-A-KEY", ProductCode = "Ultimate", CreatedOn = TestClock.Origin } ); + context.Db.Licenses.Add( new License { LicenseId = 1, LicenseKey = "NOT-A-KEY", ProductCode = "Ultimate", CreatedOn = TestClock.Origin } ); await context.Db.SaveChangesAsync(); - LicenseAvailability availability = await GetAvailabilityAsync( context ); + var availability = await GetAvailabilityAsync( context ); Assert.False( availability.CanServeLease ); Assert.Equal( 1, availability.Invalid ); @@ -119,23 +120,23 @@ public async Task Availability_UnparsableKey_CannotServe() [Fact] public async Task Availability_FullWithGraceLeft_CanServe() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); // Five seats plus 20 per cent is a grace limit of six. - License license = LicenseBuilder.Default().WithUsers( 5 ).WithGracePercent( 20 ).AddTo( context ); + var license = LicenseBuilder.Default().WithUsers( 5 ).WithGracePercent( 20 ).AddTo( context ); Occupy( context, license, 5 ); - Assert.True( (await GetAvailabilityAsync( context )).CanServeLease ); + Assert.True( ( await GetAvailabilityAsync( context ) ).CanServeLease ); } [Fact] public async Task Availability_GraceSeatsUsedUp_CannotServe() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 5 ).WithGracePercent( 20 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithUsers( 5 ).WithGracePercent( 20 ).AddTo( context ); Occupy( context, license, 6 ); - LicenseAvailability availability = await GetAvailabilityAsync( context ); + var availability = await GetAvailabilityAsync( context ); Assert.False( availability.CanServeLease ); Assert.Equal( 1, availability.Exhausted ); @@ -145,9 +146,9 @@ public async Task Availability_GraceSeatsUsedUp_CannotServe() [Fact] public async Task Availability_GracePeriodOver_CannotServe() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default() + var license = LicenseBuilder.Default() .WithUsers( 5 ) .WithGraceDays( 10 ) .WithGraceStartTime( TestClock.Days( -20 ) ) @@ -155,7 +156,7 @@ public async Task Availability_GracePeriodOver_CannotServe() Occupy( context, license, 5 ); - LicenseAvailability availability = await GetAvailabilityAsync( context ); + var availability = await GetAvailabilityAsync( context ); Assert.False( availability.CanServeLease ); Assert.Equal( 1, availability.Exhausted ); @@ -169,8 +170,8 @@ public async Task Availability_GracePeriodOver_CannotServe() [Fact] public async Task Availability_FullLicense_DoesNotStartTheGracePeriod() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 5 ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithUsers( 5 ).AddTo( context ); Occupy( context, license, 5 ); await GetAvailabilityAsync( context ); @@ -189,22 +190,22 @@ public async Task Availability_FullLicense_DoesNotStartTheGracePeriod() [InlineData( 5, true, false )] public async Task Availability_AgreesWithTheAllocator( int seatsInUse, bool graceOver, bool expected ) { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - LicenseBuilder builder = LicenseBuilder.Default().WithUsers( 5 ).WithGracePercent( 20 ).WithGraceDays( 10 ); + var builder = LicenseBuilder.Default().WithUsers( 5 ).WithGracePercent( 20 ).WithGraceDays( 10 ); if ( graceOver ) { builder = builder.WithGraceStartTime( TestClock.Days( -20 ) ); } - License license = builder.AddTo( context ); + var license = builder.AddTo( context ); Occupy( context, license, seatsInUse ); - Assert.Equal( expected, (await GetAvailabilityAsync( context )).CanServeLease ); + Assert.Equal( expected, ( await GetAvailabilityAsync( context ) ).CanServeLease ); // Asked afterwards, because allocating changes the state the check reads. - GrantedLease? granted = await context.LeaseService.GetLicenseLeaseAsync( + var granted = await context.LeaseService.GetLicenseLeaseAsync( null, new Version( 2027, 0 ), null, @@ -216,4 +217,4 @@ public async Task Availability_AgreesWithTheAllocator( int seatsInUse, bool grac Assert.Equal( expected, granted != null ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs index ffdd009..47612dd 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; namespace SharpCrafters.Backstage.LicenseServer.Tests; @@ -16,7 +18,7 @@ public sealed class LicenseValidationTests { Dictionary errors = []; - Lease? lease = await context.LeaseService.GetLeaseAsync( + var lease = await context.LeaseService.GetLeaseAsync( version ?? new Version( 2025, 1, 0 ), buildDate, "desktop-1", @@ -26,22 +28,16 @@ public sealed class LicenseValidationTests errors, [license] ); - return (lease, errors); + return ( lease, errors ); } [Fact] public async Task UnparseableKey_IsReportedAsInvalid() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); // Added straight to the database, so the fake parser has no entry for its key. - License license = new() - { - LicenseId = 99, - LicenseKey = "NOT-A-KEY", - ProductCode = "Ultimate", - CreatedOn = TestClock.Origin - }; + License license = new() { LicenseId = 99, LicenseKey = "NOT-A-KEY", ProductCode = "Ultimate", CreatedOn = TestClock.Origin }; context.Db.Licenses.Add( license ); await context.Db.SaveChangesAsync(); @@ -55,28 +51,30 @@ public async Task UnparseableKey_IsReportedAsInvalid() [Fact] public async Task LicenseNeedsANewerLicenseServer_SaysSo() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default() + var license = LicenseBuilder.Default() .WithMinPostSharpVersion( new Version( 2099, 3, 7 ) ) .AddTo( context ); var (lease, errors) = await RequestAsync( context, license ); Assert.Null( lease ); + Assert.Contains( "requires a higher version of the licensing library on the License Server", errors[1], StringComparison.Ordinal ); + Assert.Contains( "2099.3.7", errors[1], StringComparison.Ordinal ); } [Fact] public async Task ClientIsOlderThanTheLicenseRequires_SaysSo() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default() + var license = LicenseBuilder.Default() .WithMinPostSharpVersion( new Version( 2024, 0, 0 ) ) .AddTo( context ); @@ -94,9 +92,9 @@ public async Task ClientIsOlderThanTheLicenseRequires_SaysSo() [Fact] public async Task MetalamaClientIsOlderThanTheLicenseRequires_SaysSo() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default() + var license = LicenseBuilder.Default() .AsMetalamaProduct() .WithMinMetalamaVersion( new Version( 2026, 1, 0 ) ) .AddTo( context ); @@ -116,9 +114,9 @@ public async Task MetalamaClientIsOlderThanTheLicenseRequires_SaysSo() [Fact] public async Task MetalamaLicense_IgnoresTheMinimumPostSharpVersion() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default() + var license = LicenseBuilder.Default() .AsMetalamaProduct() .WithMinPostSharpVersion( new Version( 2024, 0, 0 ) ) .WithMinMetalamaVersion( null ) @@ -133,8 +131,8 @@ public async Task MetalamaLicense_IgnoresTheMinimumPostSharpVersion() [Fact] public async Task LicenseNotEligibleForALicenseServer_SaysSo() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().NotLicenseServerEligible().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().NotLicenseServerEligible().AddTo( context ); var (lease, errors) = await RequestAsync( context, license ); @@ -145,9 +143,9 @@ public async Task LicenseNotEligibleForALicenseServer_SaysSo() [Fact] public async Task BuildIsNewerThanTheSubscription_SaysSoWithTheRequestedVersion() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default() + var license = LicenseBuilder.Default() .WithSubscriptionEndDate( TestClock.Days( -30 ) ) .AddTo( context ); @@ -164,9 +162,9 @@ public async Task BuildIsNewerThanTheSubscription_SaysSoWithTheRequestedVersion( [Fact] public async Task BuildIsNewerThanTheSubscriptionOnAnOldClient_OmitsTheVersion() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default() + var license = LicenseBuilder.Default() .WithSubscriptionEndDate( TestClock.Days( -30 ) ) .AddTo( context ); @@ -180,9 +178,9 @@ public async Task BuildIsNewerThanTheSubscriptionOnAnOldClient_OmitsTheVersion() [Fact] public async Task BuildExactlyOnTheSubscriptionEndDate_IsAccepted() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default() + var license = LicenseBuilder.Default() .WithSubscriptionEndDate( TestClock.Origin ) .AddTo( context ); @@ -194,9 +192,9 @@ public async Task BuildExactlyOnTheSubscriptionEndDate_IsAccepted() [Fact] public async Task NoBuildDate_SkipsTheSubscriptionCheck() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default() + var license = LicenseBuilder.Default() .WithSubscriptionEndDate( TestClock.Days( -30 ) ) .AddTo( context ); @@ -208,11 +206,11 @@ public async Task NoBuildDate_SkipsTheSubscriptionCheck() [Fact] public async Task NoSubscriptionEndDate_SkipsTheSubscriptionCheck() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithSubscriptionEndDate( null ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().WithSubscriptionEndDate( null ).AddTo( context ); var (lease, _) = await RequestAsync( context, license, buildDate: TestClock.Days( 1000 ) ); Assert.NotNull( lease ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs index b68315e..4af5f18 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -13,9 +15,9 @@ public sealed class OpenLeasesTests [Fact] public async Task OpenLeases_LeaseNeverReplaced_IsOpen() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease lease = LeaseBuilder.For( license ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var lease = LeaseBuilder.For( license ).AddTo( context ); Assert.Equal( [lease.LeaseId], await context.Db.OpenLeases.Select( l => l.LeaseId ).ToListAsync() ); } @@ -23,11 +25,11 @@ public async Task OpenLeases_LeaseNeverReplaced_IsOpen() [Fact] public async Task OpenLeases_ReplacedLease_IsNotOpen() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease original = LeaseBuilder.For( license ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var original = LeaseBuilder.For( license ).AddTo( context ); - Lease? replacement = context.Repository.ProlongLease( original, "alice", TestClock.Days( 2.5 ) ); + var replacement = context.Repository.ProlongLease( original, "alice", TestClock.Days( 2.5 ) ); await context.Repository.SaveChangesAsync(); Assert.NotNull( replacement ); @@ -37,12 +39,12 @@ public async Task OpenLeases_ReplacedLease_IsNotOpen() [Fact] public async Task OpenLeases_ChainOfReplacements_LeavesOnlyTheLast() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); - Lease current = LeaseBuilder.For( license ).AddTo( context ); + var current = LeaseBuilder.For( license ).AddTo( context ); - for ( int i = 1; i <= 3; i++ ) + for ( var i = 1; i <= 3; i++ ) { current = context.Repository.ProlongLease( current, "alice", TestClock.Days( i * 2.5 ) )!; await context.Repository.SaveChangesAsync(); @@ -59,13 +61,13 @@ public async Task OpenLeases_ChainOfReplacements_LeavesOnlyTheLast() [Fact] public async Task OpenLeases_LeaseReplacedTwice_IsStillListedOnlyOnce() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease original = LeaseBuilder.For( license ).AddTo( context ); + await using var context = await LicenseServerTestContext.CreateAsync(); + var license = LicenseBuilder.Default().AddTo( context ); + var original = LeaseBuilder.For( license ).AddTo( context ); - Lease survivor = LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); + var survivor = LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); - foreach ( int _ in Enumerable.Range( 0, 2 ) ) + foreach ( var _ in Enumerable.Range( 0, 2 ) ) { context.Db.Leases.Add( new Lease @@ -82,7 +84,7 @@ public async Task OpenLeases_LeaseReplacedTwice_IsStillListedOnlyOnce() await context.Db.SaveChangesAsync(); - List open = await context.Db.OpenLeases.Select( l => l.LeaseId ).ToListAsync(); + var open = await context.Db.OpenLeases.Select( l => l.LeaseId ).ToListAsync(); Assert.DoesNotContain( original.LeaseId, open ); Assert.Equal( open.Count, open.Distinct().Count() ); @@ -92,11 +94,11 @@ public async Task OpenLeases_LeaseReplacedTwice_IsStillListedOnlyOnce() [Fact] public async Task OpenLeases_TranslatesToSqlAsAnAntiJoin() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); - string sql = context.Db.OpenLeases.ToQueryString(); + var sql = context.Db.OpenLeases.ToQueryString(); // Proves the filter runs in the database rather than after loading every lease. Assert.Contains( "NOT EXISTS", sql, StringComparison.OrdinalIgnoreCase ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs index 804408b..8b3cfd3 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OperationsEndpointTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Net; using System.Text.Json; using Microsoft.EntityFrameworkCore; @@ -18,9 +20,9 @@ public sealed class OperationsEndpointTests : IDisposable private async Task<(HttpStatusCode Status, JsonElement Body)> GetAsync( string url ) { - HttpResponseMessage response = await this.application.CreateClient().GetAsync( url ); + var response = await this.application.CreateClient().GetAsync( url ); - return (response.StatusCode, JsonDocument.Parse( await response.Content.ReadAsStringAsync() ).RootElement); + return ( response.StatusCode, JsonDocument.Parse( await response.Content.ReadAsStringAsync() ).RootElement ); } private static JsonElement Check( JsonElement body, string name ) @@ -35,12 +37,12 @@ public async Task Health_LicenseWithFreeCapacity_IsHealthy() { this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); - (HttpStatusCode status, JsonElement body) = await this.GetAsync( "/health" ); + var (status, body) = await this.GetAsync( "/health" ); Assert.Equal( HttpStatusCode.OK, status ); Assert.Equal( "Healthy", body.GetProperty( "status" ).GetString() ); - string[] checks = body.GetProperty( "checks" ) + var checks = body.GetProperty( "checks" ) .EnumerateArray() .Select( c => c.GetProperty( "name" ).GetString()! ) .ToArray(); @@ -56,12 +58,12 @@ public async Task Health_LicenseWithFreeCapacity_IsHealthy() [Fact] public async Task Health_NoLicense_WarnsAndStaysServed() { - (HttpStatusCode status, JsonElement body) = await this.GetAsync( "/health" ); + var (status, body) = await this.GetAsync( "/health" ); Assert.Equal( HttpStatusCode.OK, status ); Assert.Equal( "Degraded", body.GetProperty( "status" ).GetString() ); - JsonElement licenses = LicenseCheck( body ); + var licenses = LicenseCheck( body ); Assert.Equal( "Degraded", licenses.GetProperty( "status" ).GetString() ); Assert.Equal( "No license is registered.", licenses.GetProperty( "description" ).GetString() ); @@ -70,10 +72,9 @@ public async Task Health_NoLicense_WarnsAndStaysServed() [Fact] public async Task Health_ExpiredLicense_WarnsAndStaysServed() { - this.application.AddLicense( - LicenseBuilder.Default().WithUsers( 5 ).WithValidTo( TestClock.Days( -1 ) ) ); + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ).WithValidTo( TestClock.Days( -1 ) ) ); - (HttpStatusCode status, JsonElement body) = await this.GetAsync( "/health" ); + var (status, body) = await this.GetAsync( "/health" ); Assert.Equal( HttpStatusCode.OK, status ); Assert.Equal( "Degraded", body.GetProperty( "status" ).GetString() ); @@ -96,7 +97,7 @@ public async Task Health_DatabaseWithoutSchema_FailsAndSaysNothingMore() // The database keeps no schema after this test, so it must not serve another one. this.application.DoNotReuseDatabase(); - using ( LicenseServerDbContext db = this.application.CreateDbContext() ) + using ( var db = this.application.CreateDbContext() ) { // PostgreSQL folds an identifier that is not quoted to lower case, and the tables of this // schema keep their capitals. The quotation marks are accepted by all three engines. @@ -104,15 +105,15 @@ public async Task Health_DatabaseWithoutSchema_FailsAndSaysNothingMore() await db.Database.ExecuteSqlRawAsync( "DROP TABLE \"Licenses\"" ); } - (HttpStatusCode status, JsonElement body) = await this.GetAsync( "/health" ); + var (status, body) = await this.GetAsync( "/health" ); Assert.Equal( HttpStatusCode.ServiceUnavailable, status ); Assert.Equal( "Unhealthy", DatabaseCheck( body ).GetProperty( "status" ).GetString() ); Assert.Equal( "Degraded", LicenseCheck( body ).GetProperty( "status" ).GetString() ); - foreach ( JsonElement check in body.GetProperty( "checks" ).EnumerateArray() ) + foreach ( var check in body.GetProperty( "checks" ).EnumerateArray() ) { - string description = check.GetProperty( "description" ).GetString()!; + var description = check.GetProperty( "description" ).GetString()!; Assert.EndsWith( "The reason is in the log of the server.", description, StringComparison.Ordinal ); Assert.DoesNotContain( "no such table", description, StringComparison.OrdinalIgnoreCase ); @@ -126,7 +127,7 @@ public async Task Health_DatabaseWithoutSchema_FailsAndSaysNothingMore() [Fact] public async Task Liveness_NoLicense_IsHealthy() { - (HttpStatusCode status, JsonElement body) = await this.GetAsync( "/health/live" ); + var (status, body) = await this.GetAsync( "/health/live" ); Assert.Equal( HttpStatusCode.OK, status ); Assert.Equal( "Healthy", body.GetProperty( "status" ).GetString() ); @@ -136,13 +137,13 @@ public async Task Liveness_NoLicense_IsHealthy() [Fact] public async Task Version_ReportsTheProductAndTheLicensingLibrary() { - (HttpStatusCode status, JsonElement body) = await this.GetAsync( "/version" ); + var (status, body) = await this.GetAsync( "/version" ); Assert.Equal( HttpStatusCode.OK, status ); Assert.Equal( "SharpCrafters.Backstage.LicenseServer", body.GetProperty( "product" ).GetString() ); // A version, not the empty string, and without the commit hash the build appends to it. - string version = body.GetProperty( "version" ).GetString()!; + var version = body.GetProperty( "version" ).GetString()!; Assert.NotEmpty( version ); Assert.DoesNotContain( "+", version, StringComparison.Ordinal ); @@ -159,11 +160,11 @@ public async Task Version_ReportsTheProductAndTheLicensingLibrary() [InlineData( "/version" )] public async Task Endpoint_AnonymousRequest_IsServed( string url ) { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); client.DefaultRequestHeaders.Add( TestAuthenticationHandler.AnonymousHeader, "true" ); - HttpResponseMessage response = await client.GetAsync( url ); + var response = await client.GetAsync( url ); Assert.Equal( HttpStatusCode.OK, response.StatusCode ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs index 3292cdf..471566c 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs @@ -1,8 +1,12 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using Microsoft.AspNetCore.Mvc.Testing; using System.Net; using System.Text.Json; using System.Text.RegularExpressions; using SharpCrafters.Backstage.LicenseServer.Data; using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; +using System.Globalization; namespace SharpCrafters.Backstage.LicenseServer.Tests; @@ -38,9 +42,9 @@ public sealed partial class PageTests : IDisposable [InlineData( "/Admin/Export" )] public async Task Page_IsServed( string url ) { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( url ); + var response = await client.GetAsync( url ); Assert.Equal( HttpStatusCode.OK, response.StatusCode ); } @@ -48,9 +52,9 @@ public async Task Page_IsServed( string url ) [Fact] public async Task Index_NoLicenses_InvitesTheAdministratorToAddOne() { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - string body = await client.GetStringAsync( "/" ); + var body = await client.GetStringAsync( "/" ); Assert.Contains( "No license has been registered yet", body, StringComparison.Ordinal ); } @@ -59,9 +63,9 @@ public async Task Index_NoLicenses_InvitesTheAdministratorToAddOne() public async Task Index_ListsTheLicense() { this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 42 ).WithUsers( 7 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - string body = await client.GetStringAsync( "/" ); + var body = await client.GetStringAsync( "/" ); Assert.Contains( "42", body, StringComparison.Ordinal ); Assert.Contains( "Ultimate", body, StringComparison.Ordinal ); @@ -71,9 +75,9 @@ public async Task Index_ListsTheLicense() public async Task Details_IsServed() { this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( "/Admin/Details?id=3" ); + var response = await client.GetAsync( "/Admin/Details?id=3" ); Assert.Equal( HttpStatusCode.OK, response.StatusCode ); } @@ -87,11 +91,11 @@ public async Task Details_IsServed() public async Task Details_ExplainsWhatASeatIs() { this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); // The markup wraps the sentence over several lines, and where it wraps is not what this test // is about. - string body = Whitespace().Replace( await client.GetStringAsync( "/Admin/Details?id=3" ), " " ); + var body = Whitespace().Replace( await client.GetStringAsync( "/Admin/Details?id=3" ), " " ); // LicenseServerApplication configures two machines per seat. Assert.Contains( "A seat is one user working on up to 2 machines.", body, StringComparison.Ordinal ); @@ -101,6 +105,7 @@ public async Task Details_ExplainsWhatASeatIs() body, StringComparison.Ordinal ); } + /// /// The actions that change a license sit in one menu at the top of the page, and each of them /// carries the text of the confirmation it asks for before it runs. @@ -109,9 +114,9 @@ public async Task Details_ExplainsWhatASeatIs() public async Task Details_OffersItsActionsInAMenu() { this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - string body = await client.GetStringAsync( "/Admin/Details?id=3" ); + var body = await client.GetStringAsync( "/Admin/Details?id=3" ); Assert.Contains( ">Manage", body, StringComparison.Ordinal ); Assert.Contains( "handler=Disable", body, StringComparison.Ordinal ); @@ -129,14 +134,15 @@ public async Task Details_OffersItsActionsInAMenu() public async Task Details_DisabledLicense_SaysSoAndOffersToEnableOrDeleteIt() { this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ).WithPriority( -1 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - string body = Whitespace().Replace( await client.GetStringAsync( "/Admin/Details?id=3" ), " " ); + var body = Whitespace().Replace( await client.GetStringAsync( "/Admin/Details?id=3" ), " " ); Assert.Contains( "This license is disabled: it serves no new lease.", body, StringComparison.Ordinal ); Assert.Contains( "handler=Enable", body, StringComparison.Ordinal ); Assert.Contains( "handler=Delete", body, StringComparison.Ordinal ); } + /// /// The action posts against the license the page is about. The license is named in the query /// string, which a form does not inherit from the page that contains it, so an action that does @@ -147,11 +153,11 @@ public async Task Details_Disable_DisablesThatLicense() { this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ) ); - HttpResponseMessage response = await this.SubmitDetailsActionAsync( 3, "handler=Disable" ); + var response = await this.SubmitDetailsActionAsync( 3, "handler=Disable" ); Assert.Equal( HttpStatusCode.Found, response.StatusCode ); - using LicenseServerDbContext db = this.application.CreateDbContext(); + using var db = this.application.CreateDbContext(); Assert.True( db.Licenses.Single( l => l.LicenseId == 3 ).Priority < 0 ); } @@ -162,21 +168,19 @@ public async Task Details_Delete_RemovesThatLicense() this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ).WithPriority( -1 ) ); this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 4 ) ); - HttpResponseMessage response = await this.SubmitDetailsActionAsync( 3, "handler=Delete" ); + var response = await this.SubmitDetailsActionAsync( 3, "handler=Delete" ); Assert.Equal( HttpStatusCode.Found, response.StatusCode ); - using LicenseServerDbContext db = this.application.CreateDbContext(); + using var db = this.application.CreateDbContext(); Assert.Equal( [4], db.Licenses.Select( l => l.LicenseId ).ToArray() ); } - - [Fact] public async Task Details_UnknownLicense_Returns404() { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); Assert.Equal( HttpStatusCode.NotFound, ( await client.GetAsync( "/Admin/Details?id=999" ) ).StatusCode ); } @@ -185,9 +189,9 @@ public async Task Details_UnknownLicense_Returns404() public async Task Graph_IsServedWithItsDataEmbedded() { this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 5 ).WithUsers( 10 ).WithGracePercent( 20 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - string body = await client.GetStringAsync( "/Graph?id=5&days=30" ); + var body = await client.GetStringAsync( "/Graph?id=5&days=30" ); Assert.Contains( "usage-chart-data", body, StringComparison.Ordinal ); @@ -201,13 +205,13 @@ public async Task Graph_IsServedWithItsDataEmbedded() public async Task Graph_EmbeddedDataIsValidJsonCoveringTheWindow() { this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 5 ).WithUsers( 10 ).WithGracePercent( 20 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - string body = await client.GetStringAsync( "/Graph?id=5&days=90" ); - string json = ExtractChartData( body ); + var body = await client.GetStringAsync( "/Graph?id=5&days=90" ); + var json = ExtractChartData( body ); - using JsonDocument document = JsonDocument.Parse( json ); - JsonElement root = document.RootElement; + using var document = JsonDocument.Parse( json ); + var root = document.RootElement; Assert.Equal( 90, root.GetProperty( "labels" ).GetArrayLength() ); Assert.Equal( 90, root.GetProperty( "seats" ).GetArrayLength() ); @@ -220,11 +224,11 @@ public async Task Graph_EmbeddedDataIsValidJsonCoveringTheWindow() public async Task Graph_UnlimitedLicense_OmitsTheCapacityLines() { this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 6 ).WithUsers( null ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - string json = ExtractChartData( await client.GetStringAsync( "/Graph?id=6" ) ); + var json = ExtractChartData( await client.GetStringAsync( "/Graph?id=6" ) ); - using JsonDocument document = JsonDocument.Parse( json ); + using var document = JsonDocument.Parse( json ); Assert.Equal( JsonValueKind.Null, document.RootElement.GetProperty( "maximum" ).ValueKind ); Assert.Equal( JsonValueKind.Null, document.RootElement.GetProperty( "grace" ).ValueKind ); @@ -233,7 +237,7 @@ public async Task Graph_UnlimitedLicense_OmitsTheCapacityLines() [Fact] public async Task Graph_UnknownLicense_Returns404() { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); Assert.Equal( HttpStatusCode.NotFound, ( await client.GetAsync( "/Graph?id=999" ) ).StatusCode ); } @@ -247,7 +251,7 @@ public async Task Graph_UnknownLicense_Returns404() public async Task Graph_UnsupportedWindow_Returns400( string url ) { this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 5 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); Assert.Equal( HttpStatusCode.BadRequest, ( await client.GetAsync( url ) ).StatusCode ); } @@ -255,15 +259,15 @@ public async Task Graph_UnsupportedWindow_Returns400( string url ) [Fact] public async Task GetTime_ReportsTheTimeAndTheAcceleration() { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - string body = await client.GetStringAsync( "/GetTime.ashx" ); - string[] fields = body.Split( ';' ); + var body = await client.GetStringAsync( "/GetTime.ashx" ); + var fields = body.Split( ';' ); // The simulator parses this positionally. Assert.Equal( 2, fields.Length ); Assert.EndsWith( "Z", fields[0], StringComparison.Ordinal ); - Assert.Equal( 1m, decimal.Parse( fields[1], System.Globalization.CultureInfo.InvariantCulture ) ); + Assert.Equal( 1m, decimal.Parse( fields[1], CultureInfo.InvariantCulture ) ); } [Theory] @@ -273,10 +277,9 @@ public async Task GetTime_ReportsTheTimeAndTheAcceleration() [InlineData( "/Admin/AddLicense.aspx", "/Admin/AddLicense" )] public async Task LegacyUrl_RedirectsPermanently( string legacy, string expected ) { - HttpClient client = this.application.CreateClient( - new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions { AllowAutoRedirect = false } ); + var client = this.application.CreateClient( new WebApplicationFactoryClientOptions { AllowAutoRedirect = false } ); - HttpResponseMessage response = await client.GetAsync( legacy ); + var response = await client.GetAsync( legacy ); Assert.Equal( HttpStatusCode.MovedPermanently, response.StatusCode ); Assert.Equal( expected, response.Headers.Location?.OriginalString ); @@ -285,7 +288,7 @@ public async Task LegacyUrl_RedirectsPermanently( string legacy, string expected [Fact] public async Task DemoDataGenerator_IsNotAvailableOutsideDevelopment() { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); Assert.Equal( HttpStatusCode.NotFound, ( await client.GetAsync( "/Admin/GenerateDemoData" ) ).StatusCode ); } @@ -296,33 +299,31 @@ public async Task DemoDataGenerator_IsNotAvailableOutsideDevelopment() /// private async Task SubmitDetailsActionAsync( int licenseId, string handler ) { - HttpClient client = this.application.CreateClient( - new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions { AllowAutoRedirect = false } ); + var client = this.application.CreateClient( new WebApplicationFactoryClientOptions { AllowAutoRedirect = false } ); - string page = await client.GetStringAsync( $"/Admin/Details?id={licenseId}" ); + var page = await client.GetStringAsync( $"/Admin/Details?id={licenseId}" ); - Match form = Form().Matches( page ).SingleOrDefault( m => m.Groups[1].Value.Contains( handler, StringComparison.Ordinal ) ) - ?? throw new InvalidOperationException( $"The page has no form posting to {handler}." ); + var form = Form().Matches( page ).SingleOrDefault( m => m.Groups[1].Value.Contains( handler, StringComparison.Ordinal ) ) + ?? throw new InvalidOperationException( $"The page has no form posting to {handler}." ); - Match token = AntiforgeryToken().Match( form.Groups[2].Value ); + var token = AntiforgeryToken().Match( form.Groups[2].Value ); Assert.True( token.Success, "The form carries no antiforgery token." ); return await client.PostAsync( WebUtility.HtmlDecode( form.Groups[1].Value ), - new FormUrlEncodedContent( - new Dictionary { ["__RequestVerificationToken"] = token.Groups[1].Value } ) ); + new FormUrlEncodedContent( new Dictionary { ["__RequestVerificationToken"] = token.Groups[1].Value } ) ); } private static string ExtractChartData( string html ) { const string opening = "", start, StringComparison.Ordinal ); + var end = html.IndexOf( "", start, StringComparison.Ordinal ); - return System.Net.WebUtility.HtmlDecode( html[start..end] ); + return WebUtility.HtmlDecode( html[start..end] ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs index 7d43f3f..e073de4 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Licensing; using SharpCrafters.Backstage.LicenseServer.Services; using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -34,8 +36,7 @@ public void RenamedProduct_IsMatchedUnderBothSpellings( LicenseProduct product, [InlineData( "MetalamaProfessional" )] [InlineData( "PostSharpEssentials" )] [InlineData( "SomethingNobodyHasHeardOf" )] - public void ProductWithOneSpelling_IsMatchedByItself( string productCode ) - => Assert.Equal( [productCode], ProductCodes.Matching( productCode ) ); + public void ProductWithOneSpelling_IsMatchedByItself( string productCode ) => Assert.Equal( [productCode], ProductCodes.Matching( productCode ) ); /// /// The server serves a license added by an earlier version to a client that asks for the same @@ -45,10 +46,10 @@ public void ProductWithOneSpelling_IsMatchedByItself( string productCode ) [Fact] public async Task LicenseStoredUnderTheLegacyName_IsServedToAClientAskingForTheBackstageName() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); LicenseBuilder.Default().WithProduct( "Ultimate" ).WithUsers( 5 ).AddTo( context ); - GrantedLease? lease = await context.LeaseService.GetLicenseLeaseAsync( + var lease = await context.LeaseService.GetLicenseLeaseAsync( "PostSharpUltimate", new Version( 2025, 1, 0 ), null, @@ -68,10 +69,10 @@ public async Task LicenseStoredUnderTheLegacyName_IsServedToAClientAskingForTheB [Fact] public async Task LicenseOfAnotherProduct_IsStillNotServed() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); LicenseBuilder.Default().WithProduct( "Ultimate" ).WithUsers( 5 ).AddTo( context ); - GrantedLease? lease = await context.LeaseService.GetLicenseLeaseAsync( + var lease = await context.LeaseService.GetLicenseLeaseAsync( "PostSharpFramework", new Version( 2025, 1, 0 ), null, @@ -83,4 +84,4 @@ public async Task LicenseOfAnotherProduct_IsStillNotServed() Assert.Null( lease ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs index f3c53a8..38d4135 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.AspNetCore.Http; using System.Net; using Microsoft.AspNetCore.Mvc.Testing; @@ -24,12 +26,12 @@ public sealed class ReviewRegressionTests : IDisposable public async Task BuildAgent_IneligibleLicense_IsNotServed() { this.application.AddLicense( LicenseBuilder.Default().NotLicenseServerEligible() ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( - "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); + var response = await client.GetAsync( "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); + Assert.Contains( "cannot be used in the license server", await response.Content.ReadAsStringAsync(), @@ -39,13 +41,11 @@ await response.Content.ReadAsStringAsync(), [Fact] public async Task BuildAgent_LicenseRequiringANewerClient_IsNotServed() { - this.application.AddLicense( - LicenseBuilder.Default().WithMinPostSharpVersion( new Version( 2099, 1, 0 ) ) ); + this.application.AddLicense( LicenseBuilder.Default().WithMinPostSharpVersion( new Version( 2099, 1, 0 ) ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( - "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); + var response = await client.GetAsync( "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); } @@ -57,13 +57,11 @@ public async Task BuildAgent_LicenseRequiringANewerClient_IsNotServed() [Fact] public async Task BuildAgent_ExpiredLicense_IsNotServed() { - this.application.AddLicense( - LicenseBuilder.Default().WithValidTo( new DateTime( 2020, 1, 1, 0, 0, 0, DateTimeKind.Utc ) ) ); + this.application.AddLicense( LicenseBuilder.Default().WithValidTo( new DateTime( 2020, 1, 1, 0, 0, 0, DateTimeKind.Utc ) ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( - "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); + var response = await client.GetAsync( "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); } @@ -72,10 +70,9 @@ public async Task BuildAgent_ExpiredLicense_IsNotServed() public async Task BuildAgent_ValidLicense_IsStillServedWithoutALease() { this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( - "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); + var response = await client.GetAsync( "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); Assert.Equal( HttpStatusCode.OK, response.StatusCode ); @@ -96,10 +93,9 @@ public async Task UnlimitedButExpiredLicense_IsDeniedRatherThanFailing() .WithUsers( null ) .WithValidTo( new DateTime( 2020, 1, 1, 0, 0, 0, DateTimeKind.Utc ) ) ); - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); - HttpResponseMessage response = await client.GetAsync( - "/Lease.ashx?user=alice&machine=desktop-1&product=Ultimate&version=2025.1.0" ); + var response = await client.GetAsync( "/Lease.ashx?user=alice&machine=desktop-1&product=Ultimate&version=2025.1.0" ); Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); } @@ -115,7 +111,7 @@ public async Task UnlimitedButExpiredLicense_IsDeniedRatherThanFailing() [InlineData( "fy=2026&fm=1&ty=2026&tm=0" )] public async Task Export_OutOfRangeMonthOrYear_Returns400( string query ) { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); Assert.Equal( HttpStatusCode.BadRequest, @@ -125,7 +121,7 @@ public async Task Export_OutOfRangeMonthOrYear_Returns400( string query ) [Fact] public async Task Export_ValidRange_IsStillServed() { - HttpClient client = this.application.CreateClient(); + var client = this.application.CreateClient(); Assert.Equal( HttpStatusCode.OK, @@ -155,4 +151,4 @@ public void LegacyUrl_KeepsThePathBaseAndTheQueryString( target, new QueryString( queryString.Length == 0 ? null : queryString ) ) ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs index f76bb6d..42282ba 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.EntityFrameworkCore; using SharpCrafters.Backstage.LicenseServer.Data; using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -21,7 +23,7 @@ public sealed class SchemaCompatibilityTests /// private static string CreateScript() { - DbContextOptions options = + var options = new DbContextOptionsBuilder() .UseSqlServer( "Server=none;Database=none;" ) .Options; @@ -47,14 +49,14 @@ public void Licenses_LicenseKeyIsStillText() [Fact] public void Licenses_LicenseKeyIsNeverFilteredOrSorted() { - DbContextOptions options = + var options = new DbContextOptionsBuilder() .UseSqlServer( "Server=none;Database=none;" ) .Options; using LicenseServerDbContext db = new( options ); - string sql = db.Licenses.OrderBy( l => l.Priority ).ThenByDescending( l => l.LicenseId ).ToQueryString(); + var sql = db.Licenses.OrderBy( l => l.Priority ).ThenByDescending( l => l.LicenseId ).ToQueryString(); Assert.Contains( "[LicenseKey]", sql, StringComparison.OrdinalIgnoreCase ); Assert.DoesNotContain( "ORDER BY [l].[LicenseKey]", sql, StringComparison.OrdinalIgnoreCase ); @@ -65,12 +67,12 @@ public void Licenses_LicenseIdIsAssignedByTheApplication() { // The identifier comes from the license key. Making it an identity column would both break // an existing database and lose the link between the row and the key. - string script = CreateScript(); - int licensesTable = script.IndexOf( "CREATE TABLE [Licenses]", StringComparison.OrdinalIgnoreCase ); + var script = CreateScript(); + var licensesTable = script.IndexOf( "CREATE TABLE [Licenses]", StringComparison.OrdinalIgnoreCase ); Assert.True( licensesTable >= 0 ); - string licenses = script[licensesTable..script.IndexOf( ");", licensesTable, StringComparison.Ordinal )]; + var licenses = script[licensesTable..script.IndexOf( ");", licensesTable, StringComparison.Ordinal )]; Assert.Contains( "[LicenseId] int NOT NULL", licenses, StringComparison.OrdinalIgnoreCase ); Assert.DoesNotContain( "IDENTITY", licenses, StringComparison.OrdinalIgnoreCase ); @@ -83,6 +85,7 @@ public void Leases_LeaseIdIsGeneratedByTheDatabase() } [Theory] + // Types as declared by CreateTables.sql. [InlineData( "[ProductCode] varchar(50) NOT NULL" )] [InlineData( "[Priority] int NOT NULL" )] @@ -97,8 +100,7 @@ public void Leases_LeaseIdIsGeneratedByTheDatabase() [InlineData( "[Grace] bit NOT NULL" )] [InlineData( "[OverwrittenLeaseId] int NULL" )] [InlineData( "[LicenseId] int NOT NULL" )] - public void Column_KeepsItsType( string expected ) - => Assert.Contains( expected, CreateScript(), StringComparison.OrdinalIgnoreCase ); + public void Column_KeepsItsType( string expected ) => Assert.Contains( expected, CreateScript(), StringComparison.OrdinalIgnoreCase ); /// /// The timestamps keep the type datetime. The default type of EF is datetime2, and @@ -106,8 +108,7 @@ public void Column_KeepsItsType( string expected ) /// of a lease. /// [Fact] - public void Timestamps_AreNeverDateTime2() - => Assert.DoesNotContain( "datetime2", CreateScript(), StringComparison.OrdinalIgnoreCase ); + public void Timestamps_AreNeverDateTime2() => Assert.DoesNotContain( "datetime2", CreateScript(), StringComparison.OrdinalIgnoreCase ); [Theory] [InlineData( "PK_Licenses" )] @@ -116,13 +117,12 @@ public void Timestamps_AreNeverDateTime2() [InlineData( "FK_Leases_Leases" )] [InlineData( "IX_Leases_EndTime" )] [InlineData( "IX_Leases_OverwrittenLeaseId" )] - public void Constraint_KeepsItsName( string expected ) - => Assert.Contains( expected, CreateScript(), StringComparison.Ordinal ); + public void Constraint_KeepsItsName( string expected ) => Assert.Contains( expected, CreateScript(), StringComparison.Ordinal ); [Fact] public void Tables_KeepTheirNames() { - string script = CreateScript(); + var script = CreateScript(); Assert.Contains( "CREATE TABLE [Licenses]", script, StringComparison.OrdinalIgnoreCase ); Assert.Contains( "CREATE TABLE [Leases]", script, StringComparison.OrdinalIgnoreCase ); @@ -132,8 +132,7 @@ public void Tables_KeepTheirNames() /// The deletion of a license must not cascade through the chain of replaced leases. /// [Fact] - public void ForeignKeys_DoNotCascade() - => Assert.DoesNotContain( "ON DELETE CASCADE", CreateScript(), StringComparison.OrdinalIgnoreCase ); + public void ForeignKeys_DoNotCascade() => Assert.DoesNotContain( "ON DELETE CASCADE", CreateScript(), StringComparison.OrdinalIgnoreCase ); /// /// The database of an existing installation contains the column HMAC, which held the @@ -143,7 +142,7 @@ public void ForeignKeys_DoNotCascade() [Fact] public async Task LegacyHmacColumn_IsIgnored() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + await using var context = await LicenseServerTestContext.CreateAsync(); // On SQL Server the column is already there, because CreateTables.sql declares it. On the // other two engines the schema does not declare it, so the test adds it. This test modifies @@ -160,8 +159,8 @@ await context.Db.Database.ExecuteSqlRawAsync( : "ALTER TABLE Leases ADD COLUMN HMAC varchar(100) NULL" ); } - License license = LicenseBuilder.Default().AddTo( context ); - Lease lease = LeaseBuilder.For( license ).AddTo( context ); + var license = LicenseBuilder.Default().AddTo( context ); + var lease = LeaseBuilder.For( license ).AddTo( context ); Assert.Equal( 1, await context.Db.Leases.CountAsync( l => l.LeaseId == lease.LeaseId ) ); } @@ -169,7 +168,7 @@ await context.Db.Database.ExecuteSqlRawAsync( [Fact] public void Model_HasExactlyTheTwoExpectedTables() { - DbContextOptions options = + var options = new DbContextOptionsBuilder() .UseSqlServer( "Server=none;Database=none;" ) .Options; @@ -185,4 +184,4 @@ public void Model_HasExactlyTheTwoExpectedTables() Assert.Equal( ["Leases", "Licenses"], tables ); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs index 28693a3..30bbee8 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using SharpCrafters.Backstage.LicenseServer.Data; namespace SharpCrafters.Backstage.LicenseServer.Tests; @@ -9,10 +11,10 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; public sealed class SeatCountingTests { [Fact] - public void CountSeats_NoUsers_ReturnsZero() - => Assert.Equal( 0, SeatCounter.CountSeats( [], 2 ) ); + public void CountSeats_NoUsers_ReturnsZero() => Assert.Equal( 0, SeatCounter.CountSeats( [], 2 ) ); [Theory] + // One user, N machines, two machines per seat. [InlineData( 1, 1 )] [InlineData( 2, 1 )] @@ -23,12 +25,10 @@ public void CountSeats_SingleUser_RoundsMachinesUp( int machines, int expectedSe => Assert.Equal( expectedSeats, SeatCounter.CountSeats( [machines], 2 ) ); [Fact] - public void CountSeats_TwoUsersOneMachineEach_ConsumesTwoSeats() - => Assert.Equal( 2, SeatCounter.CountSeats( [1, 1], 2 ) ); + public void CountSeats_TwoUsersOneMachineEach_ConsumesTwoSeats() => Assert.Equal( 2, SeatCounter.CountSeats( [1, 1], 2 ) ); [Fact] - public void CountSeats_TwoUsersTwoMachinesEach_ConsumesTwoSeats() - => Assert.Equal( 2, SeatCounter.CountSeats( [2, 2], 2 ) ); + public void CountSeats_TwoUsersTwoMachinesEach_ConsumesTwoSeats() => Assert.Equal( 2, SeatCounter.CountSeats( [2, 2], 2 ) ); [Fact] public void CountSeats_RoundsUpPerUserNotInTotal() @@ -46,6 +46,5 @@ public void CountSeats_HonoursMachinesPerUser( int machinesPerUser, int machines => Assert.Equal( expectedSeats, SeatCounter.CountSeats( [machines], machinesPerUser ) ); [Fact] - public void CountSeats_MachinesPerUserBelowOne_Throws() - => Assert.Throws( () => SeatCounter.CountSeats( [1], 0 ) ); -} + public void CountSeats_MachinesPerUserBelowOne_Throws() => Assert.Throws( () => SeatCounter.CountSeats( [1], 0 ) ); +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs index 3b080e8..ed4b0c3 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs @@ -1,5 +1,8 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging.Abstractions; using SharpCrafters.Backstage.LicenseServer.Data; @@ -32,8 +35,7 @@ public void Dispose() private string KeyFile => Path.Combine( this.dataDirectory, "test-authority.key" ); - private TestLicenseAuthority CreateAuthority() - => TestLicenseAuthority.LoadOrCreate( this.KeyFile, NullLogger.Instance ); + private TestLicenseAuthority CreateAuthority() => TestLicenseAuthority.LoadOrCreate( this.KeyFile, NullLogger.Instance ); /// /// The key pair has to outlive the process, because the license keys it signed are in the @@ -43,7 +45,7 @@ private TestLicenseAuthority CreateAuthority() [Fact] public void Authority_IsReusedAcrossProcesses() { - string licenseKey = this.CreateAuthority() + var licenseKey = this.CreateAuthority() .CreateLicenseKey( 900001, LicenseProduct.MetalamaProfessional, LicenseType.Business, 25, 5, 20, DateTime.UtcNow.AddYears( 1 ) ); Assert.True( File.Exists( this.KeyFile ) ); @@ -58,12 +60,12 @@ public void Authority_IsReusedAcrossProcesses() [Fact] public void Authority_OfAnotherServer_IsNotAccepted() { - string licenseKey = this.CreateAuthority() + var licenseKey = this.CreateAuthority() .CreateLicenseKey( 900001, LicenseProduct.MetalamaProfessional, LicenseType.Business, 25, 5, 20, DateTime.UtcNow.AddYears( 1 ) ); - string otherDirectory = Path.Combine( this.dataDirectory, "other" ); + var otherDirectory = Path.Combine( this.dataDirectory, "other" ); - TestLicenseAuthority other = + var other = TestLicenseAuthority.LoadOrCreate( Path.Combine( otherDirectory, "test-authority.key" ), NullLogger.Instance ); Assert.Null( new BackstageLicenseParser( other.Authority ).TryParse( licenseKey ) ); @@ -72,10 +74,10 @@ public void Authority_OfAnotherServer_IsNotAccepted() [Fact] public async Task Seed_EmptyDatabase_AddsLicensesThisServerAccepts() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - TestLicenseAuthority authority = this.CreateAuthority(); + await using var context = await LicenseServerTestContext.CreateAsync(); + var authority = this.CreateAuthority(); - IReadOnlyList added = TestLicenseSeeder.Seed( + var added = TestLicenseSeeder.Seed( context.Db, authority, TimeProvider.System, @@ -85,9 +87,9 @@ public async Task Seed_EmptyDatabase_AddsLicensesThisServerAccepts() BackstageLicenseParser parser = new( authority.Authority ); - foreach ( License license in context.Db.Licenses ) + foreach ( var license in context.Db.Licenses ) { - LicenseInfo? parsed = parser.TryParse( license.LicenseKey ); + var parsed = parser.TryParse( license.LicenseKey ); Assert.NotNull( parsed ); Assert.True( parsed.IsLicenseServerEligible ); @@ -102,21 +104,20 @@ public async Task Seed_EmptyDatabase_AddsLicensesThisServerAccepts() [Fact] public async Task Seed_Twice_AddsNothingTheSecondTime() { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - TestLicenseAuthority authority = this.CreateAuthority(); + await using var context = await LicenseServerTestContext.CreateAsync(); + var authority = this.CreateAuthority(); TestLicenseSeeder.Seed( context.Db, authority, TimeProvider.System, NullLogger.Instance ); // The keys are sorted after they are read. On SQL Server the column has the type text, which // Transact-SQL refuses to sort, and the query would fail. This is the rule that // SchemaCompatibilityTests states for the queries of the server. - string[] first = ReadKeys( context ); + var first = ReadKeys( context ); Assert.Empty( TestLicenseSeeder.Seed( context.Db, authority, TimeProvider.System, NullLogger.Instance ) ); Assert.Equal( first, ReadKeys( context ) ); - static string[] ReadKeys( LicenseServerTestContext context ) - => context.Db.Licenses.Select( l => l.LicenseKey ).AsEnumerable().Order().ToArray(); + static string[] ReadKeys( LicenseServerTestContext context ) => context.Db.Licenses.Select( l => l.LicenseKey ).AsEnumerable().Order().ToArray(); } /// @@ -134,8 +135,9 @@ public void SeedTestLicenses_OutsideDevelopment_RefusesToStart( string environme ServiceCollection services = []; - InvalidOperationException exception = Assert.Throws( - () => services.AddLicenseServerLicensing( configuration, new StubEnvironment( environmentName, this.dataDirectory ) ) ); + var exception = Assert.Throws( () => services.AddLicenseServerLicensing( + configuration, + new StubEnvironment( environmentName, this.dataDirectory ) ) ); Assert.Contains( "SeedTestLicenses", exception.Message, StringComparison.Ordinal ); Assert.Contains( environmentName, exception.Message, StringComparison.Ordinal ); @@ -170,7 +172,7 @@ public StubEnvironment( string environmentName, string contentRootPath = "." ) public string ContentRootPath { get; set; } - public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = - new Microsoft.Extensions.FileProviders.NullFileProvider(); + public IFileProvider ContentRootFileProvider { get; set; } = + new NullFileProvider(); } -} +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs index 1148207..e8ec7eb 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs @@ -1,7 +1,10 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + using System.Globalization; using System.Security.Cryptography; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using SharpCrafters.Backstage.LicenseServer.Licensing; using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -24,9 +27,9 @@ public sealed class TestLicensingAuthorityTests [Fact] public void ConfiguredAuthority_InDevelopment_IsAccepted() { - (string publicKey, string licenseKey) = CreateAuthorityAndKey(); + var (publicKey, licenseKey) = CreateAuthorityAndKey(); - ILicenseParser parser = BuildParser( "Development", (keyId, publicKey) ); + var parser = BuildParser( "Development", ( keyId, publicKey ) ); Assert.NotNull( parser.TryParse( licenseKey ) ); } @@ -38,10 +41,10 @@ public void ConfiguredAuthority_InDevelopment_IsAccepted() [Fact] public void UnconfiguredAuthority_InDevelopment_IsRejected() { - (string publicKey, _) = CreateAuthorityAndKey(); - (_, string otherLicenseKey) = CreateAuthorityAndKey(); + var (publicKey, _) = CreateAuthorityAndKey(); + var (_, otherLicenseKey) = CreateAuthorityAndKey(); - ILicenseParser parser = BuildParser( "Development", (keyId, publicKey) ); + var parser = BuildParser( "Development", ( keyId, publicKey ) ); Assert.Null( parser.TryParse( otherLicenseKey ) ); } @@ -57,10 +60,9 @@ public void UnconfiguredAuthority_InDevelopment_IsRejected() [InlineData( "Testing" )] public void ConfiguredAuthority_OutsideDevelopment_RefusesToStart( string environmentName ) { - (string publicKey, _) = CreateAuthorityAndKey(); + var (publicKey, _) = CreateAuthorityAndKey(); - InvalidOperationException exception = Assert.Throws( - () => BuildParser( environmentName, (keyId, publicKey) ) ); + var exception = Assert.Throws( () => BuildParser( environmentName, ( keyId, publicKey ) ) ); Assert.Contains( "TestLicensingAuthorities", exception.Message, StringComparison.Ordinal ); Assert.Contains( environmentName, exception.Message, StringComparison.Ordinal ); @@ -73,9 +75,9 @@ public void ConfiguredAuthority_OutsideDevelopment_RefusesToStart( string enviro [Fact] public void NoConfiguredAuthority_OutsideDevelopment_Starts() { - (_, string licenseKey) = CreateAuthorityAndKey(); + var (_, licenseKey) = CreateAuthorityAndKey(); - ILicenseParser parser = BuildParser( "Production" ); + var parser = BuildParser( "Production" ); Assert.Null( parser.TryParse( licenseKey ) ); } @@ -87,9 +89,9 @@ public void NoConfiguredAuthority_OutsideDevelopment_Starts() [Fact] public void ConfiguredAuthority_TakingAProductionKeyIdentifier_IsRefused() { - (string publicKey, _) = CreateAuthorityAndKey(); + var (publicKey, _) = CreateAuthorityAndKey(); - Assert.Throws( () => BuildParser( "Development", (2, publicKey) ) ); + Assert.Throws( () => BuildParser( "Development", ( 2, publicKey ) ) ); } /// @@ -98,13 +100,13 @@ public void ConfiguredAuthority_TakingAProductionKeyIdentifier_IsRefused() /// [Fact] public void ConfiguredAuthority_WithAMalformedKey_FailsAtStartup() - => Assert.ThrowsAny( () => BuildParser( "Development", (keyId, "not-a-key") ) ); + => Assert.ThrowsAny( () => BuildParser( "Development", ( keyId, "not-a-key" ) ) ); private static ILicenseParser BuildParser( string environmentName, params (int KeyId, string PublicKey)[] authorities ) { Dictionary settings = []; - for ( int i = 0; i < authorities.Length; i++ ) + for ( var i = 0; i < authorities.Length; i++ ) { settings[$"LicenseServer:TestLicensingAuthorities:{i}:KeyId"] = authorities[i].KeyId.ToString( CultureInfo.InvariantCulture ); settings[$"LicenseServer:TestLicensingAuthorities:{i}:PublicKey"] = authorities[i].PublicKey; @@ -123,22 +125,22 @@ private static ILicenseParser BuildParser( string environmentName, params (int K /// private static (string PublicKey, string LicenseKey) CreateAuthorityAndKey() { - using ECDsa key = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); - ECParameters parameters = key.ExportParameters( true ); + using var key = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); + var parameters = key.ExportParameters( true ); string ToXml( bool includePrivateValue ) => "nistP256" + $"{Convert.ToBase64String( parameters.Q.X! )}" + $"{Convert.ToBase64String( parameters.Q.Y! )}" - + (includePrivateValue ? $"{Convert.ToBase64String( parameters.D! )}" : "") + + ( includePrivateValue ? $"{Convert.ToBase64String( parameters.D! )}" : "" ) + ""; - LicensingAuthority authority = - new ExplicitLicensingAuthorityProvider( (keyId, ToXml( true )) ).GetAuthority( keyId ); + var authority = + new ExplicitLicensingAuthorityProvider( ( keyId, ToXml( true ) ) ).GetAuthority( keyId ); - string licenseKey = TestLicenseKeys.Builder().SignAndSerialize( authority ); + var licenseKey = TestLicenseKeys.Builder().SignAndSerialize( authority ); - return (ToXml( false ), licenseKey); + return ( ToXml( false ), licenseKey ); } private sealed class StubEnvironment : IHostEnvironment @@ -154,7 +156,7 @@ public StubEnvironment( string environmentName ) public string ContentRootPath { get; set; } = AppContext.BaseDirectory; - public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = - new Microsoft.Extensions.FileProviders.NullFileProvider(); + public IFileProvider ContentRootFileProvider { get; set; } = + new NullFileProvider(); } -} +} \ No newline at end of file From 0c04278c3db10cef5a73d8e31474408cf1365d92 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Fri, 18 Sep 2026 12:37:15 +0200 Subject: [PATCH 43/44] Put one type in one file, and hold the distribution open while the tests run Ten files declared more than one type. Each type now has a file of its own, named after it: GrantedLease, LicenseAvailability, AnonymousAuthenticationHandler, TestAuthenticationHandler, the two database pools, LicenseBuilder, LeaseBuilder, ITestDatabase, DatabaseTests and DatabaseTestRun. TestData.cs held the clock alone once the two builders left, so it is now TestClock.cs. A nested type stays where it is. eng/TestDatabase.ps1 holds a process of the Windows Subsystem for Linux open while it runs the tests. The subsystem stops a distribution a few seconds after its last process ends, and the engine of Docker and its containers stop with it. A suite that runs for minutes therefore lost its server in the middle: 149 of 280 tests failed with "No connection could be made because the target machine actively refused it", and the log of the container showed a fast shutdown sixteen seconds after it had become ready. With the process held open, the same suite passes. The script warns about this when it is asked to start a server and to run no test, because the server then outlives the script. Verified: 280 tests pass on SQLite, on SQL Server and on PostgreSQL. Co-Authored-By: Claude Opus 5 --- eng/TestDatabase.ps1 | 32 +++ eng/src/DatabaseTestRun.cs | 75 ++++++ eng/src/DatabaseTests.cs | 23 ++ eng/src/Program.cs | 74 ------ .../Services/GrantedLease.cs | 18 ++ .../Services/LeaseService.cs | 7 +- .../Services/LicenseAvailability.cs | 77 ++++++ .../Services/LicenseAvailabilityService.cs | 70 ----- .../AnonymousAuthenticationHandler.cs | 25 ++ .../AuthenticationRegistration.cs | 15 -- .../Infrastructure/ITestDatabase.cs | 35 +++ .../Infrastructure/LeaseBuilder.cs | 88 +++++++ .../{TestData.cs => LicenseBuilder.cs} | 103 -------- .../LicenseServerApplication.cs | 33 --- .../Infrastructure/PostgreSqlDatabasePool.cs | 228 ++++++++++++++++ .../Infrastructure/PostgreSqlTestDatabase.cs | 219 ---------------- .../Infrastructure/SqlServerDatabasePool.cs | 247 ++++++++++++++++++ .../Infrastructure/SqlServerTestDatabase.cs | 238 ----------------- .../TestAuthenticationHandler.cs | 57 ++++ .../Infrastructure/TestClock.cs | 25 ++ .../Infrastructure/TestDatabases.cs | 30 --- 21 files changed, 931 insertions(+), 788 deletions(-) create mode 100644 eng/src/DatabaseTestRun.cs create mode 100644 eng/src/DatabaseTests.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Services/GrantedLease.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailability.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Web/AnonymousAuthenticationHandler.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/ITestDatabase.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LeaseBuilder.cs rename tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/{TestData.cs => LicenseBuilder.cs} (65%) create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlDatabasePool.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerDatabasePool.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestAuthenticationHandler.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestClock.cs diff --git a/eng/TestDatabase.ps1 b/eng/TestDatabase.ps1 index 3706f30..8f51a37 100644 --- a/eng/TestDatabase.ps1 +++ b/eng/TestDatabase.ps1 @@ -137,6 +137,24 @@ function Invoke-Docker { & $command @arguments } +<# +.SYNOPSIS + Holds the distribution of the Windows Subsystem for Linux open, and returns the process that holds + it. +.DESCRIPTION + The subsystem stops a distribution a few seconds after its last process ends. The engine of Docker + and the containers it runs stop with it, so a suite that runs for minutes loses its server in the + middle: the connections are refused and every test that touches the database fails. A process that + sleeps for the length of this script keeps the distribution running. +#> +function Start-DistributionKeepAlive { + if ( $script:dockerCommand[0] -ne 'wsl' ) { + return $null + } + + return Start-Process -FilePath 'wsl' -ArgumentList '-e', 'sleep', '86400' -PassThru -WindowStyle Hidden +} + function Wait-ForServer( [scriptblock] $Query, [int] $Timeout ) { $deadline = (Get-Date).AddSeconds( $Timeout ) @@ -161,6 +179,7 @@ function New-Password { $serverProcess = $null $startedLocalCluster = $false +$keepAlive = $null # Docker is called with a relative path, so the script works from the directory of the repository. Push-Location $repositoryDirectory @@ -283,9 +302,18 @@ try { if ( $StartOnly ) { Write-Host 'The server is running, and no test was run.' + if ( $script:dockerCommand -and $script:dockerCommand[0] -eq 'wsl' ) { + Write-Warning ( + 'Docker runs inside the Windows Subsystem for Linux, which stops a distribution a few seconds ' + + 'after its last process ends, and the container stops with it. Keep a process of the ' + + 'distribution running, for instance "wsl -e sleep 86400", for as long as you need the server.' ) + } + return } + $keepAlive = Start-DistributionKeepAlive + Write-Host "Running the tests against $Engine." & "$repositoryDirectory/Build.ps1" test @BuildArguments @@ -300,6 +328,10 @@ finally { Stop-Process -InputObject $serverProcess -ErrorAction SilentlyContinue } + if ( $keepAlive ) { + Stop-Process -InputObject $keepAlive -ErrorAction SilentlyContinue + } + if ( $startedLocalCluster -and -not $StartOnly ) { Write-Host 'Stopping PostgreSQL.' diff --git a/eng/src/DatabaseTestRun.cs b/eng/src/DatabaseTestRun.cs new file mode 100644 index 0000000..271e27a --- /dev/null +++ b/eng/src/DatabaseTestRun.cs @@ -0,0 +1,75 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using BuildBackstageLicenseServer.Docker; +using PostSharp.Engineering.BuildTools; +using PostSharp.Engineering.BuildTools.Build; +using PostSharp.Engineering.BuildTools.Build.Model; +using PostSharp.Engineering.BuildTools.Build.Solutions; +using PostSharp.Engineering.BuildTools.ContinuousIntegration; +using PostSharp.Engineering.BuildTools.ContinuousIntegration.Model; +using PostSharp.Engineering.BuildTools.ContinuousIntegration.TeamCity; +using PostSharp.Engineering.BuildTools.Docker; +using BackstageDependencies = PostSharp.Engineering.BuildTools.Dependencies.Definitions.BackstageDependencies.V2027_0; + +/// +/// One such configuration, with the image it runs in. +/// +/// +/// The name of the image. PostSharp.Engineering writes the Dockerfile of its build layer to +/// eng/docker/{name}-build.Dockerfile. +/// +/// The name of the engine, as the parameter of eng/TestDatabase.ps1 spells it. +/// The name of the engine, as a person writes it. +/// The component that installs the server into the image. +internal sealed class DatabaseTestRun +{ + private readonly string name; + private readonly string engine; + private readonly string displayName; + private readonly ContainerComponent server; + + public DatabaseTestRun( string name, string engine, string displayName, ContainerComponent server ) + { + this.name = name; + this.engine = engine; + this.displayName = displayName; + this.server = server; + } + + public AdditionalDockerfile Dockerfile( string dotNetSdkVersion ) + => new( this.name, [] ) + { + Requirements = new ContainerRequirements( ContainerHostKind.Linux ) + { + OperatingSystem = ContainerOperatingSystem.Linux, + Components = [new DotNetComponent( dotNetSdkVersion, DotNetComponentKind.Sdk ), this.server] + } + }; + + public PowershellAdditionalCiBuildConfiguration Configuration + => new( $"{this.engine}Tests", $"Tests on {this.displayName}", "./eng/TestDatabase.ps1", $"-Engine {this.engine}" ) + { + BuildAgentRequirements = LinuxContainerHost, + Dockerfile = $"eng/docker/{this.name}-build.Dockerfile", + BuildSnapshotDependency = BuildConfiguration.Debug + }; + + /// + /// Gets the agent this configuration runs on, which is a Linux host of an amd64 container. + /// + /// + /// The requirements that PostSharp.Engineering derives for a Linux container host ask for an + /// env.BuildAgentType that no agent of this farm publishes, so nothing would be compatible and the + /// build would wait instead of failing. The operating system and the architecture are what the agents offer. + /// The architecture is named because SQL Server runs on amd64 only. + /// + private static ContainerHostRequirements LinuxContainerHost + => new ContainerHostRequirements( ContainerHostKind.Linux ) with + { + Items = + [ + new BuildAgentRequirement( "teamcity.agent.jvm.os.name", "Linux" ), + new BuildAgentRequirement( "teamcity.agent.jvm.os.arch", "amd64" ) + ] + }; +} diff --git a/eng/src/DatabaseTests.cs b/eng/src/DatabaseTests.cs new file mode 100644 index 0000000..175a935 --- /dev/null +++ b/eng/src/DatabaseTests.cs @@ -0,0 +1,23 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using BuildBackstageLicenseServer.Docker; +using PostSharp.Engineering.BuildTools; +using PostSharp.Engineering.BuildTools.Build; +using PostSharp.Engineering.BuildTools.Build.Model; +using PostSharp.Engineering.BuildTools.Build.Solutions; +using PostSharp.Engineering.BuildTools.ContinuousIntegration; +using PostSharp.Engineering.BuildTools.ContinuousIntegration.Model; +using PostSharp.Engineering.BuildTools.ContinuousIntegration.TeamCity; +using PostSharp.Engineering.BuildTools.Docker; +using BackstageDependencies = PostSharp.Engineering.BuildTools.Dependencies.Definitions.BackstageDependencies.V2027_0; + +/// +/// The continuous integration configurations that run the test suite against a database server, each one in the +/// image that carries its server. +/// +internal static class DatabaseTests +{ + public static DatabaseTestRun SqlServer { get; } = new( "sqlserver", "SqlServer", "SQL Server", new SqlServerComponent() ); + + public static DatabaseTestRun PostgreSql { get; } = new( "postgresql", "PostgreSql", "PostgreSQL", new PostgreSqlComponent() ); +} diff --git a/eng/src/Program.cs b/eng/src/Program.cs index f28b9a5..d20fdb8 100644 --- a/eng/src/Program.cs +++ b/eng/src/Program.cs @@ -72,77 +72,3 @@ }; return new EngineeringApp( product ).Run( args ); - -/// -/// The continuous integration configurations that run the test suite against a database server, each one in the -/// image that carries its server. -/// -internal static class DatabaseTests -{ - public static DatabaseTestRun SqlServer { get; } = new( "sqlserver", "SqlServer", "SQL Server", new SqlServerComponent() ); - - public static DatabaseTestRun PostgreSql { get; } = new( "postgresql", "PostgreSql", "PostgreSQL", new PostgreSqlComponent() ); -} - -/// -/// One such configuration, with the image it runs in. -/// -/// -/// The name of the image. PostSharp.Engineering writes the Dockerfile of its build layer to -/// eng/docker/{name}-build.Dockerfile. -/// -/// The name of the engine, as the parameter of eng/TestDatabase.ps1 spells it. -/// The name of the engine, as a person writes it. -/// The component that installs the server into the image. -internal sealed class DatabaseTestRun -{ - private readonly string name; - private readonly string engine; - private readonly string displayName; - private readonly ContainerComponent server; - - public DatabaseTestRun( string name, string engine, string displayName, ContainerComponent server ) - { - this.name = name; - this.engine = engine; - this.displayName = displayName; - this.server = server; - } - - public AdditionalDockerfile Dockerfile( string dotNetSdkVersion ) - => new( this.name, [] ) - { - Requirements = new ContainerRequirements( ContainerHostKind.Linux ) - { - OperatingSystem = ContainerOperatingSystem.Linux, - Components = [new DotNetComponent( dotNetSdkVersion, DotNetComponentKind.Sdk ), this.server] - } - }; - - public PowershellAdditionalCiBuildConfiguration Configuration - => new( $"{this.engine}Tests", $"Tests on {this.displayName}", "./eng/TestDatabase.ps1", $"-Engine {this.engine}" ) - { - BuildAgentRequirements = LinuxContainerHost, - Dockerfile = $"eng/docker/{this.name}-build.Dockerfile", - BuildSnapshotDependency = BuildConfiguration.Debug - }; - - /// - /// Gets the agent this configuration runs on, which is a Linux host of an amd64 container. - /// - /// - /// The requirements that PostSharp.Engineering derives for a Linux container host ask for an - /// env.BuildAgentType that no agent of this farm publishes, so nothing would be compatible and the - /// build would wait instead of failing. The operating system and the architecture are what the agents offer. - /// The architecture is named because SQL Server runs on amd64 only. - /// - private static ContainerHostRequirements LinuxContainerHost - => new ContainerHostRequirements( ContainerHostKind.Linux ) with - { - Items = - [ - new BuildAgentRequirement( "teamcity.agent.jvm.os.name", "Linux" ), - new BuildAgentRequirement( "teamcity.agent.jvm.os.arch", "amd64" ) - ] - }; -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/GrantedLease.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/GrantedLease.cs new file mode 100644 index 0000000..bfdc282 --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/GrantedLease.cs @@ -0,0 +1,18 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Email; +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Options; + +namespace SharpCrafters.Backstage.LicenseServer.Services; + +/// +/// The lease granted to a client: which license key to use, and for how long. +/// +public sealed record GrantedLease( string LicenseKey, DateTime StartTime, DateTime EndTime, DateTime RenewTime ); \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs index 597a1cd..59941f0 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs @@ -507,9 +507,4 @@ public int Usage public LicenseInfo ParsedLicense => this.parsedLicense; } -} - -/// -/// The lease granted to a client: which license key to use, and for how long. -/// -public sealed record GrantedLease( string LicenseKey, DateTime StartTime, DateTime EndTime, DateTime RenewTime ); \ No newline at end of file +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailability.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailability.cs new file mode 100644 index 0000000..b05bfbc --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailability.cs @@ -0,0 +1,77 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using Microsoft.EntityFrameworkCore; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Licensing; + +namespace SharpCrafters.Backstage.LicenseServer.Services; + +/// +/// How many licenses the server holds, and why the ones it cannot serve are unusable. +/// +public sealed record LicenseAvailability +{ + /// + /// Gets the number of licenses that can serve a lease now. + /// + public required int Available { get; init; } + + /// + /// Gets the number of licenses an administrator has disabled. + /// + public required int Disabled { get; init; } + + /// + /// Gets the number of licenses whose key the server cannot parse, whose type a license server + /// may not serve, or that require a newer licensing library than this server contains. + /// + public required int Invalid { get; init; } + + /// + /// Gets the number of licenses whose validity has ended. + /// + public required int Expired { get; init; } + + /// + /// Gets the number of licenses that are full and whose grace period has ended or is full as + /// well. + /// + public required int Exhausted { get; init; } + + public int Total => this.Available + this.Disabled + this.Invalid + this.Expired + this.Exhausted; + + public bool CanServeLease => this.Available > 0; + + /// + /// Returns one sentence that says whether a lease can be served, and why not when it cannot. + /// + public string Describe() + { + if ( this.CanServeLease ) + { + return $"{this.Available} of {this.Total} license(s) can serve a lease."; + } + + if ( this.Total == 0 ) + { + return "No license is registered."; + } + + List reasons = []; + + Add( this.Exhausted, "at capacity" ); + Add( this.Expired, "expired" ); + Add( this.Invalid, "invalid" ); + Add( this.Disabled, "disabled" ); + + return $"No license can serve a lease: {string.Join( ", ", reasons )}."; + + void Add( int count, string reason ) + { + if ( count > 0 ) + { + reasons.Add( $"{count} {reason}" ); + } + } + } +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs index 5928c45..3042e4f 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LicenseAvailabilityService.cs @@ -123,74 +123,4 @@ public async Task GetAvailabilityAsync( Exhausted = exhausted }; } -} - -/// -/// How many licenses the server holds, and why the ones it cannot serve are unusable. -/// -public sealed record LicenseAvailability -{ - /// - /// Gets the number of licenses that can serve a lease now. - /// - public required int Available { get; init; } - - /// - /// Gets the number of licenses an administrator has disabled. - /// - public required int Disabled { get; init; } - - /// - /// Gets the number of licenses whose key the server cannot parse, whose type a license server - /// may not serve, or that require a newer licensing library than this server contains. - /// - public required int Invalid { get; init; } - - /// - /// Gets the number of licenses whose validity has ended. - /// - public required int Expired { get; init; } - - /// - /// Gets the number of licenses that are full and whose grace period has ended or is full as - /// well. - /// - public required int Exhausted { get; init; } - - public int Total => this.Available + this.Disabled + this.Invalid + this.Expired + this.Exhausted; - - public bool CanServeLease => this.Available > 0; - - /// - /// Returns one sentence that says whether a lease can be served, and why not when it cannot. - /// - public string Describe() - { - if ( this.CanServeLease ) - { - return $"{this.Available} of {this.Total} license(s) can serve a lease."; - } - - if ( this.Total == 0 ) - { - return "No license is registered."; - } - - List reasons = []; - - Add( this.Exhausted, "at capacity" ); - Add( this.Expired, "expired" ); - Add( this.Invalid, "invalid" ); - Add( this.Disabled, "disabled" ); - - return $"No license can serve a lease: {string.Join( ", ", reasons )}."; - - void Add( int count, string reason ) - { - if ( count > 0 ) - { - reasons.Add( $"{count} {reason}" ); - } - } - } } \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/AnonymousAuthenticationHandler.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/AnonymousAuthenticationHandler.cs new file mode 100644 index 0000000..20101cb --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/AnonymousAuthenticationHandler.cs @@ -0,0 +1,25 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using System.Runtime.InteropServices; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Negotiate; +using Microsoft.AspNetCore.Server.IISIntegration; +using Microsoft.Extensions.Options; + +namespace SharpCrafters.Backstage.LicenseServer; + +/// +/// Authenticates no caller, so that the server serves every request anonymously. +/// +public sealed class AnonymousAuthenticationHandler : AuthenticationHandler +{ + public AnonymousAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder ) : base( options, logger, encoder ) { } + + public const string SchemeName = "None"; + + protected override Task HandleAuthenticateAsync() => Task.FromResult( AuthenticateResult.NoResult() ); +} \ No newline at end of file diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs index f7d6bd3..9660804 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs @@ -105,19 +105,4 @@ private static string ResolveScheme( IConfiguration configuration ) return RuntimeInformation.IsOSPlatform( OSPlatform.Windows ) ? Negotiate : None; } -} - -/// -/// Authenticates no caller, so that the server serves every request anonymously. -/// -public sealed class AnonymousAuthenticationHandler : AuthenticationHandler -{ - public AnonymousAuthenticationHandler( - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder ) : base( options, logger, encoder ) { } - - public const string SchemeName = "None"; - - protected override Task HandleAuthenticateAsync() => Task.FromResult( AuthenticateResult.NoResult() ); } \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/ITestDatabase.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/ITestDatabase.cs new file mode 100644 index 0000000..375ac21 --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/ITestDatabase.cs @@ -0,0 +1,35 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using SharpCrafters.Backstage.LicenseServer.Data; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +/// +/// A database that belongs to one test, on the engine the test run was started with. +/// +public interface ITestDatabase : IAsyncDisposable +{ + /// + /// Gets the name of the provider, as LicenseServer:DatabaseProvider spells it. + /// + string ProviderName { get; } + + /// + /// Gets the connection string of this database, which the tests that host the whole application + /// pass to it as configuration. + /// + string ConnectionString { get; } + + /// + /// Creates a context over this database. Each context is a separate unit of work with its own + /// change tracker, so a test can distinguish a lease that is pending from a lease that is saved. + /// + LicenseServerDbContext CreateContext(); + + /// + /// States that this database must not serve another test, which a test that modifies the schema + /// calls. A SQL Server run lends its databases to one test after another, and a test that drops + /// a table would leave the next test without one. + /// + void DoNotReuse(); +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LeaseBuilder.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LeaseBuilder.cs new file mode 100644 index 0000000..fb3c1a3 --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LeaseBuilder.cs @@ -0,0 +1,88 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using SharpCrafters.Backstage.LicenseServer.Licensing; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +/// +/// Builds a lease directly, without the allocation rules, to create the initial state of a test. +/// +public sealed class LeaseBuilder +{ + private readonly License license; + private string userName = "alice"; + private string machine = "desktop-1"; + private DateTime startTime = TestClock.Origin; + private DateTime endTime = TestClock.Origin.AddDays( 3 ); + private bool grace; + + private LeaseBuilder( License license ) + { + this.license = license; + } + + public static LeaseBuilder For( License license ) => new( license ); + + public LeaseBuilder User( string value ) + { + this.userName = value; + + return this; + } + + public LeaseBuilder Machine( string value ) + { + this.machine = value; + + return this; + } + + public LeaseBuilder From( DateTime value ) + { + this.startTime = value; + + return this; + } + + public LeaseBuilder To( DateTime value ) + { + this.endTime = value; + + return this; + } + + public LeaseBuilder Lasting( double days ) + { + this.endTime = this.startTime.AddDays( days ); + + return this; + } + + public LeaseBuilder InGrace() + { + this.grace = true; + + return this; + } + + public Lease AddTo( LicenseServerTestContext context ) + { + Lease lease = new() + { + License = this.license, + LicenseId = this.license.LicenseId, + UserName = this.userName, + Machine = this.machine, + AuthenticatedUser = this.userName, + StartTime = this.startTime, + EndTime = this.endTime, + Grace = this.grace + }; + + // The lease is saved through the repository, so that it is signed as a real lease is. + context.Db.Leases.Add( lease ); + context.Repository.SaveChanges(); + + return lease; + } +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseBuilder.cs similarity index 65% rename from tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseBuilder.cs index dc35aee..83703ce 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseBuilder.cs @@ -4,26 +4,6 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -/// -/// Fixed points in time used by the tests. -/// -public static class TestClock -{ - /// - /// A Monday, at a whole second. - /// - /// - /// Monday exercises the branch of the usage graph that labels the weekdays. Whole seconds keep - /// the tests independent of the rounding of the SQL type datetime, which is 1/300 of a - /// second. - /// - public static readonly DateTime Origin = new( 2026, 1, 5, 9, 0, 0, DateTimeKind.Utc ); - - public static DateTime Days( double days ) => Origin.AddDays( days ); - - public static DateTime Hours( double hours ) => Origin.AddHours( hours ); -} - /// /// Builds a license, and the properties that the fake parser reports for its key. /// @@ -199,87 +179,4 @@ public License AddTo( LicenseServerTestContext context ) return license; } -} - -/// -/// Builds a lease directly, without the allocation rules, to create the initial state of a test. -/// -public sealed class LeaseBuilder -{ - private readonly License license; - private string userName = "alice"; - private string machine = "desktop-1"; - private DateTime startTime = TestClock.Origin; - private DateTime endTime = TestClock.Origin.AddDays( 3 ); - private bool grace; - - private LeaseBuilder( License license ) - { - this.license = license; - } - - public static LeaseBuilder For( License license ) => new( license ); - - public LeaseBuilder User( string value ) - { - this.userName = value; - - return this; - } - - public LeaseBuilder Machine( string value ) - { - this.machine = value; - - return this; - } - - public LeaseBuilder From( DateTime value ) - { - this.startTime = value; - - return this; - } - - public LeaseBuilder To( DateTime value ) - { - this.endTime = value; - - return this; - } - - public LeaseBuilder Lasting( double days ) - { - this.endTime = this.startTime.AddDays( days ); - - return this; - } - - public LeaseBuilder InGrace() - { - this.grace = true; - - return this; - } - - public Lease AddTo( LicenseServerTestContext context ) - { - Lease lease = new() - { - License = this.license, - LicenseId = this.license.LicenseId, - UserName = this.userName, - Machine = this.machine, - AuthenticatedUser = this.userName, - StartTime = this.startTime, - EndTime = this.endTime, - Grace = this.grace - }; - - // The lease is saved through the repository, so that it is signed as a real lease is. - context.Db.Leases.Add( lease ); - context.Repository.SaveChanges(); - - return lease; - } } \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs index b5f6a59..0857f89 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs @@ -187,37 +187,4 @@ protected override void Dispose( bool disposing ) Task.Run( async () => await this.database.DisposeAsync() ).GetAwaiter().GetResult(); } } -} - -/// -/// Authenticates every request as the same Windows-style identity, so the tests exercise the -/// authenticated path without a domain controller. -/// -public sealed class TestAuthenticationHandler : AuthenticationHandler -{ - public TestAuthenticationHandler( - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder ) : base( options, logger, encoder ) { } - - public const string SchemeName = "Test"; - - /// - /// The header a test sets to be served anonymously instead. - /// - public const string AnonymousHeader = "X-Test-Anonymous"; - - protected override Task HandleAuthenticateAsync() - { - if ( this.Request.Headers.ContainsKey( AnonymousHeader ) ) - { - return Task.FromResult( AuthenticateResult.NoResult() ); - } - - ClaimsIdentity identity = new( - [new Claim( ClaimTypes.Name, "DOMAIN\\tester" )], - SchemeName ); - - return Task.FromResult( AuthenticateResult.Success( new AuthenticationTicket( new ClaimsPrincipal( identity ), SchemeName ) ) ); - } } \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlDatabasePool.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlDatabasePool.cs new file mode 100644 index 0000000..250fbf9 --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlDatabasePool.cs @@ -0,0 +1,228 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore; +using Npgsql; +using SharpCrafters.Backstage.LicenseServer.Data; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +/// +/// The databases of one PostgreSQL server, lent to the tests one at a time. +/// +[SuppressMessage( + "Microsoft.Design", + "CA1001", + Justification = "A pool lives as long as the run, in a static dictionary, and nothing disposes it. " + + "Its two semaphores are released with the process." )] +internal sealed class PostgreSqlDatabasePool +{ + /// + /// The prefix of every database this pool creates. A run drops the databases that carry it before + /// it creates its own, so a run that was interrupted leaves nothing behind for long. + /// + private const string prefix = "licenseserver_test_"; + + /// + /// The number of databases the pool lends at the same time, which is also the number of tests + /// that touch the server at the same time. A test that finds no free database waits for one. + /// + private const int capacity = 8; + + private static readonly ConcurrentDictionary pools = new( StringComparer.Ordinal ); + + private readonly string serverConnectionString; + private readonly ConcurrentBag available = []; + private readonly SemaphoreSlim lease = new( capacity, capacity ); + private readonly SemaphoreSlim initialization = new( 1, 1 ); + private bool initialized; + + private PostgreSqlDatabasePool( string serverConnectionString ) + { + NpgsqlConnectionStringBuilder builder = new( serverConnectionString ); + + // A connection string that names no database connects to the maintenance database, which is + // where CREATE DATABASE and DROP DATABASE run. + if ( string.IsNullOrEmpty( builder.Database ) ) + { + builder.Database = "postgres"; + } + + this.serverConnectionString = builder.ToString(); + } + + public static PostgreSqlDatabasePool Get( string serverConnectionString ) + => pools.GetOrAdd( serverConnectionString, connectionString => new PostgreSqlDatabasePool( connectionString ) ); + + public string GetConnectionString( string databaseName ) + => new NpgsqlConnectionStringBuilder( this.serverConnectionString ) { Database = databaseName }.ToString(); + + public async Task RentAsync() + { + await this.DropLeftoverDatabasesAsync(); + await this.lease.WaitAsync(); + + try + { + if ( this.available.TryTake( out var databaseName ) ) + { + await this.ClearAsync( databaseName ); + + return databaseName; + } + + databaseName = prefix + Guid.NewGuid().ToString( "N" ); + + await ExecuteAsync( this.serverConnectionString, $"CREATE DATABASE \"{databaseName}\"" ); + await this.CreateSchemaAsync( databaseName ); + + return databaseName; + } + catch + { + this.lease.Release(); + + throw; + } + } + + /// + /// Takes a database back. A database whose schema a test modified is dropped instead of kept, and + /// the next test that finds the pool empty creates one. + /// + public async ValueTask ReturnAsync( string databaseName, bool reusable ) + { + try + { + if ( reusable ) + { + this.available.Add( databaseName ); + } + else + { + await this.DropAsync( databaseName ); + } + } + finally + { + this.lease.Release(); + } + } + + /// + /// Empties the two tables and restarts the identity of the leases, so that a database that was + /// used by an earlier test looks like a database that was just created. TRUNCATE states both, and + /// it states them for a table a foreign key points to, which is why it names both tables. + /// + private async Task ClearAsync( string databaseName ) + => await ExecuteAsync( + this.GetConnectionString( databaseName ), + "TRUNCATE TABLE \"Leases\", \"Licenses\" RESTART IDENTITY" ); + + private async Task CreateSchemaAsync( string databaseName ) + { + var script = await File.ReadAllTextAsync( LocateCreateTablesScript() ); + + // PostgreSQL has no batch separator: the whole script is one command. + await ExecuteAsync( this.GetConnectionString( databaseName ), script ); + } + + /// + /// Drops the databases that an interrupted run left behind. It runs once per pool, before the + /// first database is created. + /// + private async Task DropLeftoverDatabasesAsync() + { + if ( this.initialized ) + { + return; + } + + await this.initialization.WaitAsync(); + + try + { + if ( this.initialized ) + { + return; + } + + List leftovers = []; + + await using ( NpgsqlConnection connection = new( this.serverConnectionString ) ) + { + await connection.OpenAsync(); + + await using var query = connection.CreateCommand(); + query.CommandText = $"SELECT datname FROM pg_database WHERE datname LIKE '{prefix}%'"; + + await using var reader = await query.ExecuteReaderAsync(); + + while ( await reader.ReadAsync() ) + { + leftovers.Add( reader.GetString( 0 ) ); + } + } + + foreach ( var leftover in leftovers ) + { + await this.DropAsync( leftover ); + } + + this.initialized = true; + } + finally + { + this.initialization.Release(); + } + } + + /// + /// Drops one database. A test leaves its connections open in the pool of the client, and + /// PostgreSQL refuses to drop a database that a session is connected to, so the connections of + /// this client are closed first and the remaining sessions are ended by the server. + /// + private async Task DropAsync( string databaseName ) + { + NpgsqlConnection.ClearPool( new NpgsqlConnection( this.GetConnectionString( databaseName ) ) ); + + await ExecuteAsync( + this.serverConnectionString, + $""" + SELECT pg_terminate_backend(pid) FROM pg_stat_activity + WHERE datname = '{databaseName}' AND pid <> pg_backend_pid(); + """ ); + + await ExecuteAsync( this.serverConnectionString, $"DROP DATABASE IF EXISTS \"{databaseName}\"" ); + } + + private static async Task ExecuteAsync( string connectionString, string sql ) + { + await using NpgsqlConnection connection = new( connectionString ); + await connection.OpenAsync(); + + await using var command = connection.CreateCommand(); + command.CommandText = sql; + + await command.ExecuteNonQueryAsync(); + } + + /// + /// Finds CreateTables.PostgreSql.sql next to the test assembly, where the web project + /// copies it. + /// + private static string LocateCreateTablesScript() + { + var path = Path.Combine( AppContext.BaseDirectory, "Database", "CreateTables.PostgreSql.sql" ); + + if ( !File.Exists( path ) ) + { + throw new FileNotFoundException( + $"The schema script was not found at '{path}'. The test project copies it from the web project.", + path ); + } + + return path; + } +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlTestDatabase.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlTestDatabase.cs index 717a219..f9f58d9 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlTestDatabase.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/PostgreSqlTestDatabase.cs @@ -72,223 +72,4 @@ public async ValueTask DisposeAsync() await this.pool.ReturnAsync( this.databaseName, this.reusable ); } } -} - -/// -/// The databases of one PostgreSQL server, lent to the tests one at a time. -/// -[SuppressMessage( - "Microsoft.Design", - "CA1001", - Justification = "A pool lives as long as the run, in a static dictionary, and nothing disposes it. " - + "Its two semaphores are released with the process." )] -internal sealed class PostgreSqlDatabasePool -{ - /// - /// The prefix of every database this pool creates. A run drops the databases that carry it before - /// it creates its own, so a run that was interrupted leaves nothing behind for long. - /// - private const string prefix = "licenseserver_test_"; - - /// - /// The number of databases the pool lends at the same time, which is also the number of tests - /// that touch the server at the same time. A test that finds no free database waits for one. - /// - private const int capacity = 8; - - private static readonly ConcurrentDictionary pools = new( StringComparer.Ordinal ); - - private readonly string serverConnectionString; - private readonly ConcurrentBag available = []; - private readonly SemaphoreSlim lease = new( capacity, capacity ); - private readonly SemaphoreSlim initialization = new( 1, 1 ); - private bool initialized; - - private PostgreSqlDatabasePool( string serverConnectionString ) - { - NpgsqlConnectionStringBuilder builder = new( serverConnectionString ); - - // A connection string that names no database connects to the maintenance database, which is - // where CREATE DATABASE and DROP DATABASE run. - if ( string.IsNullOrEmpty( builder.Database ) ) - { - builder.Database = "postgres"; - } - - this.serverConnectionString = builder.ToString(); - } - - public static PostgreSqlDatabasePool Get( string serverConnectionString ) - => pools.GetOrAdd( serverConnectionString, connectionString => new PostgreSqlDatabasePool( connectionString ) ); - - public string GetConnectionString( string databaseName ) - => new NpgsqlConnectionStringBuilder( this.serverConnectionString ) { Database = databaseName }.ToString(); - - public async Task RentAsync() - { - await this.DropLeftoverDatabasesAsync(); - await this.lease.WaitAsync(); - - try - { - if ( this.available.TryTake( out var databaseName ) ) - { - await this.ClearAsync( databaseName ); - - return databaseName; - } - - databaseName = prefix + Guid.NewGuid().ToString( "N" ); - - await ExecuteAsync( this.serverConnectionString, $"CREATE DATABASE \"{databaseName}\"" ); - await this.CreateSchemaAsync( databaseName ); - - return databaseName; - } - catch - { - this.lease.Release(); - - throw; - } - } - - /// - /// Takes a database back. A database whose schema a test modified is dropped instead of kept, and - /// the next test that finds the pool empty creates one. - /// - public async ValueTask ReturnAsync( string databaseName, bool reusable ) - { - try - { - if ( reusable ) - { - this.available.Add( databaseName ); - } - else - { - await this.DropAsync( databaseName ); - } - } - finally - { - this.lease.Release(); - } - } - - /// - /// Empties the two tables and restarts the identity of the leases, so that a database that was - /// used by an earlier test looks like a database that was just created. TRUNCATE states both, and - /// it states them for a table a foreign key points to, which is why it names both tables. - /// - private async Task ClearAsync( string databaseName ) - => await ExecuteAsync( - this.GetConnectionString( databaseName ), - "TRUNCATE TABLE \"Leases\", \"Licenses\" RESTART IDENTITY" ); - - private async Task CreateSchemaAsync( string databaseName ) - { - var script = await File.ReadAllTextAsync( LocateCreateTablesScript() ); - - // PostgreSQL has no batch separator: the whole script is one command. - await ExecuteAsync( this.GetConnectionString( databaseName ), script ); - } - - /// - /// Drops the databases that an interrupted run left behind. It runs once per pool, before the - /// first database is created. - /// - private async Task DropLeftoverDatabasesAsync() - { - if ( this.initialized ) - { - return; - } - - await this.initialization.WaitAsync(); - - try - { - if ( this.initialized ) - { - return; - } - - List leftovers = []; - - await using ( NpgsqlConnection connection = new( this.serverConnectionString ) ) - { - await connection.OpenAsync(); - - await using var query = connection.CreateCommand(); - query.CommandText = $"SELECT datname FROM pg_database WHERE datname LIKE '{prefix}%'"; - - await using var reader = await query.ExecuteReaderAsync(); - - while ( await reader.ReadAsync() ) - { - leftovers.Add( reader.GetString( 0 ) ); - } - } - - foreach ( var leftover in leftovers ) - { - await this.DropAsync( leftover ); - } - - this.initialized = true; - } - finally - { - this.initialization.Release(); - } - } - - /// - /// Drops one database. A test leaves its connections open in the pool of the client, and - /// PostgreSQL refuses to drop a database that a session is connected to, so the connections of - /// this client are closed first and the remaining sessions are ended by the server. - /// - private async Task DropAsync( string databaseName ) - { - NpgsqlConnection.ClearPool( new NpgsqlConnection( this.GetConnectionString( databaseName ) ) ); - - await ExecuteAsync( - this.serverConnectionString, - $""" - SELECT pg_terminate_backend(pid) FROM pg_stat_activity - WHERE datname = '{databaseName}' AND pid <> pg_backend_pid(); - """ ); - - await ExecuteAsync( this.serverConnectionString, $"DROP DATABASE IF EXISTS \"{databaseName}\"" ); - } - - private static async Task ExecuteAsync( string connectionString, string sql ) - { - await using NpgsqlConnection connection = new( connectionString ); - await connection.OpenAsync(); - - await using var command = connection.CreateCommand(); - command.CommandText = sql; - - await command.ExecuteNonQueryAsync(); - } - - /// - /// Finds CreateTables.PostgreSql.sql next to the test assembly, where the web project - /// copies it. - /// - private static string LocateCreateTablesScript() - { - var path = Path.Combine( AppContext.BaseDirectory, "Database", "CreateTables.PostgreSql.sql" ); - - if ( !File.Exists( path ) ) - { - throw new FileNotFoundException( - $"The schema script was not found at '{path}'. The test project copies it from the web project.", - path ); - } - - return path; - } } \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerDatabasePool.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerDatabasePool.cs new file mode 100644 index 0000000..182b704 --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerDatabasePool.cs @@ -0,0 +1,247 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using SharpCrafters.Backstage.LicenseServer.Data; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +/// +/// The databases of one SQL Server, lent to the tests one at a time. +/// +[SuppressMessage( + "Microsoft.Design", + "CA1001", + Justification = "A pool lives as long as the run, in a static dictionary, and nothing disposes it. " + + "Its two semaphores are released with the process." )] +internal sealed class SqlServerDatabasePool +{ + /// + /// The prefix of every database this pool creates. A run drops the databases that carry it before + /// it creates its own, so a run that was interrupted leaves nothing behind for long. + /// + private const string prefix = "licenseserver_test_"; + + /// + /// The number of databases the pool lends at the same time, which is also the number of tests + /// that touch the server at the same time. A test that finds no free database waits for one. + /// Without this bound, every test of the suite would create a database of its own at once, and + /// the server would refuse the connections. + /// + private const int capacity = 8; + + private static readonly ConcurrentDictionary pools = new( StringComparer.Ordinal ); + + private readonly string serverConnectionString; + private readonly ConcurrentBag available = []; + private readonly SemaphoreSlim lease = new( capacity, capacity ); + private readonly SemaphoreSlim initialization = new( 1, 1 ); + private bool initialized; + + private SqlServerDatabasePool( string serverConnectionString ) + { + SqlConnectionStringBuilder builder = new( serverConnectionString ); + + // Creating a database and connecting to it while the suite runs takes longer than the + // fifteen seconds of the default. + if ( builder.ConnectTimeout < 60 ) + { + builder.ConnectTimeout = 60; + } + + this.serverConnectionString = builder.ToString(); + } + + public static SqlServerDatabasePool Get( string serverConnectionString ) + => pools.GetOrAdd( serverConnectionString, connectionString => new SqlServerDatabasePool( connectionString ) ); + + public string GetConnectionString( string databaseName ) + => new SqlConnectionStringBuilder( this.serverConnectionString ) { InitialCatalog = databaseName }.ToString(); + + public async Task RentAsync() + { + await this.DropLeftoverDatabasesAsync(); + await this.lease.WaitAsync(); + + try + { + if ( this.available.TryTake( out var databaseName ) ) + { + await this.ClearAsync( databaseName ); + + return databaseName; + } + + databaseName = prefix + Guid.NewGuid().ToString( "N" ); + + await ExecuteAsync( this.serverConnectionString, $"CREATE DATABASE [{databaseName}]" ); + await this.CreateSchemaAsync( databaseName ); + + return databaseName; + } + catch + { + this.lease.Release(); + + throw; + } + } + + /// + /// Takes a database back. A database whose schema a test modified is dropped instead of kept, and + /// the next test that finds the pool empty creates one. + /// + public async ValueTask ReturnAsync( string databaseName, bool reusable ) + { + try + { + if ( reusable ) + { + this.available.Add( databaseName ); + } + else + { + await this.DropAsync( databaseName ); + } + } + finally + { + this.lease.Release(); + } + } + + /// + /// Empties the two tables and restarts the identity of the leases, so that a database that was + /// used by an earlier test looks like a database that was just created. The first lease of the + /// next test therefore always receives the identifier 1. + /// + /// + /// The reseed is conditional. On a table that has received a row since it was created, the next + /// row takes the new value plus the increment, which is 1. On a table that has received no row, + /// the next row takes the new value itself, which is 0, and a test that asserts on the identifier + /// of a lease then fails. The column last_value is null until the first row is inserted, + /// and that case needs no reseed, because the next row already takes the seed of the column. + /// + private async Task ClearAsync( string databaseName ) + => await ExecuteAsync( + this.GetConnectionString( databaseName ), + """ + DELETE FROM [dbo].[Leases]; + DELETE FROM [dbo].[Licenses]; + + IF EXISTS ( + SELECT 1 FROM sys.identity_columns + WHERE object_id = OBJECT_ID('[dbo].[Leases]') AND last_value IS NOT NULL ) + BEGIN + DBCC CHECKIDENT ('[dbo].[Leases]', RESEED, 0) WITH NO_INFOMSGS; + END + """ ); + + private async Task CreateSchemaAsync( string databaseName ) + { + var script = await File.ReadAllTextAsync( LocateCreateTablesScript() ); + + // sqlcmd separates the batches of a script with GO, which is not a statement of Transact-SQL. + foreach ( var batch in script.Split( + "\nGO", + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ) ) + { + if ( batch.Length > 0 ) + { + await ExecuteAsync( this.GetConnectionString( databaseName ), batch ); + } + } + } + + /// + /// Drops the databases that an interrupted run left behind. It runs once per pool, before the + /// first database is created. + /// + private async Task DropLeftoverDatabasesAsync() + { + if ( this.initialized ) + { + return; + } + + await this.initialization.WaitAsync(); + + try + { + if ( this.initialized ) + { + return; + } + + await using SqlConnection connection = new( this.serverConnectionString ); + await connection.OpenAsync(); + + List leftovers = []; + + await using ( var query = connection.CreateCommand() ) + { + query.CommandText = $"SELECT name FROM sys.databases WHERE name LIKE '{prefix}%'"; + + await using var reader = await query.ExecuteReaderAsync(); + + while ( await reader.ReadAsync() ) + { + leftovers.Add( reader.GetString( 0 ) ); + } + } + + foreach ( var leftover in leftovers ) + { + await this.DropAsync( leftover ); + } + + this.initialized = true; + } + finally + { + this.initialization.Release(); + } + } + + /// + /// Drops one database. A test leaves its connections open in the pool of the client, so the + /// server closes them before it drops the database. + /// + private async Task DropAsync( string databaseName ) + => await ExecuteAsync( + this.serverConnectionString, + $""" + ALTER DATABASE [{databaseName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + DROP DATABASE [{databaseName}]; + """ ); + + private static async Task ExecuteAsync( string connectionString, string sql ) + { + await using SqlConnection connection = new( connectionString ); + await connection.OpenAsync(); + + await using var command = connection.CreateCommand(); + command.CommandText = sql; + + await command.ExecuteNonQueryAsync(); + } + + /// + /// Finds CreateTables.sql next to the test assembly, where the web project copies it. + /// + private static string LocateCreateTablesScript() + { + var path = Path.Combine( AppContext.BaseDirectory, "Database", "CreateTables.sql" ); + + if ( !File.Exists( path ) ) + { + throw new FileNotFoundException( + $"The schema script was not found at '{path}'. The test project copies it from the web project.", + path ); + } + + return path; + } +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs index 4197657..6445a02 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqlServerTestDatabase.cs @@ -75,242 +75,4 @@ public async ValueTask DisposeAsync() await this.pool.ReturnAsync( this.databaseName, this.reusable ); } } -} - -/// -/// The databases of one SQL Server, lent to the tests one at a time. -/// -[SuppressMessage( - "Microsoft.Design", - "CA1001", - Justification = "A pool lives as long as the run, in a static dictionary, and nothing disposes it. " - + "Its two semaphores are released with the process." )] -internal sealed class SqlServerDatabasePool -{ - /// - /// The prefix of every database this pool creates. A run drops the databases that carry it before - /// it creates its own, so a run that was interrupted leaves nothing behind for long. - /// - private const string prefix = "licenseserver_test_"; - - /// - /// The number of databases the pool lends at the same time, which is also the number of tests - /// that touch the server at the same time. A test that finds no free database waits for one. - /// Without this bound, every test of the suite would create a database of its own at once, and - /// the server would refuse the connections. - /// - private const int capacity = 8; - - private static readonly ConcurrentDictionary pools = new( StringComparer.Ordinal ); - - private readonly string serverConnectionString; - private readonly ConcurrentBag available = []; - private readonly SemaphoreSlim lease = new( capacity, capacity ); - private readonly SemaphoreSlim initialization = new( 1, 1 ); - private bool initialized; - - private SqlServerDatabasePool( string serverConnectionString ) - { - SqlConnectionStringBuilder builder = new( serverConnectionString ); - - // Creating a database and connecting to it while the suite runs takes longer than the - // fifteen seconds of the default. - if ( builder.ConnectTimeout < 60 ) - { - builder.ConnectTimeout = 60; - } - - this.serverConnectionString = builder.ToString(); - } - - public static SqlServerDatabasePool Get( string serverConnectionString ) - => pools.GetOrAdd( serverConnectionString, connectionString => new SqlServerDatabasePool( connectionString ) ); - - public string GetConnectionString( string databaseName ) - => new SqlConnectionStringBuilder( this.serverConnectionString ) { InitialCatalog = databaseName }.ToString(); - - public async Task RentAsync() - { - await this.DropLeftoverDatabasesAsync(); - await this.lease.WaitAsync(); - - try - { - if ( this.available.TryTake( out var databaseName ) ) - { - await this.ClearAsync( databaseName ); - - return databaseName; - } - - databaseName = prefix + Guid.NewGuid().ToString( "N" ); - - await ExecuteAsync( this.serverConnectionString, $"CREATE DATABASE [{databaseName}]" ); - await this.CreateSchemaAsync( databaseName ); - - return databaseName; - } - catch - { - this.lease.Release(); - - throw; - } - } - - /// - /// Takes a database back. A database whose schema a test modified is dropped instead of kept, and - /// the next test that finds the pool empty creates one. - /// - public async ValueTask ReturnAsync( string databaseName, bool reusable ) - { - try - { - if ( reusable ) - { - this.available.Add( databaseName ); - } - else - { - await this.DropAsync( databaseName ); - } - } - finally - { - this.lease.Release(); - } - } - - /// - /// Empties the two tables and restarts the identity of the leases, so that a database that was - /// used by an earlier test looks like a database that was just created. The first lease of the - /// next test therefore always receives the identifier 1. - /// - /// - /// The reseed is conditional. On a table that has received a row since it was created, the next - /// row takes the new value plus the increment, which is 1. On a table that has received no row, - /// the next row takes the new value itself, which is 0, and a test that asserts on the identifier - /// of a lease then fails. The column last_value is null until the first row is inserted, - /// and that case needs no reseed, because the next row already takes the seed of the column. - /// - private async Task ClearAsync( string databaseName ) - => await ExecuteAsync( - this.GetConnectionString( databaseName ), - """ - DELETE FROM [dbo].[Leases]; - DELETE FROM [dbo].[Licenses]; - - IF EXISTS ( - SELECT 1 FROM sys.identity_columns - WHERE object_id = OBJECT_ID('[dbo].[Leases]') AND last_value IS NOT NULL ) - BEGIN - DBCC CHECKIDENT ('[dbo].[Leases]', RESEED, 0) WITH NO_INFOMSGS; - END - """ ); - - private async Task CreateSchemaAsync( string databaseName ) - { - var script = await File.ReadAllTextAsync( LocateCreateTablesScript() ); - - // sqlcmd separates the batches of a script with GO, which is not a statement of Transact-SQL. - foreach ( var batch in script.Split( - "\nGO", - StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ) ) - { - if ( batch.Length > 0 ) - { - await ExecuteAsync( this.GetConnectionString( databaseName ), batch ); - } - } - } - - /// - /// Drops the databases that an interrupted run left behind. It runs once per pool, before the - /// first database is created. - /// - private async Task DropLeftoverDatabasesAsync() - { - if ( this.initialized ) - { - return; - } - - await this.initialization.WaitAsync(); - - try - { - if ( this.initialized ) - { - return; - } - - await using SqlConnection connection = new( this.serverConnectionString ); - await connection.OpenAsync(); - - List leftovers = []; - - await using ( var query = connection.CreateCommand() ) - { - query.CommandText = $"SELECT name FROM sys.databases WHERE name LIKE '{prefix}%'"; - - await using var reader = await query.ExecuteReaderAsync(); - - while ( await reader.ReadAsync() ) - { - leftovers.Add( reader.GetString( 0 ) ); - } - } - - foreach ( var leftover in leftovers ) - { - await this.DropAsync( leftover ); - } - - this.initialized = true; - } - finally - { - this.initialization.Release(); - } - } - - /// - /// Drops one database. A test leaves its connections open in the pool of the client, so the - /// server closes them before it drops the database. - /// - private async Task DropAsync( string databaseName ) - => await ExecuteAsync( - this.serverConnectionString, - $""" - ALTER DATABASE [{databaseName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; - DROP DATABASE [{databaseName}]; - """ ); - - private static async Task ExecuteAsync( string connectionString, string sql ) - { - await using SqlConnection connection = new( connectionString ); - await connection.OpenAsync(); - - await using var command = connection.CreateCommand(); - command.CommandText = sql; - - await command.ExecuteNonQueryAsync(); - } - - /// - /// Finds CreateTables.sql next to the test assembly, where the web project copies it. - /// - private static string LocateCreateTablesScript() - { - var path = Path.Combine( AppContext.BaseDirectory, "Database", "CreateTables.sql" ); - - if ( !File.Exists( path ) ) - { - throw new FileNotFoundException( - $"The schema script was not found at '{path}'. The test project copies it from the web project.", - path ); - } - - return path; - } } \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestAuthenticationHandler.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestAuthenticationHandler.cs new file mode 100644 index 0000000..345d59c --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestAuthenticationHandler.cs @@ -0,0 +1,57 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using System.Security.Claims; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Email; +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Locking; +using SharpCrafters.Backstage.LicenseServer.Options; +using SharpCrafters.Backstage.LicenseServer.Tests.Fakes; +using SharpCrafters.Common; +using System.Globalization; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +/// +/// Authenticates every request as the same Windows-style identity, so the tests exercise the +/// authenticated path without a domain controller. +/// +public sealed class TestAuthenticationHandler : AuthenticationHandler +{ + public TestAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder ) : base( options, logger, encoder ) { } + + public const string SchemeName = "Test"; + + /// + /// The header a test sets to be served anonymously instead. + /// + public const string AnonymousHeader = "X-Test-Anonymous"; + + protected override Task HandleAuthenticateAsync() + { + if ( this.Request.Headers.ContainsKey( AnonymousHeader ) ) + { + return Task.FromResult( AuthenticateResult.NoResult() ); + } + + ClaimsIdentity identity = new( + [new Claim( ClaimTypes.Name, "DOMAIN\\tester" )], + SchemeName ); + + return Task.FromResult( AuthenticateResult.Success( new AuthenticationTicket( new ClaimsPrincipal( identity ), SchemeName ) ) ); + } +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestClock.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestClock.cs new file mode 100644 index 0000000..bf5bc04 --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestClock.cs @@ -0,0 +1,25 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using SharpCrafters.Backstage.LicenseServer.Licensing; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +/// +/// Fixed points in time used by the tests. +/// +public static class TestClock +{ + /// + /// A Monday, at a whole second. + /// + /// + /// Monday exercises the branch of the usage graph that labels the weekdays. Whole seconds keep + /// the tests independent of the rounding of the SQL type datetime, which is 1/300 of a + /// second. + /// + public static readonly DateTime Origin = new( 2026, 1, 5, 9, 0, 0, DateTimeKind.Utc ); + + public static DateTime Days( double days ) => Origin.AddDays( days ); + + public static DateTime Hours( double hours ) => Origin.AddHours( hours ); +} \ No newline at end of file diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs index a494a85..c262176 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs @@ -4,36 +4,6 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -/// -/// A database that belongs to one test, on the engine the test run was started with. -/// -public interface ITestDatabase : IAsyncDisposable -{ - /// - /// Gets the name of the provider, as LicenseServer:DatabaseProvider spells it. - /// - string ProviderName { get; } - - /// - /// Gets the connection string of this database, which the tests that host the whole application - /// pass to it as configuration. - /// - string ConnectionString { get; } - - /// - /// Creates a context over this database. Each context is a separate unit of work with its own - /// change tracker, so a test can distinguish a lease that is pending from a lease that is saved. - /// - LicenseServerDbContext CreateContext(); - - /// - /// States that this database must not serve another test, which a test that modifies the schema - /// calls. A SQL Server run lends its databases to one test after another, and a test that drops - /// a table would leave the next test without one. - /// - void DoNotReuse(); -} - /// /// Creates the database of a test on the engine the run was started with. /// From 39f2fc8909b2561bc4bc73f6aa27414885447c88 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Fri, 18 Sep 2026 21:26:05 +0200 Subject: [PATCH 44/44] Move the database tests to the Docker test harness PostSharp.Engineering 2023.2.456 added a harness for tests that each need a container of their own, and this replaces the one written here. The engineering dependency is updated to 2023.2.459. tests/docker holds one directory per engine. Each carries a manifest that names the platforms the test applies to, a Dockerfile that installs the server and the .NET SDK, a RunTest.ps1 that starts the container through DockerBuild.ps1 -Test, and a run.sh that starts the server inside the container and runs the suite. eng/TestDatabase.ps1, the two container components and the two additional Dockerfiles are removed, and one DockerTestsAdditionalCiBuildConfiguration replaces the two configurations that ran them. The launcher, eng/RunDockerTests.ps1, is generated. The base image is declared as ARG OS_IMAGE, which is what makes DockerBuild.ps1 take it from the registry named by DOCKER_REGISTRY, and leaves an agent without a route to that registry building from the public image. tests/docker/prepare-restore.sh is what both tests source. The generated nuget.config and version file name directories of the machine that wrote them. In the continuous integration build those are directories of the repository, and the script does nothing. On a development machine they are Windows paths, which DockerBuild.ps1 mounts under /mnt, so the script translates them and reads the version of the licensing component from the file the dependency resolution wrote. It also sends the build to /tmp, because the repository is mounted and its bin and obj belong to the host, and it turns off the release archive, whose nested dotnet publish would restore a second time with the configuration of the repository. Verified: `pwsh ./eng/RunDockerTests.ps1 -Platform linux-x64` reports two tests passed, each running the 280 tests of the suite against its own server, and the suite still passes on the host afterwards. Co-Authored-By: Claude Opus 5 --- .gitattributes | 4 + .teamcity/settings.kts | 161 +--- Directory.Packages.props | 5 +- DockerBuild.ps1 | 375 +++++++++- README.md | 33 +- eng/RunDockerTests.ps1 | 688 ++++++++++++++++++ eng/TestDatabase.ps1 | 343 --------- eng/docker/postgresql-build.Dockerfile | 72 -- eng/docker/sqlserver-build.Dockerfile | 73 -- eng/src/DatabaseTestRun.cs | 75 -- eng/src/DatabaseTests.cs | 23 - eng/src/Docker/PostgreSqlComponent.cs | 65 -- eng/src/Docker/SqlServerComponent.cs | 58 -- eng/src/Program.cs | 38 +- .../Infrastructure/TestDatabases.cs | 3 +- tests/docker/PostgreSql/Dockerfile | 34 + tests/docker/PostgreSql/RunTest.ps1 | 28 + tests/docker/PostgreSql/run.sh | 38 + tests/docker/PostgreSql/test.psd1 | 9 + tests/docker/SqlServer/Dockerfile | 45 ++ tests/docker/SqlServer/RunTest.ps1 | 28 + tests/docker/SqlServer/run.sh | 45 ++ tests/docker/SqlServer/test.psd1 | 9 + tests/docker/prepare-restore.sh | 63 ++ 24 files changed, 1427 insertions(+), 888 deletions(-) create mode 100644 eng/RunDockerTests.ps1 delete mode 100644 eng/TestDatabase.ps1 delete mode 100644 eng/docker/postgresql-build.Dockerfile delete mode 100644 eng/docker/sqlserver-build.Dockerfile delete mode 100644 eng/src/DatabaseTestRun.cs delete mode 100644 eng/src/DatabaseTests.cs delete mode 100644 eng/src/Docker/PostgreSqlComponent.cs delete mode 100644 eng/src/Docker/SqlServerComponent.cs create mode 100644 tests/docker/PostgreSql/Dockerfile create mode 100644 tests/docker/PostgreSql/RunTest.ps1 create mode 100644 tests/docker/PostgreSql/run.sh create mode 100644 tests/docker/PostgreSql/test.psd1 create mode 100644 tests/docker/SqlServer/Dockerfile create mode 100644 tests/docker/SqlServer/RunTest.ps1 create mode 100644 tests/docker/SqlServer/run.sh create mode 100644 tests/docker/SqlServer/test.psd1 create mode 100644 tests/docker/prepare-restore.sh diff --git a/.gitattributes b/.gitattributes index 1ace532..1903bd6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,6 +12,10 @@ *.js text *.sql text *.ps1 text eol=crlf + +# The launcher of the Docker tests is generated with Unix line endings by `Build.ps1 generate-scripts`. Without +# this line the rule above rewrites the whole file at every regeneration. +eng/RunDockerTests.ps1 text eol=lf *.sh text eol=lf Dockerfile text eol=lf *.yml text eol=lf diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index dc08151..7491382 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -16,10 +16,12 @@ project { buildType(PublicBuild) buildType(PublicDeployment) buildType(VersionBump) - buildType(SqlServerTests) - buildType(PostgreSqlTests) - buildTypesOrder = arrayListOf(DebugBuild,ReleaseBuild,PublicBuild,PublicDeployment,VersionBump,SqlServerTests,PostgreSqlTests) + buildTypesOrder = arrayListOf(DebugBuild,ReleaseBuild,PublicBuild,PublicDeployment,VersionBump) + + subProject(DockerTests) + + subProjectsOrder = arrayListOf(DockerTests) } @@ -562,16 +564,16 @@ object VersionBump : BuildType({ }) -object SqlServerTests : BuildType({ +object DockerTestsLinuxX64 : BuildType({ - name = "Tests on SQL Server" + name = "Docker Tests (Linux x64)" params { text( "Exec.Arguments", "", - label ="DockerBuild.ps1 Arguments", - description = "Arguments to append to the 'Execute ./eng/TestDatabase.ps1' build step.", allowEmpty = true) + label ="eng/RunDockerTests.ps1 Arguments", + description = "Arguments to append to the 'Execute eng/RunDockerTests.ps1' build step.", allowEmpty = true) } vcs { @@ -608,37 +610,21 @@ object SqlServerTests : BuildType({ noProfile = false } powerShell { - name = "Prepare Docker image backstagelicenseserver-2027.0-sqlservertests" - id = "PrepareImage" - edition = PowerShellStep.Edition.Core - scriptMode = file { - path = "DockerBuild.ps1" - } - noProfile = false - scriptArgs = "-BuildImage -ImageName backstagelicenseserver-2027.0-sqlservertests -Dockerfile eng/docker/sqlserver-build.Dockerfile " - } - powerShell { - name = "Execute ./eng/TestDatabase.ps1" + name = "Execute eng/RunDockerTests.ps1" id = "Exec" edition = PowerShellStep.Edition.Core scriptMode = file { - path = "DockerBuild.ps1" - } - noProfile = false - scriptArgs = "-Script ./eng/TestDatabase.ps1 -ImageName backstagelicenseserver-2027.0-sqlservertests -Dockerfile eng/docker/sqlserver-build.Dockerfile -NoBuildImage -Label %system.teamcity.buildType.id%_%build.number% -Engine SqlServer %Exec.Arguments%" - } - powerShell { - name = "Cleanup Docker containers" - id = "DockerCleanup" - executionMode = BuildStep.ExecutionMode.ALWAYS - edition = PowerShellStep.Edition.Core - scriptMode = script { - content = "${'$'}label = \"%system.teamcity.buildType.id%_%build.number%\"; ${'$'}ids = docker ps -a -q --filter \"label=postsharp.build=${'$'}label\"; if (${'$'}ids) { docker rm -f ${'$'}ids 2>&1 | Out-Null }" + path = "eng/RunDockerTests.ps1" } noProfile = false + scriptArgs = "-Platform linux-x64 %Exec.Arguments%" } } + failureConditions { + executionTimeoutMin = 60 + } + requirements { equals("teamcity.agent.jvm.os.name", "Linux") equals("teamcity.agent.jvm.os.arch", "amd64") @@ -678,119 +664,12 @@ object SqlServerTests : BuildType({ }) -object PostgreSqlTests : BuildType({ - - name = "Tests on PostgreSQL" - - params { - text( - "Exec.Arguments", - "", - label ="DockerBuild.ps1 Arguments", - description = "Arguments to append to the 'Execute ./eng/TestDatabase.ps1' build step.", allowEmpty = true) - } - - vcs { - root(AbsoluteId("Backstage_BackstageLicenseServer20270")) - checkoutMode = CheckoutMode.ON_AGENT - } - - steps { - powerShell { - name = "Clean NuGet cache of produced and dependency packages" - id = "CleanNuGetCache" - edition = PowerShellStep.Edition.Core - scriptMode = script { - content = "${'$'}nugetPackages = if ( ${'$'}env:NUGET_PACKAGES ) { ${'$'}env:NUGET_PACKAGES } else { Join-Path ${'$'}HOME '.nuget' 'packages' }; ${'$'}removedDirs = 0; ${'$'}removedFiles = 0; if ( Test-Path -LiteralPath ${'$'}nugetPackages ) { foreach ( ${'$'}pattern in @('metalama.backstage*', 'postsharp.engineering', 'postsharp.engineering.*', 'sharpcrafters.backstage*', 'sharpcrafters.backstage.licenseserver*', 'sharpcrafters.common*') ) { Get-ChildItem -LiteralPath ${'$'}nugetPackages -Directory -Filter ${'$'}pattern -ErrorAction SilentlyContinue | ForEach-Object { ${'$'}files = @( Get-ChildItem -LiteralPath ${'$'}_.FullName -Recurse -File -ErrorAction SilentlyContinue ).Count; Write-Host \"Removing NuGet cache directory: ${'$'}(${'$'}_.FullName) (${'$'}files file(s))\"; Remove-Item -LiteralPath ${'$'}_.FullName -Recurse -Force -ErrorAction SilentlyContinue; if ( -not ( Test-Path -LiteralPath ${'$'}_.FullName ) ) { ${'$'}removedDirs++; ${'$'}removedFiles += ${'$'}files } } } Write-Host \"Removed ${'$'}removedDirs package directory(ies) and ${'$'}removedFiles file(s) from the NuGet cache.\"; } else { Write-Host \"NuGet packages folder not found: ${'$'}nugetPackages\" }" - } - noProfile = false - } - powerShell { - name = "Copy nuget.restored.config to nuget.config" - id = "CopyNuGetConfig" - edition = PowerShellStep.Edition.Core - scriptMode = script { - content = "Copy-Item -Path \"artifacts/publish/private/nuget.restored.config\" -Destination \"nuget.config\" -Force;" - } - noProfile = false - } - powerShell { - name = "Create eng/Versions.g.props" - id = "CreateVersionsFile" - edition = PowerShellStep.Edition.Core - scriptMode = script { - content = "New-Item -Path \"eng/Versions.g.props\" -ItemType File -Force -Value \"\" | Out-Null;" - } - noProfile = false - } - powerShell { - name = "Prepare Docker image backstagelicenseserver-2027.0-postgresqltests" - id = "PrepareImage" - edition = PowerShellStep.Edition.Core - scriptMode = file { - path = "DockerBuild.ps1" - } - noProfile = false - scriptArgs = "-BuildImage -ImageName backstagelicenseserver-2027.0-postgresqltests -Dockerfile eng/docker/postgresql-build.Dockerfile " - } - powerShell { - name = "Execute ./eng/TestDatabase.ps1" - id = "Exec" - edition = PowerShellStep.Edition.Core - scriptMode = file { - path = "DockerBuild.ps1" - } - noProfile = false - scriptArgs = "-Script ./eng/TestDatabase.ps1 -ImageName backstagelicenseserver-2027.0-postgresqltests -Dockerfile eng/docker/postgresql-build.Dockerfile -NoBuildImage -Label %system.teamcity.buildType.id%_%build.number% -Engine PostgreSql %Exec.Arguments%" - } - powerShell { - name = "Cleanup Docker containers" - id = "DockerCleanup" - executionMode = BuildStep.ExecutionMode.ALWAYS - edition = PowerShellStep.Edition.Core - scriptMode = script { - content = "${'$'}label = \"%system.teamcity.buildType.id%_%build.number%\"; ${'$'}ids = docker ps -a -q --filter \"label=postsharp.build=${'$'}label\"; if (${'$'}ids) { docker rm -f ${'$'}ids 2>&1 | Out-Null }" - } - noProfile = false - } - } - - requirements { - equals("teamcity.agent.jvm.os.name", "Linux") - equals("teamcity.agent.jvm.os.arch", "amd64") - } - - features { - swabra { - filesCleanup = Swabra.FilesCleanup.BEFORE_BUILD - lockingProcesses = Swabra.LockingProcessPolicy.KILL - verbose = true - } - gitHubAppBuildScopedToken { - parameterName = "env.GITHUB_TOKEN" - connectionId = "%GITHUB_CONNECTION_POSTSHARP_OPS%" - targetRepositories = "SharpCrafters.Backstage.LicenseServer" - } - } +object DockerTests : Project({ - dependencies { - snapshot(DebugBuild) { - onDependencyFailure = FailureAction.FAIL_TO_START - } + name = "Docker Tests" - artifacts(DebugBuild) { - cleanDestination = true - artifactRules = "+:artifacts/publish/private/**/*=>artifacts/publish/private" - } - snapshot(AbsoluteId("Backstage_Backstage20270_DebugBuild")) { - onDependencyFailure = FailureAction.FAIL_TO_START - } + buildType(DockerTestsLinuxX64) - artifacts(AbsoluteId("Backstage_Backstage20270_DebugBuild")) { - cleanDestination = true - artifactRules = "+:artifacts/publish/private/**/*=>dependencies/Backstage" - } - } + buildTypesOrder = arrayListOf(DockerTestsLinuxX64) }) - diff --git a/Directory.Packages.props b/Directory.Packages.props index b40faec..bbc7fc6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,8 +7,9 @@ - 2023.2.455 + defines the Backstage.LicenseServer product, and 2023.2.456 the first that carries the + harness of the Docker-based tests. --> + 2023.2.459 diff --git a/DockerBuild.ps1 b/DockerBuild.ps1 index fbe5584..ffa7606 100644 --- a/DockerBuild.ps1 +++ b/DockerBuild.ps1 @@ -68,6 +68,35 @@ .PARAMETER Dockerfile Path to a custom Dockerfile. Defaults to Dockerfile or Dockerfile.claude based on -Claude. +.PARAMETER OS + The operating system of the containers this run builds and starts: windows or linux. Defaults to the + operating system of the host. + + A build agent runs one engine, so there the default is the only possibility and anything else fails with + that reason. A development machine is the case that differs: Windows containers run on Docker Desktop + while Linux containers run on the Docker engine inside WSL. Asking for linux on a Windows development + machine therefore re-executes this script inside WSL, after checking that WSL, PowerShell 7 and a Linux + Docker engine of the host's own architecture are all there. + +.PARAMETER Test + Runs a Docker test container instead of the product build. Requires -Dockerfile and -Command. + The image is built and cached exactly as a product image is, with the same content-hash tag and the same + registry push and pull. What differs is that no product image chain is resolved and no product environment + variable is passed: the container gets what -Env asks for and nothing else, so the product credentials stay + out of it. The mounts are those of an ordinary build -- the repository, the caches and the dependency + repositories -- and the command runs with the repository as its working directory, so a test addresses what + it needs by a path relative to the repository root. Use it for a test that needs a container of its own. + +.PARAMETER Context + (-Test) The Docker build context directory. Defaults to the directory containing the Dockerfile. + +.PARAMETER Command + (-Test) The command line executed in the test container, instead of the build script. Mutually exclusive + with -Script: -Script names a PowerShell file that the container runs through pwsh, which the image must + therefore carry, while -Command is a command line run through the container's own shell, which any image + has. -Test requires -Command; -Script belongs to an ordinary build. The container's exit code + becomes the exit code of this script. + .PARAMETER RegistryImage Use a pre-built image from a registry, skipping Dockerfile build entirely. @@ -170,6 +199,11 @@ param( [switch]$StartVsmon, # Enable the remote debugger. [string]$Script = 'Build.ps1', # The build script to be executed inside Docker. [string]$Dockerfile, # Path to custom Dockerfile (defaults to Dockerfile or Dockerfile.claude based on -Claude). + [ValidateSet('windows', 'linux')] + [string]$OS, # The operating system of the containers. Defaults to the host's. On a Windows development machine, 'linux' re-executes this script inside WSL. + [switch]$Test, # Run an isolated Docker test container. Requires -Dockerfile and -Command. Builds no product image chain, mounts no repository directory, and passes no product environment variable. + [string]$Context, # (-Test) The Docker build context directory. Defaults to the directory containing the Dockerfile. + [string]$Command, # (-Test) The command line executed in the test container, instead of the build script. [string]$RegistryImage, # Use a pre-built image from a registry, skipping Dockerfile build entirely. [switch]$NoRegistry, # Ignore DOCKER_REGISTRY and its credentials; build locally without pulling or pushing. [switch]$NoInit, # Do not generate or call Init.g.ps1 (skips git config, safe.directory, etc). @@ -229,6 +263,203 @@ if ($null -eq $IsWindows) } $IsUnix = -not $IsWindows # Covers both Linux and macOS +# Converts a host path to the form WSL sees it under: C:\src\x becomes /mnt/c/src/x. Used only when this +# script hands its own arguments to a copy of itself running inside WSL, where every path it was given names +# a Windows location that the Linux side reaches through /mnt. +# Quotes a value as a PowerShell single-quoted literal, doubling any apostrophe it contains. The WSL hop builds +# a command line rather than passing arguments, so every value it forwards goes through here: a path holding an +# apostrophe would otherwise end the literal early and have its remainder parsed as PowerShell. +function ConvertTo-PowerShellLiteral([string]$value) +{ + return "'" + ( $value -replace "'", "''" ) + "'" +} + +function ConvertTo-WslHostPath([string]$path) +{ + if ($path -match '^([A-Za-z]):[\\/](.*)$') + { + return "/mnt/$( $Matches[1].ToLowerInvariant() )/$( $Matches[2] -replace '\\', '/' )" + } + + return $path -replace '\\', '/' +} + +# A caller that splats an ARRAY -- `& ./DockerBuild.ps1 @arguments` where $arguments is @('-Test', ...) -- does not +# bind these parameters by name. -BuildArgs takes the remaining arguments, so every value lands there and this +# script goes on to run an ordinary product build, mounting the source tree and forwarding the product secrets, +# instead of whatever mode was asked for. That failure is silent and its consequences are not, so it is refused +# here. Callers splat a hashtable: @{ Test = $true; OS = 'linux' }. +# The parameter names are read from the command rather than from $MyInvocation, whose MyCommand.Parameters is +# empty at script scope and silently matched nothing. +$declaredParameters = ( Get-Command -Name $PSCommandPath ).Parameters.Keys + +$misboundArguments = @( $BuildArgs | Where-Object { + $_ -and $_.StartsWith('-') -and $declaredParameters -contains $_.Substring(1) +} ) + +if ($misboundArguments.Count -gt 0) +{ + Write-Host "These arguments name parameters of this script but were not bound to them: $( $misboundArguments -join ', ' )" -ForegroundColor Red + Write-Host "That happens when a caller splats an array. Splat a hashtable instead, for example:" -ForegroundColor Red + Write-Host " `$arguments = @{ Test = `$true; OS = 'linux' }; ./DockerBuild.ps1 @arguments" -ForegroundColor Red + exit 1 +} + +# The operating system of the containers. The host's own is the default and, on a build agent, the only +# possibility: an agent runs one engine, and a build that needs the other one is routed to another agent. +# +# A development machine is where the two can differ, because Windows containers run on Docker Desktop while +# Linux containers run on the Docker engine inside WSL. Rather than special-casing every place that branches +# on the host -- the isolation flag, the escape character, the base image tag, the path conversion -- this +# script re-executes itself inside WSL, where it is running on a genuine Linux host and none of those places +# needs to know that a Windows machine started it. +$hostOs = if ($IsWindows) { 'windows' } else { 'linux' } + +if (-not $OS) +{ + $OS = $hostOs +} + +if ($OS -ne $hostOs) +{ + if ($IsUnix) + { + Write-Host "This host runs $hostOs containers, and -OS $OS was requested." -ForegroundColor Red + Write-Host "Windows containers need a Windows host. There is no counterpart of WSL in that direction." -ForegroundColor Red + exit 1 + } + + # Switching on an agent would run the build on an engine other than the one it was routed to, and would + # hide a wrong agent requirement behind a silent fallback. It is refused with the reason instead. + if ($env:IS_TEAMCITY_AGENT) + { + Write-Host "This agent runs $hostOs containers, and -OS $OS was requested." -ForegroundColor Red + Write-Host "A build agent does not switch. Route this build to an agent whose engine is $OS." -ForegroundColor Red + exit 1 + } + + if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) + { + Write-Host "Linux containers on a Windows development machine run on the Docker engine inside WSL, and wsl.exe was not found." -ForegroundColor Red + Write-Host "Install it with 'wsl --install', then install PowerShell 7 and a Docker engine inside the distribution." -ForegroundColor Red + exit 1 + } + + $wslPwsh = (& wsl.exe -- sh -c 'command -v pwsh' 2>&1 | Out-String).Trim() + + if ($LASTEXITCODE -ne 0 -or -not $wslPwsh) + { + Write-Host "PowerShell 7 is not installed inside the WSL distribution, so this script cannot run there." -ForegroundColor Red + Write-Host "Install pwsh in the distribution and try again." -ForegroundColor Red + exit 1 + } + + $wslEngineOs = (& wsl.exe -- docker version --format '{{.Server.Os}}' 2>&1 | Out-String).Trim() + + if ($LASTEXITCODE -ne 0 -or $wslEngineOs -ne 'linux') + { + Write-Host "The Docker engine inside WSL did not answer, or is not a Linux engine: $wslEngineOs" -ForegroundColor Red + Write-Host "Start it inside the distribution, for example with 'sudo service docker start'." -ForegroundColor Red + exit 1 + } + + # An engine of another architecture would build images this machine cannot run, and the failure would come + # much later and name something else, so it is checked here. + $wslEngineArch = (& wsl.exe -- docker version --format '{{.Server.Arch}}' 2>&1 | Out-String).Trim() + + $hostArch = switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) + { + 'X64' { 'amd64' } + 'Arm64' { 'arm64' } + default { $null } + } + + if ($hostArch -and $wslEngineArch -and $wslEngineArch -ne $hostArch) + { + Write-Host "The Docker engine inside WSL reports $wslEngineArch while this host is $hostArch." -ForegroundColor Red + Write-Host "An emulated engine produces images of the wrong architecture, so the run is refused." -ForegroundColor Red + exit 1 + } + + # Rebuild the invocation for the Linux side. Everything is forwarded as it was given except -OS, which has + # been answered by the hop itself, and the parameters holding a path, which name Windows locations. + $pathParameters = @('Dockerfile', 'Context', 'PostInit', 'BuildAgentPath') + $wslArguments = @() + + foreach ($parameter in $PSBoundParameters.GetEnumerator()) + { + if ($parameter.Key -eq 'OS' -or $parameter.Key -eq 'BuildArgs') + { + continue + } + + $value = $parameter.Value + + if ($value -is [System.Management.Automation.SwitchParameter]) + { + if ($value.IsPresent) + { + $wslArguments += "-$( $parameter.Key )" + } + + continue + } + + $items = @() + + foreach ($item in @($value)) + { + $text = [string]$item + + if ($pathParameters -contains $parameter.Key) + { + $text = ConvertTo-WslHostPath $text + } + elseif ($parameter.Key -eq 'Mount') + { + # A mount is a host directory, optionally followed by ':w'. Only the directory is a path. + if ($text -match '^(.*):w$') + { + $text = ( ConvertTo-WslHostPath $Matches[1] ) + ':w' + } + else + { + $text = ConvertTo-WslHostPath $text + } + } + + $items += $text + } + + # A collection is written as an array literal, and a single value as a scalar. Joining with commas will + # not do: `pwsh -File` passes arguments as literal strings, so '-Mount a,b' arrives as the one element + # 'a,b' rather than as two mounts, and the second would silently become part of a path that does not + # exist. Repeating the parameter is not an option either -- `pwsh -File` rejects that outright -- so the + # hop goes through -Command, where an array literal means what it says. + if ($items.Count -gt 1) + { + $wslArguments += "-$( $parameter.Key ) @(" + ( ( $items | ForEach-Object { ConvertTo-PowerShellLiteral $_ } ) -join ',' ) + ")" + } + else + { + $wslArguments += "-$( $parameter.Key ) " + ( ConvertTo-PowerShellLiteral $items[0] ) + } + } + + foreach ($buildArg in $BuildArgs) + { + $wslArguments += ConvertTo-PowerShellLiteral ([string]$buildArg) + } + + Write-Host "Linux containers run on the Docker engine inside WSL; re-executing this script there." -ForegroundColor Cyan + + $wslCommand = "& " + ( ConvertTo-PowerShellLiteral ( ConvertTo-WslHostPath $PSCommandPath ) ) + " " + ( $wslArguments -join ' ' ) + + & wsl.exe -- $wslPwsh -NoProfile -Command $wslCommand + + exit $LASTEXITCODE +} + # Docker isolation is Windows-only. Windows Server supports process isolation (faster, # no per-container VM); Windows Desktop (client) only reliably runs hyperv isolation. # Auto-detect by Windows edition unless -Isolation was passed explicitly. @@ -308,6 +539,52 @@ try exit 1 } + # -Test shares this script's image caching and registry handling, and nothing else. What it excludes is + # implied rather than requested, so that a test cannot acquire the build container's credentials by + # forgetting a flag. + if ($Test) + { + if (-not $Dockerfile) + { + Write-Error "-Test requires -Dockerfile." + exit 1 + } + + if (-not $Command) + { + Write-Error "-Test requires -Command." + exit 1 + } + + foreach ($excluded in @('Claude', 'Interactive', 'BuildImage', 'StartVsmon', 'PostInit', 'KeepInit', 'Script')) + { + if ( $PSBoundParameters.ContainsKey($excluded)) + { + Write-Error "-Test cannot be combined with -$excluded." + exit 1 + } + } + + $testContext = if ($Context) { $Context } else { Split-Path -Parent $Dockerfile } + + if (-not (Test-Path -LiteralPath $testContext -PathType Container)) + { + Write-Error "The build context directory '$testContext' does not exist." + exit 1 + } + + $script:TestContextDirectory = (Resolve-Path -LiteralPath $testContext).Path + + # Init.g.ps1 is not invoked in a test container, because a test image is chosen for the tool chain under + # test and is not required to carry PowerShell 7. That is the only reason. The environment it would have + # inlined reaches the container as -e arguments instead, so a test container is configured like a build + # container: this repository builds it from a Dockerfile it owns and runs it, and it is trusted the same + # way. Withholding the environment bought no isolation worth the cost, because NUGET_PACKAGES is in that + # set and without it NuGet falls back to $HOME/.nuget/packages and the host cache mapped below is never + # read -- every test restored over the network, the opposite of what the mount is for. + $NoInit = $true + } + # Validate and parse -Cpus parameter $isDynamicCpus = $false if ($Cpus -eq 'dynamic') @@ -834,6 +1111,13 @@ try # directory is treated as an empty context by Get-ContentHash, and Build-OneImage creates it before building. function Get-ContextDirFor([string]$dfPath) { + # A test image takes its context from the test's own directory. The test owns the files its Dockerfile + # copies, and it is not part of the repository's image chain. + if ($Test) + { + return $script:TestContextDirectory + } + return Join-Path $dockerContextDirectory (Get-DockerfileStem $dfPath) } @@ -908,7 +1192,18 @@ try # OS discriminator so ltsc2025 / ltsc2022 produce distinct tags of the same image name. Propagates to # descendants through $baseFold. - $extra = "os=$windowsVersion|base=$baseFold" + # + # The architecture discriminates too, always, including amd64. The content hash is otherwise identical + # for the same Dockerfile built on x64 and on ARM64: both agents compute one tag, the first pushes an + # image of its own architecture, and the second pulls it and fails -- with "no matching manifest" if the + # registry rejects it, or, worse, with "exec /bin/sh: exec format error" once it has been pulled, which + # names neither the image nor the architecture. + # + # Folding it in only for non-amd64 was tried and is not enough: it gives a future ARM64 build its own + # tag, but leaves amd64 on the name an ARM64 build may already have pushed to, so the poisoning survives + # the fix. Including it everywhere changes every tag once, which is a rebuild, and is what actually + # abandons the bad ones. + $extra = "os=$windowsVersion|arch=$( Get-DockerArchitecture )|base=$baseFold" # Fold the weekly stamp only for images that bake the update.timestamp cache-buster (the Claude leaf), so # @latest npm installs of the Claude CLI and plug-ins refresh once per UTC week. @@ -917,7 +1212,10 @@ try $hash = Get-ContentHash -DockerfilePath $dfPath -ContextDirectory (Get-ContextDirFor $dfPath) -DayStamp $hashDayStamp -ExtraInput $extra # The image NAME carries the product/version prefix ($DockerImagePrefix); the Dockerfile file stem does # not. e.g. stem 'build' -> image '-build'. ARG BASE_IMAGE references stems (prefix-free). - $imageName = "$DockerImagePrefix-$( Get-DockerfileStem $dfPath )" + # Lower-cased because a Docker repository name must be lower case, while the stem is the file name as + # written: a custom -Dockerfile named 'Dockerfile', which is the conventional name and the one the + # container tests use, would otherwise produce an invalid tag and fail the build. + $imageName = "$DockerImagePrefix-$( Get-DockerfileStem $dfPath )".ToLowerInvariant() $tag = if ($dockerRegistry) { "${dockerRegistry}/${imageName}:${hash}" } else { "${imageName}:${hash}" } $script:resolvedTags[$key] = $tag return $tag @@ -932,6 +1230,12 @@ try { return @" ARG MOUNTPOINTS +# The RUN below is PowerShell, so the shell has to be declared. A Windows image inherits whatever SHELL its base +# declares, and that differs between the images this script is pointed at: the .NET Framework SDK images declare +# PowerShell, while the .NET SDK images leave the Docker default of cmd, which fails on the first brace. The +# product build image happens to declare PowerShell, which is why this only surfaced once a test container -- on +# an arbitrary base image -- was given mounts and therefore a boot image. +SHELL ["powershell", "-Command", "`$ErrorActionPreference = 'Stop';"] RUN if (`$env:MOUNTPOINTS) { `` `$mounts = `$env:MOUNTPOINTS -split ';'; `` foreach (`$dir in `$mounts) { `` @@ -1859,7 +2163,8 @@ RUN if [ -n "`$MOUNTPOINTS" ]; then \ param( [Parameter(Mandatory = $true)] [string]$Path, - [switch]$Writable + [switch]$Writable, + [switch]$Requested # Asked for by -Mount, rather than part of the default build-container set. ) $normalizedPath = $Path.TrimEnd('\', '/') @@ -2012,6 +2317,7 @@ RUN if [ -n "`$MOUNTPOINTS" ]; then \ } if ($Claude) + { # Use Claude-specific environment variables (filtered and renamed) New-ClaudeEnvHashtable @@ -2327,7 +2633,7 @@ RUN if [ -n "`$MOUNTPOINTS" ]; then \ "readonly" } Write-Host "Mounting from -Mount pattern '$pattern': $dirPath ($rwStatus)" -ForegroundColor Cyan - Add-VolumeMount -Path $dirPath -Writable:$isWritable + Add-VolumeMount -Path $dirPath -Writable:$isWritable -Requested } } } @@ -2350,7 +2656,7 @@ RUN if [ -n "`$MOUNTPOINTS" ]; then \ "readonly" } Write-Host "Mounting from -Mount: $pattern ($rwStatus)" -ForegroundColor Cyan - Add-VolumeMount -Path $pattern -Writable:$isWritable + Add-VolumeMount -Path $pattern -Writable:$isWritable -Requested } else { @@ -2381,7 +2687,8 @@ RUN if [ -n "`$MOUNTPOINTS" ]; then \ } } - # Execute auto-generated DockerMounts.g.ps1 script to add more directory mounts. + # Execute auto-generated DockerMounts.g.ps1 script to add more directory mounts. A test container gets these + # like any other build: a test that consumes a source dependency needs the same repositories the build needs. $dockerMountsScript = Join-Path $EngPath 'DockerMounts.g.ps1' if (Test-Path $dockerMountsScript) { @@ -2837,7 +3144,40 @@ $envVarAssignments$gitConfigCommands$postInitCommands $volumeArgs += @("-v", $mapping) } - if ($Claude) + if ($Test) + { + Write-Host "Running the test command in the container." -ForegroundColor Green + + # The command runs through the container's own shell rather than through pwsh, because a test image + # is chosen for the tool chain under test and is not required to carry PowerShell 7. + $testCommandArgs = if ($IsUnix) + { + @('sh', '-c', $Command) + } + else + { + @('cmd', '/S', '/C', $Command) + } + + $pwshArgs = $null + $dockerArgs = @() + $inlineScript = $null + $needsMcpCleanup = $false + + # The same environment a build container gets, as -e arguments rather than through Init.g.ps1: see + # where $NoInit is set for why that script cannot be invoked in a test image. What -Env named is + # already folded into this set by New-EnvHashtable, so an explicitly named variable is here too. + $envArgs = @() + + if ($script:ContainerEnvironmentVariables) + { + foreach ($key in $script:ContainerEnvironmentVariables.Keys | Sort-Object) + { + $envArgs += @('-e', "$key=$( $script:ContainerEnvironmentVariables[$key] )") + } + } + } + elseif ($Claude) { # MCP server configuration $mcpPort = $null @@ -3039,6 +3379,13 @@ $envVarAssignments$gitConfigCommands$postInitCommands $dockerCmd += $volumeArgs $dockerCmd += $envArgs + # A test command runs with the mounted repository as its working directory, so that a test addresses + # what it needs by a path relative to the repository root rather than by one it has to compute. + if ($Test) + { + $dockerCmd += @('-w', $ContainerSourceDir) + } + # Add port mappings from -Ports parameter if ($Ports -and $Ports.Count -gt 0) { @@ -3054,7 +3401,19 @@ $envVarAssignments$gitConfigCommands$postInitCommands $dockerCmd += @('--label', "postsharp.build=$Label") } - if ($pwshArgs) + if ($Test) + { + # The label the launcher removes by, when a test overruns its timeout. The container outlives the + # process that started it, so without this a timed-out test leaves it running on the agent. + if ($env:POSTSHARP_DOCKER_TEST_RUN_ID) + { + $dockerCmd += @('--label', "postsharp.test-run=$( $env:POSTSHARP_DOCKER_TEST_RUN_ID )") + } + + # The working directory was set above, to the mounted repository. + $dockerCmd += @($ImageTag) + $testCommandArgs + } + elseif ($pwshArgs) { $dockerCmd += @('-w', $ContainerCallingDir, $ImageTag, $pwshPath, $pwshArgs, '-Command', $inlineScript) } diff --git a/README.md b/README.md index 39e4a5b..d4bdbf4 100644 --- a/README.md +++ b/README.md @@ -203,19 +203,32 @@ Commit your work before you reformat, so that the reformatting is a commit of it ### Running the tests -`./Build.ps1 test` runs the suite on SQLite, which needs no server and keeps the loop short. Run the -same tests again against each engine that customers deploy before you open a pull request. The +`./Build.ps1 test` runs the suite on SQLite, which needs no server and keeps the loop short. The same +tests run again against each engine that customers deploy, each in a container of its own. The engines differ in the collation, in the column types and in the lock that serializes the lease -requests, so a defect can appear on one of them alone: +requests, so a defect can appear on one of them alone. Run them before you open a pull request: ``` -./eng/TestDatabase.ps1 -Engine SqlServer -./eng/TestDatabase.ps1 -Engine PostgreSql +pwsh ./eng/RunDockerTests.ps1 +pwsh ./eng/RunDockerTests.ps1 -Test PostgreSql +pwsh ./tests/docker/PostgreSql/RunTest.ps1 -Platform linux-x64 ``` -Each run starts its server in a container, waits for it, and runs `Build.ps1 test` against it. Docker -is the only prerequisite. To use a server of your own instead, set the connection string and the -script leaves the server alone: +The first command runs every test that the container engine of this host can run. The second runs one +of them, reported the way the continuous integration build reports it. The third runs the same test +without the launcher, which is the shortest path while a test is being written. + +The host needs PowerShell 7.5 and a container engine, and nothing else: the .NET SDK and the database +server live in the image of the test. On Windows, Linux containers run on the engine inside the +Windows Subsystem for Linux, and the scripts reach it themselves. + +Each test is a directory under `tests/docker`: `test.psd1` names the platforms the test applies to, +`Dockerfile` installs the server and the SDK, `RunTest.ps1` starts the container through +`DockerBuild.ps1 -Test`, and `run.sh` starts the server inside the container and runs `dotnet test`. +The continuous integration build runs them in the configuration `Docker Tests (Linux x64)`. See +[the harness](https://github.com/postsharp-ops/PostSharp.Engineering/blob/HEAD/doc/docker-tests.md). + +To run the suite against a server of your own instead, name it and run the tests directly: ``` $env:LICENSESERVER_TEST_SQLSERVER = "Server=127.0.0.1,1433;User Id=sa;Password=;TrustServerCertificate=True;Encrypt=False" @@ -230,10 +243,6 @@ Entity Framework model agree. The databases are named `licenseserver_test_` foll number. They are reused during the run, and the next run drops the ones an interrupted run left behind, so the login needs the permission to create a database. -The continuous integration build runs both of them, in the configurations `Tests on SQL Server` and -`Tests on PostgreSQL`. Each one runs `eng\TestDatabase.ps1` in an image that carries its server, -described by `eng\src\Docker\SqlServerComponent.cs` and `eng\src\Docker\PostgreSqlComponent.cs`. - ### Running locally ``` diff --git a/eng/RunDockerTests.ps1 b/eng/RunDockerTests.ps1 new file mode 100644 index 0000000..7393bfd --- /dev/null +++ b/eng/RunDockerTests.ps1 @@ -0,0 +1,688 @@ +# The original of this file is in /src/PostSharp.Engineering.BuildTools/Resources/RunDockerTests.ps1. +# You can generate this file using `./Build.ps1 generate-scripts`. +# Documentation: https://raw.githubusercontent.com/postsharp/PostSharp.Engineering/HEAD/doc/docker-tests.md + +<# +.SYNOPSIS + Runs the Docker-based tests that apply to one platform, and reports each of them to TeamCity. + +.DESCRIPTION + A Docker-based test is a test that needs a container of its own. Each test is a directory containing a + manifest (test.psd1) and an entry point (RunTest.ps1). This script discovers those directories, selects + the tests whose manifest lists the current platform, runs them one at a time, and reports the result of + each one. + + The only requirements on the host are PowerShell 7.5 and a container engine whose operating system and + processor architecture match the platform. The tool chain under test lives in the test's own image, so + this script never needs the .NET SDK, MSBuild or Visual Studio. + + A test reports its own result through its exit code alone. This script owns the TeamCity protocol, so a + test stays runnable by hand. + +.PARAMETER Path + The directory containing the test directories, relative to this script. Defaults to the value the product + declares, which is what generate-scripts wrote into this file. + +.PARAMETER Platform + The platform to run: win-x64, win-arm64, linux-x64 or linux-arm64. Defaults to the platform of the + container engine this host is running. + +.PARAMETER Test + Runs only the test of this name. Without it, every test that applies to the platform is run. + +.NOTES + A test reports its outcome by its exit code: 0 passed, 4 skipped, anything else failed. The skip code is + for a scenario the test finds it cannot reproduce on this host, which is different from the manifest's Skip: + that one is known in advance, this one only once the test has looked. A skipping test should write a line + beginning with 'SKIPPED:', which becomes the reason reported. + +.PARAMETER NoTeamCity + Writes plain text instead of TeamCity service messages. Implied when TEAMCITY_VERSION is not set. + +.EXAMPLE + ./RunDockerTests.ps1 -Platform linux-x64 + +.EXAMPLE + ./RunDockerTests.ps1 -Platform win-x64 -Test Issue15-AssetsFileV4 +#> + +[CmdletBinding(PositionalBinding = $false)] +param( + [ValidateSet('win-x64', 'win-arm64', 'linux-x64', 'linux-arm64')] + [string]$Platform, + [string]$Path, # Defaults to $DockerTestsPath below. + [string]$Test, + [switch]$NoTeamCity +) + +# Require PowerShell 7.5 or higher (run with pwsh, not powershell) +if ($PSVersionTable.PSVersion -lt [Version]'7.5') +{ + Write-Error "This script requires PowerShell 7.5 or higher (run with 'pwsh', not 'powershell'). Current version: $( $PSVersionTable.PSVersion )" + exit 1 +} + +#### +# These settings are replaced by the generate-scripts command. +# +# Where the tests are is a fact about this repository, not a choice a caller makes. This script is generated into +# the repository root, so it is fixed relative to it, and a build configuration that had to pass it would be one +# more place for it to drift. +# +# Where what the tests consume is, is not here at all: a test resolves that for itself, from where it lives. It is +# a fact about the product, and this script has no opinion about it. +$DockerTestsPath = 'tests/docker' +$EngPath = 'eng' +#### + +if (-not $Path) +{ + $Path = $DockerTestsPath +} + +$DefaultTimeoutSeconds = 900 + +# The exit code by which a test reports that it decided, at run time, that its scenario cannot occur on this +# host, and that it therefore tested nothing. The manifest's Skip covers what is known before the test runs; +# this covers what is only discoverable once it has looked -- an SDK that ships a pack the scenario needs +# absent, a case-insensitive file system, a kernel without the facility under test. Reporting those as failures +# would train people to ignore red, and reporting them as passes would claim coverage that does not exist. +$SkipExitCode = 4 + +# TeamCity reads a service message up to the first unescaped delimiter, and Docker output is full of brackets, +# so an unescaped value silently truncates or corrupts the report. The vertical bar is replaced first: doing it +# later would double the bars introduced by the other replacements. +function ConvertTo-TeamCityValue([string]$value) +{ + if ($null -eq $value) + { + return '' + } + + $escaped = $value.Replace('|', '||') + $escaped = $escaped.Replace("'", "|'") + $escaped = $escaped.Replace('[', '|[') + $escaped = $escaped.Replace(']', '|]') + $escaped = $escaped.Replace("`r", '|r') + $escaped = $escaped.Replace("`n", '|n') + + return $escaped +} + +function Write-ServiceMessage([string]$name, [hashtable]$attributes) +{ + if ($NoTeamCity) + { + return + } + + $pairs = $attributes.Keys | Sort-Object | ForEach-Object { + "$_='$( ConvertTo-TeamCityValue $attributes[$_] )'" + } + + Write-Host "##teamcity[$name $( $pairs -join ' ' )]" +} + +# The operating system a platform identifier asks for, in the spelling DockerBuild.ps1 -OS uses. +function Get-RequestedOs([string]$platform) +{ + return $(if ($platform -like 'linux-*') { 'linux' } else { 'windows' }) +} + +# Asks the engine that would run the given operating system for its own platform. Which engine that is depends +# on the host: a build agent has one, while a Windows development machine runs Windows containers on Docker +# Desktop and Linux containers on the Docker engine inside WSL. The engine is asked rather than the host +# because only the engine knows what it can actually run. +function Get-EnginePlatform([string]$requestedOs) +{ + $useWsl = $IsWindows -and $requestedOs -eq 'linux' -and -not $env:IS_TEAMCITY_AGENT + + if ($useWsl) + { + if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) + { + Write-Host "Linux containers on a Windows development machine run on the Docker engine inside WSL, and wsl.exe was not found." -ForegroundColor Red + Write-Host "Install it with 'wsl --install', then install PowerShell 7 and a Docker engine inside the distribution." -ForegroundColor Red + exit 1 + } + + $engineOs = (& wsl.exe -- docker version --format '{{.Server.Os}}' 2>&1 | Out-String).Trim() + $engineArchRaw = (& wsl.exe -- docker version --format '{{.Server.Arch}}' 2>&1 | Out-String).Trim() + } + else + { + $engineOs = (docker version --format '{{.Server.Os}}' 2>&1 | Out-String).Trim() + + if ($LASTEXITCODE -ne 0) + { + Write-Host "No container engine is available on this host: $engineOs" -ForegroundColor Red + Write-Host "A Docker test host needs PowerShell 7.5 and a container engine, and nothing else." -ForegroundColor Red + exit 1 + } + + $engineArchRaw = (docker version --format '{{.Server.Arch}}' 2>&1 | Out-String).Trim() + } + + $os = switch ($engineOs) + { + 'windows' { 'win' } + 'linux' { 'linux' } + default { $null } + } + + $arch = switch ($engineArchRaw) + { + 'amd64' { 'x64' } + 'x86_64' { 'x64' } + 'arm64' { 'arm64' } + 'aarch64' { 'arm64' } + default { $null } + } + + if (-not $os -or -not $arch) + { + $where = if ($useWsl) { 'inside WSL' } else { 'on this host' } + Write-Host "The container engine $where reports an unsupported platform: os='$engineOs', arch='$engineArchRaw'." -ForegroundColor Red + exit 1 + } + + return "$os-$arch" +} + +function Read-TestManifest([string]$manifestPath) +{ + try + { + $manifest = Import-PowerShellDataFile -LiteralPath $manifestPath -ErrorAction Stop + } + catch + { + return @{ Error = "The manifest cannot be read: $( $_.Exception.Message )" } + } + + if (-not $manifest.Platforms) + { + return @{ Error = 'The manifest does not declare Platforms.' } + } + + $timeout = if ($manifest.TimeoutSeconds) { [int]$manifest.TimeoutSeconds } else { $DefaultTimeoutSeconds } + + return @{ + Platforms = @($manifest.Platforms) + TimeoutSeconds = $timeout + Skip = $manifest.Skip + } +} + +# Runs the product's own preparation, once, before any test. +# +# What a suite needs before it can run is not the same in every repository: packages may arrive as an archive +# that has to be expanded, a fixture may have to be materialised, a tool may have to be fetched. That belongs to +# the product, not here, so the launcher looks for one script at a known place and runs it if it is there. +# +# Once, not per test. A test's entry point runs for each test, so anything expensive done there is done again +# for every one of them. +function Invoke-ProductPreparation([string]$repositoryRoot) +{ + $script = Join-Path $repositoryRoot ( Join-Path $EngPath 'PrepareDockerTests.ps1' ) + + if (-not (Test-Path -LiteralPath $script)) + { + return + } + + Write-Host "Preparing the suite with '$script'." -ForegroundColor Green + + $global:LASTEXITCODE = 0 + + try + { + & $script + } + catch + { + # Without this the run continued: an exception from the script left $LASTEXITCODE at whatever the last + # native command had set, which is 0 when there was none, so the check below passed and the suite went on + # to find no tests and report success. + throw "'$script' failed: $( $_.Exception.Message )" + } + + if ($LASTEXITCODE -ne 0) + { + throw "'$script' failed with exit code $LASTEXITCODE." + } +} + +# Fails unless the repository root has a NuGet configuration, which is what lets a test resolve the product from +# the packages the build produced rather than from nuget.org. +# +# That distinction is the whole point. A Docker test consumes the product through a PackageReference, and the +# version it asks for is often one that has been released, so nuget.org can satisfy it. Without a source for the +# local packages and a packageSourceMapping sending the product's packages there, the restore would quietly +# succeed against the public package and the test would report a pass having verified binaries that nobody just +# built. A test that silently checks the wrong thing is worse than one that fails, so a missing configuration is +# fatal rather than tolerated. +# +# This only checks, it does not write. Putting the file there is the harness's job and it already does it: a +# configuration that has a build snapshot dependency is generated with a CopyNuGetConfig step that copies +# nuget.restored.config -- published beside the packages -- to the root before this script runs. On a prepared +# developer machine the root nuget.config that Build.ps1 writes is already there. Copying it again here would +# only repeat what one of those two has done. +function Assert-NuGetConfiguration([string]$repositoryRoot) +{ + $configuration = Join-Path $repositoryRoot 'nuget.config' + + if (-not (Test-Path -LiteralPath $configuration)) + { + throw "'$configuration' does not exist, so the packages these tests consume cannot be located and the restore would fall back to nuget.org. Prepare the repository, or copy artifacts/publish/private/nuget.restored.config to the root, before running the Docker tests." + } +} + +# Kills a process and everything it started. Stop-Process alone does not: it terminates the one process, and the +# children it spawned are reparented rather than killed. +function Stop-ProcessTree([int]$processId) +{ + # Win32_Process is the only way to walk the tree on Windows. On Linux and macOS pgrep does the same job, and + # the container cleanup below is what actually matters there in any case. + $children = if ($IsWindows) + { + @( Get-CimInstance Win32_Process -Filter "ParentProcessId = $processId" -ErrorAction SilentlyContinue | + ForEach-Object { [int]$_.ProcessId } ) + } + else + { + @( & pgrep -P $processId 2>$null | ForEach-Object { [int]$_ } ) + } + + foreach ($child in $children) + { + Stop-ProcessTree $child + } + + Stop-Process -Id $processId -Force -ErrorAction SilentlyContinue +} + +# Force-removes every container labelled with this test run. A container outlives the process that started it, +# so a timeout that killed only the process would leave it running. +function Remove-TestContainers([string]$runId) +{ + $containers = @(& docker ps --all --quiet --filter "label=postsharp.test-run=$runId" 2>$null) + + if ($containers.Count -eq 0) + { + return + } + + Write-Host "Removing $( $containers.Count ) container(s) left by the timed-out test." -ForegroundColor Yellow + & docker rm --force @containers 2>&1 | Out-Null +} + +# Copies whatever has been appended to the redirected output files since the last call, so that a running test +# is visible while it runs. FileShare.ReadWrite is required: the child process still holds these files open, +# and opening them any other way would fail. +function Copy-NewOutput([hashtable]$positions) +{ + foreach ($file in @($positions.Keys)) + { + if (-not (Test-Path -LiteralPath $file)) + { + continue + } + + $stream = $null + + try + { + $stream = [System.IO.File]::Open($file, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite) + + if ($stream.Length -gt $positions[$file]) + { + $stream.Position = $positions[$file] + $reader = New-Object System.IO.StreamReader($stream) + $text = $reader.ReadToEnd() + $positions[$file] = $stream.Position + + if ($text) + { + Write-Host $text -NoNewline + } + } + } + catch + { + # A transient sharing failure only delays the output to the next pass, and must never fail the test. + } + finally + { + if ($stream) + { + $stream.Dispose() + } + } + } +} + +# Runs one test to completion and returns its outcome. The output is redirected to files so that the whole of +# it can be attached to the test even when the test is killed on its timeout, and is echoed as it arrives so +# that a long test is not indistinguishable from a hung one. Pulling a Windows base image takes tens of +# minutes, and a silent log for that long is a log nobody can act on. +function Invoke-OneTest([string]$testDirectory, [int]$timeoutSeconds, [string]$platform) +{ + $rootDirectory = Join-Path ([System.IO.Path]::GetTempPath()) "dockertest-$( [System.Guid]::NewGuid().ToString('n') )" + + # The captured output is kept outside the directory handed to the test, so that a test writing into its own + # scratch directory cannot collide with it. + # Only the captured output. A test is given no scratch directory: it runs against the repository, which + # DockerBuild.ps1 mounts into the container, rather than against anything staged for it here. + $logDirectory = Join-Path $rootDirectory 'log' + New-Item -ItemType Directory -Path $logDirectory -Force | Out-Null + + $stdOutFile = Join-Path $logDirectory 'stdout.log' + $stdErrFile = Join-Path $logDirectory 'stderr.log' + + # Every container this test starts is labelled with this, so that a timeout can remove them. Killing the + # test process is not enough: it is waiting on `docker run`, and the container is a child of the engine, not + # of the process tree. Left behind, it goes on holding CPU, memory and the image, and a later test that + # expects an idle machine -- or the same port, or the same mount -- fails for a reason that has nothing to + # do with it. + $runId = [System.Guid]::NewGuid().ToString('n') + $env:POSTSHARP_DOCKER_TEST_RUN_ID = $runId + + try + { + $arguments = @( + '-NonInteractive' + '-NoProfile' + '-File' + (Join-Path $testDirectory 'RunTest.ps1') + '-Platform' + $platform + ) + + # [Environment]::ProcessPath is this pwsh, so the test runs under the same PowerShell that discovered it. + $process = Start-Process -FilePath ([Environment]::ProcessPath) ` + -ArgumentList $arguments ` + -PassThru ` + -NoNewWindow ` + -RedirectStandardOutput $stdOutFile ` + -RedirectStandardError $stdErrFile + + $timedOut = $false + $positions = @{ $stdOutFile = [long]0; $stdErrFile = [long]0 } + $deadline = [DateTime]::UtcNow.AddSeconds($timeoutSeconds) + + while (-not $process.HasExited) + { + if ([DateTime]::UtcNow -gt $deadline) + { + $timedOut = $true + break + } + + Copy-NewOutput $positions + Start-Sleep -Milliseconds 500 + } + + Copy-NewOutput $positions + + if ($timedOut) + { + Write-Host "The test exceeded its timeout of $timeoutSeconds seconds and is being killed." -ForegroundColor Red + + # The whole tree: the test process is a pwsh that started another to run DockerBuild.ps1, and killing + # only the one that was waited on leaves the rest running. + Stop-ProcessTree $process.Id + + $process.WaitForExit(30 * 1000) | Out-Null + + Remove-TestContainers $runId + } + else + { + # The parameterless overload also waits for the redirected streams to be flushed. Without it the + # captured output can be read back short of its last lines. + $process.WaitForExit() + } + + $output = @() + + foreach ($file in @($stdOutFile, $stdErrFile)) + { + if (Test-Path -LiteralPath $file) + { + $content = Get-Content -LiteralPath $file -Raw -ErrorAction SilentlyContinue + + if ($content) + { + $output += $content + } + } + } + + return @{ + TimedOut = $timedOut + ExitCode = if ($timedOut) { -1 } else { $process.ExitCode } + Output = ($output -join "`n") + } + } + finally + { + Remove-Item -LiteralPath $rootDirectory -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item Env:\POSTSHARP_DOCKER_TEST_RUN_ID -ErrorAction SilentlyContinue + } +} + +Push-Location +try +{ + # This script lives in the engineering directory, not at the root, so everything it addresses relative to + # the repository is resolved from the parent. + $repositoryRoot = ( Resolve-Path ( Join-Path $PSScriptRoot '..' ) ).Path + + Set-Location $repositoryRoot + + if (-not $env:TEAMCITY_VERSION) + { + $NoTeamCity = $true + } + + if (-not $Platform) + { + # Without a platform, the host's own engine is the one that answers, which is what a developer running + # the suite with no argument means. No requested operating system means no hop into WSL. + $Platform = Get-EnginePlatform $null + Write-Host "Platform not specified; using the container engine's own platform: $Platform" -ForegroundColor Cyan + } + else + { + $enginePlatform = Get-EnginePlatform ( Get-RequestedOs $Platform ) + + if ($Platform -ne $enginePlatform) + { + # This is the whole point of one configuration per platform. A mismatch on an agent means the build + # was routed to the wrong one, and every test would otherwise fail for the same uninformative + # reason. + Write-Host "The engine that would run the '$Platform' tests reports '$enginePlatform' instead." -ForegroundColor Red + + if ($env:IS_TEAMCITY_AGENT) + { + Write-Host "Check the agent requirements of this build configuration." -ForegroundColor Red + } + + exit 1 + } + } + + if (-not (Test-Path -LiteralPath $Path -PathType Container)) + { + Write-Host "The test directory '$Path' does not exist." -ForegroundColor Red + exit 1 + } + + Invoke-ProductPreparation $repositoryRoot + Assert-NuGetConfiguration $repositoryRoot + +$testDirectories = Get-ChildItem -LiteralPath $Path -Directory | + Where-Object { Test-Path -LiteralPath (Join-Path $_.FullName 'test.psd1') } | + Sort-Object Name + + if ($Test) + { + $testDirectories = $testDirectories | Where-Object { $_.Name -eq $Test } + + if (-not $testDirectories) + { + Write-Host "There is no test named '$Test' in '$Path'." -ForegroundColor Red + exit 1 + } + } + + if (-not $testDirectories) + { + Write-Host "No test was found in '$Path'. A test is a directory containing test.psd1." -ForegroundColor Yellow + exit 0 + } + + $suiteName = "DockerTests.$Platform" + Write-ServiceMessage 'testSuiteStarted' @{ name = $suiteName } + + $failed = @() + $passed = 0 + $ignored = 0 + + foreach ($testDirectory in $testDirectories) + { + $testName = $testDirectory.Name + $manifest = Read-TestManifest (Join-Path $testDirectory.FullName 'test.psd1') + + Write-ServiceMessage 'testStarted' @{ name = $testName; captureStandardOutput = 'false' } + + $durationMilliseconds = 0 + + # Every test's outcome is decided inside this try, so that one test's failure never ends the run. The + # launcher's own exit code is the aggregate, reported at the end. testFinished is written once, in the + # finally, and therefore always after the testFailed or testIgnored that explains the outcome. + try + { + if ($manifest.Error) + { + Write-Host "$testName : $( $manifest.Error )" -ForegroundColor Red + Write-ServiceMessage 'testFailed' @{ name = $testName; message = $manifest.Error } + $failed += $testName + } + elseif ($manifest.Skip) + { + Write-Host "$testName : skipped -- $( $manifest.Skip )" -ForegroundColor Yellow + Write-ServiceMessage 'testIgnored' @{ name = $testName; message = $manifest.Skip } + $ignored++ + } + elseif ($manifest.Platforms -notcontains $Platform) + { + $reason = "This test declares $( $manifest.Platforms -join ', ' ) and not $Platform." + Write-Host "$testName : not applicable -- $reason" -ForegroundColor DarkGray + Write-ServiceMessage 'testIgnored' @{ name = $testName; message = $reason } + $ignored++ + } + elseif (-not (Test-Path -LiteralPath (Join-Path $testDirectory.FullName 'RunTest.ps1'))) + { + $reason = 'The test directory has no RunTest.ps1.' + Write-Host "$testName : $reason" -ForegroundColor Red + Write-ServiceMessage 'testFailed' @{ name = $testName; message = $reason } + $failed += $testName + } + else + { + Write-Host "Running $testName on $Platform." -ForegroundColor Green + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + $result = Invoke-OneTest $testDirectory.FullName $manifest.TimeoutSeconds $Platform + $stopwatch.Stop() + $durationMilliseconds = [int]$stopwatch.Elapsed.TotalMilliseconds + + if ($result.Output) + { + # Not written to the console again: it was echoed as the test produced it. Only the service + # message is sent, so that the whole of the output is attached to the test in the report. + Write-ServiceMessage 'testStdOut' @{ name = $testName; out = $result.Output } + } + + if ($result.TimedOut) + { + $message = "The test exceeded its timeout of $( $manifest.TimeoutSeconds ) seconds." + Write-ServiceMessage 'testFailed' @{ name = $testName; message = $message } + $failed += $testName + } + elseif ($result.ExitCode -eq $SkipExitCode) + { + # The reason is taken from the last line the test wrote beginning with SKIPPED:, which is how + # these tests already explain themselves. Without one the exit code still counts, because the + # decision belongs to the test; only the explanation is missing. + $skipLine = @( $result.Output -split "`n" | Where-Object { $_ -match '^\s*SKIPPED:' } ) | + Select-Object -Last 1 + + $message = if ($skipLine -match '^\s*SKIPPED:\s*(.+?)\s*$') + { + $Matches[1] + } + else + { + "The test reported exit code $SkipExitCode, meaning its scenario cannot occur on this host." + } + + Write-Host "$testName : skipped -- $message" -ForegroundColor Yellow + Write-ServiceMessage 'testIgnored' @{ name = $testName; message = $message } + $ignored++ + } + elseif ($result.ExitCode -ne 0) + { + $message = "The test failed with exit code $( $result.ExitCode )." + Write-ServiceMessage 'testFailed' @{ name = $testName; message = $message } + $failed += $testName + } + else + { + $passed++ + } + } + } + catch + { + # The harness itself failed, which is a failure of this test and not of the run. + $message = "The test harness failed: $( $_.Exception.Message )" + Write-Host $message -ForegroundColor Red + Write-ServiceMessage 'testFailed' @{ name = $testName; message = $message } + $failed += $testName + } + finally + { + Write-ServiceMessage 'testFinished' @{ name = $testName; duration = [string]$durationMilliseconds } + } + } + + Write-ServiceMessage 'testSuiteFinished' @{ name = $suiteName } + + Write-Host "" + Write-Host "$Platform : $passed passed, $( $failed.Count ) failed, $ignored ignored." -ForegroundColor Cyan + + # Nothing ran at all. That is not a pass: a suite reporting success without executing a test is worse than one + # that fails, because nobody looks at it again. It happens when the discovery found tests and every one of them + # was skipped by a fault rather than by a manifest, or when preparation left the suite unable to start. + if ($passed -eq 0 -and $failed.Count -eq 0 -and $ignored -eq 0 -and $testDirectories.Count -gt 0) + { + Write-Host "$( $testDirectories.Count ) test(s) were found and none of them ran." -ForegroundColor Red + Write-ServiceMessage 'buildProblem' @{ description = "$( $testDirectories.Count ) Docker test(s) were found and none of them ran." } + + exit 1 + } + + if ($failed.Count -gt 0) + { + Write-Host "Failed: $( $failed -join ', ' )" -ForegroundColor Red + exit 1 + } + + exit 0 +} +finally +{ + Pop-Location +} diff --git a/eng/TestDatabase.ps1 b/eng/TestDatabase.ps1 deleted file mode 100644 index 8f51a37..0000000 --- a/eng/TestDatabase.ps1 +++ /dev/null @@ -1,343 +0,0 @@ -#Requires -Version 7 - -<# -.SYNOPSIS - Runs the test suite against SQL Server or PostgreSQL. - -.DESCRIPTION - The suite runs on SQLite by default, which needs no server. This script runs the same tests again - against an engine that customers deploy. The engines differ in the collation, in the column types - and in the lock that serializes the lease requests, so each one is run in full. - - The script takes the first server it finds: - - 1. The connection string in LICENSESERVER_TEST_SQLSERVER or LICENSESERVER_TEST_POSTGRESQL, when it - is already set. - 2. A server installed in the image the script runs in, which the continuous integration build - uses. The script starts it and stops it. - 3. A container it starts itself, which is the shortest path on a developer machine. The container - is left running. - - The connection string names no database. Each test receives a database of its own, created from - Database\CreateTables.sql or from Database\CreateTables.PostgreSql.sql. - -.PARAMETER Engine - SqlServer or PostgreSql. - -.PARAMETER Password - The password of the administrative login, which applies to SQL Server and to the PostgreSQL - container. It is read from MSSQL_SA_PASSWORD or POSTGRES_PASSWORD when the parameter is omitted, - and a password is generated when neither is given. - -.PARAMETER StartOnly - Starts the server, prints the connection string, and runs no test. Use it to keep a server for - the runs an editor starts. - -.PARAMETER TimeoutInSeconds - How long to wait for the server to accept a query. - -.PARAMETER BuildArguments - The arguments that are passed on to Build.ps1 test. -#> - -[CmdletBinding()] -param( - [Parameter( Mandatory = $true )] - [ValidateSet( 'SqlServer', 'PostgreSql' )] - [string] $Engine, - - [string] $Password, - - [switch] $StartOnly, - - [int] $TimeoutInSeconds = 180, - - [Parameter( ValueFromRemainingArguments = $true )] - [string[]] $BuildArguments = @() -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -$repositoryDirectory = Split-Path -Parent $PSScriptRoot - -# The address is written as 127.0.0.1 and not as localhost. On a host that resolves localhost to an -# address of version 6 first, the client reaches nothing, because the port of a container is published -# on version 4. -$address = '127.0.0.1' - -$sqlServerExecutable = '/opt/mssql/bin/sqlservr' -$sqlCmd = '/opt/mssql-tools18/bin/sqlcmd' -$sqlServerContainer = 'licenseserver-test-sqlserver' -$postgreSqlContainer = 'licenseserver-test-postgres' -$postgreSqlData = '/tmp/licenseserver-postgres' - -# A container of this script publishes its port beside the default one, so that a license server -# deployed on this machine with docker-compose.yml keeps the default port for itself. A server -# installed in an image listens on the default port, because it is alone in its container. -$sqlServerContainerPort = 14330 -$postgreSqlContainerPort = 55432 - -# How Docker is reached, which Initialize-Docker resolves at the first call. -$script:dockerCommand = $null - -<# -.SYNOPSIS - Resolves how Docker is reached, and stores the command and the arguments that precede every Docker - argument. -.DESCRIPTION - The servers run in Linux containers, so the daemon has to be one that runs Linux containers. A - Windows machine can answer on the client and still refuse them: Docker Desktop in the Windows - container mode reports a server, and pulling a Linux image then fails with "no matching manifest - for windows". The engine can also live inside a distribution of the Windows Subsystem for Linux - rather than under Docker Desktop, and the client of the distribution is then the only one that - reaches it. The daemon is therefore chosen by the operating system it reports, and not by whether - a client answers. - - Docker is called with a relative path when it is called through wsl, because wsl starts in the - translated form of the current directory and would not understand a path that names a Windows - drive. -#> -function Initialize-Docker { - if ( $script:dockerCommand ) { - return - } - - function Test-LinuxDaemon( [string[]] $Command ) { - $name = $Command[0] - $arguments = @( $Command | Select-Object -Skip 1 ) + @( 'version', '--format', '{{.Server.Os}}' ) - - $operatingSystem = & $name @arguments 2>&1 - - return $LASTEXITCODE -eq 0 -and "$operatingSystem".Trim() -eq 'linux' - } - - if ( (Get-Command docker -ErrorAction SilentlyContinue) -and (Test-LinuxDaemon @( 'docker' )) ) { - $script:dockerCommand = @( 'docker' ) - - return - } - - if ( (Get-Command wsl -ErrorAction SilentlyContinue) -and (Test-LinuxDaemon @( 'wsl', '-e', 'docker' )) ) { - Write-Host 'Reaching Docker through the Windows Subsystem for Linux.' - $script:dockerCommand = @( 'wsl', '-e', 'docker' ) - - return - } - - throw 'No Docker daemon that runs Linux containers was reached. Start one, or set the connection string of a server of your own.' -} - -function Invoke-Docker { - Initialize-Docker - - $command = $script:dockerCommand[0] - $arguments = @( $script:dockerCommand | Select-Object -Skip 1 ) + $args - - & $command @arguments -} - -<# -.SYNOPSIS - Holds the distribution of the Windows Subsystem for Linux open, and returns the process that holds - it. -.DESCRIPTION - The subsystem stops a distribution a few seconds after its last process ends. The engine of Docker - and the containers it runs stop with it, so a suite that runs for minutes loses its server in the - middle: the connections are refused and every test that touches the database fails. A process that - sleeps for the length of this script keeps the distribution running. -#> -function Start-DistributionKeepAlive { - if ( $script:dockerCommand[0] -ne 'wsl' ) { - return $null - } - - return Start-Process -FilePath 'wsl' -ArgumentList '-e', 'sleep', '86400' -PassThru -WindowStyle Hidden -} - -function Wait-ForServer( [scriptblock] $Query, [int] $Timeout ) { - $deadline = (Get-Date).AddSeconds( $Timeout ) - - while ( (Get-Date) -lt $deadline ) { - & $Query 2>&1 | Out-Null - - if ( $LASTEXITCODE -eq 0 ) { - return - } - - Start-Sleep -Seconds 2 - } - - throw "The server did not accept a query within $Timeout seconds." -} - -function New-Password { - # The server is reachable from this machine alone, and it is deleted with the container, but it - # still refuses a password that does not meet its complexity rules. - return 'Lease-' + [System.Guid]::NewGuid().ToString( 'N' ).Substring( 0, 12 ) + '-1' -} - -$serverProcess = $null -$startedLocalCluster = $false -$keepAlive = $null - -# Docker is called with a relative path, so the script works from the directory of the repository. -Push-Location $repositoryDirectory - -try { - if ( $Engine -eq 'SqlServer' ) { - if ( -not $env:LICENSESERVER_TEST_SQLSERVER ) { - if ( -not $Password ) { - $Password = if ( $env:MSSQL_SA_PASSWORD ) { $env:MSSQL_SA_PASSWORD } else { New-Password } - } - - # A server installed in the image listens on the default port; a container of this script - # publishes another one. - $port = 1433 - - if ( Test-Path $sqlServerExecutable ) { - Write-Host 'Starting the SQL Server of this image.' - - $env:ACCEPT_EULA = 'Y' - $env:MSSQL_SA_PASSWORD = $Password - $env:MSSQL_PID = 'Developer' - - # The server refuses to run as root, and the image runs the build as root, so the - # server runs under the account its own package creates. - $serverProcess = Start-Process -FilePath 'runuser' ` - -ArgumentList '-u', 'mssql', '--', $sqlServerExecutable ` - -PassThru -NoNewWindow - - Wait-ForServer { & $sqlCmd -S $address -U sa -P $Password -C -b -Q 'SELECT 1' } $TimeoutInSeconds - } - else { - Write-Host 'Starting a SQL Server container.' - - # The container carries no volume and is replaced at every run. The database service - # of docker-compose.yml is left alone: it does carry a volume, and SQL Server sets the - # password of sa when it creates its files, so a second run with another password - # would be refused by the server it started the first time. - Invoke-Docker rm --force $sqlServerContainer 2>&1 | Out-Null - - Invoke-Docker run --detach --name $sqlServerContainer ` - --env ACCEPT_EULA=Y --env MSSQL_SA_PASSWORD=$Password --env MSSQL_PID=Developer ` - --publish "${sqlServerContainerPort}:1433" mcr.microsoft.com/mssql/server:2022-latest - - if ( $LASTEXITCODE -ne 0 ) { - throw 'The SQL Server container did not start.' - } - - Wait-ForServer { - Invoke-Docker exec $sqlServerContainer ` - /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P $Password -C -b -Q 'SELECT 1' - } $TimeoutInSeconds - - $port = $sqlServerContainerPort - } - - $env:LICENSESERVER_TEST_SQLSERVER = - "Server=$address,$port;User Id=sa;Password=$Password;TrustServerCertificate=True;Encrypt=False" - } - - Write-Host "The connection string is in LICENSESERVER_TEST_SQLSERVER." - } - else { - if ( -not $env:LICENSESERVER_TEST_POSTGRESQL ) { - if ( $env:PGBINDIR -and (Test-Path "$env:PGBINDIR/initdb") ) { - Write-Host 'Starting the PostgreSQL server of this image.' - - # The package installs the server but starts no cluster, so the script creates one of - # its own. Both connection methods trust the client: the cluster lives in a container, - # it listens on the loopback address alone, and it is deleted with the container. - New-Item -ItemType Directory -Force -Path $postgreSqlData | Out-Null - & chown postgres $postgreSqlData - - & runuser -u postgres -- "$env:PGBINDIR/initdb" --pgdata=$postgreSqlData ` - --username=postgres --auth-local=trust --auth-host=trust - - if ( $LASTEXITCODE -ne 0 ) { - throw 'The PostgreSQL cluster could not be created.' - } - - $startedLocalCluster = $true - - & runuser -u postgres -- "$env:PGBINDIR/pg_ctl" --pgdata=$postgreSqlData ` - --log=/tmp/postgres.log --options="-c listen_addresses=$address" --wait start - - if ( $LASTEXITCODE -ne 0 ) { - throw 'The PostgreSQL cluster did not start.' - } - - Wait-ForServer { & runuser -u postgres -- "$env:PGBINDIR/pg_isready" -h $address } $TimeoutInSeconds - - $env:LICENSESERVER_TEST_POSTGRESQL = "Host=$address;Port=5432;Username=postgres" - } - else { - Write-Host 'Starting a PostgreSQL container.' - - if ( -not $Password ) { - $Password = if ( $env:POSTGRES_PASSWORD ) { $env:POSTGRES_PASSWORD } else { New-Password } - } - - Invoke-Docker rm --force $postgreSqlContainer 2>&1 | Out-Null - - Invoke-Docker run --detach --name $postgreSqlContainer ` - --env POSTGRES_PASSWORD=$Password ` - --publish "${postgreSqlContainerPort}:5432" postgres:17 - - if ( $LASTEXITCODE -ne 0 ) { - throw 'The PostgreSQL container did not start.' - } - - Wait-ForServer { Invoke-Docker exec $postgreSqlContainer pg_isready -U postgres } $TimeoutInSeconds - - $env:LICENSESERVER_TEST_POSTGRESQL = - "Host=$address;Port=$postgreSqlContainerPort;Username=postgres;Password=$Password" - } - } - - Write-Host "The connection string is in LICENSESERVER_TEST_POSTGRESQL." - } - - if ( $StartOnly ) { - Write-Host 'The server is running, and no test was run.' - - if ( $script:dockerCommand -and $script:dockerCommand[0] -eq 'wsl' ) { - Write-Warning ( - 'Docker runs inside the Windows Subsystem for Linux, which stops a distribution a few seconds ' + - 'after its last process ends, and the container stops with it. Keep a process of the ' + - 'distribution running, for instance "wsl -e sleep 86400", for as long as you need the server.' ) - } - - return - } - - $keepAlive = Start-DistributionKeepAlive - - Write-Host "Running the tests against $Engine." - - & "$repositoryDirectory/Build.ps1" test @BuildArguments - - if ( $LASTEXITCODE -ne 0 ) { - throw "The tests failed with the exit code $LASTEXITCODE." - } -} -finally { - if ( $serverProcess ) { - Write-Host 'Stopping SQL Server.' - Stop-Process -InputObject $serverProcess -ErrorAction SilentlyContinue - } - - if ( $keepAlive ) { - Stop-Process -InputObject $keepAlive -ErrorAction SilentlyContinue - } - - if ( $startedLocalCluster -and -not $StartOnly ) { - Write-Host 'Stopping PostgreSQL.' - - & runuser -u postgres -- "$env:PGBINDIR/pg_ctl" --pgdata=$postgreSqlData ` - --mode=immediate stop 2>&1 | Out-Null - } - - Pop-Location -} diff --git a/eng/docker/postgresql-build.Dockerfile b/eng/docker/postgresql-build.Dockerfile deleted file mode 100644 index 8731ba9..0000000 --- a/eng/docker/postgresql-build.Dockerfile +++ /dev/null @@ -1,72 +0,0 @@ -# This file is auto-generated by PostSharp.Engineering. - -# See the OS_IMAGE_REPOSITORY comment in the Windows branch: the base image is a build-arg so -# that DockerBuild.ps1 can redirect it to a registry-local mirror without changing this file. -ARG OS_IMAGE=ubuntu:22.04 -FROM ${OS_IMAGE} - -# Prepare environment -ENV DEBIAN_FRONTEND=noninteractive -ENV RUNNING_IN_DOCKER=TRUE - -# Set locale for consistent behavior regardless of host locale -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 -ENV DOTNET_CLI_UI_LANGUAGE=en - -# Install the prerequisites shared by the other components -RUN apt-get update && apt-get install -y \ - curl \ - wget \ - ca-certificates \ - libicu70 \ - libssl3 \ - && rm -rf /var/lib/apt/lists/* - - - -# Install Git -RUN apt-get update && apt-get install -y git \ - && rm -rf /var/lib/apt/lists/* - -RUN git config --system core.longpaths true && git config --system core.autocrlf false - - -# Install PowerShell 7 -RUN wget -q https://github.com/PowerShell/PowerShell/releases/download/v7.5.2/powershell_7.5.2-1.deb_amd64.deb \ - && dpkg -i powershell_7.5.2-1.deb_amd64.deb \ - && rm powershell_7.5.2-1.deb_amd64.deb - - -# Download .NET Installer -RUN curl -sSL https://dot.net/v1/dotnet-install.sh -o /usr/local/bin/dotnet-install.sh \ - && chmod +x /usr/local/bin/dotnet-install.sh - -ENV DOTNET_ROOT=/usr/share/dotnet -ENV PATH="${DOTNET_ROOT}:${PATH}" - - -# Install .NET Sdk 10.0.102 -RUN /usr/local/bin/dotnet-install.sh --version 10.0.102 --install-dir $DOTNET_ROOT - - -# .NET Dump Tool -RUN dotnet tool install --global dotnet-dump - -ENV PATH="/root/.dotnet/tools:${PATH}" - - -# Install PostgreSQL 17 -RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc -o /etc/apt/trusted.gpg.d/postgresql.asc \ - && echo "deb http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ - && apt-get update \ - && apt-get install -y postgresql-17 \ - && rm -rf /var/lib/apt/lists/* - -ENV PGBINDIR=/usr/lib/postgresql/17/bin -ENV PATH="${PGBINDIR}:${PATH}" - - -# Epilogue -# Configure .NET SDK -ENV DOTNET_NOLOGO=1 diff --git a/eng/docker/sqlserver-build.Dockerfile b/eng/docker/sqlserver-build.Dockerfile deleted file mode 100644 index 79e838a..0000000 --- a/eng/docker/sqlserver-build.Dockerfile +++ /dev/null @@ -1,73 +0,0 @@ -# This file is auto-generated by PostSharp.Engineering. - -# See the OS_IMAGE_REPOSITORY comment in the Windows branch: the base image is a build-arg so -# that DockerBuild.ps1 can redirect it to a registry-local mirror without changing this file. -ARG OS_IMAGE=ubuntu:22.04 -FROM ${OS_IMAGE} - -# Prepare environment -ENV DEBIAN_FRONTEND=noninteractive -ENV RUNNING_IN_DOCKER=TRUE - -# Set locale for consistent behavior regardless of host locale -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 -ENV DOTNET_CLI_UI_LANGUAGE=en - -# Install the prerequisites shared by the other components -RUN apt-get update && apt-get install -y \ - curl \ - wget \ - ca-certificates \ - libicu70 \ - libssl3 \ - && rm -rf /var/lib/apt/lists/* - - - -# Install Git -RUN apt-get update && apt-get install -y git \ - && rm -rf /var/lib/apt/lists/* - -RUN git config --system core.longpaths true && git config --system core.autocrlf false - - -# Install PowerShell 7 -RUN wget -q https://github.com/PowerShell/PowerShell/releases/download/v7.5.2/powershell_7.5.2-1.deb_amd64.deb \ - && dpkg -i powershell_7.5.2-1.deb_amd64.deb \ - && rm powershell_7.5.2-1.deb_amd64.deb - - -# Download .NET Installer -RUN curl -sSL https://dot.net/v1/dotnet-install.sh -o /usr/local/bin/dotnet-install.sh \ - && chmod +x /usr/local/bin/dotnet-install.sh - -ENV DOTNET_ROOT=/usr/share/dotnet -ENV PATH="${DOTNET_ROOT}:${PATH}" - - -# Install .NET Sdk 10.0.102 -RUN /usr/local/bin/dotnet-install.sh --version 10.0.102 --install-dir $DOTNET_ROOT - - -# .NET Dump Tool -RUN dotnet tool install --global dotnet-dump - -ENV PATH="/root/.dotnet/tools:${PATH}" - - -# Install SQL Server 2022 -RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc -o /etc/apt/trusted.gpg.d/microsoft.asc \ - && curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list -o /etc/apt/sources.list.d/mssql-server-2022.list \ - && curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/prod.list -o /etc/apt/sources.list.d/microsoft-prod.list \ - && apt-get update \ - && apt-get install -y mssql-server \ - && ACCEPT_EULA=Y apt-get install -y mssql-tools18 \ - && rm -rf /var/lib/apt/lists/* - -ENV PATH="/opt/mssql-tools18/bin:${PATH}" - - -# Epilogue -# Configure .NET SDK -ENV DOTNET_NOLOGO=1 diff --git a/eng/src/DatabaseTestRun.cs b/eng/src/DatabaseTestRun.cs deleted file mode 100644 index 271e27a..0000000 --- a/eng/src/DatabaseTestRun.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. - -using BuildBackstageLicenseServer.Docker; -using PostSharp.Engineering.BuildTools; -using PostSharp.Engineering.BuildTools.Build; -using PostSharp.Engineering.BuildTools.Build.Model; -using PostSharp.Engineering.BuildTools.Build.Solutions; -using PostSharp.Engineering.BuildTools.ContinuousIntegration; -using PostSharp.Engineering.BuildTools.ContinuousIntegration.Model; -using PostSharp.Engineering.BuildTools.ContinuousIntegration.TeamCity; -using PostSharp.Engineering.BuildTools.Docker; -using BackstageDependencies = PostSharp.Engineering.BuildTools.Dependencies.Definitions.BackstageDependencies.V2027_0; - -/// -/// One such configuration, with the image it runs in. -/// -/// -/// The name of the image. PostSharp.Engineering writes the Dockerfile of its build layer to -/// eng/docker/{name}-build.Dockerfile. -/// -/// The name of the engine, as the parameter of eng/TestDatabase.ps1 spells it. -/// The name of the engine, as a person writes it. -/// The component that installs the server into the image. -internal sealed class DatabaseTestRun -{ - private readonly string name; - private readonly string engine; - private readonly string displayName; - private readonly ContainerComponent server; - - public DatabaseTestRun( string name, string engine, string displayName, ContainerComponent server ) - { - this.name = name; - this.engine = engine; - this.displayName = displayName; - this.server = server; - } - - public AdditionalDockerfile Dockerfile( string dotNetSdkVersion ) - => new( this.name, [] ) - { - Requirements = new ContainerRequirements( ContainerHostKind.Linux ) - { - OperatingSystem = ContainerOperatingSystem.Linux, - Components = [new DotNetComponent( dotNetSdkVersion, DotNetComponentKind.Sdk ), this.server] - } - }; - - public PowershellAdditionalCiBuildConfiguration Configuration - => new( $"{this.engine}Tests", $"Tests on {this.displayName}", "./eng/TestDatabase.ps1", $"-Engine {this.engine}" ) - { - BuildAgentRequirements = LinuxContainerHost, - Dockerfile = $"eng/docker/{this.name}-build.Dockerfile", - BuildSnapshotDependency = BuildConfiguration.Debug - }; - - /// - /// Gets the agent this configuration runs on, which is a Linux host of an amd64 container. - /// - /// - /// The requirements that PostSharp.Engineering derives for a Linux container host ask for an - /// env.BuildAgentType that no agent of this farm publishes, so nothing would be compatible and the - /// build would wait instead of failing. The operating system and the architecture are what the agents offer. - /// The architecture is named because SQL Server runs on amd64 only. - /// - private static ContainerHostRequirements LinuxContainerHost - => new ContainerHostRequirements( ContainerHostKind.Linux ) with - { - Items = - [ - new BuildAgentRequirement( "teamcity.agent.jvm.os.name", "Linux" ), - new BuildAgentRequirement( "teamcity.agent.jvm.os.arch", "amd64" ) - ] - }; -} diff --git a/eng/src/DatabaseTests.cs b/eng/src/DatabaseTests.cs deleted file mode 100644 index 175a935..0000000 --- a/eng/src/DatabaseTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. - -using BuildBackstageLicenseServer.Docker; -using PostSharp.Engineering.BuildTools; -using PostSharp.Engineering.BuildTools.Build; -using PostSharp.Engineering.BuildTools.Build.Model; -using PostSharp.Engineering.BuildTools.Build.Solutions; -using PostSharp.Engineering.BuildTools.ContinuousIntegration; -using PostSharp.Engineering.BuildTools.ContinuousIntegration.Model; -using PostSharp.Engineering.BuildTools.ContinuousIntegration.TeamCity; -using PostSharp.Engineering.BuildTools.Docker; -using BackstageDependencies = PostSharp.Engineering.BuildTools.Dependencies.Definitions.BackstageDependencies.V2027_0; - -/// -/// The continuous integration configurations that run the test suite against a database server, each one in the -/// image that carries its server. -/// -internal static class DatabaseTests -{ - public static DatabaseTestRun SqlServer { get; } = new( "sqlserver", "SqlServer", "SQL Server", new SqlServerComponent() ); - - public static DatabaseTestRun PostgreSql { get; } = new( "postgresql", "PostgreSql", "PostgreSQL", new PostgreSqlComponent() ); -} diff --git a/eng/src/Docker/PostgreSqlComponent.cs b/eng/src/Docker/PostgreSqlComponent.cs deleted file mode 100644 index baf3302..0000000 --- a/eng/src/Docker/PostgreSqlComponent.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. - -using PostSharp.Engineering.BuildTools.Docker; -using System; -using System.IO; - -namespace BuildBackstageLicenseServer.Docker; - -/// -/// Installs PostgreSQL into a Linux image, so that the image can run the test suite against the second -/// engine supported in production. eng/TestDatabase.ps1 creates the cluster and starts it. -/// -/// -/// The packages come from the repository of the PostgreSQL project and not from Ubuntu, whose -/// repository carries the version that shipped with the distribution. The tests therefore run against -/// a version a customer can deploy today. -/// -internal sealed class PostgreSqlComponent : ContainerComponent -{ - /// - /// The major version. It is part of the path of the executables, which - /// eng/TestDatabase.ps1 reads from PGBINDIR. - /// - public const string Version = "17"; - - public override string Name => $"Install PostgreSQL {Version}"; - - public override string Key => $"{nameof(PostgreSqlComponent)}:{Version}"; - - /// - /// Gets the kind of this component. The enumeration belongs to PostSharp.Engineering and cannot be - /// extended from here, so this component takes the value of the nearest standard component, which - /// is another external tool installed into the image, and places itself with . - /// - public override ContainerComponentKind Kind => ContainerComponentKind.AzureCli; - - /// - /// Gets the position of this component, which is just before the epilogue. The standard components - /// therefore keep their place, and a change to this one invalidates no layer of theirs. - /// - public override int SortOrder => ((int) ContainerComponentKind.Epilogue * 100) - 50; - - public override void WriteDockerfile( TextWriter writer, ContainerOperatingSystem operatingSystem ) - { - if ( operatingSystem != ContainerOperatingSystem.Linux ) - { - throw new InvalidOperationException( "PostgreSQL is installed into a Linux image only." ); - } - - // jammy is Ubuntu 22.04, the base image of a Linux chain. The server is installed but no - // cluster is started here: a container image runs no service, and the test script creates a - // cluster of its own under /tmp. - writer.WriteLine( - $$""" - RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc -o /etc/apt/trusted.gpg.d/postgresql.asc \ - && echo "deb http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ - && apt-get update \ - && apt-get install -y postgresql-{{Version}} \ - && rm -rf /var/lib/apt/lists/* - - ENV PGBINDIR=/usr/lib/postgresql/{{Version}}/bin - ENV PATH="${PGBINDIR}:${PATH}" - """ ); - } -} diff --git a/eng/src/Docker/SqlServerComponent.cs b/eng/src/Docker/SqlServerComponent.cs deleted file mode 100644 index 45b964f..0000000 --- a/eng/src/Docker/SqlServerComponent.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. - -using PostSharp.Engineering.BuildTools.Docker; -using System; -using System.IO; - -namespace BuildBackstageLicenseServer.Docker; - -/// -/// Installs SQL Server 2022 and its command-line tools, so that the image can run the test suite against the -/// engine that customers use. eng/TestSqlServer.ps1 starts the server and stops it. -/// -/// -/// The server is installed in the image rather than started as a second container. A build step runs inside a -/// container already, and reaching a sibling container from there would require the Docker socket of the agent. -/// -internal sealed class SqlServerComponent : ContainerComponent -{ - public override string Name => "Install SQL Server 2022"; - - /// - /// Gets the kind of this component. The enumeration belongs to PostSharp.Engineering and cannot be extended - /// from here, so this component takes the value of the nearest standard component, which is another external - /// tool installed into the image, and places itself with . - /// - public override ContainerComponentKind Kind => ContainerComponentKind.AzureCli; - - /// - /// Gets the position of this component, which is just before the epilogue. The standard components therefore - /// keep their place, and a change to this one invalidates no layer of theirs. - /// - public override int SortOrder => ((int) ContainerComponentKind.Epilogue * 100) - 50; - - public override void WriteDockerfile( TextWriter writer, ContainerOperatingSystem operatingSystem ) - { - if ( operatingSystem != ContainerOperatingSystem.Linux ) - { - throw new InvalidOperationException( - "SQL Server is installed into a Linux image only. Microsoft publishes no SQL Server for a Windows container after the 2019 version." ); - } - - // The package repository of Microsoft for Ubuntu 22.04, which is the base image of a Linux chain. The - // tools accept the license through ACCEPT_EULA, and the server accepts it at the first start, which is - // where TestSqlServer.ps1 passes it. - writer.WriteLine( - """ - RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc -o /etc/apt/trusted.gpg.d/microsoft.asc \ - && curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list -o /etc/apt/sources.list.d/mssql-server-2022.list \ - && curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/prod.list -o /etc/apt/sources.list.d/microsoft-prod.list \ - && apt-get update \ - && apt-get install -y mssql-server \ - && ACCEPT_EULA=Y apt-get install -y mssql-tools18 \ - && rm -rf /var/lib/apt/lists/* - - ENV PATH="/opt/mssql-tools18/bin:${PATH}" - """ ); - } -} diff --git a/eng/src/Program.cs b/eng/src/Program.cs index d20fdb8..0d44dbf 100644 --- a/eng/src/Program.cs +++ b/eng/src/Program.cs @@ -1,6 +1,5 @@ // Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. -using BuildBackstageLicenseServer.Docker; using PostSharp.Engineering.BuildTools; using PostSharp.Engineering.BuildTools.Build; using PostSharp.Engineering.BuildTools.Build.Model; @@ -29,21 +28,30 @@ Components = [new DotNetComponent( dotNetSdkVersion, DotNetComponentKind.Sdk )] }, - // The image of each database test run. Both are Linux images: Microsoft publishes no SQL Server for a Windows - // container after the 2019 version, and PostgreSQL is deployed on Linux. Each image carries its server rather - // than starting a second container, which a build step running inside a container cannot do without the - // Docker socket of the agent. - AdditionalDockerfiles = - [ - DatabaseTests.SqlServer.Dockerfile( dotNetSdkVersion ), - DatabaseTests.PostgreSql.Dockerfile( dotNetSdkVersion ) - ], - // The test runs that need a database server. The build itself runs the suite on SQLite, which needs no - // server; these configurations run the same tests against the two engines that customers deploy, and they - // are what proves the lock, the collation and the column types of each one. See eng/TestDatabase.ps1 and the - // section "Running the tests" of README.md. - AdditionalCiBuildConfigurations = [DatabaseTests.SqlServer.Configuration, DatabaseTests.PostgreSql.Configuration], + // server; these runs exercise the same tests against the two engines that customers deploy, each in a + // container of its own. See Tests/Docker and the section "Running the tests" of README.md. + // + // One configuration runs every test of a platform, so there is one per platform and not one per engine. + // SQL Server runs on amd64 alone, which is why linux-x64 is the only platform declared here. + AdditionalCiBuildConfigurations = DockerTestsAdditionalCiBuildConfiguration.WithCompositeConfiguration( + new DockerTestsAdditionalCiBuildConfiguration( + "DockerTestsLinuxX64", + "Docker Tests (Linux x64)", + DockerTestPlatform.LinuxX64, + + // The directories of this repository are named in lower case, so the tests are under tests/docker + // and not under the default Tests/Docker. The name is compared as it is written on a Linux agent. + "tests/docker" ) + { + // The tests build the product from the sources of the repository, and they need the packages of the + // licensing component, which the artifacts of the debug build carry. + BuildSnapshotDependency = BuildConfiguration.Debug, + + // Each test acquires an image of several hundred megabytes on an agent that meets it for the first + // time, installs a database server, and then builds and runs the suite. + TimeoutInMinutes = 60 + } ), // Built rather than packed: the product ships a deployable archive and no NuGet package, so the Pack // target of every project would be a no-op. diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs index c262176..d899c11 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestDatabases.cs @@ -16,7 +16,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// runs the suite again against each engine that customers use. /// /// -/// Start either server with eng\TestDatabase.ps1, which also runs the suite against it. +/// A Docker test starts either server in a container and sets the variable. See tests/docker and +/// the section "Running the tests" of README.md. /// /// public static class TestDatabases diff --git a/tests/docker/PostgreSql/Dockerfile b/tests/docker/PostgreSql/Dockerfile new file mode 100644 index 0000000..04d9677 --- /dev/null +++ b/tests/docker/PostgreSql/Dockerfile @@ -0,0 +1,34 @@ +# The image of the PostgreSQL test: the server the suite runs against, and the .NET SDK that runs the suite. +# +# The repository is mounted rather than copied, so this file installs software and nothing else. +# +# OS_IMAGE is read by DockerBuild.ps1, which replaces it with the same image in the registry named by +# DOCKER_REGISTRY when that registry is configured. An agent that cannot reach the mirror builds from the +# public image instead. +ARG OS_IMAGE=ubuntu:22.04 +FROM ${OS_IMAGE} + +ENV DEBIAN_FRONTEND=noninteractive +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +RUN apt-get update && apt-get install -y curl ca-certificates libicu70 libssl3 && rm -rf /var/lib/apt/lists/* + +# The packages come from the repository of the PostgreSQL project and not from Ubuntu, whose repository carries +# the version that shipped with the distribution. The tests therefore run against a version a customer can +# deploy today. jammy is Ubuntu 22.04. The server is installed but no cluster is created: a container image +# runs no service, and run.sh creates a cluster under /tmp and starts it. +RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc -o /etc/apt/trusted.gpg.d/postgresql.asc && echo "deb http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list && apt-get update && apt-get install -y postgresql-17 && rm -rf /var/lib/apt/lists/* + +ENV PGBINDIR=/usr/lib/postgresql/17/bin +ENV PATH="${PGBINDIR}:${PATH}" + +# The feature band and not a single version: global.json pins the SDK of the repository to 10.0.1xx and rolls +# forward over its patches alone, so an image carrying another band, such as 10.0.4xx, cannot build the suite. +# The band follows the DotNetSdkVersion that eng/src/Program.cs declares. +ENV DOTNET_ROOT=/usr/share/dotnet +ENV PATH="${DOTNET_ROOT}:${PATH}" +ENV DOTNET_NOLOGO=1 +ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 + +RUN curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh && chmod +x /tmp/dotnet-install.sh && /tmp/dotnet-install.sh --channel 10.0.1xx --install-dir $DOTNET_ROOT && rm /tmp/dotnet-install.sh diff --git a/tests/docker/PostgreSql/RunTest.ps1 b/tests/docker/PostgreSql/RunTest.ps1 new file mode 100644 index 0000000..a188e33 --- /dev/null +++ b/tests/docker/PostgreSql/RunTest.ps1 @@ -0,0 +1,28 @@ +# Runs the test suite against PostgreSQL, in a container that carries the server. +# +# The suite runs on SQLite in the ordinary build, which needs no server. PostgreSQL is one of the two engines +# that customers deploy, and it differs from SQLite in the collation, in the column types and in the lock that +# serializes the lease requests, so the same tests are run against it in full. + +param( + # The platform identifier the launcher selected, for example 'linux-x64'. + [Parameter( Mandatory = $true )] [string] $Platform +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# The operating system of the container engine to use. On a Windows development machine, Linux containers run +# on the engine inside the Windows Subsystem for Linux, and DockerBuild.ps1 re-executes itself there. +$os = if ( $Platform -like 'linux-*' ) { 'linux' } else { 'windows' } + +$arguments = @{ + Test = $true + OS = $os + Dockerfile = "$PSScriptRoot/Dockerfile" + Command = 'bash tests/docker/PostgreSql/run.sh' +} + +& "$PSScriptRoot/../../../DockerBuild.ps1" @arguments + +exit $LASTEXITCODE diff --git a/tests/docker/PostgreSql/run.sh b/tests/docker/PostgreSql/run.sh new file mode 100644 index 0000000..0f07530 --- /dev/null +++ b/tests/docker/PostgreSql/run.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Creates a PostgreSQL cluster in this container, starts it, and runs the test suite against it. +# +# The script runs inside the container, with the repository mounted and as the working directory. RunTest.ps1 +# is what starts the container; this file holds the part that needs no PowerShell, because a test image is +# chosen for the tool chain under test and carries no PowerShell. + +set -e + +data=/tmp/licenseserver-postgres + +mkdir -p "$data" +chown postgres "$data" + +# Both connection methods trust the client: the cluster lives in this container, it listens on the loopback +# address alone, and it is deleted with the container. +runuser -u postgres -- "$PGBINDIR/initdb" --pgdata="$data" --username=postgres --auth-local=trust --auth-host=trust > /tmp/initdb.log 2>&1 + +runuser -u postgres -- "$PGBINDIR/pg_ctl" --pgdata="$data" --log=/tmp/postgres.log --options="-c listen_addresses=127.0.0.1" --wait start + +if ! runuser -u postgres -- "$PGBINDIR/pg_isready" -h 127.0.0.1 > /dev/null 2>&1; then + echo "PostgreSQL did not accept a connection. The log of the server follows." + tail -50 /tmp/postgres.log + exit 1 +fi + +# The suite reads this variable and gives every test a database of its own, created from +# Database/CreateTables.PostgreSql.sql. The connection string names no database. +export LICENSESERVER_TEST_POSTGRESQL="Host=127.0.0.1;Port=5432;Username=postgres" + +# The restore and the build write outside the repository, and they read the packages of the licensing +# component from the source that nuget.config names. +# shellcheck source=../prepare-restore.sh +source tests/docker/prepare-restore.sh + +dotnet restore tests/SharpCrafters.Backstage.LicenseServer.Tests $RESTORE_ONLY_ARGUMENTS $COMMON_ARGUMENTS + +dotnet test tests/SharpCrafters.Backstage.LicenseServer.Tests --no-restore --nologo $COMMON_ARGUMENTS diff --git a/tests/docker/PostgreSql/test.psd1 b/tests/docker/PostgreSql/test.psd1 new file mode 100644 index 0000000..4c32b19 --- /dev/null +++ b/tests/docker/PostgreSql/test.psd1 @@ -0,0 +1,9 @@ +@{ + # PostgreSQL publishes packages for both architectures, so the test applies to both. The continuous + # integration build declares the amd64 platform alone, because the neighbouring SQL Server test needs it. + Platforms = @( 'linux-x64', 'linux-arm64' ) + + # The server is a fraction of the size of SQL Server, so the image costs less to acquire. The rest of the + # budget is the restore, the build and the suite. + TimeoutSeconds = 1800 +} diff --git a/tests/docker/SqlServer/Dockerfile b/tests/docker/SqlServer/Dockerfile new file mode 100644 index 0000000..defa23b --- /dev/null +++ b/tests/docker/SqlServer/Dockerfile @@ -0,0 +1,45 @@ +# The image of the SQL Server test: the server the suite runs against, and the .NET SDK that runs the suite. +# +# The repository is mounted rather than copied, so this file installs software and nothing else. +# +# OS_IMAGE is read by DockerBuild.ps1, which replaces it with the same image in the registry named by +# DOCKER_REGISTRY when that registry is configured. An agent that cannot reach the mirror builds from the +# public image instead. Ubuntu 22.04 is the distribution that Microsoft publishes the server packages for. +ARG OS_IMAGE=ubuntu:22.04 +FROM ${OS_IMAGE} + +ENV DEBIAN_FRONTEND=noninteractive +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +RUN apt-get update && apt-get install -y \ + curl \ + ca-certificates \ + libicu70 \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + +# SQL Server 2022 and the command-line tools, from the package repository of Microsoft. The server is +# installed but no instance is started: a container image runs no service, and RunTest.ps1 starts one. +RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc -o /etc/apt/trusted.gpg.d/microsoft.asc \ + && curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list -o /etc/apt/sources.list.d/mssql-server-2022.list \ + && curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/prod.list -o /etc/apt/sources.list.d/microsoft-prod.list \ + && apt-get update \ + && apt-get install -y mssql-server \ + && ACCEPT_EULA=Y apt-get install -y mssql-tools18 \ + && rm -rf /var/lib/apt/lists/* + +ENV PATH="/opt/mssql-tools18/bin:${PATH}" + +# The feature band and not a single version: global.json pins the SDK of the repository to 10.0.1xx and rolls +# forward over its patches alone, so an image carrying another band, such as 10.0.4xx, cannot build the suite. +# The band follows the DotNetSdkVersion that eng/src/Program.cs declares. +ENV DOTNET_ROOT=/usr/share/dotnet +ENV PATH="${DOTNET_ROOT}:${PATH}" +ENV DOTNET_NOLOGO=1 +ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 + +RUN curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh \ + && chmod +x /tmp/dotnet-install.sh \ + && /tmp/dotnet-install.sh --channel 10.0.1xx --install-dir $DOTNET_ROOT \ + && rm /tmp/dotnet-install.sh diff --git a/tests/docker/SqlServer/RunTest.ps1 b/tests/docker/SqlServer/RunTest.ps1 new file mode 100644 index 0000000..2dd9c6e --- /dev/null +++ b/tests/docker/SqlServer/RunTest.ps1 @@ -0,0 +1,28 @@ +# Runs the test suite against SQL Server, in a container that carries the server. +# +# The suite runs on SQLite in the ordinary build, which needs no server. SQL Server is one of the two engines +# that customers deploy, and it differs from SQLite in the collation, in the column types and in the lock that +# serializes the lease requests, so the same tests are run against it in full. + +param( + # The platform identifier the launcher selected, for example 'linux-x64'. + [Parameter( Mandatory = $true )] [string] $Platform +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# The operating system of the container engine to use. On a Windows development machine, Linux containers run +# on the engine inside the Windows Subsystem for Linux, and DockerBuild.ps1 re-executes itself there. +$os = if ( $Platform -like 'linux-*' ) { 'linux' } else { 'windows' } + +$arguments = @{ + Test = $true + OS = $os + Dockerfile = "$PSScriptRoot/Dockerfile" + Command = 'bash tests/docker/SqlServer/run.sh' +} + +& "$PSScriptRoot/../../../DockerBuild.ps1" @arguments + +exit $LASTEXITCODE diff --git a/tests/docker/SqlServer/run.sh b/tests/docker/SqlServer/run.sh new file mode 100644 index 0000000..7607f39 --- /dev/null +++ b/tests/docker/SqlServer/run.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Starts the SQL Server of this container and runs the test suite against it. +# +# The script runs inside the container, with the repository mounted and as the working directory. RunTest.ps1 +# is what starts the container; this file holds the part that needs no PowerShell, because a test image is +# chosen for the tool chain under test and carries no PowerShell. + +set -e + +password="Lease-$(head -c 8 /dev/urandom | od -An -tx1 | tr -d ' \n')-1" + +export ACCEPT_EULA=Y +export MSSQL_SA_PASSWORD="$password" +export MSSQL_PID=Developer + +# The server refuses to run as root, and the container runs the command as root, so it runs under the account +# its own package creates. +runuser -u mssql -- /opt/mssql/bin/sqlservr > /tmp/sqlservr.log 2>&1 & + +for _ in $(seq 1 90); do + if /opt/mssql-tools18/bin/sqlcmd -S 127.0.0.1 -U sa -P "$password" -C -b -Q "SELECT 1" > /dev/null 2>&1; then + break + fi + + sleep 2 +done + +if ! /opt/mssql-tools18/bin/sqlcmd -S 127.0.0.1 -U sa -P "$password" -C -b -Q "SELECT 1" > /dev/null 2>&1; then + echo "SQL Server did not accept a query. The log of the server follows." + tail -50 /tmp/sqlservr.log + exit 1 +fi + +# The suite reads this variable and gives every test a database of its own, created from +# Database/CreateTables.sql. The connection string names no database. +export LICENSESERVER_TEST_SQLSERVER="Server=127.0.0.1,1433;User Id=sa;Password=$password;TrustServerCertificate=True;Encrypt=False" + +# The restore and the build write outside the repository, and they read the packages of the licensing +# component from the source that nuget.config names. +# shellcheck source=../prepare-restore.sh +source tests/docker/prepare-restore.sh + +dotnet restore tests/SharpCrafters.Backstage.LicenseServer.Tests $RESTORE_ONLY_ARGUMENTS $COMMON_ARGUMENTS + +dotnet test tests/SharpCrafters.Backstage.LicenseServer.Tests --no-restore --nologo $COMMON_ARGUMENTS diff --git a/tests/docker/SqlServer/test.psd1 b/tests/docker/SqlServer/test.psd1 new file mode 100644 index 0000000..7fe2bfa --- /dev/null +++ b/tests/docker/SqlServer/test.psd1 @@ -0,0 +1,9 @@ +@{ + # SQL Server runs on amd64 alone: Microsoft publishes no arm64 image of it. + Platforms = @( 'linux-x64' ) + + # The image is about one and a half gigabytes, and an agent that meets it for the first time pulls it + # before the test does anything. The test itself then restores, builds and runs the suite. The neighbouring + # PostgreSQL test needs less, because its server is a fraction of that size. + TimeoutSeconds = 2400 +} diff --git a/tests/docker/prepare-restore.sh b/tests/docker/prepare-restore.sh new file mode 100644 index 0000000..3616668 --- /dev/null +++ b/tests/docker/prepare-restore.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Prepares the restore of a Docker test, and is sourced by the run.sh of each of them. +# +# It sets RESTORE_ONLY_ARGUMENTS to what only the restore takes, and COMMON_ARGUMENTS to what the restore and +# the test both take: the version of the licensing component, and the directory the build writes to. +# +# In the continuous integration build the packages and the version file of that component are directories of +# the repository, which this container reaches unchanged, and this script has nothing to do. On a development +# machine the dependency is resolved to a directory of the host, named by a Windows path. DockerBuild.ps1 mounts +# each of those directories under /mnt/, so the paths are translated here. Every file that is written is +# written to /tmp, because the files of the repository belong to the host and are what the host builds with. + +set -e + +# Translates a Windows path into the path this container mounts it at. +translate_path() { + echo "$1" | sed -E 's#^([A-Za-z]):\\#/mnt/\l\1/#' | sed -E 's#\\#/#g' +} + +RESTORE_ONLY_ARGUMENTS="" +COMMON_ARGUMENTS="" + +if grep -qE 'value="[A-Za-z]:\\' nuget.config; then + echo "The dependencies are resolved to directories of the host. Translating the paths that name them." + + sed -E 's#value="([A-Za-z]):\\#value="/mnt/\l\1/#g' nuget.config \ + | sed -E '/\/mnt\//s#\\#/#g' > /tmp/nuget.config + + RESTORE_ONLY_ARGUMENTS="--configfile /tmp/nuget.config" + + # eng/Versions.g.props imports eng/Versions.Debug.g.props by an absolute path of the host, so MSBuild skips + # it here and the version of the licensing component falls back to the placeholder of + # eng/AutoUpdatedVersions.props, which names no package that exists. The version is therefore read from the + # file that the dependency resolution wrote and passed on the command line, where it wins over that + # placeholder. + version_file=$( grep -oE '[^<]+' eng/Versions.Debug.g.props | head -1 | cut -d '>' -f 2 ) + + if [ -n "$version_file" ]; then + version_file=$( translate_path "$version_file" ) + + if [ ! -f "$version_file" ]; then + echo "The version file of the dependency is not mounted at '$version_file'." + exit 1 + fi + + backstage_version=$( grep -oE '[^<]+' "$version_file" | head -1 | cut -d '>' -f 2 ) + + echo "The licensing component is version $backstage_version." + COMMON_ARGUMENTS="-p:BackstageVersion=$backstage_version" + fi +fi + +# The release archive is not what these tests exercise. Building it starts a nested dotnet publish, which +# restores a second time and with the configuration of the repository rather than the one prepared here. +COMMON_ARGUMENTS="$COMMON_ARGUMENTS -p:ProduceReleaseArchive=false" + +# The build writes outside the repository. The repository is mounted, so its bin and obj directories belong to +# the host, and a build of this container would leave the host with an assets file naming paths that exist in +# the container alone. +COMMON_ARGUMENTS="$COMMON_ARGUMENTS -p:ArtifactsPath=/tmp/artifacts" + +export RESTORE_ONLY_ARGUMENTS +export COMMON_ARGUMENTS
diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml b/src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml index 29a8efe..51a0ee3 100644 --- a/src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml +++ b/src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml @@ -4,11 +4,10 @@ ViewData["Title"] = "Export the audit log"; } -

- Back -

- -

Export the audit log

+

The export is a text file with one line per lease. User and machine names appear only as hashes, diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml b/src/PostSharp.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml index 2d0a90c..46e1a0f 100644 --- a/src/PostSharp.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml +++ b/src/PostSharp.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml @@ -4,11 +4,10 @@ ViewData["Title"] = "Generate demo data"; } -

- Back -

- -

Generate demo data

+

Simulates a team building software over a period, so that the dashboard and the usage graph have diff --git a/src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml b/src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml index 16d8109..820a6d8 100644 --- a/src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml +++ b/src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml @@ -8,30 +8,24 @@ } -

- Back -

+
diff --git a/src/PostSharp.LicenseServer.Web/Pages/Index.cshtml b/src/PostSharp.LicenseServer.Web/Pages/Index.cshtml index 603f27e..9251398 100644 --- a/src/PostSharp.LicenseServer.Web/Pages/Index.cshtml +++ b/src/PostSharp.LicenseServer.Web/Pages/Index.cshtml @@ -4,16 +4,13 @@ ViewData["Title"] = "Licenses"; } -

- Add a license | - Export the audit log -

- -

Licenses

+ @if ( Model.Licenses.Count == 0 ) { -
+

No license has been registered yet. Add a license key to let this server serve leases. @@ -22,7 +19,8 @@ } else { - +
+
@@ -47,13 +45,17 @@ else - + } -
License@license.CurrentUsers @license.GraceStartTime?.ToString( "d" ) @license.MaintenanceEndDate?.ToString( "d" )@license.Status + @license.Status + Details + · Usage
+ +

} diff --git a/src/PostSharp.LicenseServer.Web/Pages/Index.cshtml.cs b/src/PostSharp.LicenseServer.Web/Pages/Index.cshtml.cs index ae3c0a5..49fa46b 100644 --- a/src/PostSharp.LicenseServer.Web/Pages/Index.cshtml.cs +++ b/src/PostSharp.LicenseServer.Web/Pages/Index.cshtml.cs @@ -32,7 +32,12 @@ public async Task OnGetAsync( CancellationToken cancellationToken ) summaries.Add( parsedLicense == null - ? new LicenseSummary { LicenseId = license.LicenseId, LicenseType = "INVALID" } + ? new LicenseSummary + { + LicenseId = license.LicenseId, + LicenseType = "INVALID", + Status = "Invalid" + } : new LicenseSummary { LicenseId = license.LicenseId, @@ -66,5 +71,15 @@ public sealed class LicenseSummary public string? Status { get; init; } public DateTime? MaintenanceEndDate { get; init; } + + /// + /// Gets the modifier that colours the status: green for an active license, orange for a key + /// that cannot be parsed, amber while the grace period runs. + /// + public string StatusModifier + => this.LicenseType == "INVALID" ? "invalid" + : this.GraceStartTime != null ? "grace" + : this.Status == "Active" ? "active" + : "disabled"; } } diff --git a/src/PostSharp.LicenseServer.Web/Pages/Shared/_Layout.cshtml b/src/PostSharp.LicenseServer.Web/Pages/Shared/_Layout.cshtml index b51bcbe..ff20401 100644 --- a/src/PostSharp.LicenseServer.Web/Pages/Shared/_Layout.cshtml +++ b/src/PostSharp.LicenseServer.Web/Pages/Shared/_Layout.cshtml @@ -5,20 +5,37 @@ @ViewData["Title"] - PostSharp License Server + + + + + + @await RenderSectionAsync( "Head", required: false ) -
- PostSharp -

PostSharp License Server

+
+
+ + License Server + +
-
-
@RenderBody()
+
+ PostSharp and Metalama are trade names of SharpCrafters s.r.o. © 2004–@DateTime.UtcNow.Year. +
+ @await RenderSectionAsync( "Scripts", required: false ) diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/Img/PostSharpText_Light_240x33.png b/src/PostSharp.LicenseServer.Web/wwwroot/Img/PostSharpText_Light_240x33.png deleted file mode 100644 index 3437bc653c65b979e1cff83778db2d3dc93835d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5538 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 diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css b/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css index b9d778c..92fabd0 100644 --- a/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css +++ b/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css @@ -1,104 +1,419 @@ -/* The license server is an internal administration tool. It follows the reader's colour scheme - rather than forcing one, so it stays legible in a browser set to dark. */ +/* PostSharp License Server. + + The design tokens below are copied from the PostSharp website + (metalama-website/_sass/0-base/_tokens.scss) so that the license server looks like the rest of + the product. The brand is dark-first and has no light palette, so neither has this. + + The website's display face, Monosten Pro, is a commercially licensed webfont that may not be + redistributed. This application ships to customers and runs on their own servers, so the font is + named first in the stack and falls back to a monospace face, which keeps the character of the + headings without redistributing anything. */ + :root { - color-scheme: light dark; - - --background: #ffffff; - --foreground: #222222; - --muted: #666666; - --rule: #e0e0e0; - --rule-strong: #999999; - --accent: #58006e; - --danger: #b00020; - --panel: #f6f6f6; -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #1b1b1f; - --foreground: #e8e8ea; - --muted: #a0a0a8; - --rule: #35353c; - --rule-strong: #5a5a66; - --accent: #c58fd8; - --danger: #ff8a95; - --panel: #26262c; - } + color-scheme: dark; + + /* Brand purples. On dark surfaces the bright purple is used with a white wordmark; the deep + purple is the light-surface counterpart and is deliberately not interchangeable with it. */ + --purple: #973bfc; + --purple-deep: #6122b2; + --purple-medium-1: #6527a9; + --purple-dark: #190822; + --ink: #110b1d; + + /* Surfaces. */ + --bg-canvas: #191321; + --bg-footer: #120b1d; + --bg-panel: #1e0b38; + --bg-panel-2: #26183a; + --bg-code: #1e1e1e; + --doc-content-bg: #1a1229; + --doc-table-line: #2c1a4f; + + /* Accents. Cyan is the secondary accent and carries prose links. */ + --cyan: #38d5e4; + --cyan-light: #61d3e1; + --orange: #ff5e45; + --yellow: #ffc107; + --green: #28a745; + --pink-deep: #e83e8c; + + /* Text. */ + --text-primary: #ffffff; + --text-body: #bdbdbd; + --text-muted: #a0a0a0; + --text-soft: #d3d3d3; + + /* Borders. */ + --border-subtle: rgba(255, 255, 255, 0.15); + --border-card: 1px solid rgba(255, 255, 255, 0.12); + --border-hairline: rgba(255, 255, 255, 0.06); + + /* Type. Monosten is not redistributed with this application; see the note above. */ + --font-display: "Monosten", "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace; + --font-body: "LatoLatinWeb", "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + --font-code: "Consolas", "SFMono-Regular", ui-monospace, Menlo, monospace; + + --fs-base: 16px; + --fs-sm: 12.8px; + --fs-h3: 22px; + --fs-h2: 32px; + --fs-h1: 44px; + + --fw-normal: 400; + --fw-bold: 700; + + --lh-snug: 1.2; + --lh-normal: 1.4; + --lh-relaxed: 1.6; + --ls-wide: 0.04em; + + /* Buttons are sharp; panels round at 8px. */ + --radius-0: 0; + --radius-sm: 4px; + --radius-md: 8px; + + --transition-fast: 150ms; + --transition-slow: all 500ms ease; + + --container: 1400px; + --header-height: 64px; +} + +*, *::before, *::after { + box-sizing: border-box; } body { - font-family: "Segoe UI", Arial, sans-serif; - padding: 20px; margin: 0; - background: var(--background); - color: var(--foreground); + background-color: var(--bg-canvas); + color: var(--text-body); + font-family: var(--font-body); + font-size: var(--fs-base); + line-height: var(--lh-relaxed); } +/* Headings are weight 400 throughout: size does the work, not weight. */ +h1, h2, h3 { + font-family: var(--font-display); + font-weight: var(--fw-normal); + color: var(--text-primary); +} + +h1 { font-size: var(--fs-h1); line-height: var(--lh-snug); } +h2 { font-size: var(--fs-h2); line-height: var(--lh-snug); margin-top: 0; } +h3 { font-size: var(--fs-h3); line-height: var(--lh-normal); } + a { - color: var(--accent); + color: var(--cyan); + text-decoration: none; + transition: color var(--transition-fast) ease; } -h1 { - font-size: 1.8em; +a:hover { + color: var(--cyan-light); + text-decoration: underline; } -header { - cursor: pointer; +code { + font-family: var(--font-code); + color: var(--cyan-light); } -header img { - float: right; +/* The website's own focus ring. */ +:where(a, button, input, select, textarea, [tabindex]):focus-visible { + outline: 2px solid var(--cyan); + outline-offset: 2px; } -hr { - margin-top: 50px; - border: 0; - border-top: 1px solid var(--rule); +/* --- Page chrome -------------------------------------------------------- */ + +/* The company bar, reproduced from the website's header: the deepest surface, 64px tall, with the + logo on the left. */ +.company-bar { + background-color: var(--bg-footer); + border-bottom: 1px solid var(--border-hairline); +} + +.company-bar__inner { + display: flex; + align-items: center; + gap: 32px; + height: var(--header-height); + max-width: var(--container); + margin: 0 auto; + padding: 0 24px; +} + +.company-bar__logo img { + display: block; + width: 180px; + height: auto; +} + +.company-bar__title { + font-family: var(--font-display); + font-size: 15px; + color: var(--text-muted); + letter-spacing: var(--ls-wide); + text-transform: uppercase; +} + +.company-bar__nav { + margin-left: auto; + display: flex; + gap: 4px; +} + +.company-bar__nav a { + display: flex; + align-items: center; + height: var(--header-height); + padding: 0 18px; + font-family: var(--font-body); + font-size: 15px; + color: var(--text-muted); + text-decoration: none; +} + +.company-bar__nav a:hover { + color: var(--text-primary); + text-decoration: none; +} + +main { + max-width: var(--container); + margin: 0 auto; + padding: 40px 24px 80px; +} + +.page-header { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 24px; + flex-wrap: wrap; +} + +.site-footer { + max-width: var(--container); + margin: 0 auto; + padding: 32px 24px 40px; + border-top: 1px solid var(--border-hairline); + color: var(--text-muted); + font-size: var(--fs-sm); +} + +/* --- Panels and tables --------------------------------------------------- */ + +.panel { + padding: 24px; + background: var(--doc-content-bg); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: var(--radius-md); } table { + width: 100%; border-collapse: collapse; + border-radius: var(--radius-md); + overflow: hidden; } td, th { - padding: 10px; + padding: 10px 14px; text-align: left; - border-bottom: 1px solid var(--rule); + border: 1px solid var(--doc-table-line); + color: var(--text-soft); } th { - border-bottom: 2px solid var(--rule-strong); + background-color: var(--bg-panel); + color: var(--text-primary); + font-family: var(--font-display); + font-weight: var(--fw-normal); } -.no-licenses { - padding: 20px; - background: var(--panel); - border-left: 4px solid var(--accent); +tbody tr:nth-child(even) td { + background-color: rgba(255, 255, 255, 0.02); } -.validation-error, .field-validation-error, .validation-summary-errors { - color: var(--danger); +/* --- Status ------------------------------------------------------------- */ + +.status { + font-family: var(--font-display); + font-size: var(--fs-sm); + letter-spacing: var(--ls-wide); + text-transform: uppercase; } -.actions form { - display: inline; +.status--active { color: var(--green); } +.status--disabled { color: var(--text-muted); } +.status--invalid { color: var(--orange); } +.status--grace { color: var(--yellow); } + +/* --- Buttons ------------------------------------------------------------ */ + +/* Sharp, uppercase, two-pixel border: the signature of the brand's buttons. */ +.btn, +button, +input[type="submit"] { + display: inline-block; + padding: 7px 40px; + line-height: 27px; + font-family: var(--font-body); + font-size: var(--fs-base); + font-weight: var(--fw-bold); + text-transform: uppercase; + letter-spacing: var(--ls-wide); + color: var(--text-primary); + background-color: var(--purple-medium-1); + border: 2px solid var(--purple-medium-1); + border-radius: var(--radius-0); + text-align: center; + text-decoration: none; + cursor: pointer; + transition: var(--transition-slow); +} + +.btn:hover, +button:hover, +input[type="submit"]:hover { + background-color: var(--purple-dark); + border-color: var(--purple-dark); + color: var(--text-primary); + text-decoration: none; +} + +.btn--transparent { + background-color: transparent; + border-color: var(--purple); +} + +.btn--small { + padding: 8px 20px; + font-size: 14px; + line-height: 1; +} + +/* --- Forms -------------------------------------------------------------- */ + +label { + display: inline-block; + font-family: var(--font-display); + font-size: 14px; + color: var(--text-muted); + letter-spacing: var(--ls-wide); + text-transform: uppercase; +} + +input[type="text"], +input[type="number"], +select, +textarea { + padding: 10px 14px; + background: rgba(30, 11, 56, 0.55); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: var(--radius-md); + color: var(--text-primary); + font-family: var(--font-body); + font-size: var(--fs-base); + line-height: var(--lh-snug); + transition: border-color 200ms ease, box-shadow 200ms ease; +} + +textarea { + width: 100%; + font-family: var(--font-code); +} + +input:focus, select:focus, textarea:focus { + border-color: var(--purple); +} + +.validation-summary-errors, +.field-validation-error { + color: var(--pink-deep); +} + +.validation-summary-errors ul { + margin: 0; + padding-left: 20px; +} + +/* --- Callouts ----------------------------------------------------------- */ + +/* The website's note callout: a coloured left rule on a raised panel. */ +.callout { + padding: 20px 24px; + background: var(--bg-panel-2); + border-left: 4px solid var(--cyan); + border-radius: var(--radius-sm); } -.toolbar { - float: right; +.callout--warning { + border-left-color: var(--orange); } +/* --- Usage graph -------------------------------------------------------- */ + #usage-chart-container { position: relative; width: 100%; - height: 400px; + height: 420px; + padding: 24px; + background: var(--doc-content-bg); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: var(--radius-md); } -button, input[type="submit"] { - padding: 6px 14px; - cursor: pointer; +.chart-windows { + display: flex; + gap: 8px; + align-items: center; } -label { - color: var(--muted); +.chart-windows .current { + color: var(--text-primary); + font-family: var(--font-display); +} + +/* --- Narrow viewports ---------------------------------------------------- */ + +@media (max-width: 900px) { + .company-bar__title { display: none; } +} + +@media (max-width: 720px) { + /* The bar becomes two rows rather than overflowing: logo above, navigation below. */ + .company-bar__inner { + flex-wrap: wrap; + height: auto; + gap: 8px 16px; + padding: 12px 16px; + } + + .company-bar__logo img { width: 150px; } + + .company-bar__nav { + margin-left: 0; + width: 100%; + flex-wrap: wrap; + } + + .company-bar__nav a { height: 36px; padding: 0 12px 0 0; } + + main { padding: 24px 16px 48px; } + .site-footer { padding: 24px 16px 32px; } + + h1 { font-size: 30px; } + h2 { font-size: 24px; } + + .page-header { gap: 8px; } + + /* A wide table scrolls inside its own box instead of widening the page. */ + .table-scroll { overflow-x: auto; } + + .btn, button, input[type="submit"] { padding: 7px 24px; } + + #usage-chart-container { padding: 12px; height: 340px; } } diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/favicon.ico b/src/PostSharp.LicenseServer.Web/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..59a26936867c7c2cf807cc9675a96cb684f6ff1e GIT binary patch literal 15406 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 literal 0 HcmV?d00001 diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/img/icons/android-icon-96x96.png b/src/PostSharp.LicenseServer.Web/wwwroot/img/icons/android-icon-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..a0a3d4c8d40ad6e6b7cd7da3dec2314ed65cc00a GIT binary patch literal 3240 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 H(Q*F;1~xQ; literal 0 HcmV?d00001 diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/img/icons/favicon-16x16.png b/src/PostSharp.LicenseServer.Web/wwwroot/img/icons/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..df3a9d7fd10e1df3b4231158367e953fb23df5c4 GIT binary patch literal 1062 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstUx|vage(c z!@6@aFM%9|WRD45bDP46hOx7_4S6Fo+k-*%fF5l(-b& z6XH7E?mv(TB&mg_*}{whGp1Vq-&y;AUB>@C&Hq=#{-0*^f0oPtRZ0IJ9Q}WC*8j(+ z|L<=6Kg0h2qM-lRHvRwd>i^?2|3AO{e__S{xgP(g+y0;9@&Cxg|8MX8zr61M>zn`Q zdH=t<>Hovy|6g4Fe|W_tSb|?@OIJ?oHp&q455{Ptsv6OZ)TY zPvbb;RRS9(up~(>oN`R~2fJt7z-8j#r6n{`Qh}US+|KfN3#aBV^r(8CzmJ)r( zCa*LPUVD`>BH1inC;T<<_nvDxjM@3+*Zw8USkYn;wS05!!}p?#tgp|mJ$^p$9oNH8 z@3-Gkmhf@qpYnVCe}-$TMSiCm2_FMGP_@K0q9i4;B-JXpC>2OC7#SED=o%R58kvR| z8e5r|SQ(pZ8yHv_7_dz80>wB&LvDUbW?Cg~4W;KlIRiDgfov$wPb(=;EJ|hY%uP&B z^-WCAOwLv?(=*qz(6v-BGB7mJH89mRG*SpOG*ieZDJihh*Do(G*UJQ{24bLUz5Jr| zJ8f@)1~N#1Obp2=%}uhha>-9F%}vcKv9byPDlKL(T>gKzKAIY^omN&EshQ~+B@Bj^ zM*Iv#tw1&6NajG*glDFdWPp@hUZcnXR3eF_#5XfHwWP8jl>zK3{erx7`%PhfXy*8a z097#`LqrUC!} literal 0 HcmV?d00001 diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/img/icons/favicon-32x32.png b/src/PostSharp.LicenseServer.Web/wwwroot/img/icons/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..8177b6882dcb7e5ea3abb63f54ec41e6bcf10aad GIT binary patch literal 1634 zcmds%iBnTY9LJY{js_9OqSRJxv}&~)azc`)h+r@T333EPL`yD`wB%@bF~C#>1uRr+ zM+JdW4iAum7ex`tRK$8vu;88AU}3ORVv$C%$o=b!(-|3O{0I7GfBTz#`}uxWeOhuOf7VVIktc!>fT{i2i@p@>aoN zU&vbtdA?8(0ClNQC5Phm(2x$tH^RvT7<>k|t08wKT;2=gW6<<99OA;IY#_cHg>aAq zw<^KB8xHZ{+jVe81GP9VqADlD7P(O@~!1ySP4nxl!C<%e1 zVrbe2*K*<6V;Fh?EhTWX42B2bi3P46g!&y&xE4x7;qNZ!{SAr)a3&E(hv4Q>$i?7z z3_NOr7k`1J4*qBZ+kLQ{hvp)vl0jc5j10m{JHR*$_CwcoC=Q1FHE{1Nw3b3e91Q;h z7ruasEl|1v?w*GF?aRdj!Z^nq{c=ZSM%dWE9{2qpM$39U{dY&@%WsZB{tg*`ZZe{Nu@EFLL{>*5TcZu%@ zw(u@_)hszSFK^FxD$GEgMp!yY*9-WYadR%47);d%ZAwDOb*3YnZg=b$C4k*RzQ35; zB8y++yjPW$o?VbRYr4h4YmcJq4fP*7Y3Fa9Y$jWWEQ53GAL~75ct{(o8auYcxcpif z<8k4uoCTGQ<&9h7NM)odl1aGi0y%@^?rz#Z(=8RmW@S5x()XV(*|T#EB_y-DXq9sg zX@6Ff)O4p*ct4+;qm&7Lk4t#7;08sPGM zA?7UhS8WT`CgWRoB;(2cWt_Ootn%5;{B%?J=R)OI=#<;GP?+f9B8Q$lBL%{)&)XNSx7PJTL~AOK=4yGbo6Jm~>oFS5WHNcF9W)UH?WHp)tyxbAL-el0 zQLHj*q!`B6$&4C>7RRtK0!>1U3TU9c!Mv)8VJd}MslpM1$8<-AOoH*8;PW4|Q7y++ z4&+Ytf+++tX96NpYZQ2jUV&b%il)~p{c55^-|!KM2?{a1xlA7xm&xG_dC-XKkK8Bt zM5#P(?#EuN@T(h?-w)H0nmiT3(nXdG{;M8=Xz ueI#-gohM + + + + + + + + + + + + + + + diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js b/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js index 3af3d97..1ba426c 100644 --- a/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js +++ b/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js @@ -12,16 +12,18 @@ var chart = JSON.parse(dataElement.textContent); - // Follow the page's colour scheme, so axis labels and gridlines stay legible in a dark browser. + // Take the axis and gridline colours from the design tokens, so the chart stays part of the + // page rather than a white box dropped onto it. var styles = getComputedStyle(document.documentElement); - Chart.defaults.color = styles.getPropertyValue("--foreground").trim() || "#222"; - Chart.defaults.borderColor = styles.getPropertyValue("--rule").trim() || "#e0e0e0"; + Chart.defaults.color = styles.getPropertyValue("--text-muted").trim() || "#a0a0a0"; + Chart.defaults.borderColor = styles.getPropertyValue("--doc-table-line").trim() || "#2c1a4f"; + Chart.defaults.font.family = styles.getPropertyValue("--font-body").trim() || "sans-serif"; var datasets = [{ label: "Used", data: chart.used, - borderColor: "#58006e", - backgroundColor: "rgba(88, 0, 110, 0.1)", + borderColor: "#973bfc", + backgroundColor: "rgba(151, 59, 252, 0.16)", fill: true, tension: 0.1, pointRadius: 0, @@ -33,7 +35,7 @@ datasets.push({ label: "Authorized", data: chart.labels.map(function () { return chart.maximum; }), - borderColor: "#ffa500", + borderColor: "#38d5e4", borderDash: [6, 4], fill: false, pointRadius: 0 @@ -42,7 +44,7 @@ datasets.push({ label: "Grace", data: chart.labels.map(function () { return chart.grace; }), - borderColor: "#ff0000", + borderColor: "#ff5e45", borderDash: [2, 3], fill: false, pointRadius: 0 From 5edfba5ab664acbe8d54a5acdf3ab45b6bfc2ae6 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 10:10:54 +0200 Subject: [PATCH 10/44] Make the license server run away from Windows The application already targeted .NET 10, but three things still assumed Windows. Each is now a choice rather than an assumption. - Authentication was IIS or Negotiate. It is now IISIntegrated, Negotiate or None, so a host with no domain to authenticate against can still serve leases; they are recorded without a user name, exactly as an anonymous request already was. Left unset, the scheme is detected from how the application is hosted, and which one was chosen is logged at startup, because guessing wrong is quiet rather than loud. - The release package was published for win-x64. It is now portable and runs wherever the .NET 10 runtime does. That takes it from 12 MB to 48 MB, which is the cost of carrying every platform's native libraries in one artifact. - The simulator imported Microsoft.Win32 for a type that comes from the PostSharp SDK. Add a container deployment: a Dockerfile, and a compose file that starts the server, a SQL Server database and a job that creates the schema from CreateTables.sql. It is a test deployment, and docs/docker.md says plainly which four things make it one. Add .gitattributes, so the working tree is the same on every platform. Verified by running the stack: the schema job creates the database from the unmodified CreateTables.sql, and the server answers on Linux against real SQL Server 2022. That also exercises the rewritten queries -- the anti-join, the seat-count grouping and the counting-point timeline -- on SQL Server for the first time, where they had previously only run on SQLite, and confirms that timestamps come back tagged as UTC from the engine that returns them unspecified. The audit-log timestamp test no longer asks for Windows time zone identifiers, and now proves what it was meant to: that a lease which has been through the database still serializes as UTC. Co-Authored-By: Claude Opus 5 --- .dockerignore | 13 ++ .gitattributes | 22 ++++ Dockerfile | 36 ++++++ README.md | 38 +++++- docker-compose.yml | 83 ++++++++++++ docs/configuration.md | 31 ++++- docs/docker.md | 90 +++++++++++++ eng/Package.ps1 | 42 ++++--- .../AuthenticationRegistration.cs | 119 ++++++++++++++++++ src/PostSharp.LicenseServer.Web/Program.cs | 26 ++-- .../ClientSimulator.cs | 1 - .../LeaseAuditLineTests.cs | 63 +++++----- 12 files changed, 497 insertions(+), 67 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitattributes create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 docs/docker.md create mode 100644 src/PostSharp.LicenseServer.Web/AuthenticationRegistration.cs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..bb61ea9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +**/bin +**/obj +**/.vs +.git +.idea +artifacts +packages +tests +docs +*.db +*.db-shm +*.db-wal +App_Data diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ace532 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Normalize line endings, so that the repository is the same on every platform. +* text=auto + +*.cs text diff=csharp +*.cshtml text +*.csproj text +*.props text +*.slnx text +*.json text +*.md text +*.css text +*.js text +*.sql text +*.ps1 text eol=crlf +*.sh text eol=lf +Dockerfile text eol=lf +*.yml text eol=lf + +*.png binary +*.ico binary +*.woff binary +*.woff2 binary diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cd5762d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# Builds the PostSharp License Server as a Linux container. +# +# The image runs the same application as the Windows release package; only the host differs. See +# docker-compose.yml for a ready-to-run deployment with a SQL Server database. + +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src + +# Restore against the manifests alone, so that a change to the sources does not invalidate the +# restore layer. +COPY Directory.Build.props Directory.Packages.props nuget.config global.json ./ +COPY src/PostSharp.LicenseServer.Core/PostSharp.LicenseServer.Core.csproj src/PostSharp.LicenseServer.Core/ +COPY src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj src/PostSharp.LicenseServer.Web/ +RUN dotnet restore src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj + +COPY src/ src/ +RUN dotnet publish src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj \ + --configuration Release \ + --no-restore \ + --output /app + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime +WORKDIR /app + +# The audit signing key is written here on first start. Mount a volume over it, or the signature +# chain restarts whenever the container is replaced. +RUN mkdir -p /app/App_Data && useradd --uid 64198 --create-home licenseserver \ + && chown -R licenseserver /app +USER licenseserver + +COPY --from=build --chown=licenseserver /app ./ + +ENV ASPNETCORE_HTTP_PORTS=8080 +EXPOSE 8080 + +ENTRYPOINT ["dotnet", "PostSharp.LicenseServer.dll"] diff --git a/README.md b/README.md index 37de310..8ae277e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ This repository contains the source code and releases of PostSharp License Serve The use of the license server is optional. Since all commercial licenses are floating ones, the license server can help teams knowing how many licenses they actually use. -The license server is an ASP.NET Core application with an MS SQL back-end. +The license server is an ASP.NET Core application with an MS SQL back-end. It runs on Windows behind +IIS, and on Linux or macOS under its own process or in a container. We at PostSharp consider that it is the customer's sole responsibility to respect the license agreement, and this is why we are providing the source code of the license server. Note that the use of licenses keys [is audited anyway](http://doc.postsharp.net/license-audit); if this is not an option for your organization, you can ask the PostSharp sales team for a license key with audit waiver. @@ -22,7 +23,20 @@ You can download the latest release from https://github.com/postsharp/PostSharp. * [Installing the license server](http://doc.postsharp.net/license-server-admin). * [Using the license server](http://doc.postsharp.net/license-server). -## Installing +## Trying it out + +The quickest way to see the license server working, on any machine with Docker, is the container +deployment. It starts the server, a SQL Server database and a job that creates the schema: + +``` +docker compose up --build +``` + +Then open http://localhost:8080 and add your license key. See +[docs/docker.md](docs/docker.md) for what it contains and what to change before using it for +anything other than a trial. + +## Installing on IIS ### Requirements @@ -41,6 +55,26 @@ You can download the latest release from https://github.com/postsharp/PostSharp. **Anonymous Authentication** if you want every lease request to be attributed to a user. 6. Browse to the application and add your license key. +## Installing elsewhere + +The release package is portable: the same zip runs wherever the .NET 10 runtime does. + +1. Install the [.NET 10 runtime](https://dotnet.microsoft.com/download/dotnet/10.0) + (`aspnetcore-runtime-10.0`). +2. Create the database and run `Database/CreateTables.sql` against it. +3. Unpack the zip, edit `appsettings.json`, and run it: + + ``` + dotnet PostSharp.LicenseServer.dll + ``` + +Two settings usually need changing away from Windows. The connection string cannot use +`Integrated Security=True` unless the host is joined to the domain, so use a SQL Server login or a +managed identity instead. And Windows authentication needs Kerberos, so either join the host to the +domain and set `Authentication:Scheme` to `Negotiate`, or set it to `None` and accept that leases +will not record who requested them. Both are covered in +[docs/configuration.md](docs/configuration.md). + ## Upgrading from version 2025.1 or earlier Earlier versions ran on .NET Framework and ASP.NET WebForms. The database schema has not changed, diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5fb9bda --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,83 @@ +# A complete PostSharp License Server for testing: the server, a SQL Server database, and a one-shot +# job that creates the schema from Database/CreateTables.sql. +# +# docker compose up --build +# open http://localhost:8080 +# +# This is a TEST deployment. The password below is in plain text, the database is thrown away with +# its volume, and nobody is authenticated, so every lease is recorded without a user name. See +# docs/configuration.md before running it for real. + +name: postsharp-license-server + +services: + + database: + image: mcr.microsoft.com/mssql/server:2022-latest + environment: + ACCEPT_EULA: "Y" + MSSQL_SA_PASSWORD: "LicenseServer!2026" + MSSQL_PID: Developer + ports: + - "1433:1433" + volumes: + - database-data:/var/opt/mssql + healthcheck: + # The server answers long before it is ready to take a query, so the check is a real query. + test: + - CMD-SHELL + - /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$$MSSQL_SA_PASSWORD" -C -Q "SELECT 1" -b + interval: 10s + timeout: 5s + retries: 12 + start_period: 20s + + database-schema: + # The server image already carries sqlcmd, so the schema job needs no second image to pull. + image: mcr.microsoft.com/mssql/server:2022-latest + depends_on: + database: + condition: service_healthy + volumes: + - ./src/PostSharp.LicenseServer.Web/Database:/schema:ro + entrypoint: + - /bin/bash + - -c + - | + set -e + SQLCMD=/opt/mssql-tools18/bin/sqlcmd + $$SQLCMD -S database -U sa -P "LicenseServer!2026" -C -b \ + -Q "IF DB_ID('PostSharpLicenseServer') IS NULL CREATE DATABASE PostSharpLicenseServer" + # Re-running compose must not fail on an existing schema. + TABLES=$$($$SQLCMD -S database -U sa -P "LicenseServer!2026" -C -h -1 -W -d PostSharpLicenseServer \ + -Q "SET NOCOUNT ON; SELECT COUNT(*) FROM sys.tables WHERE name = 'Licenses'") + if [ "$$TABLES" = "0" ]; then + $$SQLCMD -S database -U sa -P "LicenseServer!2026" -C -b -d PostSharpLicenseServer -i /schema/CreateTables.sql + echo "Schema created." + else + echo "Schema already present." + fi + + licenseserver: + build: + context: . + depends_on: + database-schema: + condition: service_completed_successfully + ports: + - "8080:8080" + environment: + LicenseServer__DatabaseProvider: SqlServer + ConnectionStrings__SharpCrafters_LicenseServerConnectionString: >- + Server=database,1433;Database=PostSharpLicenseServer;User Id=sa;Password=LicenseServer!2026;TrustServerCertificate=True + # No domain controller in a container, so nobody is authenticated. + Authentication__Scheme: None + # Keeps the audit signature chain stable across restarts. + LicenseServer__AuditHmacKey: "dGVzdC1vbmx5LWF1ZGl0LWtleS1kby1ub3QtdXNlLWxpdmU=" + Smtp__Enabled: "false" + volumes: + - licenseserver-data:/app/App_Data + +volumes: + database-data: + licenseserver-data: diff --git a/docs/configuration.md b/docs/configuration.md index 558df68..d9dde14 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -23,7 +23,20 @@ start. A relative path is resolved against the application directory, not agains working directory happens to be. Note that the modern SQL client encrypts connections by default. Against a server whose certificate -the web server does not trust, add `Encrypt=False` to the connection string. +the web server does not trust, add `Encrypt=False` to the connection string, or +`TrustServerCertificate=True` to keep the encryption and skip only the certificate check. + +The default connection string uses `Integrated Security=True`, which authenticates as the Windows +account the application runs under. That does not work on a host which is not joined to the domain, +so off Windows use a SQL Server login: + +``` +Server=db.example.com,1433;Database=PostSharpLicenseServer;User Id=licenseserver;Password=...;TrustServerCertificate=True +``` + +Supply that as the environment variable +`ConnectionStrings__SharpCrafters_LicenseServerConnectionString` rather than writing the password +into `appsettings.json`. ## Licensing rules @@ -54,10 +67,24 @@ logged and never denies a developer their license. | Setting | Default | Meaning | |---|---|---| -| `Authentication:Scheme` | `IISIntegrated` | `IISIntegrated` behind IIS, `Negotiate` for self-hosting. | +| `Authentication:Scheme` | detected | `IISIntegrated`, `Negotiate` or `None`. See below. | | `LicenseServer:AdminRoles` | empty | The Windows groups allowed to reach the administrative pages, for example `["DOMAIN\\PostSharp Administrators"]`. | | `LicenseServer:RequireAuthenticatedLeaseRequests` | `false` | Whether a lease request must be authenticated. | +The authentication scheme decides how the server learns who is borrowing a license, which is what it +records in the `AuthenticatedUser` column of the audit log. + +| Scheme | Use it when | +|---|---| +| `IISIntegrated` | The application is hosted by IIS. Windows authentication must also be enabled on the site in IIS Manager. | +| `Negotiate` | The application runs under its own process on a host joined to your domain. On anything other than Windows this needs Kerberos and a keytab. | +| `None` | There is no domain to authenticate against, as in a container. Requests are served anonymously and leases record an empty user. The server warns at startup that it is doing this. | + +Left unset, the server picks `IISIntegrated` when it finds itself hosted by IIS, `Negotiate` on +Windows, and `None` elsewhere, and logs which one it chose. Set the value explicitly on anything you +care about: guessing wrong is quiet rather than loud, because a server that authenticates nobody +still serves leases perfectly well — it just cannot say who took them. + **Both default to open**, which is how the license server has always shipped, so that an upgrade cannot lock an administrator out of their own server. The administrative pages are the only way to add or revoke a license, so setting `AdminRoles` is worth doing; until it is set, the server says so diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 0000000..af0bc2d --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,90 @@ +# Running the license server in a container + +`docker-compose.yml` brings up a complete license server: the application, a SQL Server database, +and a one-shot job that creates the schema from `Database/CreateTables.sql`. + +``` +docker compose up --build +``` + +The server is then at http://localhost:8080 and the database at `localhost:1433`. Add a license key +on the **Add a license** page and point a PostSharp client at +`http://localhost:8080/Lease.ashx`. + +To stop it, keeping the data: + +``` +docker compose down +``` + +To stop it and throw the database away: + +``` +docker compose down --volumes +``` + +## What it starts + +| Service | What it is | +|---|---| +| `database` | SQL Server 2022 Developer Edition. Its health check runs a real query, because the server accepts connections well before it can answer one. | +| `database-schema` | Runs once: creates the database if it does not exist, then runs `CreateTables.sql` if the tables are not already there. Re-running `docker compose up` does not fail on an existing schema. It reuses the SQL Server image, which already carries `sqlcmd`, rather than pulling a second one. | +| `licenseserver` | The application, built from `Dockerfile`. Waits for the schema job to finish. | + +The application keeps its audit signing key in the `licenseserver-data` volume, so the signature +chain survives the container being replaced. The database keeps its files in `database-data`. + +## This is a test deployment + +It is meant for trying the license server out and for development. Four things make it unsuitable as +it stands for anything else. + +- **The `sa` password is in the compose file in plain text**, and `sa` is what the application + connects as. A real deployment uses a login with rights on the one database, and supplies the + password from a secret rather than from a file in the repository. +- **Nobody is authenticated.** There is no domain controller in a container, so + `Authentication__Scheme` is `None` and every lease is recorded without a user name. To attribute + leases, run the container on a host joined to your domain with a Kerberos keytab and set the + scheme to `Negotiate`. +- **The administrative pages are open**, as they are in the default configuration everywhere. Set + `LicenseServer__AdminRoles` once you have an identity to check against. +- **The audit signing key is a fixed value in the compose file**, so that restarting the stack does + not start a new signature chain. Remove it for a real deployment and let the server generate one + into the volume. + +## The image on its own + +The image does not need the compose file. Against an existing SQL Server: + +``` +docker build -t postsharp-licenseserver . +docker run --rm -p 8080:8080 \ + -e ConnectionStrings__SharpCrafters_LicenseServerConnectionString="Server=db;Database=PostSharpLicenseServer;User Id=licenseserver;Password=...;TrustServerCertificate=True" \ + -e Authentication__Scheme=None \ + -v licenseserver-data:/app/App_Data \ + postsharp-licenseserver +``` + +Any setting from [configuration.md](configuration.md) can be given as an environment variable, with +a double underscore where the setting name has a colon. + +For a trial with no database at all, the server can keep its data in a SQLite file, which it creates +itself: + +``` +docker run --rm -p 8080:8080 \ + -e LicenseServer__DatabaseProvider=Sqlite \ + -e ConnectionStrings__SharpCrafters_LicenseServerConnectionString="DataSource=/app/App_Data/licenseserver.db" \ + -v licenseserver-data:/app/App_Data \ + postsharp-licenseserver +``` + +SQL Server remains the supported engine for a real installation. + +## The image + +`Dockerfile` builds in two stages, publishing with the .NET SDK image and running on the ASP.NET +runtime image. It runs as a non-root user and listens on port 8080. + +It does not run the tests: run `dotnet test` before building, or use `eng/Package.ps1`, which runs +them for you. diff --git a/eng/Package.ps1 b/eng/Package.ps1 index 9043bc5..c70fc1d 100644 --- a/eng/Package.ps1 +++ b/eng/Package.ps1 @@ -4,25 +4,36 @@ .DESCRIPTION Publishes the web application and zips it into artifacts/PostSharp.LicenseServer.zip, which is - the artifact attached to a GitHub release and which an administrator unpacks into an IIS site. + the artifact attached to a GitHub release. - The package is framework-dependent: the target machine needs the ASP.NET Core Hosting Bundle. + The package is portable: it carries no platform-specific build and runs wherever the .NET 10 + runtime does. On Windows that means unpacking it into an IIS application, with the ASP.NET Core + Hosting Bundle installed. Elsewhere it is run with `dotnet PostSharp.LicenseServer.dll`. + + Runs on Windows PowerShell and on PowerShell 7 for Linux and macOS. .PARAMETER Configuration The build configuration. Release by default. .PARAMETER OutputPath Where to write the zip. artifacts/ by default. + +.PARAMETER SkipTests + Skips the test run. Intended for iterating on the packaging itself. #> [CmdletBinding()] param( [string] $Configuration = 'Release', - [string] $OutputPath = (Join-Path $PSScriptRoot '..' 'artifacts') + [string] $OutputPath, + [switch] $SkipTests ) $ErrorActionPreference = 'Stop' -$repositoryRoot = Resolve-Path (Join-Path $PSScriptRoot '..') +$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path + +if ( -not $OutputPath ) { $OutputPath = Join-Path $repositoryRoot 'artifacts' } + $project = Join-Path $repositoryRoot 'src' 'PostSharp.LicenseServer.Web' 'PostSharp.LicenseServer.Web.csproj' $publishPath = Join-Path $repositoryRoot 'artifacts' 'publish' $zipPath = Join-Path $OutputPath 'PostSharp.LicenseServer.zip' @@ -30,18 +41,17 @@ $zipPath = Join-Path $OutputPath 'PostSharp.LicenseServer.zip' if ( Test-Path $publishPath ) { Remove-Item $publishPath -Recurse -Force } New-Item -ItemType Directory -Force -Path $OutputPath | Out-Null -Write-Host "Testing..." -Push-Location $repositoryRoot -try { dotnet test --configuration $Configuration --nologo } -finally { Pop-Location } -if ( $LASTEXITCODE -ne 0 ) { throw "The tests failed." } - -Write-Host "Publishing..." -# Targeting the Windows runtime keeps the Linux and macOS native libraries -- which an IIS -# deployment never loads -- out of the package. It stays framework-dependent: the target machine -# needs the ASP.NET Core Hosting Bundle. -dotnet publish $project --configuration $Configuration --output $publishPath --runtime win-x64 --self-contained false --nologo -if ( $LASTEXITCODE -ne 0 ) { throw "The publish failed." } +if ( -not $SkipTests ) { + Write-Host 'Testing...' + Push-Location $repositoryRoot + try { dotnet test --configuration $Configuration --nologo } + finally { Pop-Location } + if ( $LASTEXITCODE -ne 0 ) { throw 'The tests failed.' } +} + +Write-Host 'Publishing...' +dotnet publish $project --configuration $Configuration --output $publishPath --nologo +if ( $LASTEXITCODE -ne 0 ) { throw 'The publish failed.' } # Development-only settings must not reach a customer's server. Remove-Item (Join-Path $publishPath 'appsettings.Development.json') -Force -ErrorAction SilentlyContinue diff --git a/src/PostSharp.LicenseServer.Web/AuthenticationRegistration.cs b/src/PostSharp.LicenseServer.Web/AuthenticationRegistration.cs new file mode 100644 index 0000000..691d614 --- /dev/null +++ b/src/PostSharp.LicenseServer.Web/AuthenticationRegistration.cs @@ -0,0 +1,119 @@ +using System.Runtime.InteropServices; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Negotiate; +using Microsoft.AspNetCore.Server.IISIntegration; +using Microsoft.Extensions.Options; + +namespace PostSharp.LicenseServer; + +/// +/// Chooses how the license server identifies the person borrowing a license. +/// +public static class AuthenticationRegistration +{ + /// + /// Windows authentication handled by IIS. Required for in-process hosting behind IIS. + /// + public const string IisIntegrated = "IISIntegrated"; + + /// + /// Windows authentication handled by the application. Works on Windows out of the box, and on + /// other systems once the host is joined to the domain and has a Kerberos keytab. + /// + public const string Negotiate = "Negotiate"; + + /// + /// No authentication. Leases are recorded with an empty authenticated user, exactly as an + /// anonymous request is already recorded today. + /// + public const string None = "None"; + + /// + /// Registers the scheme named by Authentication:Scheme. + /// + /// The scheme that was registered, for logging. + public static string AddLicenseServerAuthentication( + this IServiceCollection services, + IConfiguration configuration ) + { + string scheme = ResolveScheme( configuration ); + + switch ( scheme ) + { + case IisIntegrated: + services.AddAuthentication( IISDefaults.AuthenticationScheme ); + + break; + + case Negotiate: + services.AddAuthentication( NegotiateDefaults.AuthenticationScheme ).AddNegotiate(); + + break; + + case None: + services.AddAuthentication( AnonymousAuthenticationHandler.SchemeName ) + .AddScheme( + AnonymousAuthenticationHandler.SchemeName, + _ => { } ); + + break; + + default: + throw new InvalidOperationException( + $"Unknown authentication scheme '{scheme}'. Use '{IisIntegrated}', '{Negotiate}' or '{None}'." ); + } + + return scheme; + } + + /// + /// Works out which scheme to use when the configuration does not say. + /// + /// + /// The same package runs behind IIS, on a Windows machine under its own process, and on a Linux + /// host that may have no domain at all. Guessing wrong is quiet rather than loud -- the server + /// would simply record every lease against an empty user -- so the choice is made from how the + /// application is actually being hosted, and is logged at startup. + /// + private static string ResolveScheme( IConfiguration configuration ) + { + string? configured = configuration["Authentication:Scheme"]; + + if ( !string.IsNullOrWhiteSpace( configured ) ) + { + // Accept any casing, so that "negotiate" in a container's environment works. + foreach ( string known in new[] { IisIntegrated, Negotiate, None } ) + { + if ( string.Equals( configured, known, StringComparison.OrdinalIgnoreCase ) ) + { + return known; + } + } + + return configured; + } + + // The ASP.NET Core Module sets this when it hosts the application. + if ( Environment.GetEnvironmentVariable( "ASPNETCORE_IIS_PHYSICAL_PATH" ) != null ) + { + return IisIntegrated; + } + + return RuntimeInformation.IsOSPlatform( OSPlatform.Windows ) ? Negotiate : None; + } +} + +/// +/// Authenticates nobody, so that requests are served anonymously. +/// +public sealed class AnonymousAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder ) : AuthenticationHandler( options, logger, encoder ) +{ + public const string SchemeName = "None"; + + protected override Task HandleAuthenticateAsync() + => Task.FromResult( AuthenticateResult.NoResult() ); +} diff --git a/src/PostSharp.LicenseServer.Web/Program.cs b/src/PostSharp.LicenseServer.Web/Program.cs index 204d06d..e0dc4c1 100644 --- a/src/PostSharp.LicenseServer.Web/Program.cs +++ b/src/PostSharp.LicenseServer.Web/Program.cs @@ -1,5 +1,3 @@ -using Microsoft.AspNetCore.Authentication.Negotiate; -using Microsoft.AspNetCore.Server.IISIntegration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using PostSharp.LicenseServer; @@ -96,18 +94,7 @@ }; } ); -// Windows authentication. IIS handles it in-process; Negotiate covers Kestrel and out-of-process -// hosting, which is what `dotnet run` uses during development. -string authenticationScheme = builder.Configuration["Authentication:Scheme"] ?? "Negotiate"; - -if ( string.Equals( authenticationScheme, "IISIntegrated", StringComparison.OrdinalIgnoreCase ) ) -{ - builder.Services.AddAuthentication( IISDefaults.AuthenticationScheme ); -} -else -{ - builder.Services.AddAuthentication( NegotiateDefaults.AuthenticationScheme ).AddNegotiate(); -} +string authenticationScheme = builder.Services.AddLicenseServerAuthentication( builder.Configuration ); builder.Services.AddAuthorizationBuilder() .AddPolicy( @@ -196,6 +183,17 @@ } } +// Which scheme was chosen is worth stating, because leases recorded under "None" carry no user. +app.Logger.LogInformation( "Authenticating with the {Scheme} scheme.", authenticationScheme ); + +if ( authenticationScheme == AuthenticationRegistration.None ) +{ + app.Logger.LogWarning( + "Authentication is disabled, so leases will not record who requested them. Set {Setting} to " + + "\"Negotiate\" on a host that is joined to your domain.", + "Authentication:Scheme" ); +} + app.Run(); /// diff --git a/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs b/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs index 7e10a09..4667395 100644 --- a/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs +++ b/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs @@ -1,4 +1,3 @@ -using Microsoft.Win32; using PostSharp.Platform; using PostSharp.Sdk; using PostSharp.Sdk.Extensibility.Licensing; diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseAuditLineTests.cs b/tests/PostSharp.LicenseServer.Tests/LeaseAuditLineTests.cs index 749bab4..07f13dd 100644 --- a/tests/PostSharp.LicenseServer.Tests/LeaseAuditLineTests.cs +++ b/tests/PostSharp.LicenseServer.Tests/LeaseAuditLineTests.cs @@ -1,4 +1,7 @@ +using Microsoft.EntityFrameworkCore; using System.Globalization; +using PostSharp.LicenseServer.Data; +using PostSharp.LicenseServer.Tests.Infrastructure; namespace PostSharp.LicenseServer.Tests; @@ -64,29 +67,40 @@ public void Write_NeverDisclosesTheUserOrMachineName() } /// - /// Timestamps must be absolute. The legacy implementation serialized values whose - /// was -- which is what SQL - /// Server returns -- in a mode that treated them as local time and shifted them, so the exported - /// file depended on the time zone of the machine that produced it. + /// Timestamps must be absolute, whatever time zone the server keeps. /// + /// + /// The legacy implementation serialized values whose was + /// -- which is what SQL Server returns -- in a mode that + /// treated them as local time and shifted them, so the exported file depended on the machine + /// that produced it. + /// [Fact] - public void Write_IsIndependentOfTheServerTimeZone() + public void Write_EmitsAbsoluteTimestamps() { - string expected = CreateLease().ToAuditLine( false ); + string[] fields = CreateLease().ToAuditLine( false ).Split( ';' ); - foreach ( string timeZoneId in new[] { "UTC", "Pacific Standard Time", "Tokyo Standard Time" } ) - { - if ( !TryFindTimeZone( timeZoneId, out _ ) ) - { - continue; - } + Assert.EndsWith( "Z", fields[3], StringComparison.Ordinal ); + Assert.EndsWith( "Z", fields[4], StringComparison.Ordinal ); + } - // The value carries its own UTC offset, so no ambient time zone can change it. - Assert.Equal( expected, CreateLease().ToAuditLine( false ) ); - } + /// + /// A lease that has been through the database must still serialize as UTC. This is the half of + /// the guarantee that lives in the model rather than in Lease.Write. + /// + [Fact] + public async Task Write_AfterReload_StillEmitsUtc() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + Lease saved = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); - Assert.EndsWith( "Z", expected.Split( ';' )[3], StringComparison.Ordinal ); - Assert.EndsWith( "Z", expected.Split( ';' )[4], StringComparison.Ordinal ); + await using LicenseServerDbContext reader = context.CreateFreshContext(); + Lease reloaded = await reader.Leases.SingleAsync( l => l.LeaseId == saved.LeaseId ); + + Assert.Equal( DateTimeKind.Utc, reloaded.StartTime.Kind ); + Assert.Equal( DateTimeKind.Utc, reloaded.EndTime.Kind ); + Assert.Equal( saved.ToAuditLine( false ), reloaded.ToAuditLine( false ) ); } [Fact] @@ -108,19 +122,4 @@ public void Write_UsesInvariantFormatting() } } - private static bool TryFindTimeZone( string id, out TimeZoneInfo? timeZone ) - { - try - { - timeZone = TimeZoneInfo.FindSystemTimeZoneById( id ); - - return true; - } - catch ( TimeZoneNotFoundException ) - { - timeZone = null; - - return false; - } - } } From 0be4f7ff63a39e8565e987bbc7baf6d197992365 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 10:34:35 +0200 Subject: [PATCH 11/44] Address the review findings Nine defects were raised on the pull request. Each was real, and each had been inherited from the legacy implementation rather than introduced by the migration. The audit chain was the substantial one, and two findings shared a root cause: leases were signed before they were inserted. The signed payload therefore carried a lease identifier of zero while the exported line carried the one the database assigned, so a signature could not be recomputed from an export; and leases saved together all chained from the same predecessor, so removing one of them broke no later signature. Leases are now signed after the insert, in the order the database assigned, with the insert and the signature sharing a transaction so that a lease is never readable without its signature. A test recomputes a whole chain from the exported lines, which is what the chain is for. The rest: - A build agent was served any license whose key merely parsed, so an ineligible or expired one could be handed out, and the lease it was given was never clamped to the end of the license. Build agents now go through the same validation as anybody else, and keep only their exemption from consuming a seat. - A license with no seat limit that reached the grace period dereferenced a null maximum and answered 500 instead of denying the request. There is no capacity to exceed on such a license, so there is no grace period either. - Both redirects dropped the path base, so they left the application when it is installed below an IIS site root. - The audit export accepted a year outside the range of a date, passing validation and then failing while the date was constructed. - The audit export built the whole file in memory twice before answering. It is written to the response as the rows arrive, as the legacy handler did. - The usage graph floored the grace capacity where the allocator rounds it up, so a one-seat license with 20% grace was drawn as allowing one seat while the server granted two. - The user and machine names reaching the slow-request log are stripped of control characters, so they cannot forge a line in a plain-text log. The build-agent, unlimited-license, export-range and path-base fixes each have a test, checked by reintroducing the defect and confirming the intended test fails. Co-Authored-By: Claude Opus 5 --- .../Data/LeaseRepository.cs | 109 ++++++++--- .../Services/LeaseService.cs | 39 +++- .../Endpoints/LegacyUrlRedirects.cs | 29 ++- .../Endpoints/LicenseServerEndpoints.cs | 71 +++++-- .../Pages/Admin/Export.cshtml.cs | 5 +- .../Pages/Graph.cshtml.cs | 6 +- .../Infrastructure/TestData.cs | 5 +- .../LeaseSignatureTests.cs | 173 ++++++++++-------- .../ReviewRegressionTests.cs | 157 ++++++++++++++++ 9 files changed, 465 insertions(+), 129 deletions(-) create mode 100644 tests/PostSharp.LicenseServer.Tests/ReviewRegressionTests.cs diff --git a/src/PostSharp.LicenseServer.Core/Data/LeaseRepository.cs b/src/PostSharp.LicenseServer.Core/Data/LeaseRepository.cs index 42d98d4..b12bb53 100644 --- a/src/PostSharp.LicenseServer.Core/Data/LeaseRepository.cs +++ b/src/PostSharp.LicenseServer.Core/Data/LeaseRepository.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Options; using PostSharp.LicenseServer.Licensing; using PostSharp.LicenseServer.Options; @@ -22,26 +23,47 @@ public sealed class LeaseRepository( public IQueryable Licenses => db.Licenses; /// - /// Signs a lease, chaining it to the signature of the most recently persisted lease. + /// Signs one lease, chaining it to the signature of the lease before it. /// /// - /// The query deliberately reads the database rather than the change tracker, so that leases - /// created within one unit of work all chain from the same committed predecessor. This matches - /// the LINQ to SQL implementation, which never flushed pending inserts before a query. + /// The payload is the previous signature, a semicolon, and the lease's own audit line. Signing + /// the line that is actually exported is what lets an auditor recompute the chain from an + /// exported file. /// - public string GetSignature( Lease lease ) + public string ComputeSignature( string? previousSignature, Lease lease ) + => signer.Sign( (previousSignature ?? string.Empty) + ";" + lease.ToAuditLine( false ) ); + + /// + /// Signs the leases that have just been inserted, in the order the database assigned them. + /// + /// + /// Leases are signed after they are inserted rather than before, because the audit line starts + /// with the lease identifier and the database is what assigns it. Signing beforehand would put a + /// zero in every payload, so a signature could not be recomputed from an exported line, and + /// leases saved together would all chain from the same predecessor instead of from each other -- + /// which would let one of them be removed without breaking any later signature. + /// + private void SignInsertedLeases( IReadOnlyList inserted ) { - Lease? lastLease = db.Leases + if ( inserted.Count == 0 ) + { + return; + } + + int firstInsertedId = inserted.Min( l => l.LeaseId ); + + string? previousSignature = db.Leases .AsNoTracking() + .Where( l => l.LeaseId < firstInsertedId ) .OrderByDescending( l => l.LeaseId ) + .Select( l => l.HMAC ) .FirstOrDefault(); - StringWriter stringWriter = new(); - stringWriter.Write( lastLease != null ? lastLease.HMAC : "" ); - stringWriter.Write( ';' ); - lease.Write( stringWriter, false ); - - return signer.Sign( stringWriter.ToString() ); + foreach ( Lease lease in inserted.OrderBy( l => l.LeaseId ) ) + { + previousSignature = this.ComputeSignature( previousSignature, lease ); + lease.HMAC = previousSignature; + } } public Lease? CreateLease( @@ -56,10 +78,8 @@ public string GetSignature( Lease lease ) { License = license, - // The foreign key is assigned explicitly because the lease is signed before it is added - // to the change tracker, and EF Core does not populate foreign keys from navigation - // properties until then. LINQ to SQL assigned it inside the navigation setter, so - // omitting this would silently change what gets signed. + // Assigned alongside the navigation property rather than left to EF Core's fixup, so + // that the lease is fully formed before it is tracked. LicenseId = license.LicenseId, AuthenticatedUser = authenticatedUserName, @@ -175,8 +195,7 @@ private bool FixLease( Lease lease, DateTime time, bool fixEndTime = true ) } } - lease.HMAC = this.GetSignature( lease ); - + // The signature is applied by SaveChanges, once the database has assigned an identifier. return true; } @@ -261,8 +280,56 @@ public IEnumerable GetLeaseCountingPoints( } } - public Task SaveChangesAsync( CancellationToken cancellationToken = default ) - => db.SaveChangesAsync( cancellationToken ); + /// + /// Saves the unit of work, signing any newly inserted leases. + /// + /// + /// The insert and the signature are two statements, so they run in one transaction: a lease must + /// never be readable without its signature. + /// + public async Task SaveChangesAsync( CancellationToken cancellationToken = default ) + { + List inserted = this.GetPendingLeases(); - public int SaveChanges() => db.SaveChanges(); + if ( inserted.Count == 0 ) + { + return await db.SaveChangesAsync( cancellationToken ); + } + + // The caller may already have opened a transaction, as deleting a license does. + IDbContextTransaction? transaction = db.Database.CurrentTransaction == null + ? await db.Database.BeginTransactionAsync( cancellationToken ) + : null; + + try + { + int result = await db.SaveChangesAsync( cancellationToken ); + + this.SignInsertedLeases( inserted ); + await db.SaveChangesAsync( cancellationToken ); + + if ( transaction != null ) + { + await transaction.CommitAsync( cancellationToken ); + } + + return result; + } + finally + { + if ( transaction != null ) + { + await transaction.DisposeAsync(); + } + } + } + + public int SaveChanges() + => this.SaveChangesAsync().GetAwaiter().GetResult(); + + private List GetPendingLeases() + => db.ChangeTracker.Entries() + .Where( e => e.State == EntityState.Added ) + .Select( e => e.Entity ) + .ToList(); } diff --git a/src/PostSharp.LicenseServer.Core/Services/LeaseService.cs b/src/PostSharp.LicenseServer.Core/Services/LeaseService.cs index 3ff0d41..ab093a2 100644 --- a/src/PostSharp.LicenseServer.Core/Services/LeaseService.cs +++ b/src/PostSharp.LicenseServer.Core/Services/LeaseService.cs @@ -173,16 +173,37 @@ public LeaseService( if ( this.IsBuildServer( machine ) ) { - License? buildServerLicense = licenses.FirstOrDefault( this.IsLicenseValid ); + Dictionary buildServerStates = []; - if ( buildServerLicense != null ) + // A build agent is exempt from consuming a seat, not from the rules about which licenses + // may be served at all, so the same validation runs as for anybody else. + foreach ( License candidate in licenses ) { + LicenseState? state = + this.GetLicenseState( candidate, version, buildDate, now, buildServerStates, errors ); + + if ( state == null ) + { + continue; + } + DateTime endTime = now.AddDays( this.settings.NewLeaseDays ); + if ( state.ParsedLicense.ValidTo.HasValue && state.ParsedLicense.ValidTo < endTime ) + { + endTime = state.ParsedLicense.ValidTo.Value; + } + + if ( endTime <= now ) + { + // The license expires before the lease would begin. + continue; + } + // A build server's lease is never persisted, so that build agents cannot consume // the seats of the developers they build for. return new GrantedLease( - buildServerLicense.LicenseKey, + candidate.LicenseKey, now, endTime, endTime.AddDays( -this.settings.MinLeaseDays ) ); @@ -338,10 +359,18 @@ public LeaseService( continue; } + if ( !licenseState.Maximum.HasValue ) + { + // A license with no seat limit has no capacity to exceed, so there is no grace + // period to fall back on. It reaches this pass only when the second one declined to + // grant a lease for some other reason, such as the license having expired. + continue; + } + license.GraceStartTime ??= now; int graceLimit = (int) Math.Ceiling( - licenseState.Maximum!.Value * (100.0 + licenseState.ParsedLicense.GracePercent) / 100.0 ); + licenseState.Maximum.Value * (100.0 + licenseState.ParsedLicense.GracePercent) / 100.0 ); DateTime graceEnd = license.GraceStartTime.Value.AddDays( licenseState.ParsedLicense.GraceDays ); @@ -404,8 +433,6 @@ await this.SendEmailAsync( return null; } - private bool IsLicenseValid( License license ) => this.licenseParser.TryParse( license.LicenseKey ) != null; - /// /// Determines whether a machine is a build agent, ignoring the unique-identifier suffix that /// build agents append to their name. diff --git a/src/PostSharp.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs b/src/PostSharp.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs index b432350..de028ff 100644 --- a/src/PostSharp.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs +++ b/src/PostSharp.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs @@ -17,15 +17,38 @@ private static readonly (string Legacy, string Current)[] redirects = ("/Admin/GenerateDemoData.aspx", "/Admin/GenerateDemoData") ]; + /// + /// Works out where a legacy URL should redirect to. + /// + /// + /// The path base matters: installed as an application below an IIS site root, a redirect to + /// /Graph would land on the parent site rather than on this one. The query string carries + /// the license identifier and the graph window, so it has to survive as well. + /// + public static string BuildRedirectLocation( PathString pathBase, string target, QueryString queryString ) + { + if ( !pathBase.HasValue ) + { + return target + queryString; + } + + // The home page is the path base itself, rather than the path base followed by a slash. + string path = target == "/" ? pathBase.Value! : pathBase.Value + target; + + return path + queryString; + } + public static void MapLegacyUrlRedirects( this WebApplication app ) { foreach ( (string legacy, string current) in redirects ) { string target = current; - // The query string carries the license identifier and the graph window, so it has to - // survive the redirect. - app.MapGet( legacy, ( HttpContext context ) => Results.Redirect( target + context.Request.QueryString, true ) ); + app.MapGet( + legacy, + ( HttpContext context ) => Results.Redirect( + BuildRedirectLocation( context.Request.PathBase, target, context.Request.QueryString ), + true ) ); } } } diff --git a/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs index 38a84e7..07be3c0 100644 --- a/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs +++ b/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs @@ -132,7 +132,13 @@ private static async Task GetLeaseAsync( if ( elapsed > TimeSpan.FromSeconds( 1 ) ) { - logger.LogWarning( "The lease request for {User} on {Machine} took {Elapsed}.", userName, machine, elapsed ); + // The user and machine names come from the query string, so they are stripped of + // anything that could forge a line in a plain-text log. + logger.LogWarning( + "The lease request for {User} on {Machine} took {Elapsed}.", + Sanitize( userName ), + Sanitize( machine ), + elapsed ); } return Results.Text( @@ -167,9 +173,18 @@ private static async Task ExportAsync( int? tm, CancellationToken cancellationToken ) { - if ( fy is null or < 1 || ty is null or < 1 || fm is null or < 1 or > 12 || tm is null or < 1 or > 12 ) + // The same range the form offers. Without an upper bound, a year such as 10000 passes the + // check and then throws when the date is constructed. + const int firstYear = 2010; + const int lastYear = 2100; + + if ( fy is null or < firstYear or > lastYear + || ty is null or < firstYear or > lastYear + || fm is null or < 1 or > 12 + || tm is null or < 1 or > 12 ) { - return Results.BadRequest( "The range of months is missing or invalid." ); + return Results.BadRequest( + $"The range of months is missing or invalid. Years must be between {firstYear} and {lastYear}." ); } DateTime fromTime = new( fy.Value, fm.Value, 1 ); @@ -195,23 +210,45 @@ private static async Task ExportAsync( return Results.Text( string.Empty, "text/plain" ); } - List leases = await repository.Leases - .Where( l => l.LeaseId >= bounds.MinLeaseId && l.LeaseId <= bounds.MaxLeaseId ) - .OrderBy( l => l.LeaseId ) - .AsNoTracking() - .ToListAsync( cancellationToken ); + int minLeaseId = bounds.MinLeaseId; + int maxLeaseId = bounds.MaxLeaseId; - StringWriter writer = new(); - - foreach ( Lease lease in leases ) - { - lease.Write( writer, true ); - writer.WriteLine(); - } - - return Results.Text( writer.ToString(), "text/plain" ); + // Written straight to the response as the rows arrive. An audit log covering years of + // activity is far too large to assemble in memory first, and the caller should not wait for + // the whole of it before the download starts. + return Results.Stream( + async stream => + { + await using StreamWriter writer = new( stream ); + + IAsyncEnumerable leases = repository.Leases + .Where( l => l.LeaseId >= minLeaseId && l.LeaseId <= maxLeaseId ) + .OrderBy( l => l.LeaseId ) + .AsNoTracking() + .AsAsyncEnumerable(); + + await foreach ( Lease lease in leases.WithCancellation( cancellationToken ) ) + { + lease.Write( writer, true ); + await writer.WriteLineAsync(); + } + }, + "text/plain" ); } private static IResult Error( int statusCode, string description ) => Results.Text( description, "text/plain", statusCode: statusCode ); + + /// + /// Removes control characters from a value taken from the request, so that it cannot forge a + /// line break in a log, and caps its length. + /// + private static string Sanitize( string value ) + { + const int maximumLength = 200; + + string cleaned = new( value.Where( c => !char.IsControl( c ) ).ToArray() ); + + return cleaned.Length <= maximumLength ? cleaned : cleaned[..maximumLength]; + } } diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml.cs b/src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml.cs index 0de0392..d5f90a4 100644 --- a/src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml.cs +++ b/src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml.cs @@ -60,7 +60,10 @@ public IActionResult OnPost() return this.Page(); } + // Resolved through Url.Content, so that the link still works when the server is installed as + // an application below an IIS site root. return this.Redirect( - $"/Admin/Export.ashx?fy={this.FromYear}&fm={this.FromMonth}&ty={this.ToYear}&tm={this.ToMonth}" ); + this.Url.Content( + $"~/Admin/Export.ashx?fy={this.FromYear}&fm={this.FromMonth}&ty={this.ToYear}&tm={this.ToMonth}" ) ); } } diff --git a/src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml.cs b/src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml.cs index cb18aa1..9429f16 100644 --- a/src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml.cs +++ b/src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml.cs @@ -58,7 +58,11 @@ public async Task OnGetAsync( CancellationToken cancellationToken if ( parsedLicense?.UserNumber != null ) { maximum = parsedLicense.UserNumber; - graceMaximum = maximum.Value * (100 + parsedLicense.GracePercent) / 100; + + // The same arithmetic the allocator uses, rounding up. Integer division would floor it, + // so a one-seat license with 20% grace would be drawn as allowing one seat while the + // server actually grants two. + graceMaximum = (int) Math.Ceiling( maximum.Value * (100.0 + parsedLicense.GracePercent) / 100.0 ); axisMaximum = graceMaximum.Value; } diff --git a/tests/PostSharp.LicenseServer.Tests/Infrastructure/TestData.cs b/tests/PostSharp.LicenseServer.Tests/Infrastructure/TestData.cs index fa34219..f8040e0 100644 --- a/tests/PostSharp.LicenseServer.Tests/Infrastructure/TestData.cs +++ b/tests/PostSharp.LicenseServer.Tests/Infrastructure/TestData.cs @@ -245,10 +245,9 @@ public Lease AddTo( LicenseServerTestContext context ) Grace = this.grace }; - lease.HMAC = context.Repository.GetSignature( lease ); - + // Saved through the repository, so the lease is signed the way a real one is. context.Db.Leases.Add( lease ); - context.Db.SaveChanges(); + context.Repository.SaveChanges(); return lease; } diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseSignatureTests.cs b/tests/PostSharp.LicenseServer.Tests/LeaseSignatureTests.cs index c514ee5..ded1eb1 100644 --- a/tests/PostSharp.LicenseServer.Tests/LeaseSignatureTests.cs +++ b/tests/PostSharp.LicenseServer.Tests/LeaseSignatureTests.cs @@ -1,10 +1,11 @@ +using Microsoft.EntityFrameworkCore; using PostSharp.LicenseServer.Tests.Infrastructure; namespace PostSharp.LicenseServer.Tests; /// -/// The audit log is a chain: each lease is signed together with the signature of the previously -/// persisted lease, so removing or altering a row breaks every signature after it. +/// The audit log is a chain: each lease is signed together with the signature of the lease before +/// it, so removing or altering a row breaks every signature after it. /// /// /// These assertions encode behaviour that is documented nowhere and that is invisible in a code @@ -14,89 +15,123 @@ public sealed class LeaseSignatureTests { private static string[] Fields( string payload ) => payload.Split( ';' ); + /// + /// The payload of the signature applied to a given lease. + /// + private static string PayloadFor( LicenseServerTestContext context, int leaseId ) + => context.Signer.Payloads.Single( p => Fields( p )[1] == leaseId.ToString() ); + [Fact] - public async Task GetSignature_ChainsFromThePreviousLease() + public async Task Signature_ChainsFromThePreviousLease() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); + License license = LicenseBuilder.Default().WithUsers( 10 ).AddTo( context ); Lease first = LeaseBuilder.For( license ).AddTo( context ); context.Signer.Clear(); context.Repository.CreateLease( license, "bob", "desktop-2", "bob", TestClock.Days( 1 ), false ); + await context.Repository.SaveChangesAsync(); Assert.StartsWith( first.HMAC + ";", context.Signer.LastPayload!, StringComparison.Ordinal ); } [Fact] - public async Task GetSignature_FirstLeaseEver_ChainsFromAnEmptySignature() + public async Task Signature_FirstLeaseEver_ChainsFromAnEmptySignature() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); context.Signer.Clear(); context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); + await context.Repository.SaveChangesAsync(); Assert.StartsWith( ";", context.Signer.LastPayload!, StringComparison.Ordinal ); } /// - /// The chain is anchored to what is committed, not to what is pending. Several leases created in - /// one unit of work therefore all chain from the same predecessor. + /// Leases saved together must chain to each other, not all to the same predecessor. Were they + /// siblings, any one of them could be removed without breaking a later signature. /// [Fact] - public async Task GetSignature_DoesNotSeePendingInserts() + public async Task Signature_LeasesSavedTogether_ChainToEachOther() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().WithUsers( 10 ).AddTo( context ); - Lease committed = LeaseBuilder.For( license ).AddTo( context ); - context.Signer.Clear(); - context.Repository.CreateLease( license, "bob", "desktop-2", "bob", TestClock.Days( 1 ), false ); - context.Repository.CreateLease( license, "carol", "desktop-3", "carol", TestClock.Days( 1 ), false ); + context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); + context.Repository.CreateLease( license, "bob", "desktop-2", "bob", TestClock.Origin, false ); + context.Repository.CreateLease( license, "carol", "desktop-3", "carol", TestClock.Origin, false ); + await context.Repository.SaveChangesAsync(); + + List leases = await context.CreateFreshContext().Leases.OrderBy( l => l.LeaseId ).ToListAsync(); + + Assert.Equal( 3, leases.Count ); - Assert.Equal( 2, context.Signer.Payloads.Count ); - Assert.All( - context.Signer.Payloads, - payload => Assert.StartsWith( committed.HMAC + ";", payload, StringComparison.Ordinal ) ); + // Each lease's payload opens with the signature of the one before it. + for ( int i = 1; i < leases.Count; i++ ) + { + Assert.StartsWith( + leases[i - 1].HMAC + ";", + PayloadFor( context, leases[i].LeaseId ), + StringComparison.Ordinal ); + } } + /// + /// The signature covers the lease identifier the database assigned, so an auditor can recompute + /// the chain from an exported file. Signing before the insert would put a zero there. + /// [Fact] - public async Task GetSignature_AfterSave_ChainsFromTheNewlyPersistedLease() + public async Task Signature_CoversTheAssignedLeaseId() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().WithUsers( 10 ).AddTo( context ); - LeaseBuilder.For( license ).AddTo( context ); - - Lease? second = context.Repository.CreateLease( license, "bob", "desktop-2", "bob", TestClock.Days( 1 ), false ); - await context.Repository.SaveChangesAsync(); + License license = LicenseBuilder.Default().AddTo( context ); context.Signer.Clear(); - context.Repository.CreateLease( license, "carol", "desktop-3", "carol", TestClock.Days( 1 ), false ); + Lease? lease = context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); + await context.Repository.SaveChangesAsync(); - Assert.StartsWith( second!.HMAC + ";", context.Signer.LastPayload!, StringComparison.Ordinal ); + Assert.NotEqual( 0, lease!.LeaseId ); + Assert.Equal( lease.LeaseId.ToString(), Fields( context.Signer.LastPayload! )[1] ); } /// - /// A lease is signed before it is inserted, so its own identifier is not yet known. + /// The whole point of the chain: an exported line plus the previous signature reproduce the + /// signature, so a tampered row is detectable. /// [Fact] - public async Task GetSignature_SignedPayloadCarriesLeaseIdZero() + public async Task Signature_CanBeRecomputedFromTheExportedLine() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().WithUsers( 10 ).AddTo( context ); + + LeaseBuilder.For( license ).User( "alice" ).AddTo( context ); + LeaseBuilder.For( license ).User( "bob" ).Machine( "desktop-2" ).AddTo( context ); + + List leases = await context.CreateFreshContext().Leases.OrderBy( l => l.LeaseId ).ToListAsync(); + + string? previous = null; + + foreach ( Lease lease in leases ) + { + Assert.Equal( lease.HMAC, context.Repository.ComputeSignature( previous, lease ) ); + previous = lease.HMAC; + } + } + + [Fact] + public async Task Signature_AlteredLease_NoLongerMatches() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); + Lease lease = LeaseBuilder.For( license ).AddTo( context ); - context.Signer.Clear(); - context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); + string recorded = lease.HMAC!; + lease.Machine = "somebody-elses-machine"; - // Field 0 is the chained signature, so the lease's own fields start at index 1. - Assert.Equal( "0", Fields( context.Signer.LastPayload! )[1] ); + Assert.NotEqual( recorded, context.Repository.ComputeSignature( null, lease ) ); } - /// - /// Regression guard: EF Core does not populate a foreign key from a navigation property until - /// the entity is tracked, and leases are signed before that. If the key were left unassigned the - /// license would silently be signed as zero. - /// [Fact] public async Task CreateLease_SignedPayloadCarriesTheLicenseId() { @@ -105,14 +140,11 @@ public async Task CreateLease_SignedPayloadCarriesTheLicenseId() context.Signer.Clear(); context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); + await context.Repository.SaveChangesAsync(); Assert.Equal( "7", Fields( context.Signer.LastPayload! )[3] ); } - /// - /// The same regression guard, for the self-referencing key that makes the log an append-only - /// chain of replacements. - /// [Fact] public async Task ProlongLease_SignedPayloadCarriesTheOverwrittenLeaseId() { @@ -122,6 +154,7 @@ public async Task ProlongLease_SignedPayloadCarriesTheOverwrittenLeaseId() context.Signer.Clear(); context.Repository.ProlongLease( original, "alice", TestClock.Days( 2.5 ) ); + await context.Repository.SaveChangesAsync(); Assert.Equal( original.LeaseId.ToString(), Fields( context.Signer.LastPayload! )[2] ); } @@ -134,66 +167,52 @@ public async Task CreateLease_SignedPayloadHasNoOverwrittenLeaseId() context.Signer.Clear(); context.Repository.CreateLease( license, "alice", "desktop-1", "alice", TestClock.Origin, false ); + await context.Repository.SaveChangesAsync(); Assert.Equal( "", Fields( context.Signer.LastPayload! )[2] ); } - /// - /// The whole point of replacing the randomly-keyed HMAC: signing the same content twice now - /// yields the same signature, so the chain can actually be verified. - /// [Fact] public async Task Signature_IsDeterministic() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); + Lease lease = LeaseBuilder.For( license ).AddTo( context ); - Lease lease = new() - { - License = license, - LicenseId = license.LicenseId, - UserName = "alice", - Machine = "desktop-1", - AuthenticatedUser = "alice", - StartTime = TestClock.Origin, - EndTime = TestClock.Days( 3 ) - }; - - Assert.Equal( context.Repository.GetSignature( lease ), context.Repository.GetSignature( lease ) ); + Assert.Equal( + context.Repository.ComputeSignature( null, lease ), + context.Repository.ComputeSignature( null, lease ) ); } [Fact] - public async Task Signature_DiffersWhenTheLeaseDiffers() + public async Task Signature_FitsTheDatabaseColumn() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); + Lease lease = LeaseBuilder.For( license ).AddTo( context ); - Lease lease = new() - { - License = license, - LicenseId = license.LicenseId, - UserName = "alice", - Machine = "desktop-1", - AuthenticatedUser = "alice", - StartTime = TestClock.Origin, - EndTime = TestClock.Days( 3 ) - }; - - string before = context.Repository.GetSignature( lease ); - lease.Machine = "desktop-2"; - - Assert.NotEqual( before, context.Repository.GetSignature( lease ) ); + // The HMAC column is varchar(100) and is not being widened by this migration. + Assert.NotNull( lease.HMAC ); + Assert.InRange( lease.HMAC.Length, 1, 100 ); } + /// + /// A lease must never be readable without its signature, so the insert and the signature share a + /// transaction. + /// [Fact] - public async Task Signature_FitsTheDatabaseColumn() + public async Task Signature_EveryPersistedLeaseIsSigned() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - Lease lease = LeaseBuilder.For( license ).AddTo( context ); + License license = LicenseBuilder.Default().WithUsers( 10 ).AddTo( context ); - // The HMAC column is varchar(100) and is not being widened by this migration. - Assert.NotNull( lease.HMAC ); - Assert.InRange( lease.HMAC.Length, 1, 100 ); + for ( int i = 0; i < 5; i++ ) + { + context.Repository.CreateLease( license, $"user{i}", $"machine-{i}", $"user{i}", TestClock.Origin, false ); + } + + await context.Repository.SaveChangesAsync(); + + Assert.Empty( await context.CreateFreshContext().Leases.Where( l => l.HMAC == null ).ToListAsync() ); } } diff --git a/tests/PostSharp.LicenseServer.Tests/ReviewRegressionTests.cs b/tests/PostSharp.LicenseServer.Tests/ReviewRegressionTests.cs new file mode 100644 index 0000000..d9709d1 --- /dev/null +++ b/tests/PostSharp.LicenseServer.Tests/ReviewRegressionTests.cs @@ -0,0 +1,157 @@ +using Microsoft.AspNetCore.Http; +using System.Net; +using Microsoft.AspNetCore.Mvc.Testing; +using PostSharp.LicenseServer.Endpoints; +using PostSharp.LicenseServer.Tests.Infrastructure; + +namespace PostSharp.LicenseServer.Tests; + +/// +/// Defects found while reviewing the migration. Each of these passed unnoticed because the legacy +/// implementation behaved the same way. +/// +public sealed class ReviewRegressionTests : IDisposable +{ + private readonly LicenseServerApplication application = new(); + + public void Dispose() => this.application.Dispose(); + + /// + /// A build agent is exempt from consuming a seat, not from the rules about which licenses may be + /// served. The legacy code only checked that the key parsed. + /// + [Fact] + public async Task BuildAgent_IneligibleLicense_IsNotServed() + { + this.application.AddLicense( LicenseBuilder.Default().NotLicenseServerEligible() ); + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( + "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); + + Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); + Assert.Contains( + "cannot be used in the license server", + await response.Content.ReadAsStringAsync(), + StringComparison.Ordinal ); + } + + [Fact] + public async Task BuildAgent_LicenseRequiringANewerClient_IsNotServed() + { + this.application.AddLicense( + LicenseBuilder.Default().WithMinPostSharpVersion( new Version( 2099, 1, 0 ) ) ); + + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( + "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); + + Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); + } + + /// + /// An expired license must not be handed to a build agent with three more days on it. + /// + [Fact] + public async Task BuildAgent_ExpiredLicense_IsNotServed() + { + this.application.AddLicense( + LicenseBuilder.Default().WithValidTo( new DateTime( 2020, 1, 1, 0, 0, 0, DateTimeKind.Utc ) ) ); + + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( + "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); + + Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); + } + + [Fact] + public async Task BuildAgent_ValidLicense_IsStillServedWithoutALease() + { + this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( + "/Lease.ashx?user=alice&machine=buildagent-1f2e&product=Ultimate&version=2025.1.0" ); + + Assert.Equal( HttpStatusCode.OK, response.StatusCode ); + + await using var db = this.application.CreateDbContext(); + Assert.Empty( db.Leases ); + } + + /// + /// A license with no seat limit has no capacity to exceed, so there is no grace period. Reaching + /// the grace pass with one used to dereference a null maximum and answer 500 instead of denying + /// the request. + /// + [Fact] + public async Task UnlimitedButExpiredLicense_IsDeniedRatherThanFailing() + { + this.application.AddLicense( + LicenseBuilder.Default() + .WithUsers( null ) + .WithValidTo( new DateTime( 2020, 1, 1, 0, 0, 0, DateTimeKind.Utc ) ) ); + + HttpClient client = this.application.CreateClient(); + + HttpResponseMessage response = await client.GetAsync( + "/Lease.ashx?user=alice&machine=desktop-1&product=Ultimate&version=2025.1.0" ); + + Assert.Equal( HttpStatusCode.Forbidden, response.StatusCode ); + } + + /// + /// A year outside the range of a date used to pass validation and then throw while the date was + /// being constructed. + /// + [Theory] + [InlineData( "fy=10000&fm=1&ty=10000&tm=2" )] + [InlineData( "fy=0&fm=1&ty=2026&tm=2" )] + [InlineData( "fy=2026&fm=13&ty=2026&tm=1" )] + [InlineData( "fy=2026&fm=1&ty=2026&tm=0" )] + public async Task Export_OutOfRangeMonthOrYear_Returns400( string query ) + { + HttpClient client = this.application.CreateClient(); + + Assert.Equal( + HttpStatusCode.BadRequest, + ( await client.GetAsync( $"/Admin/Export.ashx?{query}" ) ).StatusCode ); + } + + [Fact] + public async Task Export_ValidRange_IsStillServed() + { + HttpClient client = this.application.CreateClient(); + + Assert.Equal( + HttpStatusCode.OK, + ( await client.GetAsync( "/Admin/Export.ashx?fy=2026&fm=1&ty=2026&tm=12" ) ).StatusCode ); + } + + /// + /// Installed below a site root, a redirect has to keep the path base or it lands on the parent + /// site. + /// + [Theory] + [InlineData( "/LicenseServer", "/Graph", "?id=5", "/LicenseServer/Graph?id=5" )] + [InlineData( "/LicenseServer", "/", "", "/LicenseServer" )] + [InlineData( "/LicenseServer", "/Admin/Details", "?id=5", "/LicenseServer/Admin/Details?id=5" )] + [InlineData( "", "/Graph", "?id=5", "/Graph?id=5" )] + [InlineData( "", "/", "", "/" )] + public void LegacyUrl_KeepsThePathBaseAndTheQueryString( + string pathBase, + string target, + string queryString, + string expected ) + { + Assert.Equal( + expected, + LegacyUrlRedirects.BuildRedirectLocation( + new PathString( pathBase.Length == 0 ? null : pathBase ), + target, + new QueryString( queryString.Length == 0 ? null : queryString ) ) ); + } +} From 8ffb8417483487e715b5532b8644e332640fad34 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 10:46:35 +0200 Subject: [PATCH 12/44] Hash the names written to the slow-request log The audit log deliberately reduces user and machine names to hashes, so that an exported file can be shared without disclosing who works where. Writing those same names verbatim into the application log contradicted that, and let a caller forge a line in a plain-text log through the query string. The log now records the same hash the audit log uses, so an administrator can still correlate the two. Co-Authored-By: Claude Opus 5 --- .../Data/Lease.Audit.cs | 11 +++++++++ .../Endpoints/LicenseServerEndpoints.cs | 24 +++++-------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs b/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs index 26ccac5..c4c94eb 100644 --- a/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs +++ b/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs @@ -5,6 +5,17 @@ namespace PostSharp.LicenseServer; public partial class Lease { + /// + /// Hashes a user or machine name the way the audit log does. + /// + /// + /// Use this anywhere a name would otherwise be written somewhere it can be read by people who + /// have no business knowing who works where, such as a log file. The hash is stable, so it still + /// correlates with the audit log. + /// + public static string HashName( string name ) + => CryptoUtilities.ComputeStringHash64( name ).ToString( "x" ); + /// /// Writes the audit-log representation of this lease: a semicolon-separated line in which the /// machine and user names appear only as hashes, so the log can be shared without disclosing who diff --git a/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs index 07be3c0..fb6be96 100644 --- a/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs +++ b/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs @@ -132,12 +132,13 @@ private static async Task GetLeaseAsync( if ( elapsed > TimeSpan.FromSeconds( 1 ) ) { - // The user and machine names come from the query string, so they are stripped of - // anything that could forge a line in a plain-text log. + // Hashed, as in the audit log: the names come from the query string, so writing them + // verbatim would both let a caller forge a line in a plain-text log and disclose who + // works where to anyone who can read it. The hash still correlates with the audit log. logger.LogWarning( - "The lease request for {User} on {Machine} took {Elapsed}.", - Sanitize( userName ), - Sanitize( machine ), + "A lease request for user {User} on machine {Machine} took {Elapsed}.", + Lease.HashName( userName ), + Lease.HashName( machine ), elapsed ); } @@ -238,17 +239,4 @@ private static async Task ExportAsync( private static IResult Error( int statusCode, string description ) => Results.Text( description, "text/plain", statusCode: statusCode ); - - /// - /// Removes control characters from a value taken from the request, so that it cannot forge a - /// line break in a log, and caps its length. - /// - private static string Sanitize( string value ) - { - const int maximumLength = 200; - - string cleaned = new( value.Where( c => !char.IsControl( c ) ).ToArray() ); - - return cleaned.Length <= maximumLength ? cleaned : cleaned[..maximumLength]; - } } From e544dd21ab5353192847037a447e653962113af6 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 10:47:16 +0200 Subject: [PATCH 13/44] Revert "Hash the names written to the slow-request log" This reverts commit 8ffb8417483487e715b5532b8644e332640fad34. --- .../Data/Lease.Audit.cs | 11 --------- .../Endpoints/LicenseServerEndpoints.cs | 24 ++++++++++++++----- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs b/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs index c4c94eb..26ccac5 100644 --- a/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs +++ b/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs @@ -5,17 +5,6 @@ namespace PostSharp.LicenseServer; public partial class Lease { - /// - /// Hashes a user or machine name the way the audit log does. - /// - /// - /// Use this anywhere a name would otherwise be written somewhere it can be read by people who - /// have no business knowing who works where, such as a log file. The hash is stable, so it still - /// correlates with the audit log. - /// - public static string HashName( string name ) - => CryptoUtilities.ComputeStringHash64( name ).ToString( "x" ); - /// /// Writes the audit-log representation of this lease: a semicolon-separated line in which the /// machine and user names appear only as hashes, so the log can be shared without disclosing who diff --git a/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs index fb6be96..07be3c0 100644 --- a/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs +++ b/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs @@ -132,13 +132,12 @@ private static async Task GetLeaseAsync( if ( elapsed > TimeSpan.FromSeconds( 1 ) ) { - // Hashed, as in the audit log: the names come from the query string, so writing them - // verbatim would both let a caller forge a line in a plain-text log and disclose who - // works where to anyone who can read it. The hash still correlates with the audit log. + // The user and machine names come from the query string, so they are stripped of + // anything that could forge a line in a plain-text log. logger.LogWarning( - "A lease request for user {User} on machine {Machine} took {Elapsed}.", - Lease.HashName( userName ), - Lease.HashName( machine ), + "The lease request for {User} on {Machine} took {Elapsed}.", + Sanitize( userName ), + Sanitize( machine ), elapsed ); } @@ -239,4 +238,17 @@ private static async Task ExportAsync( private static IResult Error( int statusCode, string description ) => Results.Text( description, "text/plain", statusCode: statusCode ); + + /// + /// Removes control characters from a value taken from the request, so that it cannot forge a + /// line break in a log, and caps its length. + /// + private static string Sanitize( string value ) + { + const int maximumLength = 200; + + string cleaned = new( value.Where( c => !char.IsControl( c ) ).ToArray() ); + + return cleaned.Length <= maximumLength ? cleaned : cleaned[..maximumLength]; + } } From 20febb78430222a646d6d5a348c67f0d935c2e1b Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 11:30:20 +0200 Subject: [PATCH 14/44] Rename the projects after the product they become The license server joins the Backstage 2027.0 family as the Backstage.LicenseServer product, whose repository and packages are named SharpCrafters.Backstage.LicenseServer. This commit only moves files, so that rename detection works and the next commit shows the real changes. The load simulator goes: it compiles only against the PostSharp SDK that the next commit removes, and SharpCrafters.Backstage already carries LicenseServerLoadSimulator, which drives this server through its own client. Co-Authored-By: Claude Opus 5 --- ...SharpCrafters.Backstage.LicenseServer.slnx | 0 .../Data/ILeaseRepository.cs | 0 .../Data/Lease.Audit.cs | 0 .../Data/Lease.Entity.cs | 0 .../Data/LeaseConfiguration.cs | 0 .../Data/LeaseCountingPoint.cs | 0 .../Data/LeaseCountingPointKind.cs | 0 .../Data/LeaseRepository.cs | 0 .../Data/License.cs | 0 .../Data/LicenseConfiguration.cs | 0 .../Data/LicenseServerDbContext.cs | 0 .../Data/SeatCounter.cs | 0 .../Email/EmailMessage.cs | 0 .../Email/IEmailSender.cs | 0 .../Email/NullEmailSender.cs | 0 .../Email/SmtpEmailSender.cs | 0 .../Licensing/CachingLicenseParser.cs | 0 .../Licensing/ILeaseSerializer.cs | 0 .../Licensing/ILicenseParser.cs | 0 .../Licensing/ILicenseServerVersion.cs | 0 .../Licensing/LicenseInfo.cs | 0 .../Licensing/PostSharpLeaseSerializer.cs | 0 .../Licensing/PostSharpLicenseParser.cs | 0 .../Licensing/PostSharpPlatform.cs | 0 .../Licensing/PostSharpServerVersion.cs | 0 .../Locking/ILeaseLock.cs | 0 .../Locking/InProcessLeaseLock.cs | 0 .../Locking/NullLeaseLock.cs | 0 .../Options/LeaseLockMode.cs | 0 .../Options/LicenseServerOptions.cs | 0 .../Options/LicenseServerOptionsValidator.cs | 0 .../Options/SmtpOptions.cs | 0 .../Security/FileAuditKeyProvider.cs | 0 .../Security/HmacLeaseSigner.cs | 0 .../Security/IAuditKeyProvider.cs | 0 .../Security/ILeaseSigner.cs | 0 .../Services/LeaseService.cs | 0 ...fters.Backstage.LicenseServer.Core.csproj} | 0 .../Time/AcceleratedTimeProvider.cs | 0 .../AuthenticationRegistration.cs | 0 .../Database/CreateTables.sql | 0 .../DatabaseRegistration.cs | 0 .../Endpoints/LegacyUrlRedirects.cs | 0 .../Endpoints/LicenseServerEndpoints.cs | 0 .../Pages/Admin/AddLicense.cshtml | 0 .../Pages/Admin/AddLicense.cshtml.cs | 0 .../Pages/Admin/Cancel.cshtml | 0 .../Pages/Admin/Cancel.cshtml.cs | 0 .../Pages/Admin/Details.cshtml | 0 .../Pages/Admin/Details.cshtml.cs | 0 .../Pages/Admin/Export.cshtml | 0 .../Pages/Admin/Export.cshtml.cs | 0 .../Pages/Admin/GenerateDemoData.cshtml | 0 .../Pages/Admin/GenerateDemoData.cshtml.cs | 0 .../Pages/Error.cshtml | 0 .../Pages/Error.cshtml.cs | 0 .../Pages/Graph.cshtml | 0 .../Pages/Graph.cshtml.cs | 0 .../Pages/Index.cshtml | 0 .../Pages/Index.cshtml.cs | 0 .../Pages/Shared/_Layout.cshtml | 0 .../Pages/_ViewImports.cshtml | 0 .../Pages/_ViewStart.cshtml | 0 .../Program.cs | 0 .../Properties/launchSettings.json | 0 ...afters.Backstage.LicenseServer.Web.csproj} | 0 .../appsettings.Development.json | 0 .../appsettings.json | 0 .../wwwroot/css/site.css | 0 .../wwwroot/favicon.ico | Bin .../img/icons/android-icon-192x192.png | Bin .../wwwroot/img/icons/android-icon-96x96.png | Bin .../wwwroot/img/icons/apple-icon-152x152.png | Bin .../wwwroot/img/icons/favicon-16x16.png | Bin .../wwwroot/img/icons/favicon-32x32.png | Bin .../wwwroot/img/postsharp-logo.svg | 0 .../wwwroot/js/graph.js | 0 .../wwwroot/lib/chartjs/LICENSE.md | 0 .../wwwroot/lib/chartjs/chart.umd.min.js | 0 .../wwwroot/robots.txt | 0 .../ClientSimulator.cs | 137 ---------- .../MemoryRegistryKey.cs | 208 --------------- .../MessageSink.cs | 13 - .../PostSharp.LicenseServer.Simulator.csproj | 21 -- .../Program.cs | 236 ------------------ .../PostSharp.LicenseServer.Simulator/User.cs | 17 -- .../VirtualDateTime.cs | 50 ---- .../BuildServerDetectionTests.cs | 0 .../CancelLeaseTests.cs | 0 .../ConfigurationTests.cs | 0 .../EmailSenderTests.cs | 0 .../Fakes/FakeLicenseParser.cs | 0 .../Fakes/FixedServerVersion.cs | 0 .../Fakes/InMemoryEmailSender.cs | 0 .../Fakes/NeverAcquiringLeaseLock.cs | 0 .../Fakes/RecordingLeaseSigner.cs | 0 .../Fakes/StaticAuditKeyProvider.cs | 0 .../GetActiveLeadsTests.cs | 0 .../LicenseServerApplication.cs | 0 .../LicenseServerTestContext.cs | 0 .../Infrastructure/SqliteDatabaseFixture.cs | 0 .../Infrastructure/TestData.cs | 0 .../LeaseAllocationTests.cs | 0 .../LeaseAuditLineTests.cs | 0 .../LeaseCountingPointsTests.cs | 0 .../LeaseEndpointTests.cs | 0 .../LeaseSignatureTests.cs | 0 .../LicenseValidationTests.cs | 0 .../OpenLeasesTests.cs | 0 .../PageTests.cs | 0 .../ReviewRegressionTests.cs | 0 .../SchemaCompatibilityTests.cs | 0 .../SeatCountingTests.cs | 0 ...ters.Backstage.LicenseServer.Tests.csproj} | 0 114 files changed, 682 deletions(-) rename PostSharp.LicenseServer.slnx => SharpCrafters.Backstage.LicenseServer.slnx (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Data/ILeaseRepository.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Data/Lease.Audit.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Data/Lease.Entity.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Data/LeaseConfiguration.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Data/LeaseCountingPoint.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Data/LeaseCountingPointKind.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Data/LeaseRepository.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Data/License.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Data/LicenseConfiguration.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Data/LicenseServerDbContext.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Data/SeatCounter.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Email/EmailMessage.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Email/IEmailSender.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Email/NullEmailSender.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Email/SmtpEmailSender.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Licensing/CachingLicenseParser.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Licensing/ILeaseSerializer.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Licensing/ILicenseParser.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Licensing/ILicenseServerVersion.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Licensing/LicenseInfo.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Licensing/PostSharpLeaseSerializer.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Licensing/PostSharpLicenseParser.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Licensing/PostSharpPlatform.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Licensing/PostSharpServerVersion.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Locking/ILeaseLock.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Locking/InProcessLeaseLock.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Locking/NullLeaseLock.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Options/LeaseLockMode.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Options/LicenseServerOptions.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Options/LicenseServerOptionsValidator.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Options/SmtpOptions.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Security/FileAuditKeyProvider.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Security/HmacLeaseSigner.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Security/IAuditKeyProvider.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Security/ILeaseSigner.cs (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Services/LeaseService.cs (100%) rename src/{PostSharp.LicenseServer.Core/PostSharp.LicenseServer.Core.csproj => SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj} (100%) rename src/{PostSharp.LicenseServer.Core => SharpCrafters.Backstage.LicenseServer.Core}/Time/AcceleratedTimeProvider.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/AuthenticationRegistration.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Database/CreateTables.sql (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/DatabaseRegistration.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Endpoints/LegacyUrlRedirects.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Endpoints/LicenseServerEndpoints.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Admin/AddLicense.cshtml (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Admin/AddLicense.cshtml.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Admin/Cancel.cshtml (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Admin/Cancel.cshtml.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Admin/Details.cshtml (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Admin/Details.cshtml.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Admin/Export.cshtml (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Admin/Export.cshtml.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Admin/GenerateDemoData.cshtml (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Admin/GenerateDemoData.cshtml.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Error.cshtml (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Error.cshtml.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Graph.cshtml (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Graph.cshtml.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Index.cshtml (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Index.cshtml.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/Shared/_Layout.cshtml (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/_ViewImports.cshtml (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Pages/_ViewStart.cshtml (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Program.cs (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/Properties/launchSettings.json (100%) rename src/{PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj => SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj} (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/appsettings.Development.json (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/appsettings.json (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/css/site.css (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/favicon.ico (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/img/icons/android-icon-192x192.png (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/img/icons/android-icon-96x96.png (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/img/icons/apple-icon-152x152.png (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/img/icons/favicon-16x16.png (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/img/icons/favicon-32x32.png (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/img/postsharp-logo.svg (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/js/graph.js (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/lib/chartjs/LICENSE.md (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/lib/chartjs/chart.umd.min.js (100%) rename src/{PostSharp.LicenseServer.Web => SharpCrafters.Backstage.LicenseServer.Web}/wwwroot/robots.txt (100%) delete mode 100644 tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs delete mode 100644 tests/PostSharp.LicenseServer.Simulator/MemoryRegistryKey.cs delete mode 100644 tests/PostSharp.LicenseServer.Simulator/MessageSink.cs delete mode 100644 tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Simulator.csproj delete mode 100644 tests/PostSharp.LicenseServer.Simulator/Program.cs delete mode 100644 tests/PostSharp.LicenseServer.Simulator/User.cs delete mode 100644 tests/PostSharp.LicenseServer.Simulator/VirtualDateTime.cs rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/BuildServerDetectionTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/CancelLeaseTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/ConfigurationTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/EmailSenderTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/Fakes/FakeLicenseParser.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/Fakes/FixedServerVersion.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/Fakes/InMemoryEmailSender.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/Fakes/NeverAcquiringLeaseLock.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/Fakes/RecordingLeaseSigner.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/Fakes/StaticAuditKeyProvider.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/GetActiveLeadsTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/Infrastructure/LicenseServerApplication.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/Infrastructure/LicenseServerTestContext.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/Infrastructure/SqliteDatabaseFixture.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/Infrastructure/TestData.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/LeaseAllocationTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/LeaseAuditLineTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/LeaseCountingPointsTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/LeaseEndpointTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/LeaseSignatureTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/LicenseValidationTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/OpenLeasesTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/PageTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/ReviewRegressionTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/SchemaCompatibilityTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests => SharpCrafters.Backstage.LicenseServer.Tests}/SeatCountingTests.cs (100%) rename tests/{PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj => SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj} (100%) diff --git a/PostSharp.LicenseServer.slnx b/SharpCrafters.Backstage.LicenseServer.slnx similarity index 100% rename from PostSharp.LicenseServer.slnx rename to SharpCrafters.Backstage.LicenseServer.slnx diff --git a/src/PostSharp.LicenseServer.Core/Data/ILeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Data/ILeaseRepository.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs diff --git a/src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Data/Lease.Audit.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs diff --git a/src/PostSharp.LicenseServer.Core/Data/Lease.Entity.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Data/Lease.Entity.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs diff --git a/src/PostSharp.LicenseServer.Core/Data/LeaseConfiguration.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Data/LeaseConfiguration.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs diff --git a/src/PostSharp.LicenseServer.Core/Data/LeaseCountingPoint.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Data/LeaseCountingPoint.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs diff --git a/src/PostSharp.LicenseServer.Core/Data/LeaseCountingPointKind.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPointKind.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Data/LeaseCountingPointKind.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPointKind.cs diff --git a/src/PostSharp.LicenseServer.Core/Data/LeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Data/LeaseRepository.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs diff --git a/src/PostSharp.LicenseServer.Core/Data/License.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/License.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Data/License.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Data/License.cs diff --git a/src/PostSharp.LicenseServer.Core/Data/LicenseConfiguration.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseConfiguration.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Data/LicenseConfiguration.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseConfiguration.cs diff --git a/src/PostSharp.LicenseServer.Core/Data/LicenseServerDbContext.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Data/LicenseServerDbContext.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs diff --git a/src/PostSharp.LicenseServer.Core/Data/SeatCounter.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Data/SeatCounter.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs diff --git a/src/PostSharp.LicenseServer.Core/Email/EmailMessage.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/EmailMessage.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Email/EmailMessage.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Email/EmailMessage.cs diff --git a/src/PostSharp.LicenseServer.Core/Email/IEmailSender.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/IEmailSender.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Email/IEmailSender.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Email/IEmailSender.cs diff --git a/src/PostSharp.LicenseServer.Core/Email/NullEmailSender.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/NullEmailSender.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Email/NullEmailSender.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Email/NullEmailSender.cs diff --git a/src/PostSharp.LicenseServer.Core/Email/SmtpEmailSender.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Email/SmtpEmailSender.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs diff --git a/src/PostSharp.LicenseServer.Core/Licensing/CachingLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Licensing/CachingLicenseParser.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs diff --git a/src/PostSharp.LicenseServer.Core/Licensing/ILeaseSerializer.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILeaseSerializer.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Licensing/ILeaseSerializer.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILeaseSerializer.cs diff --git a/src/PostSharp.LicenseServer.Core/Licensing/ILicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseParser.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Licensing/ILicenseParser.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseParser.cs diff --git a/src/PostSharp.LicenseServer.Core/Licensing/ILicenseServerVersion.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Licensing/ILicenseServerVersion.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs diff --git a/src/PostSharp.LicenseServer.Core/Licensing/LicenseInfo.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Licensing/LicenseInfo.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs diff --git a/src/PostSharp.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs diff --git a/src/PostSharp.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs diff --git a/src/PostSharp.LicenseServer.Core/Licensing/PostSharpPlatform.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpPlatform.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Licensing/PostSharpPlatform.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpPlatform.cs diff --git a/src/PostSharp.LicenseServer.Core/Licensing/PostSharpServerVersion.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpServerVersion.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Licensing/PostSharpServerVersion.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpServerVersion.cs diff --git a/src/PostSharp.LicenseServer.Core/Locking/ILeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/ILeaseLock.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Locking/ILeaseLock.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Locking/ILeaseLock.cs diff --git a/src/PostSharp.LicenseServer.Core/Locking/InProcessLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/InProcessLeaseLock.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Locking/InProcessLeaseLock.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Locking/InProcessLeaseLock.cs diff --git a/src/PostSharp.LicenseServer.Core/Locking/NullLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/NullLeaseLock.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Locking/NullLeaseLock.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Locking/NullLeaseLock.cs diff --git a/src/PostSharp.LicenseServer.Core/Options/LeaseLockMode.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Options/LeaseLockMode.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs diff --git a/src/PostSharp.LicenseServer.Core/Options/LicenseServerOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Options/LicenseServerOptions.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs diff --git a/src/PostSharp.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs diff --git a/src/PostSharp.LicenseServer.Core/Options/SmtpOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/SmtpOptions.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Options/SmtpOptions.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Options/SmtpOptions.cs diff --git a/src/PostSharp.LicenseServer.Core/Security/FileAuditKeyProvider.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/FileAuditKeyProvider.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Security/FileAuditKeyProvider.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Security/FileAuditKeyProvider.cs diff --git a/src/PostSharp.LicenseServer.Core/Security/HmacLeaseSigner.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/HmacLeaseSigner.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Security/HmacLeaseSigner.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Security/HmacLeaseSigner.cs diff --git a/src/PostSharp.LicenseServer.Core/Security/IAuditKeyProvider.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/IAuditKeyProvider.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Security/IAuditKeyProvider.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Security/IAuditKeyProvider.cs diff --git a/src/PostSharp.LicenseServer.Core/Security/ILeaseSigner.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/ILeaseSigner.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Security/ILeaseSigner.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Security/ILeaseSigner.cs diff --git a/src/PostSharp.LicenseServer.Core/Services/LeaseService.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Services/LeaseService.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs diff --git a/src/PostSharp.LicenseServer.Core/PostSharp.LicenseServer.Core.csproj b/src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj similarity index 100% rename from src/PostSharp.LicenseServer.Core/PostSharp.LicenseServer.Core.csproj rename to src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj diff --git a/src/PostSharp.LicenseServer.Core/Time/AcceleratedTimeProvider.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Time/AcceleratedTimeProvider.cs similarity index 100% rename from src/PostSharp.LicenseServer.Core/Time/AcceleratedTimeProvider.cs rename to src/SharpCrafters.Backstage.LicenseServer.Core/Time/AcceleratedTimeProvider.cs diff --git a/src/PostSharp.LicenseServer.Web/AuthenticationRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/AuthenticationRegistration.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs diff --git a/src/PostSharp.LicenseServer.Web/Database/CreateTables.sql b/src/SharpCrafters.Backstage.LicenseServer.Web/Database/CreateTables.sql similarity index 100% rename from src/PostSharp.LicenseServer.Web/Database/CreateTables.sql rename to src/SharpCrafters.Backstage.LicenseServer.Web/Database/CreateTables.sql diff --git a/src/PostSharp.LicenseServer.Web/DatabaseRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/DatabaseRegistration.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs diff --git a/src/PostSharp.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs diff --git a/src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/AddLicense.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Admin/AddLicense.cshtml rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/Cancel.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Admin/Cancel.cshtml rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/Details.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Admin/Details.cshtml rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/Details.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Admin/Details.cshtml.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Admin/Export.cshtml.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml diff --git a/src/PostSharp.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs diff --git a/src/PostSharp.LicenseServer.Web/Pages/Error.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Error.cshtml rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml diff --git a/src/PostSharp.LicenseServer.Web/Pages/Error.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Error.cshtml.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml.cs diff --git a/src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml diff --git a/src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Graph.cshtml.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs diff --git a/src/PostSharp.LicenseServer.Web/Pages/Index.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Index.cshtml rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml diff --git a/src/PostSharp.LicenseServer.Web/Pages/Index.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Index.cshtml.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs diff --git a/src/PostSharp.LicenseServer.Web/Pages/Shared/_Layout.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Shared/_Layout.cshtml similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/Shared/_Layout.cshtml rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Shared/_Layout.cshtml diff --git a/src/PostSharp.LicenseServer.Web/Pages/_ViewImports.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/_ViewImports.cshtml similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/_ViewImports.cshtml rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/_ViewImports.cshtml diff --git a/src/PostSharp.LicenseServer.Web/Pages/_ViewStart.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/_ViewStart.cshtml similarity index 100% rename from src/PostSharp.LicenseServer.Web/Pages/_ViewStart.cshtml rename to src/SharpCrafters.Backstage.LicenseServer.Web/Pages/_ViewStart.cshtml diff --git a/src/PostSharp.LicenseServer.Web/Program.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs similarity index 100% rename from src/PostSharp.LicenseServer.Web/Program.cs rename to src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs diff --git a/src/PostSharp.LicenseServer.Web/Properties/launchSettings.json b/src/SharpCrafters.Backstage.LicenseServer.Web/Properties/launchSettings.json similarity index 100% rename from src/PostSharp.LicenseServer.Web/Properties/launchSettings.json rename to src/SharpCrafters.Backstage.LicenseServer.Web/Properties/launchSettings.json diff --git a/src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj b/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj similarity index 100% rename from src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj rename to src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj diff --git a/src/PostSharp.LicenseServer.Web/appsettings.Development.json b/src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.Development.json similarity index 100% rename from src/PostSharp.LicenseServer.Web/appsettings.Development.json rename to src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.Development.json diff --git a/src/PostSharp.LicenseServer.Web/appsettings.json b/src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.json similarity index 100% rename from src/PostSharp.LicenseServer.Web/appsettings.json rename to src/SharpCrafters.Backstage.LicenseServer.Web/appsettings.json diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/css/site.css b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/css/site.css rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/favicon.ico b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/favicon.ico similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/favicon.ico rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/favicon.ico diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/img/icons/android-icon-192x192.png b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/icons/android-icon-192x192.png similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/img/icons/android-icon-192x192.png rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/icons/android-icon-192x192.png diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/img/icons/android-icon-96x96.png b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/icons/android-icon-96x96.png similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/img/icons/android-icon-96x96.png rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/icons/android-icon-96x96.png diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/img/icons/apple-icon-152x152.png b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/icons/apple-icon-152x152.png similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/img/icons/apple-icon-152x152.png rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/icons/apple-icon-152x152.png diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/img/icons/favicon-16x16.png b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/icons/favicon-16x16.png similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/img/icons/favicon-16x16.png rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/icons/favicon-16x16.png diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/img/icons/favicon-32x32.png b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/icons/favicon-32x32.png similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/img/icons/favicon-32x32.png rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/icons/favicon-32x32.png diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/img/postsharp-logo.svg b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/postsharp-logo.svg similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/img/postsharp-logo.svg rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/img/postsharp-logo.svg diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/js/graph.js rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/LICENSE.md b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/lib/chartjs/LICENSE.md similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/LICENSE.md rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/lib/chartjs/LICENSE.md diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/chart.umd.min.js b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/lib/chartjs/chart.umd.min.js similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/lib/chartjs/chart.umd.min.js rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/lib/chartjs/chart.umd.min.js diff --git a/src/PostSharp.LicenseServer.Web/wwwroot/robots.txt b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/robots.txt similarity index 100% rename from src/PostSharp.LicenseServer.Web/wwwroot/robots.txt rename to src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/robots.txt diff --git a/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs b/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs deleted file mode 100644 index 4667395..0000000 --- a/tests/PostSharp.LicenseServer.Simulator/ClientSimulator.cs +++ /dev/null @@ -1,137 +0,0 @@ -using PostSharp.Platform; -using PostSharp.Sdk; -using PostSharp.Sdk.Extensibility.Licensing; -using PostSharp.Sdk.Extensibility.Licensing.Helpers; -using System; -using System.Diagnostics; -using System.Threading; - -namespace SharpCrafters.LicenseServer.Test -{ - - class ClientSimulator - { - readonly Random random = new Random(); - private readonly User user; - private static int waitingUsers; - - public ClientSimulator( User user ) - { - this.user = user; - } - - public void Main() - { - - Guid guid = Guid.NewGuid(); - - MessageSink messageSink = new MessageSink(); - - IRegistryKey registryKey = new MemoryRegistryKey(); - - using ( registryKey ) - { - LicenseLease lease = null; - while ( true ) - { - DateTime day = VirtualDateTime.UtcNow.ToLocalTime(); - - // We don't start before 9. - DateTime startTime = day.Date.AddHours( 8 + (random.NextDouble()-0.5)*4 ); - VirtualDateTime.WakeOn( startTime ); - - double probWorksToday; - switch (day.DayOfWeek) - { - case DayOfWeek.Sunday: - probWorksToday = user.WorksOnWeekend; - break; - case DayOfWeek.Saturday: - probWorksToday = user.WorksOnWeekend; - break; - default: - probWorksToday =1; - break; - } - - if (random.NextDouble() < probWorksToday) - { - DateTime endTime = startTime.AddHours( 9 + random.NextDouble() ); - - // Which machine will he use this day? - int machineIndex = (int)Math.Floor(random.NextDouble() * user.Machines.Count); - string machine = user.Machines[machineIndex]; - - // We are running PostSharp every 15 minute. - for ( DateTime time = startTime; time < endTime; time = time.AddMinutes( 30*random.NextDouble() ) ) - { - - //Console.WriteLine("{2} {0} waiting until {1}", VirtualDateTime.UtcNow.ToLocalTime(), time, user); - VirtualDateTime.WakeOn( time ); - - if ( lease == null || lease.RenewTime < time ) - { - Interlocked.Increment( ref waitingUsers ); - Console.WriteLine("{2} {0} on {3}: Acquiring lease; waiting users = {1}", user.AuthenticatedName, waitingUsers, VirtualDateTime.UtcNow.ToLocalTime(), machine); - - string url = Program.Url.TrimEnd('/') + string.Format("/Lease.ashx?user={0}&machine={1}&product={2}&version=2025.1.0", - user.AuthenticatedName, machine, - LicensedProduct.Ultimate); - - Stopwatch stopwatch = Stopwatch.StartNew(); - lease = LicenseServerClient.TryGetLease( url, registryKey, VirtualDateTime.UtcNow.ToLocalTime(), messageSink ); - - if ( lease == null ) - { - Console.WriteLine("Could not get a valid lease: lease is null, downloading a new lease..."); - - // TODO: This simulator cannot run until a client can download a - // lease. LicenseServerClient.TryDownloadLease is not public, - // and the replacement client is being written in - // SharpCrafters.Backstage, which this license server is to - // be migrated to. Restore the call below, remove the throw, - // and delete the pragma once one of the two is available. - - //lease = LicenseServerClient.TryDownloadLease(messageSink, url, registryKey); - - throw new Exception("Upgrade PostSharp to make these tests work (see above comment)."); - -#pragma warning disable CS0162 // Unreachable until the download call above is restored. - if (lease == null) - { - Console.WriteLine("Could not download a new lease."); - } - else - { - Console.WriteLine($"Key {lease.LicenseString}:"); - Console.WriteLine($"Leased until: {lease.EndTime}:"); - } -#pragma warning restore CS0162 - } - else if ( lease.EndTime < time ) - { - Console.WriteLine("Could not get a valid lease: lease end time is in the past"); - } - else if ( lease.RenewTime < time ) - { - Console.WriteLine("Got a lease with past renewal time: {0}, it is now {1}", - lease.RenewTime, time); - Program.FixVirtualTime(); - } - - Interlocked.Decrement(ref waitingUsers); - Console.WriteLine("{3} {0}: response received in {1}, waiting users = '{2}'", user, stopwatch.Elapsed, waitingUsers, VirtualDateTime.UtcNow.ToLocalTime()); - - } - } - - } - - } - - } - - } - - } -} \ No newline at end of file diff --git a/tests/PostSharp.LicenseServer.Simulator/MemoryRegistryKey.cs b/tests/PostSharp.LicenseServer.Simulator/MemoryRegistryKey.cs deleted file mode 100644 index 051c792..0000000 --- a/tests/PostSharp.LicenseServer.Simulator/MemoryRegistryKey.cs +++ /dev/null @@ -1,208 +0,0 @@ -using PostSharp.Platform; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace SharpCrafters.LicenseServer.Test -{ - class MemoryRegistryKey : IRegistryKey - { - private Dictionary values = new Dictionary(); - private Dictionary subKeys = new Dictionary(); - - public void Dispose() - { - } - - public string[] GetSubKeyNames() - { - List subKeyNames = new List(subKeys.Keys); - return subKeyNames.ToArray(); - } - - public IRegistryKey OpenSubKey(string subKey) - { - if (subKeys.TryGetValue(subKey, out IRegistryKey subKeyValue)) - { - return subKeyValue; - } - else - { - throw new ArgumentException($"Subkey '{subKey}' does not exist."); - } - } - - public IRegistryKey OpenSubKey(string name, bool writable) - { - if (subKeys.TryGetValue(name, out IRegistryKey subKeyValue)) - { - return subKeyValue; - } - else - { - throw new ArgumentException($"Subkey '{name}' does not exist."); - } - } - - public IRegistryKey CreateSubKey(string subKey) - { - if (!subKeys.ContainsKey(subKey)) - { - MemoryRegistryKey newSubKey = new MemoryRegistryKey(); - subKeys[subKey] = newSubKey; - return newSubKey; - } - else - { - throw new ArgumentException($"Subkey '{subKey}' already exists."); - } - } - - public void DeleteSubKey(string subKey) - { - if (subKeys.ContainsKey(subKey)) - { - subKeys.Remove(subKey); - } - else - { - throw new ArgumentException($"Subkey '{subKey}' does not exist."); - } - } - - public void DeleteSubKey(string subKey, bool throwOnMissingSubKey) - { - if (subKeys.ContainsKey(subKey)) - { - subKeys.Remove(subKey); - } - else if (throwOnMissingSubKey) - { - throw new ArgumentException($"Subkey '{subKey}' does not exist."); - } - } - - public void DeleteSubKeyTree(string subKey) - { - if (subKeys.ContainsKey(subKey)) - { - subKeys.Remove(subKey); - } - else - { - throw new ArgumentException($"Subkey '{subKey}' does not exist."); - } - } - - public void DeleteSubKeyTree(string subKey, bool throwOnMissingSubKey) - { - if (subKeys.ContainsKey(subKey)) - { - subKeys.Remove(subKey); - } - else if (throwOnMissingSubKey) - { - throw new ArgumentException($"Subkey '{subKey}' does not exist."); - } - } - - public string[] GetValueNames() - { - List valueNames = new List(values.Keys); - return valueNames.ToArray(); - } - - public object GetValue(string name) - { - if (values.TryGetValue(name, out object value)) - { - return value; - } - else - { - throw new ArgumentException($"Value '{name}' does not exist."); - } - } - - public object GetValue(string name, object defaultValue) - { - if (values.TryGetValue(name, out object value)) - { - return value; - } - else - { - return defaultValue; - } - } - - public void SetValue(string name, string value) - { - if (values.ContainsKey(name)) - { - values[name] = value; - } - else - { - values.Add(name, value); - } - } - - public void SetDWordValue(string name, int value) - { - if (values.ContainsKey(name)) - { - values[name] = value; - } - else - { - values.Add(name, value); - } - } - - public void SetQWordValue(string name, long value) - { - if (values.ContainsKey(name)) - { - values[name] = value; - } - else - { - values.Add(name, value); - } - } - - public void DeleteValue(string name) - { - if (values.ContainsKey(name)) - { - values.Remove(name); - } - else - { - throw new ArgumentException($"Value '{name}' does not exist."); - } - } - - public void DeleteValue(string name, bool throwOnMissingSubKey) - { - if (values.ContainsKey(name)) - { - values.Remove(name); - } - else if (throwOnMissingSubKey) - { - throw new ArgumentException($"Value '{name}' does not exist."); - } - } - - public void Close() - { - // No action needed for in-memory registry key. - } - - public bool CanMonitorChanges => false; - - public SafeHandle Handle => null; - } -} \ No newline at end of file diff --git a/tests/PostSharp.LicenseServer.Simulator/MessageSink.cs b/tests/PostSharp.LicenseServer.Simulator/MessageSink.cs deleted file mode 100644 index d510021..0000000 --- a/tests/PostSharp.LicenseServer.Simulator/MessageSink.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using PostSharp.Extensibility; - -namespace SharpCrafters.LicenseServer.Test -{ - internal class MessageSink : IMessageSink - { - public void Write( Message message ) - { - Console.WriteLine(message.MessageText); - } - } -} \ No newline at end of file diff --git a/tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Simulator.csproj b/tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Simulator.csproj deleted file mode 100644 index 9b333b7..0000000 --- a/tests/PostSharp.LicenseServer.Simulator/PostSharp.LicenseServer.Simulator.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - Exe - net10.0 - SharpCrafters.LicenseServer.Test - PostSharp.LicenseServer.Simulator - disable - - false - false - - - - - - - - - - diff --git a/tests/PostSharp.LicenseServer.Simulator/Program.cs b/tests/PostSharp.LicenseServer.Simulator/Program.cs deleted file mode 100644 index 2cdfbf9..0000000 --- a/tests/PostSharp.LicenseServer.Simulator/Program.cs +++ /dev/null @@ -1,236 +0,0 @@ -using PostSharp.Platform.Neutral; -using PostSharp.Sdk.Extensibility; -using PostSharp.Sdk.Extensibility.Licensing; -using PostSharp.Sdk.User; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Xml; - -namespace SharpCrafters.LicenseServer.Test -{ - class Program - { - static readonly Random random = new Random(); - private const string unparsedNames = - @"David,Rahm -Jimmy,Smith -Carroll,Lash -Keith,Smith -Wm,Martinez -Marsha,Bassham -Mike,Bergeron -Julio,Bayliss -Salvatore,Nail -Herbert,Thompson -Keith,Gentile -Gary,Turner -Jesse,Stinson -Louis,Callender -Duane,Rudder -Joan,Lowrey -Thomas,Bourdeau -Richard,Vega -Charles,Numbers -Paul,Cooper -Albert,Speier -Jerry,Johnson -Sidney,Painter -John,Denton -Monica,Grise -William,Davis -Miguel,Collins -Melanie,Johnson -Nida,Perez -George,Young -Gary,Wilkes -Thomas,Stoneburner -Richard,Bushong -Edmund,Davis -Michael,Smart -Dena,Anderson -Bobbie,Sherman -Ray,Roberts -Francisco,Linck -Jeremy,Thompson -Hubert,Nunnally -Marshall,Hoffman -Stephen,Montes -Wilfred,Cassel -Matthew,Sease -Edward,Johnson -Timothy,Hassell -Brian,Jones -Israel,West -Jesse,Bombard -Don,Tann -Brian,Stringer -Marilyn,Belanger -Donald,Miller -Patrick,Clark -Milton,Cranston -Russell,Christensen -Bill,Piper -Chester,Bennett -Dean,Mcgrath -Dennis,Ortiz -Paul,Arno -Manuel,Cole -Felix,Johnson -Nancy,Mccormick -Robert,Callan -Bernard,Vanwyk -Kelly,Gary -Robert,Murphy -Quincy,Grossman -Richard,Brown -Theodore,Flemming -Christopher,Young -David,Riddle -Donnie,Comstock -Bryan,Hartsock -Timothy,Villarreal -Troy,Thompson -Frederick,Johnson -Joseph,Davis -Christopher,Ayala -Cyrus,Smith -Thomas,Johnson -Monica,Tobin -Kyle,Pierce -Ginger,Miller -John,Smith -Michael,Hines -William,Hansen -Joseph,Hurlburt -Stephen,Green -Noe,Houle -Troy,Lofton -Kristofer,Booth -Joseph,Hoffman -Larry,Mcknight -Brian,Watson -Tom,Greenwood -Cory,Ryals -David,Bradway"; - - public static string Url = "http://localhost:44670/"; - - private static void OnTime( object state ) - { - Console.WriteLine(VirtualDateTime.UtcNow.ToLocalTime()); - } - - static void Main(string[] args) - { - if ( args.Length > 0 ) - Url = args[0]; - - CommonDefaultSystemServices.Initialize(); - Messenger.Initialize(); - Messenger.Current.Message += ( sender, eventArgs ) => Console.WriteLine( VirtualDateTime.UtcNow.ToLocalTime().ToString() + ": " + - eventArgs.Message.MessageText ); - - FixVirtualTime(); - - List users = GetUsers(75, 2, 2); - - foreach ( User user in users) - { - StartUserSimulation( user ); - } - - new Thread( PrintTime ).Start(); - - } - - private static void PrintTime() - { - while ( true ) - { - Thread.Sleep( TimeSpan.FromSeconds( 5 ) ); - Console.WriteLine("Current time: {0}", VirtualDateTime.UtcNow.ToLocalTime() ); - } - } - - public static void FixVirtualTime() - { - string getTimeUrl = Url + "GetTime.ashx"; - WebClient webClient = new WebClient(); - string[] remoteTime = webClient.DownloadString( getTimeUrl ).Split( ';' ); - VirtualDateTime.Initialize( XmlConvert.ToDateTime( remoteTime[0], XmlDateTimeSerializationMode.Utc ), XmlConvert.ToDecimal( remoteTime[1] ) ); - } - - private static void StartUserSimulation( User user ) - { - ClientSimulator clientSimulator = new ClientSimulator( user ); - Thread thread = new Thread( clientSimulator.Main ) - { - Name = string.Format( "Simulation for user {0} ", user ) - }; - - thread.Start(); - } - - private static List GetUsers(int userCount, int buildServerUserCount, int buildServerMachineCount) - { - StringReader reader = new StringReader( unparsedNames ); - List users = new List(); - - - string line; - while ( (line = reader.ReadLine()) != null ) - { - string[] parts = line.Split( ',' ); - - User user = new User - { - UserName = string.Format( "{0}.{1}", parts[0], parts[1] ).ToUpper(), - AuthenticatedName = string.Format( "CONTOSO\\{0}.{1}", parts[0], parts[1] ).ToUpper(), - WorksOnWeekend = random.NextDouble() - }; - double machineProb = random.NextDouble(); - - if ( machineProb < 0.7 ) - { - user.Machines.Add( string.Format( "DESKTOP-{0:x}", random.Next( 0, ushort.MaxValue ) ) ); - } - else - { - user.Machines.Add( string.Format( "DESKTOP-{0:x}", random.Next( 0, ushort.MaxValue ) ) ); - user.Machines.Add( string.Format( "NOTEBOOK-{0:x}", random.Next( 0, ushort.MaxValue ) ) ); - } - - users.Add( user ); - - if ( users.Count >= userCount ) - break; - } - - for ( int i = 0; i < buildServerUserCount; i++ ) - { - User user = new User - { - UserName = string.Format("BUILDSERVER.{0}", i), - AuthenticatedName = string.Format("CONTOSO\\BUILDSERVER.{0}", i), - WorksOnWeekend = 1 - }; - - for ( int j = 0; j < buildServerMachineCount; j++ ) - { - user.Machines.Add(string.Format("SERVER-{0:x}", random.Next( 0, ushort.MaxValue ))); - } - - users.Add(user); - } - - return users; - } - - - } -} diff --git a/tests/PostSharp.LicenseServer.Simulator/User.cs b/tests/PostSharp.LicenseServer.Simulator/User.cs deleted file mode 100644 index b4c356c..0000000 --- a/tests/PostSharp.LicenseServer.Simulator/User.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; - -namespace SharpCrafters.LicenseServer.Test -{ - class User - { - public string UserName; - public string AuthenticatedName; - public double WorksOnWeekend; - public readonly List Machines = new List(); - - public override string ToString() - { - return this.UserName; - } - } -} \ No newline at end of file diff --git a/tests/PostSharp.LicenseServer.Simulator/VirtualDateTime.cs b/tests/PostSharp.LicenseServer.Simulator/VirtualDateTime.cs deleted file mode 100644 index e930c54..0000000 --- a/tests/PostSharp.LicenseServer.Simulator/VirtualDateTime.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Threading; - -namespace SharpCrafters.LicenseServer.Test -{ - public static class VirtualDateTime - { - private static DateTime initTimeUtc = DateTime.UtcNow; - private static DateTime startTimeUtc = DateTime.UtcNow; - private static decimal acceleration = 0; - - public static void Initialize( DateTime time, decimal acceleration ) - { - Console.WriteLine("Setting time from {0} to {1}, acceleration={2}", startTimeUtc, time, acceleration); - VirtualDateTime.initTimeUtc = DateTime.UtcNow; - VirtualDateTime.startTimeUtc = time; - VirtualDateTime.acceleration = acceleration; - } - - public static DateTime UtcNow - { - get - { -#if DEBUG - if (acceleration != 0 && acceleration != 1) - { - return startTimeUtc.AddMilliseconds((DateTime.UtcNow - initTimeUtc).TotalMilliseconds * (double)acceleration); - } - else - { - return DateTime.UtcNow; - } -#else - return DateTime.UtcNow; -#endif - } - } - - public static void WakeOn( DateTime time ) - { - DateTime realTime = initTimeUtc.AddMilliseconds( ((time.ToUniversalTime() - startTimeUtc).TotalMilliseconds/(double) acceleration) ); - TimeSpan timeSpan = realTime - DateTime.UtcNow; - if ( timeSpan.TotalMilliseconds > 0 ) - { - Thread.Sleep( (int) timeSpan.TotalMilliseconds ); - } - } - - } -} \ No newline at end of file diff --git a/tests/PostSharp.LicenseServer.Tests/BuildServerDetectionTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/BuildServerDetectionTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/CancelLeaseTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/CancelLeaseTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/ConfigurationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/ConfigurationTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/EmailSenderTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/EmailSenderTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/EmailSenderTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/EmailSenderTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/FakeLicenseParser.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/Fakes/FakeLicenseParser.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/FixedServerVersion.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/Fakes/FixedServerVersion.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs diff --git a/tests/PostSharp.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs diff --git a/tests/PostSharp.LicenseServer.Tests/GetActiveLeadsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveLeadsTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/GetActiveLeadsTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveLeadsTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs diff --git a/tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs diff --git a/tests/PostSharp.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs diff --git a/tests/PostSharp.LicenseServer.Tests/Infrastructure/TestData.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/Infrastructure/TestData.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseAllocationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/LeaseAllocationTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseAuditLineTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/LeaseAuditLineTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseCountingPointsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/LeaseCountingPointsTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseEndpointTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/LeaseEndpointTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/LeaseSignatureTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/LeaseSignatureTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/LicenseValidationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/LicenseValidationTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/OpenLeasesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/OpenLeasesTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/PageTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/PageTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/ReviewRegressionTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/ReviewRegressionTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/SchemaCompatibilityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/SchemaCompatibilityTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/SeatCountingTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/SeatCountingTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs diff --git a/tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj similarity index 100% rename from tests/PostSharp.LicenseServer.Tests/PostSharp.LicenseServer.Tests.csproj rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj From 68a5b7a45b32288341e550284eb7f6505623ab14 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 11:30:57 +0200 Subject: [PATCH 15/44] Rename the product to SharpCrafters.Backstage.LicenseServer The namespaces, assembly names and the solution follow the folders renamed in the previous commit. The published entry point becomes SharpCrafters.Backstage.LicenseServer.dll, which the Dockerfile, the packaging script and the documentation are updated for. Co-Authored-By: Claude Opus 5 --- .claude/launch.json | 4 ++-- Dockerfile | 10 ++++----- README.md | 20 ++++++++--------- SharpCrafters.Backstage.LicenseServer.slnx | 7 +++--- docker-compose.yml | 2 +- eng/Package.ps1 | 8 +++---- .../Data/ILeaseRepository.cs | 2 +- .../Data/Lease.Audit.cs | 2 +- .../Data/Lease.Entity.cs | 2 +- .../Data/LeaseConfiguration.cs | 2 +- .../Data/LeaseCountingPoint.cs | 2 +- .../Data/LeaseCountingPointKind.cs | 2 +- .../Data/LeaseRepository.cs | 8 +++---- .../Data/License.cs | 2 +- .../Data/LicenseConfiguration.cs | 2 +- .../Data/LicenseServerDbContext.cs | 2 +- .../Data/SeatCounter.cs | 2 +- .../Email/EmailMessage.cs | 2 +- .../Email/IEmailSender.cs | 2 +- .../Email/NullEmailSender.cs | 2 +- .../Email/SmtpEmailSender.cs | 4 ++-- .../Licensing/CachingLicenseParser.cs | 2 +- .../Licensing/ILeaseSerializer.cs | 2 +- .../Licensing/ILicenseParser.cs | 2 +- .../Licensing/ILicenseServerVersion.cs | 2 +- .../Licensing/LicenseInfo.cs | 2 +- .../Licensing/PostSharpLeaseSerializer.cs | 2 +- .../Licensing/PostSharpLicenseParser.cs | 2 +- .../Licensing/PostSharpPlatform.cs | 2 +- .../Licensing/PostSharpServerVersion.cs | 2 +- .../Locking/ILeaseLock.cs | 2 +- .../Locking/InProcessLeaseLock.cs | 2 +- .../Locking/NullLeaseLock.cs | 2 +- .../Options/LeaseLockMode.cs | 2 +- .../Options/LicenseServerOptions.cs | 2 +- .../Options/LicenseServerOptionsValidator.cs | 2 +- .../Options/SmtpOptions.cs | 2 +- .../Security/FileAuditKeyProvider.cs | 4 ++-- .../Security/HmacLeaseSigner.cs | 2 +- .../Security/IAuditKeyProvider.cs | 2 +- .../Security/ILeaseSigner.cs | 2 +- .../Services/LeaseService.cs | 10 ++++----- ...afters.Backstage.LicenseServer.Core.csproj | 5 ++--- .../Time/AcceleratedTimeProvider.cs | 2 +- .../AuthenticationRegistration.cs | 2 +- .../DatabaseRegistration.cs | 4 ++-- .../Endpoints/LegacyUrlRedirects.cs | 2 +- .../Endpoints/LicenseServerEndpoints.cs | 12 +++++----- .../Pages/Admin/AddLicense.cshtml | 2 +- .../Pages/Admin/AddLicense.cshtml.cs | 6 ++--- .../Pages/Admin/Cancel.cshtml | 2 +- .../Pages/Admin/Cancel.cshtml.cs | 4 ++-- .../Pages/Admin/Details.cshtml | 2 +- .../Pages/Admin/Details.cshtml.cs | 4 ++-- .../Pages/Admin/Export.cshtml | 6 ++--- .../Pages/Admin/Export.cshtml.cs | 2 +- .../Pages/Admin/GenerateDemoData.cshtml | 2 +- .../Pages/Admin/GenerateDemoData.cshtml.cs | 6 ++--- .../Pages/Error.cshtml.cs | 2 +- .../Pages/Graph.cshtml.cs | 6 ++--- .../Pages/Index.cshtml.cs | 6 ++--- .../Pages/_ViewImports.cshtml | 6 ++--- .../Program.cs | 22 +++++++++---------- .../Properties/launchSettings.json | 2 +- ...rafters.Backstage.LicenseServer.Web.csproj | 12 +++++----- .../BuildServerDetectionTests.cs | 10 ++++----- .../CancelLeaseTests.cs | 4 ++-- .../ConfigurationTests.cs | 12 +++++----- .../EmailSenderTests.cs | 6 ++--- .../Fakes/FakeLicenseParser.cs | 4 ++-- .../Fakes/FixedServerVersion.cs | 4 ++-- .../Fakes/InMemoryEmailSender.cs | 4 ++-- .../Fakes/NeverAcquiringLeaseLock.cs | 4 ++-- .../Fakes/RecordingLeaseSigner.cs | 4 ++-- .../Fakes/StaticAuditKeyProvider.cs | 4 ++-- .../GetActiveLeadsTests.cs | 4 ++-- .../LicenseServerApplication.cs | 18 +++++++-------- .../LicenseServerTestContext.cs | 12 +++++----- .../Infrastructure/SqliteDatabaseFixture.cs | 4 ++-- .../Infrastructure/TestData.cs | 4 ++-- .../LeaseAllocationTests.cs | 4 ++-- .../LeaseAuditLineTests.cs | 6 ++--- .../LeaseCountingPointsTests.cs | 4 ++-- .../LeaseEndpointTests.cs | 8 +++---- .../LeaseSignatureTests.cs | 4 ++-- .../LicenseValidationTests.cs | 4 ++-- .../OpenLeasesTests.cs | 4 ++-- .../PageTests.cs | 4 ++-- .../ReviewRegressionTests.cs | 6 ++--- .../SchemaCompatibilityTests.cs | 4 ++-- .../SeatCountingTests.cs | 4 ++-- ...fters.Backstage.LicenseServer.Tests.csproj | 6 ++--- 92 files changed, 207 insertions(+), 209 deletions(-) diff --git a/.claude/launch.json b/.claude/launch.json index 112cfdd..0d6896a 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -2,9 +2,9 @@ "version": "0.0.1", "configurations": [ { - "name": "PostSharp.LicenseServer", + "name": "SharpCrafters.Backstage.LicenseServer", "runtimeExecutable": "dotnet", - "runtimeArgs": ["run", "--project", "src/PostSharp.LicenseServer.Web"], + "runtimeArgs": ["run", "--project", "src/SharpCrafters.Backstage.LicenseServer.Web"], "port": 44670, "url": "http://localhost:44670" } diff --git a/Dockerfile b/Dockerfile index cd5762d..369cd3d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,12 +9,12 @@ WORKDIR /src # Restore against the manifests alone, so that a change to the sources does not invalidate the # restore layer. COPY Directory.Build.props Directory.Packages.props nuget.config global.json ./ -COPY src/PostSharp.LicenseServer.Core/PostSharp.LicenseServer.Core.csproj src/PostSharp.LicenseServer.Core/ -COPY src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj src/PostSharp.LicenseServer.Web/ -RUN dotnet restore src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj +COPY src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj src/SharpCrafters.Backstage.LicenseServer.Core/ +COPY src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj src/SharpCrafters.Backstage.LicenseServer.Web/ +RUN dotnet restore src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj COPY src/ src/ -RUN dotnet publish src/PostSharp.LicenseServer.Web/PostSharp.LicenseServer.Web.csproj \ +RUN dotnet publish src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj \ --configuration Release \ --no-restore \ --output /app @@ -33,4 +33,4 @@ COPY --from=build --chown=licenseserver /app ./ ENV ASPNETCORE_HTTP_PORTS=8080 EXPOSE 8080 -ENTRYPOINT ["dotnet", "PostSharp.LicenseServer.dll"] +ENTRYPOINT ["dotnet", "SharpCrafters.Backstage.LicenseServer.dll"] diff --git a/README.md b/README.md index 8ae277e..3292885 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PostSharp.LicenseServer +# SharpCrafters.Backstage.LicenseServer This repository contains the source code and releases of PostSharp License Server. @@ -16,7 +16,7 @@ The license server itself is licensed under the *MIT License*. Note that PostSha ## Download -You can download the latest release from https://github.com/postsharp/PostSharp.LicenseServer/releases/latest. +You can download the latest release from https://github.com/postsharp/SharpCrafters.Backstage.LicenseServer/releases/latest. ## Documentation @@ -48,7 +48,7 @@ anything other than a trial. 1. Install the ASP.NET Core Hosting Bundle on the web server, then restart IIS with `iisreset`. 2. Create the database and run `Database\CreateTables.sql` against it. -3. Unpack `PostSharp.LicenseServer.zip` into the directory of an IIS application. +3. Unpack `SharpCrafters.Backstage.LicenseServer.zip` into the directory of an IIS application. 4. Edit `appsettings.json`: set the connection string, the notification e-mail addresses and the SMTP server. The settings are described in [docs/configuration.md](docs/configuration.md). 5. In IIS Manager, enable **Windows Authentication** on the application and disable @@ -65,7 +65,7 @@ The release package is portable: the same zip runs wherever the .NET 10 runtime 3. Unpack the zip, edit `appsettings.json`, and run it: ``` - dotnet PostSharp.LicenseServer.dll + dotnet SharpCrafters.Backstage.LicenseServer.dll ``` Two settings usually need changing away from Windows. The connection string cannot use @@ -108,12 +108,12 @@ dotnet test .\eng\Package.ps1 ``` -The package is written to `artifacts\PostSharp.LicenseServer.zip`. +The package is written to `artifacts\SharpCrafters.Backstage.LicenseServer.zip`. ### Running locally ``` -dotnet run --project src\PostSharp.LicenseServer.Web +dotnet run --project src\SharpCrafters.Backstage.LicenseServer.Web ``` The development configuration uses a SQLite database created on first start, so no SQL Server is @@ -124,10 +124,10 @@ page exists only in a development environment. | Project | Contents | |---|---| -| `src\PostSharp.LicenseServer.Core` | The licensing rules, the database model and the services they depend on. | -| `src\PostSharp.LicenseServer.Web` | The web application: the pages, the endpoints and the composition root. | -| `tests\PostSharp.LicenseServer.Tests` | The test suite. Runs against an in-memory database, so it needs no SQL Server. | -| `tests\PostSharp.LicenseServer.Simulator` | A manual load-testing tool. See the note below. | +| `src\SharpCrafters.Backstage.LicenseServer.Core` | The licensing rules, the database model and the services they depend on. | +| `src\SharpCrafters.Backstage.LicenseServer.Web` | The web application: the pages, the endpoints and the composition root. | +| `tests\SharpCrafters.Backstage.LicenseServer.Tests` | The test suite. Runs against an in-memory database, so it needs no SQL Server. | +| `tests\SharpCrafters.Backstage.LicenseServer.Simulator` | A manual load-testing tool. See the note below. | The simulator does not currently run: it needs a client that can download a lease, which is being written in SharpCrafters.Backstage. It is kept building so that it is ready when that client is. diff --git a/SharpCrafters.Backstage.LicenseServer.slnx b/SharpCrafters.Backstage.LicenseServer.slnx index 8d1bffe..c75cfec 100644 --- a/SharpCrafters.Backstage.LicenseServer.slnx +++ b/SharpCrafters.Backstage.LicenseServer.slnx @@ -1,10 +1,9 @@ - - + + - - + diff --git a/docker-compose.yml b/docker-compose.yml index 5fb9bda..677b9f9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,7 +39,7 @@ services: database: condition: service_healthy volumes: - - ./src/PostSharp.LicenseServer.Web/Database:/schema:ro + - ./src/SharpCrafters.Backstage.LicenseServer.Web/Database:/schema:ro entrypoint: - /bin/bash - -c diff --git a/eng/Package.ps1 b/eng/Package.ps1 index c70fc1d..0f3d0fb 100644 --- a/eng/Package.ps1 +++ b/eng/Package.ps1 @@ -3,12 +3,12 @@ Builds the release package of the PostSharp License Server. .DESCRIPTION - Publishes the web application and zips it into artifacts/PostSharp.LicenseServer.zip, which is + Publishes the web application and zips it into artifacts/SharpCrafters.Backstage.LicenseServer.zip, which is the artifact attached to a GitHub release. The package is portable: it carries no platform-specific build and runs wherever the .NET 10 runtime does. On Windows that means unpacking it into an IIS application, with the ASP.NET Core - Hosting Bundle installed. Elsewhere it is run with `dotnet PostSharp.LicenseServer.dll`. + Hosting Bundle installed. Elsewhere it is run with `dotnet SharpCrafters.Backstage.LicenseServer.dll`. Runs on Windows PowerShell and on PowerShell 7 for Linux and macOS. @@ -34,9 +34,9 @@ $repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path if ( -not $OutputPath ) { $OutputPath = Join-Path $repositoryRoot 'artifacts' } -$project = Join-Path $repositoryRoot 'src' 'PostSharp.LicenseServer.Web' 'PostSharp.LicenseServer.Web.csproj' +$project = Join-Path $repositoryRoot 'src' 'SharpCrafters.Backstage.LicenseServer.Web' 'SharpCrafters.Backstage.LicenseServer.Web.csproj' $publishPath = Join-Path $repositoryRoot 'artifacts' 'publish' -$zipPath = Join-Path $OutputPath 'PostSharp.LicenseServer.zip' +$zipPath = Join-Path $OutputPath 'SharpCrafters.Backstage.LicenseServer.zip' if ( Test-Path $publishPath ) { Remove-Item $publishPath -Recurse -Force } New-Item -ItemType Directory -Force -Path $OutputPath | Out-Null diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs index 0efae2c..db82c89 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Data; +namespace SharpCrafters.Backstage.LicenseServer.Data; /// /// Reads and writes leases. Replaces the methods that used to hang off the LINQ to SQL diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs index 26ccac5..17bc060 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs @@ -1,7 +1,7 @@ using System.Xml; using PostSharp.Sdk.Extensibility.Licensing; -namespace PostSharp.LicenseServer; +namespace SharpCrafters.Backstage.LicenseServer; public partial class Lease { diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs index 1cfd427..6b8e0b2 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Entity.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer; +namespace SharpCrafters.Backstage.LicenseServer; /// /// A seat of a license, held by one user on one machine for a period of time. Maps to the diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs index c9331f0..05004c2 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseConfiguration.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace PostSharp.LicenseServer.Data; +namespace SharpCrafters.Backstage.LicenseServer.Data; /// /// Maps onto the dbo.Leases table created by CreateTables.sql. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs index b59eace..0fdb0f0 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer; +namespace SharpCrafters.Backstage.LicenseServer; /// /// A point on the usage timeline of a license: the moment a lease starts or ends, together with the diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPointKind.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPointKind.cs index 65094a0..faf2e9f 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPointKind.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPointKind.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer; +namespace SharpCrafters.Backstage.LicenseServer; /// /// The kind of event a represents. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs index b12bb53..7ca50e1 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs @@ -1,11 +1,11 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Options; -using PostSharp.LicenseServer.Licensing; -using PostSharp.LicenseServer.Options; -using PostSharp.LicenseServer.Security; +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Options; +using SharpCrafters.Backstage.LicenseServer.Security; -namespace PostSharp.LicenseServer.Data; +namespace SharpCrafters.Backstage.LicenseServer.Data; /// public sealed class LeaseRepository( diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/License.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/License.cs index b906a19..84b591d 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/License.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/License.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer; +namespace SharpCrafters.Backstage.LicenseServer; /// /// A license key registered on the license server. Maps to the dbo.Licenses table. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseConfiguration.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseConfiguration.cs index a4757e4..5136e4c 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseConfiguration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseConfiguration.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace PostSharp.LicenseServer.Data; +namespace SharpCrafters.Backstage.LicenseServer.Data; /// /// Maps onto the dbo.Licenses table created by CreateTables.sql. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs index 6b55048..db34bb2 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LicenseServerDbContext.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -namespace PostSharp.LicenseServer.Data; +namespace SharpCrafters.Backstage.LicenseServer.Data; /// /// The license server database. The provider is chosen by the hosting application: SQL Server in diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs index 389c491..8f8f227 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Data; +namespace SharpCrafters.Backstage.LicenseServer.Data; /// /// Converts machine counts into seat counts. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/EmailMessage.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/EmailMessage.cs index 786f4c0..4ea55e9 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/EmailMessage.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/EmailMessage.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Email; +namespace SharpCrafters.Backstage.LicenseServer.Email; /// /// A notification email to the license administrator. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/IEmailSender.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/IEmailSender.cs index aef81ef..9ca7096 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/IEmailSender.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/IEmailSender.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Email; +namespace SharpCrafters.Backstage.LicenseServer.Email; /// /// Sends notification emails. A failure to send must never fail a lease request, so implementations diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/NullEmailSender.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/NullEmailSender.cs index 4cd7987..ee3600c 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/NullEmailSender.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/NullEmailSender.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Email; +namespace SharpCrafters.Backstage.LicenseServer.Email; /// /// Discards notification emails. Used when SMTP is disabled and when generating demo data. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs index 7e9172d..37acc92 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Email/SmtpEmailSender.cs @@ -3,9 +3,9 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MimeKit; -using PostSharp.LicenseServer.Options; +using SharpCrafters.Backstage.LicenseServer.Options; -namespace PostSharp.LicenseServer.Email; +namespace SharpCrafters.Backstage.LicenseServer.Email; /// /// Sends notification emails over SMTP. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs index a8de57e..91ca6e0 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CachingLicenseParser.cs @@ -1,6 +1,6 @@ using System.Collections.Concurrent; -namespace PostSharp.LicenseServer.Licensing; +namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// /// Caches parse results, because a license key is parsed on every lease request and on every render diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILeaseSerializer.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILeaseSerializer.cs index ea8690d..47364ba 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILeaseSerializer.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILeaseSerializer.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Licensing; +namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// /// Produces the response body of the lease endpoint. This is the wire contract with the PostSharp diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseParser.cs index c2cfed7..f77dd2a 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseParser.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseParser.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Licensing; +namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// /// Parses and validates PostSharp license keys. Replaces the static ParsedLicenseManager. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs index 60af1c8..59ffe1d 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Licensing; +namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// /// The version of the PostSharp SDK embedded in this license server. A license requiring a higher diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs index 2d13173..bc96dbe 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseInfo.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Licensing; +namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// /// The facts the license server needs about a license key, projected out of the PostSharp SDK so diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs index e860e66..08cc5ad 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs @@ -1,6 +1,6 @@ using PostSharp.Sdk.Extensibility.Licensing; -namespace PostSharp.LicenseServer.Licensing; +namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// /// Serializes a lease in the format expected by the PostSharp client. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs index 524b9c3..c1746cd 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs @@ -1,7 +1,7 @@ using PostSharp.Sdk.Extensibility.Licensing; using ParsedLicense = PostSharp.Sdk.Extensibility.Licensing.License; -namespace PostSharp.LicenseServer.Licensing; +namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// /// Parses license keys with the PostSharp SDK. This is the only class that touches the SDK's diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpPlatform.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpPlatform.cs index 182cfda..7547e25 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpPlatform.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpPlatform.cs @@ -1,7 +1,7 @@ using PostSharp.Platform.NetStandard20; using PostSharp.Platform.Neutral; -namespace PostSharp.LicenseServer.Licensing; +namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// /// Initializes the PostSharp SDK platform services. Must run once before any license key is parsed. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpServerVersion.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpServerVersion.cs index 0d2db54..8ae0c1e 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpServerVersion.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpServerVersion.cs @@ -1,6 +1,6 @@ using PostSharp.Sdk; -namespace PostSharp.LicenseServer.Licensing; +namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// /// Reports the version of the PostSharp SDK embedded in this license server. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/ILeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/ILeaseLock.cs index 7900165..c9ddd9f 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/ILeaseLock.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/ILeaseLock.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Locking; +namespace SharpCrafters.Backstage.LicenseServer.Locking; /// /// Serializes lease requests, so that two concurrent requests cannot both decide that the last diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/InProcessLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/InProcessLeaseLock.cs index 340d2c4..25906aa 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/InProcessLeaseLock.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/InProcessLeaseLock.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Locking; +namespace SharpCrafters.Backstage.LicenseServer.Locking; /// /// Serializes lease requests within the current process. This is the default, and is correct for diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/NullLeaseLock.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/NullLeaseLock.cs index 26ba928..36a9c4e 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/NullLeaseLock.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Locking/NullLeaseLock.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Locking; +namespace SharpCrafters.Backstage.LicenseServer.Locking; /// /// Does not serialize anything. Intended for tests that do not exercise concurrency. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs index f7035c0..b860c72 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LeaseLockMode.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Options; +namespace SharpCrafters.Backstage.LicenseServer.Options; /// /// Determines how the license server serializes concurrent lease requests. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs index e6ad859..675e8cd 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace PostSharp.LicenseServer.Options; +namespace SharpCrafters.Backstage.LicenseServer.Options; /// /// Settings of the license server. Replaces the applicationSettings section of the legacy diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs index d2d27e0..1c3b0b4 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptionsValidator.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Options; -namespace PostSharp.LicenseServer.Options; +namespace SharpCrafters.Backstage.LicenseServer.Options; /// /// Validates the relationships between settings that data annotations cannot express. The legacy diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/SmtpOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/SmtpOptions.cs index 2444eba..dda9ee0 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/SmtpOptions.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/SmtpOptions.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Options; +namespace SharpCrafters.Backstage.LicenseServer.Options; /// /// Settings of the SMTP server used to send notification emails. Replaces the diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/FileAuditKeyProvider.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/FileAuditKeyProvider.cs index e333438..fcb00b3 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/FileAuditKeyProvider.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/FileAuditKeyProvider.cs @@ -1,9 +1,9 @@ using System.Security.Cryptography; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using PostSharp.LicenseServer.Options; +using SharpCrafters.Backstage.LicenseServer.Options; -namespace PostSharp.LicenseServer.Security; +namespace SharpCrafters.Backstage.LicenseServer.Security; /// /// Supplies the audit signing key, taking it from configuration when set, and otherwise generating diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/HmacLeaseSigner.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/HmacLeaseSigner.cs index 023fb2c..528d1e3 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/HmacLeaseSigner.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/HmacLeaseSigner.cs @@ -1,7 +1,7 @@ using System.Security.Cryptography; using System.Text; -namespace PostSharp.LicenseServer.Security; +namespace SharpCrafters.Backstage.LicenseServer.Security; /// /// Signs the audit log with HMAC-SHA256. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/IAuditKeyProvider.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/IAuditKeyProvider.cs index 76b4b10..a9d3d87 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/IAuditKeyProvider.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/IAuditKeyProvider.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Security; +namespace SharpCrafters.Backstage.LicenseServer.Security; /// /// Supplies the key used to sign the lease audit log. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/ILeaseSigner.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/ILeaseSigner.cs index 867b8a2..51be72a 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/ILeaseSigner.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/ILeaseSigner.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Security; +namespace SharpCrafters.Backstage.LicenseServer.Security; /// /// Signs the lease audit log. Each lease is signed together with the signature of the previous diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs index ab093a2..f5fbb69 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs @@ -3,12 +3,12 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Email; -using PostSharp.LicenseServer.Licensing; -using PostSharp.LicenseServer.Options; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Email; +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Options; -namespace PostSharp.LicenseServer.Services; +namespace SharpCrafters.Backstage.LicenseServer.Services; /// /// Decides which license, if any, satisfies a lease request. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj b/src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj index 1f11a51..8639a18 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj @@ -2,9 +2,8 @@ net10.0 - - PostSharp.LicenseServer - PostSharp.LicenseServer.Core + SharpCrafters.Backstage.LicenseServer + SharpCrafters.Backstage.LicenseServer.Core diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Time/AcceleratedTimeProvider.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Time/AcceleratedTimeProvider.cs index 5391905..d677897 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Time/AcceleratedTimeProvider.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Time/AcceleratedTimeProvider.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Time; +namespace SharpCrafters.Backstage.LicenseServer.Time; /// /// A that makes time pass faster than it really does, so that a diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs index 691d614..5198763 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/AuthenticationRegistration.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Server.IISIntegration; using Microsoft.Extensions.Options; -namespace PostSharp.LicenseServer; +namespace SharpCrafters.Backstage.LicenseServer; /// /// Chooses how the license server identifies the person borrowing a license. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs index 147d193..7180541 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/DatabaseRegistration.cs @@ -1,8 +1,8 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Data; -namespace PostSharp.LicenseServer; +namespace SharpCrafters.Backstage.LicenseServer; /// /// Chooses the database engine the license server runs against. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs index de028ff..41e7f54 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LegacyUrlRedirects.cs @@ -1,4 +1,4 @@ -namespace PostSharp.LicenseServer.Endpoints; +namespace SharpCrafters.Backstage.LicenseServer.Endpoints; /// /// Redirects the WebForms URLs of previous versions to the pages that replaced them, so that diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs index 07be3c0..bb64511 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs @@ -3,13 +3,13 @@ using System.Xml; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Licensing; -using PostSharp.LicenseServer.Locking; -using PostSharp.LicenseServer.Options; -using PostSharp.LicenseServer.Services; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Locking; +using SharpCrafters.Backstage.LicenseServer.Options; +using SharpCrafters.Backstage.LicenseServer.Services; -namespace PostSharp.LicenseServer.Endpoints; +namespace SharpCrafters.Backstage.LicenseServer.Endpoints; /// /// The endpoints the PostSharp client talks to. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml index 8bc9a01..31b262a 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml @@ -1,5 +1,5 @@ @page -@model PostSharp.LicenseServer.Pages.Admin.AddLicenseModel +@model SharpCrafters.Backstage.LicenseServer.Pages.Admin.AddLicenseModel @{ ViewData["Title"] = "Add a license"; } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs index 77e31bf..4ce91ac 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/AddLicense.cshtml.cs @@ -2,10 +2,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Licensing; -namespace PostSharp.LicenseServer.Pages.Admin; +namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; /// /// Registers a license key so that the server can serve leases against it. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml index d9700ee..f37d0ab 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml @@ -1,5 +1,5 @@ @page -@model PostSharp.LicenseServer.Pages.Admin.CancelModel +@model SharpCrafters.Backstage.LicenseServer.Pages.Admin.CancelModel @{ ViewData["Title"] = "Cancel a lease"; } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs index 991aedd..136f826 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Cancel.cshtml.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Data; -namespace PostSharp.LicenseServer.Pages.Admin; +namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; /// /// Ends a lease early, freeing the seat for somebody else. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml index ad9ba42..8ba0281 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml @@ -1,5 +1,5 @@ @page -@model PostSharp.LicenseServer.Pages.Admin.DetailsModel +@model SharpCrafters.Backstage.LicenseServer.Pages.Admin.DetailsModel @{ ViewData["Title"] = $"License {Model.Id}"; } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs index 8e776a2..5985a4c 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Data; -namespace PostSharp.LicenseServer.Pages.Admin; +namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; /// /// The leases currently held against one license, and the actions an administrator can take on it. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml index 51a0ee3..b667680 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml @@ -1,5 +1,5 @@ @page -@model PostSharp.LicenseServer.Pages.Admin.ExportModel +@model SharpCrafters.Backstage.LicenseServer.Pages.Admin.ExportModel @{ ViewData["Title"] = "Export the audit log"; } @@ -19,14 +19,14 @@

- +

- +

diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs index d5f90a4..3d0e919 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Export.cshtml.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.RazorPages; -namespace PostSharp.LicenseServer.Pages.Admin; +namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; /// /// Chooses the range of months to export from the lease audit log. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml index 46e1a0f..ab24e62 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml @@ -1,5 +1,5 @@ @page -@model PostSharp.LicenseServer.Pages.Admin.GenerateDemoDataModel +@model SharpCrafters.Backstage.LicenseServer.Pages.Admin.GenerateDemoDataModel @{ ViewData["Title"] = "Generate demo data"; } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs index 85d8aa9..fcbe283 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/GenerateDemoData.cshtml.cs @@ -1,10 +1,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Services; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Services; -namespace PostSharp.LicenseServer.Pages.Admin; +namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; /// /// Fills the database with a plausible history of lease activity, so that the dashboard and the diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml.cs index b323cec..8b315dd 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Error.cshtml.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -namespace PostSharp.LicenseServer.Pages; +namespace SharpCrafters.Backstage.LicenseServer.Pages; [ResponseCache( Duration = 0, Location = ResponseCacheLocation.None, NoStore = true )] public sealed class ErrorModel : PageModel diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs index 9429f16..4e48b2e 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs @@ -2,10 +2,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Licensing; -namespace PostSharp.LicenseServer.Pages; +namespace SharpCrafters.Backstage.LicenseServer.Pages; /// /// The usage history of one license: how many seats were in use on each of the last N days, against diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs index 49fa46b..cde560f 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Licensing; -namespace PostSharp.LicenseServer.Pages; +namespace SharpCrafters.Backstage.LicenseServer.Pages; /// /// The dashboard: every registered license, with how much of it is in use right now. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/_ViewImports.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/_ViewImports.cshtml index d455b92..fd907e3 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/_ViewImports.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/_ViewImports.cshtml @@ -1,4 +1,4 @@ -@using PostSharp.LicenseServer -@using PostSharp.LicenseServer.Pages -@namespace PostSharp.LicenseServer.Pages +@using SharpCrafters.Backstage.LicenseServer +@using SharpCrafters.Backstage.LicenseServer.Pages +@namespace SharpCrafters.Backstage.LicenseServer.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs index e0dc4c1..a432eff 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs @@ -1,15 +1,15 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; -using PostSharp.LicenseServer; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Email; -using PostSharp.LicenseServer.Endpoints; -using PostSharp.LicenseServer.Licensing; -using PostSharp.LicenseServer.Locking; -using PostSharp.LicenseServer.Options; -using PostSharp.LicenseServer.Security; -using PostSharp.LicenseServer.Services; -using PostSharp.LicenseServer.Time; +using SharpCrafters.Backstage.LicenseServer; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Email; +using SharpCrafters.Backstage.LicenseServer.Endpoints; +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Locking; +using SharpCrafters.Backstage.LicenseServer.Options; +using SharpCrafters.Backstage.LicenseServer.Security; +using SharpCrafters.Backstage.LicenseServer.Services; +using SharpCrafters.Backstage.LicenseServer.Time; WebApplicationBuilder builder = WebApplication.CreateBuilder( args ); @@ -89,7 +89,7 @@ LeaseLockMode.None => new NullLeaseLock(), LeaseLockMode.SqlApplicationLock => throw new NotSupportedException( "LeaseLockMode.SqlApplicationLock is not implemented yet. Run a single worker process, or open an " - + "issue at https://github.com/postsharp/PostSharp.LicenseServer." ), + + "issue at https://github.com/postsharp/SharpCrafters.Backstage.LicenseServer." ), _ => throw new InvalidOperationException( $"Unknown lease lock mode '{options.LeaseLockMode}'." ) }; } ); diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Properties/launchSettings.json b/src/SharpCrafters.Backstage.LicenseServer.Web/Properties/launchSettings.json index b37673e..ade9693 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Properties/launchSettings.json +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Properties/launchSettings.json @@ -1,6 +1,6 @@ { "profiles": { - "PostSharp.LicenseServer": { + "SharpCrafters.Backstage.LicenseServer": { "commandName": "Project", "launchBrowser": false, "applicationUrl": "http://localhost:44670", diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj b/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj index 1a314cf..eeb68b6 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj @@ -2,11 +2,11 @@ net10.0 - - PostSharp.LicenseServer - PostSharp.LicenseServer - postsharp-license-server + + SharpCrafters.Backstage.LicenseServer + SharpCrafters.Backstage.LicenseServer + sharpcrafters-backstage-license-server @@ -17,7 +17,7 @@ - + diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs index 1d3e386..26c1d35 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs @@ -1,11 +1,11 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Options; -using PostSharp.LicenseServer.Services; -using PostSharp.LicenseServer.Tests.Fakes; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Options; +using SharpCrafters.Backstage.LicenseServer.Services; +using SharpCrafters.Backstage.LicenseServer.Tests.Fakes; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// Build agents are recognised by name so that their leases are not persisted and therefore do not diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs index 17ca36c..346ce08 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// An administrator can end a lease early, which inserts a replacement ending now rather than diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs index 0a0df7b..8f2a0a3 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ConfigurationTests.cs @@ -1,13 +1,13 @@ using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.Options; using Microsoft.Extensions.Time.Testing; -using PostSharp.LicenseServer.Licensing; -using PostSharp.LicenseServer.Options; -using PostSharp.LicenseServer.Time; -using PostSharp.LicenseServer.Tests.Fakes; -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Options; +using SharpCrafters.Backstage.LicenseServer.Time; +using SharpCrafters.Backstage.LicenseServer.Tests.Fakes; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// Settings validation, the accelerated clock and the license parse cache. diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/EmailSenderTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/EmailSenderTests.cs index a568cb1..bfe70b1 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/EmailSenderTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/EmailSenderTests.cs @@ -1,7 +1,7 @@ -using PostSharp.LicenseServer.Email; -using PostSharp.LicenseServer.Tests.Fakes; +using SharpCrafters.Backstage.LicenseServer.Email; +using SharpCrafters.Backstage.LicenseServer.Tests.Fakes; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// The in-memory used throughout the test suite, and the guarantee that diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs index 9b58ef1..86f5c1d 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FakeLicenseParser.cs @@ -1,6 +1,6 @@ -using PostSharp.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Licensing; -namespace PostSharp.LicenseServer.Tests.Fakes; +namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// /// Resolves synthetic license keys to the facts a test wants them to carry. diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs index eaf2554..f80c8df 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs @@ -1,6 +1,6 @@ -using PostSharp.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Licensing; -namespace PostSharp.LicenseServer.Tests.Fakes; +namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// /// Reports a fixed PostSharp SDK version, so that the "the license server itself is too old" branch diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs index fbc938b..19a16b0 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/InMemoryEmailSender.cs @@ -1,7 +1,7 @@ using System.Collections.Concurrent; -using PostSharp.LicenseServer.Email; +using SharpCrafters.Backstage.LicenseServer.Email; -namespace PostSharp.LicenseServer.Tests.Fakes; +namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// /// An that keeps the messages in memory instead of sending them, so that diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs index b80972f..a5e9c9e 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/NeverAcquiringLeaseLock.cs @@ -1,6 +1,6 @@ -using PostSharp.LicenseServer.Locking; +using SharpCrafters.Backstage.LicenseServer.Locking; -namespace PostSharp.LicenseServer.Tests.Fakes; +namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// /// Never grants the lock, so that the "service overloaded" path can be exercised. diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs index 25163f6..1afba73 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/RecordingLeaseSigner.cs @@ -1,8 +1,8 @@ using System.Security.Cryptography; using System.Text; -using PostSharp.LicenseServer.Security; +using SharpCrafters.Backstage.LicenseServer.Security; -namespace PostSharp.LicenseServer.Tests.Fakes; +namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// /// Signs deterministically and keeps every payload it was asked to sign, so that tests can assert on diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs index 523c53f..9dbe16c 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/StaticAuditKeyProvider.cs @@ -1,7 +1,7 @@ using System.Text; -using PostSharp.LicenseServer.Security; +using SharpCrafters.Backstage.LicenseServer.Security; -namespace PostSharp.LicenseServer.Tests.Fakes; +namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// /// Supplies a constant audit signing key. diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveLeadsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveLeadsTests.cs index dc94a21..14479fa 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveLeadsTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveLeadsTests.cs @@ -1,6 +1,6 @@ -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// How many seats of a license are in use at a given moment. diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs index f0e1851..d684d54 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs @@ -11,15 +11,15 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Email; -using PostSharp.LicenseServer.Licensing; -using PostSharp.LicenseServer.Locking; -using PostSharp.LicenseServer.Options; -using PostSharp.LicenseServer.Security; -using PostSharp.LicenseServer.Tests.Fakes; - -namespace PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Email; +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Locking; +using SharpCrafters.Backstage.LicenseServer.Options; +using SharpCrafters.Backstage.LicenseServer.Security; +using SharpCrafters.Backstage.LicenseServer.Tests.Fakes; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// /// Hosts the real application in memory, with the SQL Server database swapped for SQLite and the diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs index f73da15..b191743 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerTestContext.cs @@ -1,12 +1,12 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Licensing; -using PostSharp.LicenseServer.Options; -using PostSharp.LicenseServer.Services; -using PostSharp.LicenseServer.Tests.Fakes; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Options; +using SharpCrafters.Backstage.LicenseServer.Services; +using SharpCrafters.Backstage.LicenseServer.Tests.Fakes; -namespace PostSharp.LicenseServer.Tests.Infrastructure; +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// /// A license server wired up for a test: a real and diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs index 8621560..ee78028 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/SqliteDatabaseFixture.cs @@ -1,8 +1,8 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Data; -namespace PostSharp.LicenseServer.Tests.Infrastructure; +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// /// An in-memory SQLite database, created from the EF Core model, that behaves like a real relational diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs index f8040e0..32bafa2 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestData.cs @@ -1,6 +1,6 @@ -using PostSharp.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Licensing; -namespace PostSharp.LicenseServer.Tests.Infrastructure; +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// /// Fixed points in time used by the tests. diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs index 7278fbe..f6eb2ad 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// The rules that decide whether a developer gets a license: reuse what they hold, grant spare diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs index 07f13dd..c52e662 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAuditLineTests.cs @@ -1,9 +1,9 @@ using Microsoft.EntityFrameworkCore; using System.Globalization; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// The audit-log line is a serialization contract: exported files are archived by customers and diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs index 1351f25..4199c3a 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs @@ -1,6 +1,6 @@ -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// The usage timeline behind the graph: a sequence of lease open and close events carrying the diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs index de0a8a7..f073506 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseEndpointTests.cs @@ -1,10 +1,10 @@ using System.Net; using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Data; -using PostSharp.LicenseServer.Tests.Fakes; -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Tests.Fakes; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// The contract with the PostSharp client: the URL, the query string, the status codes and the diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs index ded1eb1..8b6a554 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSignatureTests.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// The audit log is a chain: each lease is signed together with the signature of the lease before diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs index 5b8acf2..f116c72 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs @@ -1,6 +1,6 @@ -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// Why a license may refuse to serve a request. Each reason is reported back to the developer in the diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs index 9ca3b80..94751b4 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/OpenLeasesTests.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// Leases are never updated in place: prolonging or cancelling one inserts a replacement that points diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs index 2cbc106..5a59a52 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs @@ -1,8 +1,8 @@ using System.Net; using System.Text.Json; -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// The pages an administrator uses, exercised through the real pipeline. diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs index d9709d1..ac40636 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ReviewRegressionTests.cs @@ -1,10 +1,10 @@ using Microsoft.AspNetCore.Http; using System.Net; using Microsoft.AspNetCore.Mvc.Testing; -using PostSharp.LicenseServer.Endpoints; -using PostSharp.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.LicenseServer.Endpoints; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// Defects found while reviewing the migration. Each of these passed unnoticed because the legacy diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs index 7bfddb5..0544f22 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SchemaCompatibilityTests.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; -using PostSharp.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Data; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// The schema an existing installation already has, which this version must keep using unchanged. diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs index edf73c1..d4181ad 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeatCountingTests.cs @@ -1,6 +1,6 @@ -using PostSharp.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Data; -namespace PostSharp.LicenseServer.Tests; +namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// The rounding rule that decides how many seats a set of users consumes. This arithmetic used to diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj index 7a277a1..6e363cb 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj @@ -2,7 +2,7 @@ net10.0 - PostSharp.LicenseServer.Tests + SharpCrafters.Backstage.LicenseServer.Tests false @@ -21,8 +21,8 @@ - - + + From 0a1011c5dc34e13a2da7c704d5e7e8e06221ebfa Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 11:36:25 +0200 Subject: [PATCH 16/44] Build the license server with PostSharp.Engineering The product is Backstage.LicenseServer of the Backstage 2027.0 family, defined in PostSharp.Engineering 2023.2.455. `./Build.ps1 build` and `./Build.ps1 test` replace the direct dotnet invocations, and the TeamCity configuration is generated from the product definition rather than written by hand. global.json and nuget.config are now generated by `./Build.ps1 prepare`, so they leave source control. eng/Package.ps1 goes with them: the release archive is built by the PackAndZip target of the web project and collected as the public artifact of the product, which is what the deployment uploads. Co-Authored-By: Claude Opus 5 --- .gitignore | 12 + .teamcity/pom.xml | 104 ++++ .teamcity/settings.kts | 512 ++++++++++++++++++ Build.ps1 | 249 +++++++++ Directory.Build.props | 16 +- Directory.Build.targets | 6 + Directory.Packages.props | 11 + build.sh | 9 + eng/.gitignore | 1 + eng/AutoUpdatedVersions.props | 9 + eng/DeterministicZip.tasks | 37 ++ eng/MainVersion.props | 6 + eng/Package.ps1 | 63 --- eng/Versions.props | 28 + eng/src/BuildBackstageLicenseServer.csproj | 17 + eng/src/Directory.Build.props | 3 + eng/src/Directory.Build.targets | 2 + eng/src/Program.cs | 32 ++ global.json | 6 - nuget.config | 7 - ...rafters.Backstage.LicenseServer.Web.csproj | 38 ++ 21 files changed, 1087 insertions(+), 81 deletions(-) create mode 100644 .teamcity/pom.xml create mode 100644 .teamcity/settings.kts create mode 100644 Build.ps1 create mode 100644 Directory.Build.targets create mode 100644 build.sh create mode 100644 eng/.gitignore create mode 100644 eng/AutoUpdatedVersions.props create mode 100644 eng/DeterministicZip.tasks create mode 100644 eng/MainVersion.props delete mode 100644 eng/Package.ps1 create mode 100644 eng/Versions.props create mode 100644 eng/src/BuildBackstageLicenseServer.csproj create mode 100644 eng/src/Directory.Build.props create mode 100644 eng/src/Directory.Build.targets create mode 100644 eng/src/Program.cs delete mode 100644 global.json delete mode 100644 nuget.config diff --git a/.gitignore b/.gitignore index 9428fd3..6475bbf 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,15 @@ obj *.db-shm *.db-wal /App_Data + +# Generated by PostSharp.Engineering. `./Build.ps1 prepare` writes them from the product definition +# and from the resolved dependencies, so they carry machine-local paths and must not be committed. +global.json +nuget.config +*.g.props +*.g.ps1 +*.g.json +*.Import.props +/source-dependencies/ +/eng/tools/ +/.config/dotnet-tools.json diff --git a/.teamcity/pom.xml b/.teamcity/pom.xml new file mode 100644 index 0000000..ecdeb4e --- /dev/null +++ b/.teamcity/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + Backstage_BackstageLicenseServer20270 Config DSL Script + Backstage_BackstageLicenseServer20270 + Backstage_BackstageLicenseServer20270_dsl + 1.0-SNAPSHOT + + + org.jetbrains.teamcity + configs-dsl-kotlin-parent + 1.0-SNAPSHOT + + + + + jetbrains-all + https://download.jetbrains.com/teamcity-repository + + true + + + + teamcity-server + https://postsharp.teamcity.com/app/dsl-plugins-repository + + true + + + + + + + JetBrains + https://download.jetbrains.com/teamcity-repository + + + + + ${basedir} + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + + + compile + process-sources + + compile + + + + test-compile + process-test-sources + + test-compile + + + + + + org.jetbrains.teamcity + teamcity-configs-maven-plugin + ${teamcity.dsl.version} + + kotlin + target/generated-configs + + + + + + + + org.jetbrains.teamcity + configs-dsl-kotlin-latest + ${teamcity.dsl.version} + compile + + + org.jetbrains.teamcity + configs-dsl-kotlin-plugins-latest + 1.0-SNAPSHOT + pom + compile + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + compile + + + org.jetbrains.kotlin + kotlin-script-runtime + ${kotlin.version} + compile + + + \ No newline at end of file diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts new file mode 100644 index 0000000..24c134a --- /dev/null +++ b/.teamcity/settings.kts @@ -0,0 +1,512 @@ +// This file is automatically generated by `Build.ps1 generate-scripts`. + +import jetbrains.buildServer.configs.kotlin.* +import jetbrains.buildServer.configs.kotlin.buildFeatures.* +import jetbrains.buildServer.configs.kotlin.buildSteps.* +import jetbrains.buildServer.configs.kotlin.failureConditions.* +import jetbrains.buildServer.configs.kotlin.triggers.* +import jetbrains.buildServer.configs.kotlin.projectFeatures.* + +version = "2025.11" + +project { + + buildType(DebugBuild) + buildType(ReleaseBuild) + buildType(PublicBuild) + buildType(PublicDeployment) + buildType(VersionBump) + + buildTypesOrder = arrayListOf(DebugBuild,ReleaseBuild,PublicBuild,PublicDeployment,VersionBump) + +} + +object DebugBuild : BuildType({ + + name = "Build [Debug]" + + artifactRules = """+:artifacts/publish/public/**/*=>artifacts/publish/public ++:artifacts/publish/private/**/*=>artifacts/publish/private ++:artifacts/testResults/**/*=>artifacts/testResults ++:artifacts/logs/**/*=>logs ++:artifacts/dumps/**/*=>dumps +""" + + params { + text( + "Build.Arguments", + "", + label ="Build.ps1 Arguments", + description = "Arguments to append to the 'Build' build step.", allowEmpty = true) + param("Build.Timeout", "30") + } + + vcs { + root(AbsoluteId("Backstage_BackstageLicenseServer20270")) + checkoutMode = CheckoutMode.ON_AGENT + } + + steps { + powerShell { + name = "Clean NuGet cache of produced and dependency packages" + id = "CleanNuGetCache" + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "${'$'}nugetPackages = if ( ${'$'}env:NUGET_PACKAGES ) { ${'$'}env:NUGET_PACKAGES } else { Join-Path ${'$'}HOME '.nuget' 'packages' }; ${'$'}removedDirs = 0; ${'$'}removedFiles = 0; if ( Test-Path -LiteralPath ${'$'}nugetPackages ) { foreach ( ${'$'}pattern in @('metalama.backstage*', 'postsharp.engineering', 'postsharp.engineering.*', 'sharpcrafters.backstage*', 'sharpcrafters.backstage.licenseserver*', 'sharpcrafters.common*') ) { Get-ChildItem -LiteralPath ${'$'}nugetPackages -Directory -Filter ${'$'}pattern -ErrorAction SilentlyContinue | ForEach-Object { ${'$'}files = @( Get-ChildItem -LiteralPath ${'$'}_.FullName -Recurse -File -ErrorAction SilentlyContinue ).Count; Write-Host \"Removing NuGet cache directory: ${'$'}(${'$'}_.FullName) (${'$'}files file(s))\"; Remove-Item -LiteralPath ${'$'}_.FullName -Recurse -Force -ErrorAction SilentlyContinue; if ( -not ( Test-Path -LiteralPath ${'$'}_.FullName ) ) { ${'$'}removedDirs++; ${'$'}removedFiles += ${'$'}files } } } Write-Host \"Removed ${'$'}removedDirs package directory(ies) and ${'$'}removedFiles file(s) from the NuGet cache.\"; } else { Write-Host \"NuGet packages folder not found: ${'$'}nugetPackages\" }" + } + noProfile = false + } + powerShell { + name = "Kill background processes before cleanup" + id = "PreKill" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "Build.ps1" + } + noProfile = false + scriptArgs = "tools kill " + } + powerShell { + name = "Build" + id = "Build" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "Build.ps1" + } + noProfile = false + scriptArgs = "test --configuration Debug --buildNumber %build.number% --buildType %system.teamcity.buildType.id% --timeout %Build.Timeout% %Build.Arguments%" + } + powerShell { + name = "Kill background processes before next build" + id = "PostKill" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "Build.ps1" + } + noProfile = false + scriptArgs = "tools kill " + } + } + + requirements { + equals("env.BuildAgentType", "caravela04cloud") + } + + features { + swabra { + filesCleanup = Swabra.FilesCleanup.BEFORE_BUILD + lockingProcesses = Swabra.LockingProcessPolicy.KILL + verbose = true + } + gitHubAppBuildScopedToken { + parameterName = "env.GITHUB_TOKEN" + connectionId = "%GITHUB_CONNECTION_POSTSHARP_OPS%" + targetRepositories = "SharpCrafters.Backstage.LicenseServer" + } + commitStatusPublisher { + vcsRootExtId = "Backstage_BackstageLicenseServer20270" + publisher = github { + githubUrl = "https://api.github.com" + authType = vcsRoot() + } + } + pullRequests { + vcsRootExtId = "Backstage_BackstageLicenseServer20270" + provider = github { + authType = vcsRoot() + filterTargetBranch = "+:refs/heads/develop/2027.0" + filterAuthorRole = PullRequests.GitHubRoleFilter.EVERYBODY + } + } + } + + triggers { + vcs { + watchChangesInDependencies = true + branchFilter = "+:develop/2027.0" + quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_CUSTOM + quietPeriod = 7200 + // Build will not trigger automatically if the commit message contains comment value. + triggerRules = "-:comment=<>|<>:**" + } + } + + dependencies { + snapshot(AbsoluteId("Backstage_Backstage20270_DebugBuild")) { + onDependencyFailure = FailureAction.FAIL_TO_START + } + + artifacts(AbsoluteId("Backstage_Backstage20270_DebugBuild")) { + cleanDestination = true + artifactRules = "+:artifacts/publish/private/**/*=>dependencies/Backstage" + } + } + +}) + +object ReleaseBuild : BuildType({ + + name = "Build [Release]" + + artifactRules = """+:artifacts/publish/public/**/*=>artifacts/publish/public ++:artifacts/publish/private/**/*=>artifacts/publish/private ++:artifacts/testResults/**/*=>artifacts/testResults ++:artifacts/logs/**/*=>logs ++:artifacts/dumps/**/*=>dumps +""" + + params { + text( + "Build.Arguments", + "", + label ="Build.ps1 Arguments", + description = "Arguments to append to the 'Build' build step.", allowEmpty = true) + param("Build.Timeout", "30") + } + + vcs { + root(AbsoluteId("Backstage_BackstageLicenseServer20270")) + checkoutMode = CheckoutMode.ON_AGENT + } + + steps { + powerShell { + name = "Clean NuGet cache of produced and dependency packages" + id = "CleanNuGetCache" + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "${'$'}nugetPackages = if ( ${'$'}env:NUGET_PACKAGES ) { ${'$'}env:NUGET_PACKAGES } else { Join-Path ${'$'}HOME '.nuget' 'packages' }; ${'$'}removedDirs = 0; ${'$'}removedFiles = 0; if ( Test-Path -LiteralPath ${'$'}nugetPackages ) { foreach ( ${'$'}pattern in @('metalama.backstage*', 'postsharp.engineering', 'postsharp.engineering.*', 'sharpcrafters.backstage*', 'sharpcrafters.backstage.licenseserver*', 'sharpcrafters.common*') ) { Get-ChildItem -LiteralPath ${'$'}nugetPackages -Directory -Filter ${'$'}pattern -ErrorAction SilentlyContinue | ForEach-Object { ${'$'}files = @( Get-ChildItem -LiteralPath ${'$'}_.FullName -Recurse -File -ErrorAction SilentlyContinue ).Count; Write-Host \"Removing NuGet cache directory: ${'$'}(${'$'}_.FullName) (${'$'}files file(s))\"; Remove-Item -LiteralPath ${'$'}_.FullName -Recurse -Force -ErrorAction SilentlyContinue; if ( -not ( Test-Path -LiteralPath ${'$'}_.FullName ) ) { ${'$'}removedDirs++; ${'$'}removedFiles += ${'$'}files } } } Write-Host \"Removed ${'$'}removedDirs package directory(ies) and ${'$'}removedFiles file(s) from the NuGet cache.\"; } else { Write-Host \"NuGet packages folder not found: ${'$'}nugetPackages\" }" + } + noProfile = false + } + powerShell { + name = "Kill background processes before cleanup" + id = "PreKill" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "Build.ps1" + } + noProfile = false + scriptArgs = "tools kill " + } + powerShell { + name = "Build" + id = "Build" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "Build.ps1" + } + noProfile = false + scriptArgs = "test --configuration Release --buildNumber %build.number% --buildType %system.teamcity.buildType.id% --timeout %Build.Timeout% %Build.Arguments%" + } + powerShell { + name = "Kill background processes before next build" + id = "PostKill" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "Build.ps1" + } + noProfile = false + scriptArgs = "tools kill " + } + } + + requirements { + equals("env.BuildAgentType", "caravela04cloud") + } + + features { + swabra { + filesCleanup = Swabra.FilesCleanup.BEFORE_BUILD + lockingProcesses = Swabra.LockingProcessPolicy.KILL + verbose = true + } + gitHubAppBuildScopedToken { + parameterName = "env.GITHUB_TOKEN" + connectionId = "%GITHUB_CONNECTION_POSTSHARP_OPS%" + targetRepositories = "SharpCrafters.Backstage.LicenseServer" + } + commitStatusPublisher { + vcsRootExtId = "Backstage_BackstageLicenseServer20270" + publisher = github { + githubUrl = "https://api.github.com" + authType = vcsRoot() + } + } + pullRequests { + vcsRootExtId = "Backstage_BackstageLicenseServer20270" + provider = github { + authType = vcsRoot() + filterTargetBranch = "+:refs/heads/develop/2027.0" + filterAuthorRole = PullRequests.GitHubRoleFilter.EVERYBODY + } + } + } + + dependencies { + snapshot(AbsoluteId("Backstage_Backstage20270_ReleaseBuild")) { + onDependencyFailure = FailureAction.FAIL_TO_START + } + + artifacts(AbsoluteId("Backstage_Backstage20270_ReleaseBuild")) { + cleanDestination = true + artifactRules = "+:artifacts/publish/private/**/*=>dependencies/Backstage" + } + } + +}) + +object PublicBuild : BuildType({ + + name = "Build [Public]" + + artifactRules = """+:artifacts/publish/public/**/*=>artifacts/publish/public ++:artifacts/publish/private/**/*=>artifacts/publish/private ++:artifacts/testResults/**/*=>artifacts/testResults ++:artifacts/logs/**/*=>logs ++:artifacts/dumps/**/*=>dumps +""" + + params { + text( + "Build.Arguments", + "", + label ="Build.ps1 Arguments", + description = "Arguments to append to the 'Build' build step.", allowEmpty = true) + param("Build.Timeout", "30") + } + + vcs { + root(AbsoluteId("Backstage_BackstageLicenseServer20270")) + checkoutMode = CheckoutMode.ON_AGENT + } + + steps { + powerShell { + name = "Clean NuGet cache of produced and dependency packages" + id = "CleanNuGetCache" + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "${'$'}nugetPackages = if ( ${'$'}env:NUGET_PACKAGES ) { ${'$'}env:NUGET_PACKAGES } else { Join-Path ${'$'}HOME '.nuget' 'packages' }; ${'$'}removedDirs = 0; ${'$'}removedFiles = 0; if ( Test-Path -LiteralPath ${'$'}nugetPackages ) { foreach ( ${'$'}pattern in @('metalama.backstage*', 'postsharp.engineering', 'postsharp.engineering.*', 'sharpcrafters.backstage*', 'sharpcrafters.backstage.licenseserver*', 'sharpcrafters.common*') ) { Get-ChildItem -LiteralPath ${'$'}nugetPackages -Directory -Filter ${'$'}pattern -ErrorAction SilentlyContinue | ForEach-Object { ${'$'}files = @( Get-ChildItem -LiteralPath ${'$'}_.FullName -Recurse -File -ErrorAction SilentlyContinue ).Count; Write-Host \"Removing NuGet cache directory: ${'$'}(${'$'}_.FullName) (${'$'}files file(s))\"; Remove-Item -LiteralPath ${'$'}_.FullName -Recurse -Force -ErrorAction SilentlyContinue; if ( -not ( Test-Path -LiteralPath ${'$'}_.FullName ) ) { ${'$'}removedDirs++; ${'$'}removedFiles += ${'$'}files } } } Write-Host \"Removed ${'$'}removedDirs package directory(ies) and ${'$'}removedFiles file(s) from the NuGet cache.\"; } else { Write-Host \"NuGet packages folder not found: ${'$'}nugetPackages\" }" + } + noProfile = false + } + powerShell { + name = "Kill background processes before cleanup" + id = "PreKill" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "Build.ps1" + } + noProfile = false + scriptArgs = "tools kill " + } + powerShell { + name = "Build" + id = "Build" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "Build.ps1" + } + noProfile = false + scriptArgs = "test --configuration Public --buildNumber %build.number% --buildType %system.teamcity.buildType.id% --timeout %Build.Timeout% %Build.Arguments%" + } + powerShell { + name = "Kill background processes before next build" + id = "PostKill" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "Build.ps1" + } + noProfile = false + scriptArgs = "tools kill " + } + } + + requirements { + equals("env.BuildAgentType", "caravela04cloud") + } + + features { + swabra { + filesCleanup = Swabra.FilesCleanup.BEFORE_BUILD + lockingProcesses = Swabra.LockingProcessPolicy.KILL + verbose = true + } + gitHubAppBuildScopedToken { + parameterName = "env.GITHUB_TOKEN" + connectionId = "%GITHUB_CONNECTION_POSTSHARP_OPS%" + targetRepositories = "SharpCrafters.Backstage.LicenseServer" + } + commitStatusPublisher { + vcsRootExtId = "Backstage_BackstageLicenseServer20270" + publisher = github { + githubUrl = "https://api.github.com" + authType = vcsRoot() + } + } + pullRequests { + vcsRootExtId = "Backstage_BackstageLicenseServer20270" + provider = github { + authType = vcsRoot() + filterTargetBranch = "+:refs/heads/develop/2027.0" + filterAuthorRole = PullRequests.GitHubRoleFilter.EVERYBODY + } + } + } + + dependencies { + snapshot(AbsoluteId("Backstage_Backstage20270_PublicBuild")) { + onDependencyFailure = FailureAction.FAIL_TO_START + } + + artifacts(AbsoluteId("Backstage_Backstage20270_PublicBuild")) { + cleanDestination = true + artifactRules = "+:artifacts/publish/private/**/*=>dependencies/Backstage" + } + } + +}) + +object PublicDeployment : BuildType({ + + name = "Deploy [Public]" + + type = Type.DEPLOYMENT + + params { + text( + "Publish.Arguments", + "", + label ="Build.ps1 Arguments", + description = "Arguments to append to the 'Publish' build step.", allowEmpty = true) + param("Publish.Timeout", "30") + } + + vcs { + root(AbsoluteId("Backstage_BackstageLicenseServer20270")) + checkoutMode = CheckoutMode.ON_AGENT + } + + steps { + powerShell { + name = "Clean NuGet cache of produced and dependency packages" + id = "CleanNuGetCache" + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "${'$'}nugetPackages = if ( ${'$'}env:NUGET_PACKAGES ) { ${'$'}env:NUGET_PACKAGES } else { Join-Path ${'$'}HOME '.nuget' 'packages' }; ${'$'}removedDirs = 0; ${'$'}removedFiles = 0; if ( Test-Path -LiteralPath ${'$'}nugetPackages ) { foreach ( ${'$'}pattern in @('metalama.backstage*', 'postsharp.engineering', 'postsharp.engineering.*', 'sharpcrafters.backstage*', 'sharpcrafters.backstage.licenseserver*', 'sharpcrafters.common*') ) { Get-ChildItem -LiteralPath ${'$'}nugetPackages -Directory -Filter ${'$'}pattern -ErrorAction SilentlyContinue | ForEach-Object { ${'$'}files = @( Get-ChildItem -LiteralPath ${'$'}_.FullName -Recurse -File -ErrorAction SilentlyContinue ).Count; Write-Host \"Removing NuGet cache directory: ${'$'}(${'$'}_.FullName) (${'$'}files file(s))\"; Remove-Item -LiteralPath ${'$'}_.FullName -Recurse -Force -ErrorAction SilentlyContinue; if ( -not ( Test-Path -LiteralPath ${'$'}_.FullName ) ) { ${'$'}removedDirs++; ${'$'}removedFiles += ${'$'}files } } } Write-Host \"Removed ${'$'}removedDirs package directory(ies) and ${'$'}removedFiles file(s) from the NuGet cache.\"; } else { Write-Host \"NuGet packages folder not found: ${'$'}nugetPackages\" }" + } + noProfile = false + } + powerShell { + name = "Publish" + id = "Publish" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "Build.ps1" + } + noProfile = false + scriptArgs = "publish --configuration Public --deployment default --timeout %Publish.Timeout% %Publish.Arguments%" + } + } + + requirements { + equals("env.BuildAgentType", "caravela04cloud") + } + + features { + swabra { + filesCleanup = Swabra.FilesCleanup.BEFORE_BUILD + lockingProcesses = Swabra.LockingProcessPolicy.KILL + verbose = true + } + gitHubAppBuildScopedToken { + parameterName = "env.GITHUB_TOKEN" + connectionId = "%GITHUB_CONNECTION_POSTSHARP_OPS%" + targetRepositories = "SharpCrafters.Backstage.LicenseServer" + } + } + + dependencies { + snapshot(AbsoluteId("Backstage_Backstage20270_PublicBuild")) { + onDependencyFailure = FailureAction.FAIL_TO_START + } + + artifacts(AbsoluteId("Backstage_Backstage20270_PublicBuild")) { + cleanDestination = true + artifactRules = "+:artifacts/publish/private/**/*=>dependencies/Backstage" + } + snapshot(AbsoluteId("Backstage_Backstage20270_PublicDeployment")) { + onDependencyFailure = FailureAction.FAIL_TO_START + } + snapshot(PublicBuild) { + onDependencyFailure = FailureAction.FAIL_TO_START + } + + artifacts(PublicBuild) { + cleanDestination = true + artifactRules = "+:artifacts/publish/public/**/*=>artifacts/publish/public\n+:artifacts/publish/private/**/*=>artifacts/publish/private" + } + } + +}) + +object VersionBump : BuildType({ + + name = "Version Bump" + + params { + text( + "Bump.Arguments", + "", + label ="Build.ps1 Arguments", + description = "Arguments to append to the 'Bump' build step.", allowEmpty = true) + param("Bump.Timeout", "15") + } + + vcs { + root(AbsoluteId("Backstage_BackstageLicenseServer20270")) + checkoutMode = CheckoutMode.ON_AGENT + } + + steps { + powerShell { + name = "Clean NuGet cache of produced and dependency packages" + id = "CleanNuGetCache" + edition = PowerShellStep.Edition.Core + scriptMode = script { + content = "${'$'}nugetPackages = if ( ${'$'}env:NUGET_PACKAGES ) { ${'$'}env:NUGET_PACKAGES } else { Join-Path ${'$'}HOME '.nuget' 'packages' }; ${'$'}removedDirs = 0; ${'$'}removedFiles = 0; if ( Test-Path -LiteralPath ${'$'}nugetPackages ) { foreach ( ${'$'}pattern in @('metalama.backstage*', 'postsharp.engineering', 'postsharp.engineering.*', 'sharpcrafters.backstage*', 'sharpcrafters.backstage.licenseserver*', 'sharpcrafters.common*') ) { Get-ChildItem -LiteralPath ${'$'}nugetPackages -Directory -Filter ${'$'}pattern -ErrorAction SilentlyContinue | ForEach-Object { ${'$'}files = @( Get-ChildItem -LiteralPath ${'$'}_.FullName -Recurse -File -ErrorAction SilentlyContinue ).Count; Write-Host \"Removing NuGet cache directory: ${'$'}(${'$'}_.FullName) (${'$'}files file(s))\"; Remove-Item -LiteralPath ${'$'}_.FullName -Recurse -Force -ErrorAction SilentlyContinue; if ( -not ( Test-Path -LiteralPath ${'$'}_.FullName ) ) { ${'$'}removedDirs++; ${'$'}removedFiles += ${'$'}files } } } Write-Host \"Removed ${'$'}removedDirs package directory(ies) and ${'$'}removedFiles file(s) from the NuGet cache.\"; } else { Write-Host \"NuGet packages folder not found: ${'$'}nugetPackages\" }" + } + noProfile = false + } + powerShell { + name = "Bump" + id = "Bump" + edition = PowerShellStep.Edition.Core + scriptMode = file { + path = "Build.ps1" + } + noProfile = false + scriptArgs = "bump --timeout %Bump.Timeout% %Bump.Arguments%" + } + } + + requirements { + equals("env.BuildAgentType", "caravela04cloud") + } + + features { + swabra { + filesCleanup = Swabra.FilesCleanup.BEFORE_BUILD + lockingProcesses = Swabra.LockingProcessPolicy.KILL + verbose = true + } + gitHubAppBuildScopedToken { + parameterName = "env.GITHUB_TOKEN" + connectionId = "%GITHUB_CONNECTION_POSTSHARP_OPS%" + targetRepositories = "SharpCrafters.Backstage.LicenseServer" + } + } + +}) + diff --git a/Build.ps1 b/Build.ps1 new file mode 100644 index 0000000..acc1b6b --- /dev/null +++ b/Build.ps1 @@ -0,0 +1,249 @@ +# *** DO NOT EDIT THIS FILE DIRECTLY *** +# This file is auto-generated from src/PostSharp.Engineering.BuildTools/Resources/Build.ps1 +# Edit the source version, then run `./Build.ps1 generate-scripts` to regenerate this file. + +[CmdletBinding(PositionalBinding = $false)] +param( + [switch]$Interactive, # Opens an interactive PowerShell session + [switch]$StartVsmon, # Enable the remote debugger. + [switch]$NoCache, # Bypass the build cache for `eng`, and force a rebuild. + [switch]$Snapshot, # Copy built output to temp directory to avoid file locking during execution. + [switch]$SnapshotRepo, # Copy the repo to a temp directory and execute the build from there. + [Parameter(ValueFromRemainingArguments)] + [string[]]$BuildArgs # Arguments passed to `Build.ps1` within the container. +) + +# Require PowerShell 7.4 or higher (the version installed on GitHub build agents) +if ($PSVersionTable.PSVersion -lt [Version]'7.4') +{ + Write-Error "This script requires PowerShell 7.4 or higher (run with 'pwsh', not 'powershell'). Current version: $($PSVersionTable.PSVersion)" + exit 1 +} + +#### +# These settings are replaced by the generate-scripts command. +$EngPath = 'eng' +$ProductName = 'BackstageLicenseServer' +#### + +if ($StartVsmon) +{ + $vsmonport = 4024 + Write-Host "Starting Visual Studio Remote Debugger, listening at port $vsmonport." -ForegroundColor Cyan + $vsmonProcess = Start-Process -FilePath "C:\msvsmon\msvsmon.exe" ` + -ArgumentList "/noauth","/anyuser","/silent","/port:$vsmonport","/timeout:2147483647" ` + -NoNewWindow -PassThru +} + +# Change the prompt and window title in Docker. +if ($env:RUNNING_IN_DOCKER) +{ + function global:prompt + { + $host.UI.RawUI.WindowTitle = "[docker] " + (Get-Location).Path + "[docker] $( Get-Location )> " + } +} + + +# The exit code of the engineering tool, propagated as the script's own exit code at the very end. Initialized here, +# at script scope, so a purely interactive run that never invokes the tool still exits 0. +$engExitCode = 0 + +if (-not $Interactive -or $BuildArgs) +{ + # The generate-scripts command implies -NoCache + if ($BuildArgs -contains 'generate-scripts') + { + $NoCache = $true + } + + # Change the working directory so we can use a global.json that is specific to eng. + $previousLocation = Get-Location + $engSrcPath = Join-Path $PSScriptRoot $EngPath "src" + + Set-Location $engSrcPath + + $snapshotDir = $null + $snapshotRepoDir = $null + + try + { + # SnapshotRepo mode: copy the repo to a temp directory before building + if ($SnapshotRepo) + { + $snapshotRepoDir = Join-Path $env:TEMP "eng-snapshot-repo-$([Guid]::NewGuid().ToString('N').Substring(0,8))" + Write-Host "Creating snapshot of repository at $snapshotRepoDir..." -ForegroundColor Cyan + $snapshotStopwatch = [Diagnostics.Stopwatch]::StartNew() + & robocopy $PSScriptRoot $snapshotRepoDir /E /XD bin obj artifacts /NJH /NJS /NP | Out-Null + if ($LASTEXITCODE -ge 8) + { + throw "robocopy failed with exit code $LASTEXITCODE" + } + $snapshotStopwatch.Stop() + Write-Host "Snapshot created in $($snapshotStopwatch.Elapsed.TotalSeconds.ToString('F1'))s." -ForegroundColor Cyan + # Redirect paths to the snapshot + $engSrcPath = Join-Path $snapshotRepoDir $EngPath "src" + Set-Location $engSrcPath + } + + # Build caching: check if we need to rebuild + $projectPath = Join-Path $engSrcPath "Build$ProductName.csproj" + + # Find the output DLL by looking for the first DLL in bin/Debug/*/ + $binDebugPath = Join-Path $engSrcPath "bin" "Debug" + $outputDll = $null + $tfmDir = $null + if (Test-Path $binDebugPath) + { + $outputDll = Get-ChildItem -Path $binDebugPath -Filter "Build$ProductName.dll" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 | ForEach-Object { $_.FullName } + if ($outputDll) + { + $tfmDir = Split-Path $outputDll -Parent + } + } + + $needsBuild = $false + + if ($NoCache) + { + # Cache bypassed by -NoCache switch + $needsBuild = $true + } + elseif (-not $outputDll -or -not (Test-Path $outputDll)) + { + # DLL doesn't exist, need to build + $needsBuild = $true + } + else + { + # Get the DLL's last write time + $dllTime = (Get-Item $outputDll).LastWriteTime + + # Check files in $EngPath/src (non-recursive) + $engSrcFiles = Get-ChildItem -Path (Join-Path $PSScriptRoot $EngPath "src") -File -ErrorAction SilentlyContinue + + # Check files in $EngPath (non-recursive) + $engFiles = Get-ChildItem -Path (Join-Path $PSScriptRoot $EngPath) -File -ErrorAction SilentlyContinue + + # Check files in $ScriptRoot (non-recursive) + $rootFiles = Get-ChildItem -Path $PSScriptRoot -File -ErrorAction SilentlyContinue + + # Combine all files and check if any are newer than the DLL + $allFiles = @($engSrcFiles) + @($engFiles) + @($rootFiles) + foreach ($file in $allFiles) + { + if ($file.LastWriteTime -gt $dllTime) + { + $needsBuild = $true + break + } + } + } + + if ($needsBuild) + { + # Build is needed + Write-Host "Building Build$ProductName..." -ForegroundColor Cyan + & dotnet build $projectPath + if ($LASTEXITCODE -ne 0) + { + throw "Build failed with exit code $LASTEXITCODE" + } + + # Re-find the output DLL after build + if (Test-Path $binDebugPath) + { + $outputDll = Get-ChildItem -Path $binDebugPath -Filter "Build$ProductName.dll" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 | ForEach-Object { $_.FullName } + if ($outputDll) + { + $tfmDir = Split-Path $outputDll -Parent + } + } + + # Update the DLL timestamp to mark the cache as valid + if ($outputDll -and (Test-Path $outputDll)) + { + (Get-Item $outputDll).LastWriteTime = Get-Date + } + else + { + throw "Build succeeded but output DLL '$outputDll' not found." + } + } + + # Run the project using dotnet exec (faster than dotnet run) + if (-not $outputDll -or -not (Test-Path $outputDll)) + { + throw "Output DLL not found. Expected path: '$outputDll'." + } + + # Snapshot mode: copy the TFM output directory to temp to avoid file locking during execution + $execDll = $outputDll + if ($Snapshot -and $tfmDir) + { + $snapshotDir = Join-Path $env:TEMP "eng-snapshot-$([Guid]::NewGuid().ToString('N').Substring(0,8))" + Write-Host "Creating snapshot of build output at $snapshotDir..." -ForegroundColor Cyan + Copy-Item -Path $tfmDir -Destination $snapshotDir -Recurse -Force + $execDll = Join-Path $snapshotDir "Build$ProductName.dll" + } + + # Set the repository directory via environment variable (needed when running from snapshot) + $env:ENG_REPO_DIRECTORY = if ($snapshotRepoDir) { $snapshotRepoDir } else { $PSScriptRoot } + & dotnet exec $execDll $BuildArgs + + # Captured on the very next line: $LASTEXITCODE is clobbered by any later command, including the vsmon kill + # just below and everything in the finally block. Without this, the wrapper always exited 0 and every failure + # of the tool was reported to CI as success. The tool returns 1 for a reported failure and 100 for an + # unhandled exception; both are preserved rather than flattened into a `throw`, so the two stay distinguishable. + $engExitCode = $LASTEXITCODE + + if ($StartVsmon) + { + Write-Host "" + Write-Host "Killing vsmon.exe." + $vsmonProcess.Kill() + } + } + finally + { + Set-Location $previousLocation + + # Cleanup snapshot directory if it was created + if ($snapshotDir -and (Test-Path $snapshotDir)) + { + Write-Host "Cleaning up snapshot directory..." -ForegroundColor Cyan + Remove-Item -Path $snapshotDir -Recurse -Force -ErrorAction SilentlyContinue + } + + # Copy artifacts back from repo snapshot + if ($snapshotRepoDir -and (Test-Path $snapshotRepoDir)) + { + $snapshotArtifacts = Join-Path $snapshotRepoDir "artifacts" + $repoArtifacts = Join-Path $PSScriptRoot "artifacts" + + if (Test-Path $snapshotArtifacts) + { + Write-Host "Copying artifacts from snapshot back to repository..." -ForegroundColor Cyan + if (Test-Path $repoArtifacts) + { + Remove-Item -Path $repoArtifacts -Recurse -Force + } + Copy-Item -Path $snapshotArtifacts -Destination $repoArtifacts -Recurse -Force + } + } + + # Reset environment variable + $env:ENG_REPO_DIRECTORY = "" + } +} + +if ($Interactive) +{ + Write-Host "Entering interactive PowerShell." -ForegroundColor Green +} + +# The last statement, so it is the script's exit code. The finally block above has already run, so the working +# directory is restored and snapshots are cleaned up before this. A `throw` earlier in the script (a build failure, +# a missing DLL) exits non-zero on its own and never reaches here. +exit $engExitCode diff --git a/Directory.Build.props b/Directory.Build.props index 7c7dec3..11f2417 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,14 +1,20 @@ - latest - enable + $(MSBuildThisFileDirectory) + GitHub + + + + + + + enable - PostSharp License Server + SharpCrafters Backstage License Server PostSharp Technologies - Copyright (c) PostSharp Technologies - 2025.1.0 + Copyright (c) SharpCrafters s.r.o. diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..a8a269e --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,6 @@ + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 089b3e2..f18564f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,6 +4,17 @@ true + + + 2023.2.455 + + + + + + diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..c878010 --- /dev/null +++ b/build.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# *** DO NOT EDIT THIS FILE DIRECTLY *** +# This file is auto-generated from src/PostSharp.Engineering.BuildTools/Resources/build.sh +# Edit the source version, then run `./Build.ps1 generate-scripts` to regenerate this file. + +# Wrapper script to call Build.ps1 with PowerShell +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +pwsh -File "$SCRIPT_DIR/Build.ps1" "$@" +exit $? diff --git a/eng/.gitignore b/eng/.gitignore new file mode 100644 index 0000000..02690fd --- /dev/null +++ b/eng/.gitignore @@ -0,0 +1 @@ +Dependencies.props diff --git a/eng/AutoUpdatedVersions.props b/eng/AutoUpdatedVersions.props new file mode 100644 index 0000000..46ed20b --- /dev/null +++ b/eng/AutoUpdatedVersions.props @@ -0,0 +1,9 @@ + + + + + 2027.0.1-preview + + + diff --git a/eng/DeterministicZip.tasks b/eng/DeterministicZip.tasks new file mode 100644 index 0000000..ed32f82 --- /dev/null +++ b/eng/DeterministicZip.tasks @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eng/MainVersion.props b/eng/MainVersion.props new file mode 100644 index 0000000..999fcc3 --- /dev/null +++ b/eng/MainVersion.props @@ -0,0 +1,6 @@ + + + 2027.0.0 + -preview + + diff --git a/eng/Package.ps1 b/eng/Package.ps1 deleted file mode 100644 index 0f3d0fb..0000000 --- a/eng/Package.ps1 +++ /dev/null @@ -1,63 +0,0 @@ -<# -.SYNOPSIS - Builds the release package of the PostSharp License Server. - -.DESCRIPTION - Publishes the web application and zips it into artifacts/SharpCrafters.Backstage.LicenseServer.zip, which is - the artifact attached to a GitHub release. - - The package is portable: it carries no platform-specific build and runs wherever the .NET 10 - runtime does. On Windows that means unpacking it into an IIS application, with the ASP.NET Core - Hosting Bundle installed. Elsewhere it is run with `dotnet SharpCrafters.Backstage.LicenseServer.dll`. - - Runs on Windows PowerShell and on PowerShell 7 for Linux and macOS. - -.PARAMETER Configuration - The build configuration. Release by default. - -.PARAMETER OutputPath - Where to write the zip. artifacts/ by default. - -.PARAMETER SkipTests - Skips the test run. Intended for iterating on the packaging itself. -#> -[CmdletBinding()] -param( - [string] $Configuration = 'Release', - [string] $OutputPath, - [switch] $SkipTests -) - -$ErrorActionPreference = 'Stop' - -$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path - -if ( -not $OutputPath ) { $OutputPath = Join-Path $repositoryRoot 'artifacts' } - -$project = Join-Path $repositoryRoot 'src' 'SharpCrafters.Backstage.LicenseServer.Web' 'SharpCrafters.Backstage.LicenseServer.Web.csproj' -$publishPath = Join-Path $repositoryRoot 'artifacts' 'publish' -$zipPath = Join-Path $OutputPath 'SharpCrafters.Backstage.LicenseServer.zip' - -if ( Test-Path $publishPath ) { Remove-Item $publishPath -Recurse -Force } -New-Item -ItemType Directory -Force -Path $OutputPath | Out-Null - -if ( -not $SkipTests ) { - Write-Host 'Testing...' - Push-Location $repositoryRoot - try { dotnet test --configuration $Configuration --nologo } - finally { Pop-Location } - if ( $LASTEXITCODE -ne 0 ) { throw 'The tests failed.' } -} - -Write-Host 'Publishing...' -dotnet publish $project --configuration $Configuration --output $publishPath --nologo -if ( $LASTEXITCODE -ne 0 ) { throw 'The publish failed.' } - -# Development-only settings must not reach a customer's server. -Remove-Item (Join-Path $publishPath 'appsettings.Development.json') -Force -ErrorAction SilentlyContinue - -Write-Host "Packing $zipPath..." -if ( Test-Path $zipPath ) { Remove-Item $zipPath -Force } -Compress-Archive -Path (Join-Path $publishPath '*') -DestinationPath $zipPath - -Write-Host "Created $zipPath ($([math]::Round((Get-Item $zipPath).Length / 1MB, 1)) MB)." diff --git a/eng/Versions.props b/eng/Versions.props new file mode 100644 index 0000000..6d1ed1a --- /dev/null +++ b/eng/Versions.props @@ -0,0 +1,28 @@ + + + + + + + + + $(MainVersion)$(PackageVersionSuffix) + $(MainVersion) + + + + + + + + + + $(BackstageLicenseServerAssemblyVersion) + $(BackstageLicenseServerVersion) + + + + + + diff --git a/eng/src/BuildBackstageLicenseServer.csproj b/eng/src/BuildBackstageLicenseServer.csproj new file mode 100644 index 0000000..c4adcaf --- /dev/null +++ b/eng/src/BuildBackstageLicenseServer.csproj @@ -0,0 +1,17 @@ + + + + Exe + net10.0 + latest + enable + + + $(NoWarn);NU1903;NU1904 + + + + + + + diff --git a/eng/src/Directory.Build.props b/eng/src/Directory.Build.props new file mode 100644 index 0000000..5fd85ab --- /dev/null +++ b/eng/src/Directory.Build.props @@ -0,0 +1,3 @@ + + + diff --git a/eng/src/Directory.Build.targets b/eng/src/Directory.Build.targets new file mode 100644 index 0000000..8c119d5 --- /dev/null +++ b/eng/src/Directory.Build.targets @@ -0,0 +1,2 @@ + + diff --git a/eng/src/Program.cs b/eng/src/Program.cs new file mode 100644 index 0000000..ed43136 --- /dev/null +++ b/eng/src/Program.cs @@ -0,0 +1,32 @@ +// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details. + +using PostSharp.Engineering.BuildTools; +using PostSharp.Engineering.BuildTools.Build.Model; +using PostSharp.Engineering.BuildTools.Build.Solutions; +using BackstageDependencies = PostSharp.Engineering.BuildTools.Dependencies.Definitions.BackstageDependencies.V2027_0; + +var product = new Product( BackstageDependencies.BackstageLicenseServer ) +{ + // The product consumes SharpCrafters.Backstage, whose packages carry a prefix that does not match the + // default source, so the generated nuget.config has to carry the source mapping of the dependency. + GenerateNuGetConfig = true, + + DotNetSdkVersion = new DotNetSdkVersion( BackstageDependencies.Family.PreferredVersions.DotNetSdk.V_10_0 ), + + // Built rather than packed: the product ships a deployable archive and no NuGet package, so the Pack + // target of every project would be a no-op. + Solutions = + [ + new DotNetSolution( "SharpCrafters.Backstage.LicenseServer.slnx" ) + { + BuildMethod = BuildMethod.Build, CanFormatCode = true + } + ], + + // The deliverable is the archive that an administrator unpacks into an IIS application or runs with + // `dotnet SharpCrafters.Backstage.LicenseServer.dll`. The web project builds it; see the PackAndZip + // target of SharpCrafters.Backstage.LicenseServer.Web.csproj. + PublicArtifacts = Pattern.Create( "SharpCrafters.Backstage.LicenseServer.$(PackageVersion).zip" ) +}; + +return new EngineeringApp( product ).Run( args ); diff --git a/global.json b/global.json deleted file mode 100644 index 01cb658..0000000 --- a/global.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "sdk": { - "version": "10.0.400", - "rollForward": "latestFeature" - } -} diff --git a/nuget.config b/nuget.config deleted file mode 100644 index 4d736c1..0000000 --- a/nuget.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj b/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj index eeb68b6..2f8d66d 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj @@ -25,4 +25,42 @@ + + + + + + $(PackageOutputPath)$(AssemblyName).$(Version).zip + + + $(BuildDate) + 2000-01-01 + + + + + + + $([MSBuild]::NormalizeDirectory('$(OutputPath)Packed')) + + + + + + + + + + + + + + From 05ceefd1be69293aab12ff51a52fbd6c0175cdef Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 11:42:50 +0200 Subject: [PATCH 17/44] Parse license keys with SharpCrafters.Backstage The PostSharp SDK is replaced by the licensing component of the product family this server now belongs to. The public API of SharpCrafters.Backstage covers everything the server reads from a license key, including the rules that derive a value from several fields, so the parser projects LicenseRegistrationProperties rather than reading the fields itself. The platform initialization the SDK required disappears with it. Two things the package does not provide are written here instead. The lease response is serialized by the server, because a client only ever parses one; the format is the one the SDK produced and is now pinned by a test. The hash that anonymises a name in the audit log is reproduced, because the method of Backstage is internal; the golden values in LeaseAuditLineTests prove it produces what PostSharp produced. The parser takes its licensing authority as an argument, so the tests sign real license keys with a key pair of their own and exercise the component end to end -- no license key signed by the production authority can live in a public repository. Two differences between the two libraries are handled explicitly: - The product names differ: the enumeration of PostSharp calls a product Ultimate where Backstage calls it PostSharpUltimate. An installation upgraded from a previous version holds the PostSharp spelling in ProductCode, while a client of the Backstage generation asks by the Backstage one, so a request naming a product would have found nothing. ProductCodes reconciles the two when matching, and new rows are written with the Backstage spelling. - A key that carries no grace period now gets thirty days rather than none, which is the default Backstage applies. LicenseKeyData.GraceDays is not nullable, so an absent field cannot be told from a field set to thirty and the previous value cannot be restored. Keys issued with an explicit grace period are unaffected. The grace percentage, which Backstage leaves null, keeps the thirty percent PostSharp applied. Co-Authored-By: Claude Opus 5 --- Directory.Packages.props | 12 +- .../Data/Lease.Audit.cs | 6 +- .../Licensing/BackstageLicenseParser.cs | 83 +++++++++ .../Licensing/BackstageServerVersion.cs | 17 ++ .../Licensing/ILicenseServerVersion.cs | 6 +- .../Licensing/LeaseSerializer.cs | 40 ++++ .../Licensing/LicenseServerProductCatalog.cs | 67 +++++++ .../Licensing/PostSharpLeaseSerializer.cs | 12 -- .../Licensing/PostSharpLicenseParser.cs | 44 ----- .../Licensing/PostSharpPlatform.cs | 37 ---- .../Licensing/PostSharpServerVersion.cs | 11 -- .../Licensing/ProductCodes.cs | 74 ++++++++ .../Security/StringHash.cs | 60 ++++++ .../Services/LeaseService.cs | 12 +- ...afters.Backstage.LicenseServer.Core.csproj | 7 +- .../Program.cs | 9 +- .../BackstageLicenseParserTests.cs | 171 ++++++++++++++++++ .../Fakes/FixedServerVersion.cs | 6 +- .../Infrastructure/TestLicenseKeys.cs | 98 ++++++++++ .../LeaseSerializerTests.cs | 55 ++++++ .../LicenseValidationTests.cs | 5 +- .../ProductCodesTests.cs | 85 +++++++++ 22 files changed, 784 insertions(+), 133 deletions(-) create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageServerVersion.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseServerProductCatalog.cs delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpPlatform.cs delete mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpServerVersion.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ProductCodes.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Security/StringHash.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index f18564f..cdc7f5d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,13 +15,11 @@ - - - - - - + + + diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs index 17bc060..a810929 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/Lease.Audit.cs @@ -1,5 +1,5 @@ using System.Xml; -using PostSharp.Sdk.Extensibility.Licensing; +using SharpCrafters.Backstage.LicenseServer.Security; namespace SharpCrafters.Backstage.LicenseServer; @@ -29,9 +29,9 @@ public void Write( TextWriter textWriter, bool includeHmac ) textWriter.Write( ';' ); textWriter.Write( XmlConvert.ToString( this.EndTime, XmlDateTimeSerializationMode.RoundtripKind ) ); textWriter.Write( ';' ); - textWriter.Write( CryptoUtilities.ComputeStringHash64( this.Machine ).ToString( "x" ) ); + textWriter.Write( StringHash.ComputeStringHash64( this.Machine ).ToString( "x" ) ); textWriter.Write( ';' ); - textWriter.Write( CryptoUtilities.ComputeStringHash64( this.UserName ).ToString( "x" ) ); + textWriter.Write( StringHash.ComputeStringHash64( this.UserName ).ToString( "x" ) ); if ( includeHmac ) { diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs new file mode 100644 index 0000000..89469af --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs @@ -0,0 +1,83 @@ +using SharpCrafters.Backstage.Licensing; +using SharpCrafters.Backstage.Licensing.Licenses; +using SharpCrafters.Backstage.Licensing.Registration; + +namespace SharpCrafters.Backstage.LicenseServer.Licensing; + +/// +/// Parses and validates license keys with SharpCrafters.Backstage. This is the only class that +/// touches the licensing types of that package; everything else works with . +/// +/// +/// The authority of the keys whose signature is accepted. Production keys by default; a test signs +/// with an authority of its own. +/// +public sealed class BackstageLicenseParser( ILicensingAuthorityProvider? authorities = null ) : ILicenseParser +{ + /// + /// The percentage of additional seats tolerated during the grace period, when the license key + /// does not carry one. Backstage leaves the field null, so the default is applied here; it is the + /// one PostSharp applied, so a license key that was served before is served the same way. + /// + private const int defaultGracePercent = 30; + + private readonly ILicensingAuthorityProvider authorities = + authorities ?? new ProductionLicensingAuthorityProvider(); + + public LicenseInfo? TryParse( string licenseKey ) + { + if ( string.IsNullOrWhiteSpace( licenseKey ) ) + { + return null; + } + + if ( !LicenseKeyData.TryDeserialize( licenseKey, out LicenseKeyData? data, out _ ) ) + { + return null; + } + + // The fields are checked before the signature, as the consumption path of a client does: a + // key that carries a must-understand field this version does not know cannot be served, + // whether or not the signature is valid. + if ( !data.ValidateFields( out _ ) || !data.TryVerifySignature( this.authorities, out _ ) ) + { + return null; + } + + // The registration properties are where the rules that derive a value from several fields + // live -- the eligibility of a key that predates the LicenseServerEligible field, the + // minimal PostSharp version of a key that predates MinPostSharpVersion, the normalization of + // the products that were renamed. Reading the fields directly would reimplement them. + LicenseRegistrationProperties properties = data.ToLicenseRegistrationProperties( + LicenseServerProductCatalog.Instance, + licenseKey ); + + return new LicenseInfo + { + LicenseId = data.LicenseId, + Product = ProductCodes.ForStorage( properties.Product ), + LicenseType = properties.LicenseType.ToString(), + UserNumber = data.UserNumber, + ValidTo = properties.ValidTo, + SubscriptionEndDate = properties.SubscriptionEndDate, + MinPostSharpVersion = properties.MinPostSharpVersion, + GraceDays = data.GraceDays, + GracePercent = data.GracePercent ?? defaultGracePercent, + IsLicenseServerEligible = properties.LicenseServerEligible, + LicenseTypeName = properties.LicenseType.GetLicenseTypeName(), + ProductName = LicenseServerProductCatalog.Instance.GetDisplayName( properties.Product ) + }; + } + + /// + /// Removes the whitespace that a license key picks up when it is pasted out of an email. + /// + /// + /// A license key is Base32 after its identifier and a hyphen, so no character it can legitimately + /// contain is whitespace. Backstage has no equivalent method: it trims a license string and + /// nothing more, because its keys arrive from a command line or a configuration file rather than + /// from a web form. + /// + public string CleanLicenseString( string licenseKey ) + => new( licenseKey.Where( c => !char.IsWhiteSpace( c ) ).ToArray() ); +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageServerVersion.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageServerVersion.cs new file mode 100644 index 0000000..00c95fa --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageServerVersion.cs @@ -0,0 +1,17 @@ +using SharpCrafters.Backstage.Licensing.Licenses; + +namespace SharpCrafters.Backstage.LicenseServer.Licensing; + +/// +/// Reports the version of SharpCrafters.Backstage, which is the library that parses license keys in +/// this server. +/// +/// +/// The version is read from the assembly rather than written down, so that upgrading the package is +/// all that is needed to serve a license key that requires a newer one. +/// +public sealed class BackstageServerVersion : ILicenseServerVersion +{ + public Version LicensingLibraryVersion { get; } = + typeof(LicenseKeyData).Assembly.GetName().Version ?? new Version( 0, 0 ); +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs index 59ffe1d..4acef3d 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ILicenseServerVersion.cs @@ -1,10 +1,10 @@ namespace SharpCrafters.Backstage.LicenseServer.Licensing; /// -/// The version of the PostSharp SDK embedded in this license server. A license requiring a higher -/// version cannot be served until the license server itself is upgraded. +/// The version of the licensing library embedded in this license server. A license key that declares +/// a higher minimal version cannot be served until the server itself is upgraded. /// public interface ILicenseServerVersion { - Version SdkVersion { get; } + Version LicensingLibraryVersion { get; } } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs new file mode 100644 index 0000000..7f82e1a --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LeaseSerializer.cs @@ -0,0 +1,40 @@ +using System.Xml; + +namespace SharpCrafters.Backstage.LicenseServer.Licensing; + +/// +/// Serializes a lease in the format the client parses. +/// +/// +/// +/// The format is the one PostSharp's LicenseLease.Serialize produced and the one +/// SharpCrafters.Backstage.Licensing.LicenseServer.LicenseLease.TryDeserialize reads: the +/// four parts separated by "; ", each named and followed by a colon, with the instants in the +/// XML round-trip representation of UTC. It is written here rather than called, because the type of +/// Backstage is internal to that package and has no serializer -- a client only ever reads a lease. +/// +/// +/// Every deployed client parses this, so the shape is a contract and is pinned by a test. The +/// parsing on the client side is lenient -- parts are matched by name without regard to case and an +/// unknown part is ignored -- so a later version of the server may add a part, but may not rename or +/// reorder one. +/// +/// +public sealed class LeaseSerializer : ILeaseSerializer +{ + public string Serialize( string licenseKey, DateTime startTime, DateTime endTime, DateTime renewTime ) + => $"License: {licenseKey}" + + $"; StartTime: {ToUtcString( startTime )}" + + $"; EndTime: {ToUtcString( endTime )}" + + $"; RenewTime: {ToUtcString( renewTime )}"; + + /// + /// and not : + /// the times come from the database, where a datetime has no time zone, and the mode has + /// to be the one that reads an unspecified instant as UTC rather than as local time. The lease + /// times this server produces are already tagged as UTC, so the two agree -- but a caller that + /// hands over an untagged instant gets the right answer as well. + /// + private static string ToUtcString( DateTime value ) + => XmlConvert.ToString( value, XmlDateTimeSerializationMode.Utc ); +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseServerProductCatalog.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseServerProductCatalog.cs new file mode 100644 index 0000000..9937add --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/LicenseServerProductCatalog.cs @@ -0,0 +1,67 @@ +using System.Collections.Immutable; +using SharpCrafters.Backstage.Licensing; + +namespace SharpCrafters.Backstage.LicenseServer.Licensing; + +/// +/// Names the products whose license keys the server holds. +/// +/// +/// +/// A answers two kinds of question: what a product is called, +/// and how a license key of that product is registered on a developer's machine. Only the first +/// kind has a meaning here, because a license server serves the license keys its administrator +/// added and never registers one. The base class already answers it for every product of both +/// families, so this class only has to state that the server is not a product family of its own. +/// +/// +/// The members of the second kind exist so that a client can decide which edition to offer, which +/// license key to keep when another is registered, and where to store it. A server has no such +/// decisions to make, so they throw rather than return a value that would quietly be wrong. +/// +/// +public sealed class LicenseServerProductCatalog : LicenseProductCatalog +{ + private const string notAClient = + "The license server names products but does not register license keys, so it has no " + + "editions of its own."; + + /// + /// Gets the single instance, which holds no state. + /// + public static LicenseServerProductCatalog Instance { get; } = new(); + + private LicenseServerProductCatalog() { } + + /// + /// A server pools the license keys of whatever products its administrator added, including the + /// products of both families side by side. + /// + public override bool IsProductOfFamily( LicenseProduct product ) => true; + + /// + public override bool IsFreeProduct( LicenseProduct product ) + => product is LicenseProduct.MetalamaCommunity or LicenseProduct.PostSharpEssentials; + + /// + /// The server stores every license key in the same table, and the version-specific registration + /// of a client has no equivalent. + /// + public override bool RequiresVersionSpecificRegistration( LicenseProduct product ) => false; + + /// + public override ImmutableArray GetProductsCoexistingWith( LicenseProduct product ) + => throw new NotSupportedException( notAClient ); + + /// + public override string PremiumEditionDisplayName => throw new NotSupportedException( notAClient ); + + /// + public override LicenseProduct EvaluationProduct => throw new NotSupportedException( notAClient ); + + /// + public override LicenseProduct? CommunityProduct => throw new NotSupportedException( notAClient ); + + /// + public override LicenseProduct? LegacyFreeProduct => throw new NotSupportedException( notAClient ); +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs deleted file mode 100644 index 08cc5ad..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLeaseSerializer.cs +++ /dev/null @@ -1,12 +0,0 @@ -using PostSharp.Sdk.Extensibility.Licensing; - -namespace SharpCrafters.Backstage.LicenseServer.Licensing; - -/// -/// Serializes a lease in the format expected by the PostSharp client. -/// -public sealed class PostSharpLeaseSerializer : ILeaseSerializer -{ - public string Serialize( string licenseKey, DateTime startTime, DateTime endTime, DateTime renewTime ) - => new LicenseLease( licenseKey, startTime, endTime, renewTime ).Serialize(); -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs deleted file mode 100644 index c1746cd..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpLicenseParser.cs +++ /dev/null @@ -1,44 +0,0 @@ -using PostSharp.Sdk.Extensibility.Licensing; -using ParsedLicense = PostSharp.Sdk.Extensibility.Licensing.License; - -namespace SharpCrafters.Backstage.LicenseServer.Licensing; - -/// -/// Parses license keys with the PostSharp SDK. This is the only class that touches the SDK's -/// licensing types; everything else works with . -/// -public sealed class PostSharpLicenseParser : ILicenseParser -{ - public LicenseInfo? TryParse( string licenseKey ) - { - if ( string.IsNullOrWhiteSpace( licenseKey ) ) - { - return null; - } - - ParsedLicense? parsedLicense = ParsedLicense.Deserialize( licenseKey ); - - if ( parsedLicense == null || !parsedLicense.Validate( null, out _ ) ) - { - return null; - } - - return new LicenseInfo - { - LicenseId = parsedLicense.LicenseId, - Product = parsedLicense.Product.ToString(), - LicenseType = parsedLicense.LicenseType.ToString(), - UserNumber = parsedLicense.UserNumber, - ValidTo = parsedLicense.ValidTo, - SubscriptionEndDate = parsedLicense.SubscriptionEndDate, - MinPostSharpVersion = parsedLicense.MinPostSharpVersion, - GraceDays = parsedLicense.GetGraceDaysOrDefault(), - GracePercent = parsedLicense.GetGracePercentOrDefault(), - IsLicenseServerEligible = parsedLicense.IsLicenseServerEligible(), - LicenseTypeName = parsedLicense.GetLicenseTypeName(), - ProductName = parsedLicense.GetProductName() - }; - } - - public string CleanLicenseString( string licenseKey ) => ParsedLicense.CleanLicenseString( licenseKey ); -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpPlatform.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpPlatform.cs deleted file mode 100644 index 7547e25..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpPlatform.cs +++ /dev/null @@ -1,37 +0,0 @@ -using PostSharp.Platform.NetStandard20; -using PostSharp.Platform.Neutral; - -namespace SharpCrafters.Backstage.LicenseServer.Licensing; - -/// -/// Initializes the PostSharp SDK platform services. Must run once before any license key is parsed. -/// -/// -/// The legacy code did this in the static constructor of ParsedLicenseManager and used -/// NetFrameworkDefaultSystemServices, which does not exist outside .NET Framework. -/// -public static class PostSharpPlatform -{ - private static readonly Lock sync = new(); - private static bool initialized; - - public static void EnsureInitialized() - { - if ( initialized ) - { - return; - } - - lock ( sync ) - { - if ( initialized ) - { - return; - } - - CommonDefaultSystemServices.Initialize(); - NetCoreAppDefaultSystemServices.Initialize(); - initialized = true; - } - } -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpServerVersion.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpServerVersion.cs deleted file mode 100644 index 8ae0c1e..0000000 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/PostSharpServerVersion.cs +++ /dev/null @@ -1,11 +0,0 @@ -using PostSharp.Sdk; - -namespace SharpCrafters.Backstage.LicenseServer.Licensing; - -/// -/// Reports the version of the PostSharp SDK embedded in this license server. -/// -public sealed class PostSharpServerVersion : ILicenseServerVersion -{ - public Version SdkVersion => ApplicationInfo.Version; -} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ProductCodes.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ProductCodes.cs new file mode 100644 index 0000000..7b1b089 --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/ProductCodes.cs @@ -0,0 +1,74 @@ +using System.Collections.Immutable; +using SharpCrafters.Backstage.Licensing; + +namespace SharpCrafters.Backstage.LicenseServer.Licensing; + +/// +/// The value of the ProductCode column, and the names a client may ask for it by. +/// +/// +/// +/// The column holds the name of the licensed product, and a client that names a product in its +/// request is served only from the licenses that carry that name. The two spellings of a name have to +/// be treated as one, because PostSharp and SharpCrafters.Backstage do not agree on them: the +/// enumeration of PostSharp calls the products Ultimate and Framework, while the +/// enumeration of Backstage calls the same values PostSharpUltimate and +/// PostSharpFramework. +/// +/// +/// A database created by a previous version of this server therefore holds the PostSharp spelling, +/// while a client of the Backstage generation asks for the Backstage one. Without this mapping, a +/// request that names a product would find none of the licenses an existing installation holds. New +/// rows are written with the Backstage spelling, which is the one the clients send, so a database +/// that has been through the upgrade holds both -- which is why the matching, and not the storage, is +/// where the two are reconciled. +/// +/// +public static class ProductCodes +{ + /// + /// The name each product had in the LicensedProduct enumeration of the PostSharp SDK, for + /// the products whose name changed. The values that were never written by a license server, and + /// the Metalama products, which postdate the change, are absent. + /// + private static readonly ImmutableDictionary legacyNames = + new Dictionary( StringComparer.OrdinalIgnoreCase ) + { + [nameof(LicenseProduct.PostSharpUltimate)] = "Ultimate", + [nameof(LicenseProduct.PostSharpFramework)] = "Framework", + [nameof(LicenseProduct.PostSharpDiagnosticsLibrary)] = "DiagnosticsLibrary", + [nameof(LicenseProduct.PostSharpModelLibrary)] = "ModelLibrary", + [nameof(LicenseProduct.PostSharpThreadingLibrary)] = "ThreadingLibrary", + [nameof(LicenseProduct.PostSharpCachingLibrary)] = "CachingLibrary", +#pragma warning disable CS0618 // The product is obsolete, but a row naming it may still exist. + [nameof(LicenseProduct.PostSharpUltimate1)] = "PostSharp30" +#pragma warning restore CS0618 + }.ToImmutableDictionary( StringComparer.OrdinalIgnoreCase ); + + private static readonly ImmutableDictionary currentNames = + legacyNames.ToImmutableDictionary( x => x.Value, x => x.Key, StringComparer.OrdinalIgnoreCase ); + + /// + /// Gets the value to store in the ProductCode column for a product. + /// + public static string ForStorage( LicenseProduct product ) => product.ToString(); + + /// + /// Gets every value of the ProductCode column that satisfies a request for a product, + /// which is the name the client asked for and, when the name changed, the other spelling of it. + /// + public static IReadOnlyList Matching( string productCode ) + { + if ( legacyNames.TryGetValue( productCode, out string? legacy ) ) + { + return [productCode, legacy]; + } + + if ( currentNames.TryGetValue( productCode, out string? current ) ) + { + return [productCode, current]; + } + + return [productCode]; + } +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Security/StringHash.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/StringHash.cs new file mode 100644 index 0000000..26c0cb6 --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Security/StringHash.cs @@ -0,0 +1,60 @@ +using System.Security.Cryptography; +using System.Text; + +namespace SharpCrafters.Backstage.LicenseServer.Security; + +/// +/// The unkeyed 64-bit hash that anonymises a user or a machine name in the audit log. +/// +/// +/// +/// This is the algorithm of CryptoUtilities.ComputeStringHash64 in the PostSharp SDK and of +/// HashUtilities.ComputeStringHash64 in SharpCrafters.Backstage. It is reproduced here rather +/// than called, because the method of Backstage is internal to that package. +/// +/// +/// The values are part of the audit-log format: customers archive the exported files and compare +/// them across years, and the license audit of Backstage hashes the same names the same way, so +/// that one person is counted once whatever the mixture of products they use. Changing the +/// algorithm would break both. LeaseAuditLineTests pins the values. +/// +/// +/// MD5 is not chosen for its cryptographic properties, which are irrelevant to an unkeyed +/// anonymising hash, but because the values have to equal the ones PostSharp has been producing +/// since 2013. +/// +/// +public static class StringHash +{ + /// + /// Computes the hash of a string, or 0 when it is null. + /// + public static long ComputeStringHash64( string? value ) + { + if ( value == null ) + { + return 0; + } + + // The name is normalized first, so that the same person is hashed to the same value whatever + // the case and the padding of the name the client sent. + byte[] bytes = Encoding.UTF8.GetBytes( value.Trim().ToLowerInvariant().Normalize() ); + +#pragma warning disable CA5350, CA5351 // MD5 is required to reproduce the values of PostSharp. + byte[] hash = MD5.HashData( bytes ); +#pragma warning restore CA5350, CA5351 + + // The first eight bytes read as a little-endian signed integer. The bytes are combined + // explicitly rather than reinterpreted, so that the value does not depend on the endianness + // of the platform. + long hash64 = 0; + + for ( int i = 7; i >= 0; i-- ) + { + hash64 = (hash64 << 8) | hash[i]; + } + + // A non-null string never hashes to the value that stands for null. + return hash64 == 0 ? -1 : hash64; + } +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs index f5fbb69..85e1787 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs @@ -87,10 +87,10 @@ public LeaseService( return null; } - if ( parsedLicense.MinPostSharpVersion > this.serverVersion.SdkVersion ) + if ( parsedLicense.MinPostSharpVersion > this.serverVersion.LicensingLibraryVersion ) { errors[license.LicenseId] = string.Format( - "The license #{0} requires higher version of PostSharp on the License Server. Please upgrade PostSharp NuGet package of the License Server to >= {1}.{2}.{3}", + "The license #{0} requires a higher version of the licensing library on the License Server. Please upgrade the License Server to >= {1}.{2}.{3}", license.LicenseId, parsedLicense.MinPostSharpVersion.Major, parsedLicense.MinPostSharpVersion.Minor, @@ -165,8 +165,14 @@ public LeaseService( Dictionary errors, CancellationToken cancellationToken = default ) { + // A client that names no product is served from any pool, which is what every PostSharp + // client relies on: none of them sent the argument. + IReadOnlyList productCodes = string.IsNullOrEmpty( productCode ) + ? [] + : ProductCodes.Matching( productCode ); + License[] licenses = await this.repository.Licenses - .Where( license => (string.IsNullOrEmpty( productCode ) || license.ProductCode == productCode) + .Where( license => (productCodes.Count == 0 || productCodes.Contains( license.ProductCode )) && license.Priority >= 0 ) .OrderBy( license => license.Priority ) .ToArrayAsync( cancellationToken ); diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj b/src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj index 8639a18..f74065f 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj @@ -12,9 +12,10 @@ - - - + + + diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs index a432eff..19f439c 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs @@ -13,9 +13,6 @@ WebApplicationBuilder builder = WebApplication.CreateBuilder( args ); -// The PostSharp SDK parses license keys, and has to be initialized once before it is first used. -PostSharpPlatform.EnsureInitialized(); - builder.Services .AddOptions() .Bind( builder.Configuration.GetSection( LicenseServerOptions.SectionName ) ) @@ -36,10 +33,10 @@ builder.Services.AddScoped(); builder.Services.AddSingleton( - _ => new CachingLicenseParser( new PostSharpLicenseParser() ) ); + _ => new CachingLicenseParser( new BackstageLicenseParser() ) ); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton( services => new FileAuditKeyProvider( diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs new file mode 100644 index 0000000..5f651ae --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs @@ -0,0 +1,171 @@ +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.Licensing; +using SharpCrafters.Backstage.Licensing.Licenses; + +namespace SharpCrafters.Backstage.LicenseServer.Tests; + +/// +/// The licensing component, which is SharpCrafters.Backstage. Every other test in this suite works +/// against FakeLicenseParser, so this is the only place where a real license key is parsed and +/// the only place that would notice the package changing what it reports. +/// +public sealed class BackstageLicenseParserTests +{ + private static readonly BackstageLicenseParser parser = new( TestLicenseKeys.Authority ); + + [Fact] + public void SignedKey_IsParsedIntoTheFactsTheServerNeeds() + { + LicenseKeyDataBuilder builder = TestLicenseKeys.Builder( licenseId: 4242 ); + builder.UserNumber = 25; + builder.ValidTo = new DateTime( 2030, 6, 30, 0, 0, 0, DateTimeKind.Utc ); + builder.SubscriptionEndDate = new DateTime( 2029, 6, 30, 0, 0, 0, DateTimeKind.Utc ); + builder.GraceDays = 7; + builder.GracePercent = 15; + + LicenseInfo? license = parser.TryParse( builder.Sign() ); + + Assert.NotNull( license ); + Assert.Equal( 4242, license.LicenseId ); + Assert.Equal( "PostSharpUltimate", license.Product ); + + // LicenseType.Business and LicenseType.PerUser are two names of the same value, and PerUser is + // the one declared first, so it is the one the enumeration formats. + Assert.Equal( "PerUser", license.LicenseType ); + Assert.Equal( 25, license.UserNumber ); + Assert.Equal( new DateTime( 2030, 6, 30 ), license.ValidTo ); + Assert.Equal( new DateTime( 2029, 6, 30 ), license.SubscriptionEndDate ); + Assert.Equal( 7, license.GraceDays ); + Assert.Equal( 15, license.GracePercent ); + Assert.True( license.IsLicenseServerEligible ); + Assert.Equal( "Business License", license.LicenseTypeName ); + Assert.Equal( "PostSharp Ultimate", license.ProductName ); + } + + /// + /// The signature is what stops a customer from minting their own licenses, so a key signed by + /// anyone else must not be served. + /// + [Fact] + public void KeySignedByAnotherAuthority_IsRejected() + => Assert.Null( parser.TryParse( TestLicenseKeys.Builder().SignWithAnotherAuthority() ) ); + + [Fact] + public void UnsignedKeyOfATypeThatRequiresASignature_IsRejected() + => Assert.Null( parser.TryParse( TestLicenseKeys.Builder().Unsigned() ) ); + + /// + /// An evaluation key carries no signature by design, and the server may serve it. + /// + [Fact] + public void UnsignedKeyOfATypeThatRequiresNoSignature_IsParsed() + { + string key = TestLicenseKeys.Builder( licenseType: LicenseType.Evaluation ).Unsigned(); + + LicenseInfo? license = parser.TryParse( key ); + + Assert.NotNull( license ); + Assert.Equal( nameof(LicenseType.Evaluation), license.LicenseType ); + } + + [Theory] + [InlineData( "" )] + [InlineData( " " )] + [InlineData( "NOT-A-KEY" )] + [InlineData( "1-AAAAAAAA" )] + [InlineData( "no-hyphen-prefix" )] + public void MalformedKey_IsRejected( string key ) => Assert.Null( parser.TryParse( key ) ); + + /// + /// A key that carries no grace percentage is served with the percentage PostSharp applied, so a + /// license that was being served before the migration is served the same way after it. + /// + [Fact] + public void KeyWithoutAGracePercentage_GetsThirtyPercent() + { + LicenseKeyDataBuilder builder = TestLicenseKeys.Builder(); + builder.GracePercent = null; + + Assert.Equal( 30, parser.TryParse( builder.Sign() )!.GracePercent ); + } + + /// + /// A key that carries no grace period gets the thirty days that SharpCrafters.Backstage applies. + /// + /// + /// This is a change of behaviour. The PostSharp SDK returned zero days here, so such a license + /// denied a request as soon as its capacity was exceeded, while it now keeps serving for thirty + /// days beyond capacity. The default is applied inside LicenseKeyData.GraceDays, whose type + /// is not nullable, so the parser cannot tell an absent field from a field set to thirty and + /// cannot restore the old value. Keys issued with an explicit grace period are unaffected. + /// + [Fact] + public void KeyWithoutAGracePeriod_GetsThirtyDays() + { + LicenseKeyDataBuilder builder = TestLicenseKeys.Builder(); + + Assert.Equal( 30, parser.TryParse( builder.Sign() )!.GraceDays ); + } + + /// + /// A key that carries no minimal client version is not served to every client regardless: the + /// version is derived from the other fields. An eligible key is readable by PostSharp 5.0.22 and + /// later, which is the version that introduced the license server. + /// + [Fact] + public void MinPostSharpVersion_IsDerivedWhenTheKeyDoesNotDeclareIt() + { + LicenseInfo? license = parser.TryParse( TestLicenseKeys.Builder().Sign() ); + + Assert.NotNull( license ); + Assert.Equal( new Version( 5, 0, 22 ), license.MinPostSharpVersion ); + } + + /// + /// The product name stored in the database is the one of the Backstage enumeration, which is the + /// one a client of that generation asks for. covers the licenses + /// that a previous version of this server stored under the PostSharp spelling. + /// + [Fact] + public void Product_IsStoredUnderItsBackstageName() + { + LicenseInfo? license = parser.TryParse( + TestLicenseKeys.Builder( product: LicenseProduct.PostSharpFramework ).Sign() ); + + Assert.Equal( "PostSharpFramework", license!.Product ); + } + + /// + /// A key that says it may not be leased is not served, whatever else it carries. + /// + [Fact] + public void KeyThatIsNotEligibleForALicenseServer_SaysSo() + { + LicenseKeyDataBuilder builder = TestLicenseKeys.Builder(); + builder.LicenseServerEligible = false; + + Assert.False( parser.TryParse( builder.Sign() )!.IsLicenseServerEligible ); + } + + [Theory] + [InlineData( " 1-ABCDEF ", "1-ABCDEF" )] + [InlineData( "1-ABC DEF", "1-ABCDEF" )] + [InlineData( "1-ABC\r\n\tDEF", "1-ABCDEF" )] + public void CleanLicenseString_RemovesEveryKindOfWhitespace( string pasted, string expected ) + => Assert.Equal( expected, parser.CleanLicenseString( pasted ) ); + + /// + /// A key pasted with the whitespace an email adds still parses once it has been cleaned, which is + /// the sequence the Add License page performs. + /// + [Fact] + public void PastedKey_ParsesAfterCleaning() + { + string key = TestLicenseKeys.Builder().Sign(); + string pasted = key[..10] + " \r\n " + key[10..]; + + Assert.Null( parser.TryParse( pasted ) ); + Assert.NotNull( parser.TryParse( parser.CleanLicenseString( pasted ) ) ); + } +} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs index f80c8df..63339eb 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Fakes/FixedServerVersion.cs @@ -3,12 +3,12 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Fakes; /// -/// Reports a fixed PostSharp SDK version, so that the "the license server itself is too old" branch -/// can be reached from a test. +/// Reports a fixed version of the licensing library, so that the "the license server itself is too +/// old" branch can be reached from a test. /// public sealed class FixedServerVersion( Version version ) : ILicenseServerVersion { public FixedServerVersion() : this( new Version( 2025, 1, 5 ) ) { } - public Version SdkVersion { get; } = version; + public Version LicensingLibraryVersion { get; } = version; } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs new file mode 100644 index 0000000..c72ef42 --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs @@ -0,0 +1,98 @@ +using System.Security.Cryptography; +using SharpCrafters.Backstage.Licensing; +using SharpCrafters.Backstage.Licensing.Licenses; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +/// +/// Builds real license keys, so that the parser can be tested against the format it meets in +/// production. +/// +/// +/// +/// No license key signed by the production authority is present in this repository, and none can be: +/// it is public and MIT-licensed. The keys are therefore signed with a key pair generated here, and +/// the parser under test is given the public half of that pair. What this cannot cover is the +/// production authority itself, whose public keys are constants of SharpCrafters.Backstage and are +/// covered by the tests of that package. +/// +/// +/// The key pair is Elliptic Curve DSA on nistP256, which is the algorithm of every license key +/// issued since 2026 and the only one available on every platform. +/// +/// +public static class TestLicenseKeys +{ + /// + /// An identifier outside the range of the production keys and of the test keys of Backstage, so + /// that a key signed here can never be mistaken for one of theirs. + /// + private const byte authorityKeyId = 200; + + private static readonly ILicensingAuthorityProvider signingAuthority; + + /// + /// Gets the authority that verifies the keys this class signs, holding the public key only. A + /// parser under test is constructed with it. + /// + public static ILicensingAuthorityProvider Authority { get; } + + static TestLicenseKeys() + { + using ECDsa key = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); + ECParameters parameters = key.ExportParameters( true ); + + signingAuthority = new ExplicitLicensingAuthorityProvider( (authorityKeyId, ToXml( parameters, true )) ); + Authority = new ExplicitLicensingAuthorityProvider( (authorityKeyId, ToXml( parameters, false )) ); + } + + /// + /// Creates a builder for a license key that the license server accepts, which the caller modifies + /// before serializing it. + /// + public static LicenseKeyDataBuilder Builder( + int licenseId = 1, + LicenseType licenseType = LicenseType.Business, + LicenseProduct product = LicenseProduct.PostSharpUltimate ) + => new() + { + LicenseId = licenseId, + LicenseType = licenseType, + Product = product, + UserNumber = 5, + LicenseServerEligible = true + }; + + /// + /// Signs and serializes a license key with the test authority. + /// + public static string Sign( this LicenseKeyDataBuilder builder ) + => builder.SignAndSerialize( signingAuthority.GetAuthority( authorityKeyId ) ); + + /// + /// Serializes a license key without signing it. Only the types that require no signature parse + /// this way. + /// + public static string Unsigned( this LicenseKeyDataBuilder builder ) => builder.Serialize(); + + /// + /// Signs a license key with a second key pair, which no parser under test is given the authority + /// of. + /// + public static string SignWithAnotherAuthority( this LicenseKeyDataBuilder builder ) + { + using ECDsa other = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); + + var provider = new ExplicitLicensingAuthorityProvider( + (authorityKeyId, ToXml( other.ExportParameters( true ), true )) ); + + return builder.SignAndSerialize( provider.GetAuthority( authorityKeyId ) ); + } + + private static string ToXml( ECParameters parameters, bool includePrivateValue ) + => "nistP256" + + $"{Convert.ToBase64String( parameters.Q.X! )}" + + $"{Convert.ToBase64String( parameters.Q.Y! )}" + + (includePrivateValue ? $"{Convert.ToBase64String( parameters.D! )}" : string.Empty) + + ""; +} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs new file mode 100644 index 0000000..3c13a77 --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseSerializerTests.cs @@ -0,0 +1,55 @@ +using SharpCrafters.Backstage.LicenseServer.Licensing; + +namespace SharpCrafters.Backstage.LicenseServer.Tests; + +/// +/// The body of a lease response. Every deployed client parses it, so it is pinned against a literal +/// expected string rather than against a re-implementation of the same formatting. +/// +/// +/// The expected values are the ones the LicenseLease.Serialize of the PostSharp SDK produced, +/// so that a client that was talking to the previous version of this server sees no change. +/// +public sealed class LeaseSerializerTests +{ + private static readonly LeaseSerializer serializer = new(); + + private static readonly DateTime start = new( 2026, 1, 5, 9, 0, 0, DateTimeKind.Utc ); + + [Fact] + public void Serialize_ProducesTheExpectedBody() + => Assert.Equal( + "License: 1-ABCDEF" + + "; StartTime: 2026-01-05T09:00:00Z" + + "; EndTime: 2026-01-08T09:00:00Z" + + "; RenewTime: 2026-01-07T09:00:00Z", + serializer.Serialize( "1-ABCDEF", start, start.AddDays( 3 ), start.AddDays( 2 ) ) ); + + /// + /// A time read from a datetime column carries no kind. It is a UTC instant all the same, + /// and must not be shifted by the time zone the server happens to keep. + /// + [Fact] + public void Serialize_TreatsAnUntaggedTimeAsUtc() + { + DateTime unspecified = new( 2026, 1, 5, 9, 0, 0, DateTimeKind.Unspecified ); + + Assert.Equal( + serializer.Serialize( "1-ABCDEF", start, start, start ), + serializer.Serialize( "1-ABCDEF", unspecified, unspecified, unspecified ) ); + } + + /// + /// The client splits the body on ; and each part at its first :, so a key may not + /// contain either character. A license key is an identifier, a hyphen and Base32, so it never + /// does -- this pins the assumption rather than the behaviour. + /// + [Fact] + public void Serialize_ProducesFourPartsTheClientCanSplit() + { + string[] parts = serializer.Serialize( "1-ABCDEF", start, start, start ).Split( ';' ); + + Assert.Equal( 4, parts.Length ); + Assert.Equal( ["License", "StartTime", "EndTime", "RenewTime"], parts.Select( p => p[..p.IndexOf( ':' )].Trim() ) ); + } +} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs index f116c72..779bf3c 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LicenseValidationTests.cs @@ -64,7 +64,10 @@ public async Task LicenseNeedsANewerLicenseServer_SaysSo() var (lease, errors) = await RequestAsync( context, license ); Assert.Null( lease ); - Assert.Contains( "requires higher version of PostSharp on the License Server", errors[1], StringComparison.Ordinal ); + Assert.Contains( + "requires a higher version of the licensing library on the License Server", + errors[1], + StringComparison.Ordinal ); Assert.Contains( "2099.3.7", errors[1], StringComparison.Ordinal ); } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs new file mode 100644 index 0000000..111b684 --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/ProductCodesTests.cs @@ -0,0 +1,85 @@ +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Services; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.Licensing; + +namespace SharpCrafters.Backstage.LicenseServer.Tests; + +/// +/// The ProductCode column of an installation that has been upgraded holds the product names of +/// the PostSharp SDK, while the clients of the Backstage generation ask by the names of the Backstage +/// enumeration. A request that names a product has to find the licenses under either spelling. +/// +public sealed class ProductCodesTests +{ + [Theory] + [InlineData( LicenseProduct.PostSharpUltimate, "PostSharpUltimate", "Ultimate" )] + [InlineData( LicenseProduct.PostSharpFramework, "PostSharpFramework", "Framework" )] + [InlineData( LicenseProduct.PostSharpCachingLibrary, "PostSharpCachingLibrary", "CachingLibrary" )] + [InlineData( LicenseProduct.PostSharpDiagnosticsLibrary, "PostSharpDiagnosticsLibrary", "DiagnosticsLibrary" )] + [InlineData( LicenseProduct.PostSharpModelLibrary, "PostSharpModelLibrary", "ModelLibrary" )] + [InlineData( LicenseProduct.PostSharpThreadingLibrary, "PostSharpThreadingLibrary", "ThreadingLibrary" )] + public void RenamedProduct_IsMatchedUnderBothSpellings( LicenseProduct product, string current, string legacy ) + { + Assert.Equal( current, ProductCodes.ForStorage( product ) ); + Assert.Equal( [current, legacy], ProductCodes.Matching( current ) ); + Assert.Equal( [legacy, current], ProductCodes.Matching( legacy ) ); + } + + /// + /// The products that postdate the rename, and any value the server does not recognize, are matched + /// by themselves alone. + /// + [Theory] + [InlineData( "MetalamaProfessional" )] + [InlineData( "PostSharpEssentials" )] + [InlineData( "SomethingNobodyHasHeardOf" )] + public void ProductWithOneSpelling_IsMatchedByItself( string productCode ) + => Assert.Equal( [productCode], ProductCodes.Matching( productCode ) ); + + /// + /// A license added by a previous version of this server is served to a client that asks for the + /// same product by its Backstage name. Without the mapping the request would be denied although + /// the license is there, which is what an upgraded installation would have seen. + /// + [Fact] + public async Task LicenseStoredUnderTheLegacyName_IsServedToAClientAskingForTheBackstageName() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + LicenseBuilder.Default().WithProduct( "Ultimate" ).WithUsers( 5 ).AddTo( context ); + + GrantedLease? lease = await context.LeaseService.GetLicenseLeaseAsync( + "PostSharpUltimate", + new Version( 2025, 1, 0 ), + null, + "desktop-1", + "alice", + "alice", + TestClock.Origin, + [] ); + + Assert.NotNull( lease ); + } + + /// + /// The mapping widens what a request matches; it does not make every product match every other. + /// + [Fact] + public async Task LicenseOfAnotherProduct_IsStillNotServed() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + LicenseBuilder.Default().WithProduct( "Ultimate" ).WithUsers( 5 ).AddTo( context ); + + GrantedLease? lease = await context.LeaseService.GetLicenseLeaseAsync( + "PostSharpFramework", + new Version( 2025, 1, 0 ), + null, + "desktop-1", + "alice", + "alice", + TestClock.Origin, + [] ); + + Assert.Null( lease ); + } +} From d14c29edbaafd38d76dd7915f615df57c089a2aa Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 11:45:13 +0200 Subject: [PATCH 18/44] Make the container image carry the release archive The image used to build the sources itself, which no longer works: the licensing component comes from a private feed, and the build needs the global.json and nuget.config that `./Build.ps1 prepare` generates and that are not in source control. Neither belongs in a container a customer may build. The Dockerfile now has a single stage and copies artifacts/app, which is where the PackAndZip target leaves the contents of the release archive, so the container runs exactly what is released. The documentation follows, including the two differences an upgrade meets now that license keys are parsed by SharpCrafters.Backstage: the default grace period and the product name recorded in the ProductCode column. Co-Authored-By: Claude Opus 5 --- .dockerignore | 10 +++- Dockerfile | 34 +++++-------- README.md | 51 +++++++++++++++---- docs/docker.md | 28 ++++++---- ...rafters.Backstage.LicenseServer.Web.csproj | 10 ++-- 5 files changed, 86 insertions(+), 47 deletions(-) diff --git a/.dockerignore b/.dockerignore index bb61ea9..955d35b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,11 +3,17 @@ **/.vs .git .idea -artifacts -packages +.teamcity +eng +src tests docs +packages *.db *.db-shm *.db-wal App_Data + +# Everything the build produces, except the unpacked release archive, which is what the image runs. +artifacts/* +!artifacts/app diff --git a/Dockerfile b/Dockerfile index 369cd3d..e8c6349 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,16 @@ -# Builds the PostSharp License Server as a Linux container. +# Runs the SharpCrafters Backstage License Server as a Linux container. # -# The image runs the same application as the Windows release package; only the host differs. See -# docker-compose.yml for a ready-to-run deployment with a SQL Server database. - -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build -WORKDIR /src - -# Restore against the manifests alone, so that a change to the sources does not invalidate the -# restore layer. -COPY Directory.Build.props Directory.Packages.props nuget.config global.json ./ -COPY src/SharpCrafters.Backstage.LicenseServer.Core/SharpCrafters.Backstage.LicenseServer.Core.csproj src/SharpCrafters.Backstage.LicenseServer.Core/ -COPY src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj src/SharpCrafters.Backstage.LicenseServer.Web/ -RUN dotnet restore src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj - -COPY src/ src/ -RUN dotnet publish src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj \ - --configuration Release \ - --no-restore \ - --output /app +# The image carries the contents of the release archive, so it runs exactly what is released rather +# than a second build of the same sources. Build the product first: +# +# ./Build.ps1 build +# docker compose up +# +# See docker-compose.yml for a ready-to-run deployment with a SQL Server database. +# +# The build is not done inside the image on purpose. The licensing component comes from a private +# feed and the build needs the generated global.json and nuget.config that `./Build.ps1 prepare` +# writes, none of which belongs in a container that a customer may build. FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime WORKDIR /app @@ -28,7 +21,8 @@ RUN mkdir -p /app/App_Data && useradd --uid 64198 --create-home licenseserver \ && chown -R licenseserver /app USER licenseserver -COPY --from=build --chown=licenseserver /app ./ +# Written by the PackAndZip target of the web project; it is what the release archive contains. +COPY --chown=licenseserver artifacts/app/ ./ ENV ASPNETCORE_HTTP_PORTS=8080 EXPOSE 8080 diff --git a/README.md b/README.md index 3292885..262b52e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # SharpCrafters.Backstage.LicenseServer -This repository contains the source code and releases of PostSharp License Server. +This repository contains the source code and the releases of the license server of PostSharp and +Metalama. It serves the license keys of both product families: which products it serves is decided +by the license keys its administrator adds to it. The use of the license server is optional. Since all commercial licenses are floating ones, the license server can help teams knowing how many licenses they actually use. @@ -16,7 +18,7 @@ The license server itself is licensed under the *MIT License*. Note that PostSha ## Download -You can download the latest release from https://github.com/postsharp/SharpCrafters.Backstage.LicenseServer/releases/latest. +You can download the latest release from https://github.com/postsharp-ops/SharpCrafters.Backstage.LicenseServer/releases/latest. ## Documentation @@ -29,9 +31,13 @@ The quickest way to see the license server working, on any machine with Docker, deployment. It starts the server, a SQL Server database and a job that creates the schema: ``` -docker compose up --build +./Build.ps1 build +docker compose up ``` +The image carries the contents of the release archive, so the build comes first and the container +runs exactly what is released. + Then open http://localhost:8080 and add your license key. See [docs/docker.md](docs/docker.md) for what it contains and what to change before using it for anything other than a trial. @@ -48,7 +54,7 @@ anything other than a trial. 1. Install the ASP.NET Core Hosting Bundle on the web server, then restart IIS with `iisreset`. 2. Create the database and run `Database\CreateTables.sql` against it. -3. Unpack `SharpCrafters.Backstage.LicenseServer.zip` into the directory of an IIS application. +3. Unpack `SharpCrafters.Backstage.LicenseServer..zip` into the directory of an IIS application. 4. Edit `appsettings.json`: set the connection string, the notification e-mail addresses and the SMTP server. The settings are described in [docs/configuration.md](docs/configuration.md). 5. In IIS Manager, enable **Windows Authentication** on the application and disable @@ -91,6 +97,17 @@ so an existing database is used as it is, with no migration step. Four things do Previously they were written as if local time were UTC, which shifted them by the server's offset. Exports taken after the upgrade therefore differ from earlier ones by that offset. +License keys are now parsed by SharpCrafters.Backstage instead of the PostSharp SDK, which changes +two things in what a key is taken to mean. + +* **A license key that carries no grace period now gets thirty days rather than none.** Such a + license used to deny a request as soon as its capacity was exceeded; it now keeps serving for + thirty days beyond capacity, with the warning e-mail sent as usual. License keys issued with an + explicit grace period are unaffected, and the grace capacity is unchanged. +* **Products are recorded under a new name.** A license added from now on is stored as + `PostSharpUltimate` where it used to be stored as `Ultimate`. Existing rows are left alone and a + request naming either spelling finds both, so nothing has to be migrated. + The audit log is signed with a key kept in `App_Data\audit-signing.key`, generated on first start. Include it in your backups and preserve it across upgrades: losing it does not invalidate existing rows, but it does start a new signature chain. @@ -100,19 +117,32 @@ rows, but it does start a new signature chain. ### Requirements * The [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). +* PowerShell 7.4 or later. +* Access to the package feed that carries `SharpCrafters.Backstage`, which is where the licensing + component comes from. ### Instructions +The repository is built with [PostSharp.Engineering](https://github.com/postsharp/PostSharp.Engineering) +as the `Backstage.LicenseServer` product of the Backstage 2027.0 family. + ``` -dotnet test -.\eng\Package.ps1 +./Build.ps1 prepare +./Build.ps1 test ``` -The package is written to `artifacts\SharpCrafters.Backstage.LicenseServer.zip`. +`prepare` resolves the dependencies and writes `global.json`, `nuget.config` and the version files, +none of which is in source control. `build` and `test` do it themselves, so `prepare` is only needed +before opening the solution in an IDE or running `dotnet` directly. + +`./Build.ps1 build` writes the release archive to `artifacts\publish\private`, and leaves its +contents unpacked in `artifacts\app`, which is what the container image is made from. A public build +also copies the archive to `artifacts\publish\public`, which is what the deployment uploads. ### Running locally ``` +./Build.ps1 prepare dotnet run --project src\SharpCrafters.Backstage.LicenseServer.Web ``` @@ -127,10 +157,11 @@ page exists only in a development environment. | `src\SharpCrafters.Backstage.LicenseServer.Core` | The licensing rules, the database model and the services they depend on. | | `src\SharpCrafters.Backstage.LicenseServer.Web` | The web application: the pages, the endpoints and the composition root. | | `tests\SharpCrafters.Backstage.LicenseServer.Tests` | The test suite. Runs against an in-memory database, so it needs no SQL Server. | -| `tests\SharpCrafters.Backstage.LicenseServer.Simulator` | A manual load-testing tool. See the note below. | +| `eng` | The product definition and the version files that PostSharp.Engineering builds from. | -The simulator does not currently run: it needs a client that can download a lease, which is being -written in SharpCrafters.Backstage. It is kept building so that it is ready when that client is. +To put a server under load, use `LicenseServerLoadSimulator` in the SharpCrafters.Backstage +repository. It simulates an organization of many users on many machines, and it builds the same +request the product builds, so what it measures is what a real client costs. ## Support diff --git a/docs/docker.md b/docs/docker.md index af0bc2d..68a39b8 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -4,9 +4,13 @@ and a one-shot job that creates the schema from `Database/CreateTables.sql`. ``` -docker compose up --build +./Build.ps1 build +docker compose up ``` +The build comes first because the image carries the contents of the release archive rather than +building the sources again, so the container runs exactly what is released. + The server is then at http://localhost:8080 and the database at `localhost:1433`. Add a license key on the **Add a license** page and point a PostSharp client at `http://localhost:8080/Lease.ashx`. @@ -29,7 +33,7 @@ docker compose down --volumes |---|---| | `database` | SQL Server 2022 Developer Edition. Its health check runs a real query, because the server accepts connections well before it can answer one. | | `database-schema` | Runs once: creates the database if it does not exist, then runs `CreateTables.sql` if the tables are not already there. Re-running `docker compose up` does not fail on an existing schema. It reuses the SQL Server image, which already carries `sqlcmd`, rather than pulling a second one. | -| `licenseserver` | The application, built from `Dockerfile`. Waits for the schema job to finish. | +| `licenseserver` | The application, from `Dockerfile`. Waits for the schema job to finish. | The application keeps its audit signing key in the `licenseserver-data` volume, so the signature chain survives the container being replaced. The database keeps its files in `database-data`. @@ -57,12 +61,13 @@ it stands for anything else. The image does not need the compose file. Against an existing SQL Server: ``` -docker build -t postsharp-licenseserver . +./Build.ps1 build +docker build -t backstage-licenseserver . docker run --rm -p 8080:8080 \ -e ConnectionStrings__SharpCrafters_LicenseServerConnectionString="Server=db;Database=PostSharpLicenseServer;User Id=licenseserver;Password=...;TrustServerCertificate=True" \ -e Authentication__Scheme=None \ -v licenseserver-data:/app/App_Data \ - postsharp-licenseserver + backstage-licenseserver ``` Any setting from [configuration.md](configuration.md) can be given as an environment variable, with @@ -76,15 +81,20 @@ docker run --rm -p 8080:8080 \ -e LicenseServer__DatabaseProvider=Sqlite \ -e ConnectionStrings__SharpCrafters_LicenseServerConnectionString="DataSource=/app/App_Data/licenseserver.db" \ -v licenseserver-data:/app/App_Data \ - postsharp-licenseserver + backstage-licenseserver ``` SQL Server remains the supported engine for a real installation. ## The image -`Dockerfile` builds in two stages, publishing with the .NET SDK image and running on the ASP.NET -runtime image. It runs as a non-root user and listens on port 8080. +`Dockerfile` has a single stage on the ASP.NET runtime image. It copies `artifacts/app`, which is +what `./Build.ps1 build` unpacks the release archive into, so the image and the archive carry the +same files. It runs as a non-root user and listens on port 8080. + +The product is deliberately not built inside the image. The licensing component comes from a private +feed, and the build needs the `global.json` and `nuget.config` that `./Build.ps1 prepare` generates, +neither of which belongs in a container a customer may build. -It does not run the tests: run `dotnet test` before building, or use `eng/Package.ps1`, which runs -them for you. +`./Build.ps1 build` does not run the tests. Run `./Build.ps1 test` if the image is going anywhere +beyond your own machine. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj b/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj index 2f8d66d..cb09c5e 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/SharpCrafters.Backstage.LicenseServer.Web.csproj @@ -35,6 +35,10 @@ output; it copies the ones the product declares public into artifacts/publish/public. --> $(PackageOutputPath)$(AssemblyName).$(Version).zip + + $([MSBuild]::NormalizeDirectory('$(RepoDirectory)artifacts\app')) + @@ -44,12 +48,6 @@ - - - $([MSBuild]::NormalizeDirectory('$(OutputPath)Packed')) - - From 9f069f209fa58650cafd4d284812efddc1fcd868 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 11:45:55 +0200 Subject: [PATCH 19/44] Sign only the assemblies of this product in the release archive The archive carries the whole dependency tree of an ASP.NET Core application, and the sign service descends into a container and signs every executable it finds. Without a filter it would sign MailKit, the Azure libraries and the rest with our certificate, asserting authorship of code that is not ours. Also records that no default publisher matches the archive, so the upload to S3 still has to be added to the product definition. Co-Authored-By: Claude Opus 5 --- eng/src/Program.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/eng/src/Program.cs b/eng/src/Program.cs index ed43136..bf142cb 100644 --- a/eng/src/Program.cs +++ b/eng/src/Program.cs @@ -26,7 +26,17 @@ // The deliverable is the archive that an administrator unpacks into an IIS application or runs with // `dotnet SharpCrafters.Backstage.LicenseServer.dll`. The web project builds it; see the PackAndZip // target of SharpCrafters.Backstage.LicenseServer.Web.csproj. - PublicArtifacts = Pattern.Create( "SharpCrafters.Backstage.LicenseServer.$(PackageVersion).zip" ) + // + // Note that none of the default publishers matches it: they publish NuGet packages and Visual Studio + // extensions. A public build therefore produces the archive and uploads nothing, until the upload to + // S3 is added here. + PublicArtifacts = Pattern.Create( "SharpCrafters.Backstage.LicenseServer.$(PackageVersion).zip" ), + + // The archive carries the whole dependency tree of an ASP.NET Core application, and the sign service + // descends into a container and signs every executable it finds. Without this filter it would sign + // MailKit, the Azure libraries and the rest with our certificate, asserting authorship of code that + // is not ours. + SigningFilter = ["**/SharpCrafters.Backstage.LicenseServer*.dll"] }; return new EngineeringApp( product ).Run( args ); From 4bee8d7629ec43780fbc091d86947e4e073852bc Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 11:49:38 +0200 Subject: [PATCH 20/44] Sign the test license keys with the Backstage test authority The tests generated a key pair of their own, which verified the parser against a signature scheme rather than against the one the product uses. They now sign with the test licensing authority of SharpCrafters.Backstage, reached through TestLicenseKeyProvider in SharpCrafters.Backstage.Testing, and verify against the very authority object that signed. The ready-made keys that Backstage issues for its own tests parse here too, which is what shows the two agree on the whole of the format. That turned up a defect. An authority provider throws when it is asked for a key identifier it does not hold, so a license key whose signature named an authority nobody issued escaped TryParse as a KeyNotFoundException instead of being reported as invalid -- a 500 on the Add License page, for a value an administrator pastes into a web form. The parser now checks the identifier before verification. xunit moves to 2.9.3, which is the version SharpCrafters.Backstage.Testing requires. Co-Authored-By: Claude Opus 5 --- Directory.Packages.props | 6 +- README.md | 7 ++ .../Licensing/BackstageLicenseParser.cs | 17 ++- .../BackstageLicenseParserTests.cs | 32 +++++- .../Infrastructure/TestLicenseKeys.cs | 103 ++++++++++++------ ...fters.Backstage.LicenseServer.Tests.csproj | 4 + 6 files changed, 131 insertions(+), 38 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index cdc7f5d..5c3573d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,6 +20,10 @@ `./Build.ps1 prepare` into eng/Versions.g.props. eng/AutoUpdatedVersions.props holds the value used when nothing else resolves it. --> + + + @@ -35,7 +39,7 @@ - + diff --git a/README.md b/README.md index 262b52e..0037f45 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,13 @@ before opening the solution in an IDE or running `dotnet` directly. contents unpacked in `artifacts\app`, which is what the container image is made from. A public build also copies the archive to `artifacts\publish\public`, which is what the deployment uploads. +To build against a local checkout of the licensing component instead of the published packages, +point the dependency at it and build that repository first: + +``` +./Build.ps1 dependencies set local Backstage --path +``` + ### Running locally ``` diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs index 89469af..8090268 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs @@ -39,7 +39,22 @@ public sealed class BackstageLicenseParser( ILicensingAuthorityProvider? authori // The fields are checked before the signature, as the consumption path of a client does: a // key that carries a must-understand field this version does not know cannot be served, // whether or not the signature is valid. - if ( !data.ValidateFields( out _ ) || !data.TryVerifySignature( this.authorities, out _ ) ) + if ( !data.ValidateFields( out _ ) ) + { + return null; + } + + // Verification asks the authority provider for the key the signature was created with, and a + // provider throws when it holds no key of that identifier. A license key is pasted into a web + // form by an administrator, so one naming an identifier nobody ever issued has to be reported + // as an invalid key rather than escape as an unhandled exception. + if ( data.RequiresSignature() + && (data.SignatureKeyId == null || !this.authorities.KeyIds.Contains( data.SignatureKeyId.Value )) ) + { + return null; + } + + if ( !data.TryVerifySignature( this.authorities, out _ ) ) { return null; } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs index 5f651ae..859e4d8 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BackstageLicenseParserTests.cs @@ -44,12 +44,36 @@ public void SignedKey_IsParsedIntoTheFactsTheServerNeeds() } /// - /// The signature is what stops a customer from minting their own licenses, so a key signed by - /// anyone else must not be served. + /// A license key that SharpCrafters.Backstage issues for its own tests parses here too. This is + /// what shows that the server and the package agree on the whole of the format, and not only on + /// the parts a key built in this file happens to use. /// [Fact] - public void KeySignedByAnotherAuthority_IsRejected() - => Assert.Null( parser.TryParse( TestLicenseKeys.Builder().SignWithAnotherAuthority() ) ); + public void KeyIssuedByTheBackstageTestProvider_IsParsed() + { + LicenseInfo? license = parser.TryParse( TestLicenseKeys.Keys.PostSharpUltimate ); + + Assert.NotNull( license ); + Assert.Equal( "PostSharpUltimate", license.Product ); + Assert.True( license.IsLicenseServerEligible ); + } + + /// + /// The signature is what stops a customer from minting their own licenses, so a key signed with a + /// key the authority does not hold must not be served. + /// + [Fact] + public void KeySignedWithAForgedKey_IsRejected() + => Assert.Null( parser.TryParse( TestLicenseKeys.Builder().SignWithAForgedKey() ) ); + + /// + /// A key whose signature names an authority nobody issued is an invalid key, not a crash. The + /// provider throws when it is asked for a key it does not hold, and an administrator pastes + /// license keys into a web form. + /// + [Fact] + public void KeySignedByAnUnknownAuthority_IsRejectedWithoutThrowing() + => Assert.Null( parser.TryParse( TestLicenseKeys.Builder().SignWithAnUnknownAuthority() ) ); [Fact] public void UnsignedKeyOfATypeThatRequiresASignature_IsRejected() diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs index c72ef42..cad23db 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/TestLicenseKeys.cs @@ -1,6 +1,7 @@ using System.Security.Cryptography; using SharpCrafters.Backstage.Licensing; using SharpCrafters.Backstage.Licensing.Licenses; +using SharpCrafters.Backstage.Testing; namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; @@ -10,40 +11,49 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; /// /// /// -/// No license key signed by the production authority is present in this repository, and none can be: -/// it is public and MIT-licensed. The keys are therefore signed with a key pair generated here, and -/// the parser under test is given the public half of that pair. What this cannot cover is the -/// production authority itself, whose public keys are constants of SharpCrafters.Backstage and are -/// covered by the tests of that package. +/// The keys are signed by the test licensing authority of SharpCrafters.Backstage, reached through +/// . That authority generates its key pair in the current +/// process, so a license key signed here is valid in this process and nowhere else -- which is what +/// lets a public, MIT-licensed repository test the real signature path. What this cannot cover is +/// the production authority itself, whose public keys are constants of SharpCrafters.Backstage and +/// are covered by the tests of that package. /// /// -/// The key pair is Elliptic Curve DSA on nistP256, which is the algorithm of every license key -/// issued since 2026 and the only one available on every platform. +/// holds the same authority object that signs, so the tests verify against +/// exactly what signed them rather than against a reconstruction of it. /// /// public static class TestLicenseKeys { + private static readonly TestLicenseKeyProvider provider = new(); + /// - /// An identifier outside the range of the production keys and of the test keys of Backstage, so - /// that a key signed here can never be mistaken for one of theirs. + /// The identifier of the key of the test authority. It is a constant of SharpCrafters.Backstage + /// but an internal one, so it is read back from a license key that the authority has signed. /// - private const byte authorityKeyId = 200; - - private static readonly ILicensingAuthorityProvider signingAuthority; + private static readonly byte authorityKeyId; /// - /// Gets the authority that verifies the keys this class signs, holding the public key only. A - /// parser under test is constructed with it. + /// Gets the authority that verifies the keys this class signs, which is what a parser under test + /// is constructed with. /// public static ILicensingAuthorityProvider Authority { get; } + /// + /// Gets the ready-made license keys that SharpCrafters.Backstage issues for its own tests, one + /// per product and license type it sells. + /// + public static TestLicenseKeyProvider Keys => provider; + static TestLicenseKeys() { - using ECDsa key = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); - ECParameters parameters = key.ExportParameters( true ); + string probe = new LicenseKeyDataBuilder { LicenseId = 1, LicenseType = LicenseType.Business } + .SignAndSerialize( provider.Authority ); + + LicenseKeyData.TryDeserialize( probe, out LicenseKeyData? data, out _ ); + authorityKeyId = data!.SignatureKeyId!.Value; - signingAuthority = new ExplicitLicensingAuthorityProvider( (authorityKeyId, ToXml( parameters, true )) ); - Authority = new ExplicitLicensingAuthorityProvider( (authorityKeyId, ToXml( parameters, false )) ); + Authority = new TestAuthorityProvider( provider.Authority, authorityKeyId ); } /// @@ -67,7 +77,7 @@ public static LicenseKeyDataBuilder Builder( /// Signs and serializes a license key with the test authority. /// public static string Sign( this LicenseKeyDataBuilder builder ) - => builder.SignAndSerialize( signingAuthority.GetAuthority( authorityKeyId ) ); + => builder.SignAndSerialize( provider.Authority ); /// /// Serializes a license key without signing it. Only the types that require no signature parse @@ -76,23 +86,52 @@ public static string Sign( this LicenseKeyDataBuilder builder ) public static string Unsigned( this LicenseKeyDataBuilder builder ) => builder.Serialize(); /// - /// Signs a license key with a second key pair, which no parser under test is given the authority - /// of. + /// Signs a license key with a key of the test authority's identifier that the test authority does + /// not hold, which is a forgery: the parser looks the identifier up, finds the real key and the + /// signature does not verify against it. /// - public static string SignWithAnotherAuthority( this LicenseKeyDataBuilder builder ) + public static string SignWithAForgedKey( this LicenseKeyDataBuilder builder ) + => builder.SignAndSerialize( CreateStandaloneAuthority( authorityKeyId ) ); + + /// + /// Signs a license key with an authority the parser has never heard of, so that the identifier of + /// the signature matches no key it holds. + /// + /// + /// The identifier is outside the range of the production keys and of the test keys of Backstage. + /// + public static string SignWithAnUnknownAuthority( this LicenseKeyDataBuilder builder ) + => builder.SignAndSerialize( CreateStandaloneAuthority( 200 ) ); + + private static LicensingAuthority CreateStandaloneAuthority( byte keyId ) { - using ECDsa other = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); + using ECDsa key = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); + ECParameters parameters = key.ExportParameters( true ); - var provider = new ExplicitLicensingAuthorityProvider( - (authorityKeyId, ToXml( other.ExportParameters( true ), true )) ); + string xml = "nistP256" + + $"{Convert.ToBase64String( parameters.Q.X! )}" + + $"{Convert.ToBase64String( parameters.Q.Y! )}" + + $"{Convert.ToBase64String( parameters.D! )}" + + ""; - return builder.SignAndSerialize( provider.GetAuthority( authorityKeyId ) ); + return new ExplicitLicensingAuthorityProvider( (keyId, xml) ).GetAuthority( keyId ); } - private static string ToXml( ECParameters parameters, bool includePrivateValue ) - => "nistP256" - + $"{Convert.ToBase64String( parameters.Q.X! )}" - + $"{Convert.ToBase64String( parameters.Q.Y! )}" - + (includePrivateValue ? $"{Convert.ToBase64String( parameters.D! )}" : string.Empty) - + ""; + /// + /// Answers with the single authority that signed, for the identifier that authority's key carries. + /// + /// + /// cannot be used for this, because it builds an + /// authority from the XML representation of a key and the key of the test authority is generated + /// in the process rather than written down. + /// + private sealed class TestAuthorityProvider( LicensingAuthority authority, byte keyId ) : ILicensingAuthorityProvider + { + public IEnumerable KeyIds => [keyId]; + + public LicensingAuthority GetAuthority( byte id ) + => id == keyId + ? authority + : throw new KeyNotFoundException( $"There is no test licensing authority key of identifier {id}." ); + } } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj index 6e363cb..89f0473 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SharpCrafters.Backstage.LicenseServer.Tests.csproj @@ -8,6 +8,10 @@ + + + From 1c36bd0786e6cc09405c37eb1b71b1bcdf3d02a0 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 11:52:43 +0200 Subject: [PATCH 21/44] Link the signature key workaround to the issue it works around postsharp-ops/SharpCrafters.Backstage#2 Co-Authored-By: Claude Opus 5 --- .../Licensing/BackstageLicenseParser.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs index 8090268..00eea87 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/BackstageLicenseParser.cs @@ -47,7 +47,9 @@ public sealed class BackstageLicenseParser( ILicensingAuthorityProvider? authori // Verification asks the authority provider for the key the signature was created with, and a // provider throws when it holds no key of that identifier. A license key is pasted into a web // form by an administrator, so one naming an identifier nobody ever issued has to be reported - // as an invalid key rather than escape as an unhandled exception. + // as an invalid key rather than escape as an unhandled exception. This works around + // postsharp-ops/SharpCrafters.Backstage#2 and can go once TryVerifySignature returns false + // for an unknown identifier. if ( data.RequiresSignature() && (data.SignatureKeyId == null || !this.authorities.KeyIds.Contains( data.SignatureKeyId.Value )) ) { From b16f68cf3db1eccbf3319ef8d98c43cdb2c28d08 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 12:08:07 +0200 Subject: [PATCH 22/44] Let a development server accept a test licensing authority A load simulation needs a license key that the server serves, and every key the production authority signs is one that was sold. LicenseServer:TestLicensingAuthorities names the authorities a development server accepts besides that one, so a key pair generated for the occasion is enough to drive the server. The server refuses to start with the setting outside the Development environment rather than ignoring it: whoever holds the private half of such a pair can mint license keys that a server configured this way honours. It also refuses a key that takes one of the production identifiers, and builds each authority at startup so that a malformed key fails there rather than in the middle of a lease request. The key pair itself stays out of source control. App_Data is now ignored wherever it appears, not only at the repository root, so the generated audit signing key cannot be committed either. Co-Authored-By: Claude Opus 5 --- .claude/launch.json | 15 ++ .gitignore | 4 +- docs/configuration.md | 28 +++- .../CompositeLicensingAuthorityProvider.cs | 42 +++++ .../Options/LicenseServerOptions.cs | 8 + .../Options/TestLicensingAuthority.cs | 26 +++ .../LicensingRegistration.cs | 66 ++++++++ .../Program.cs | 12 +- .../TestLicensingAuthorityTests.cs | 154 ++++++++++++++++++ 9 files changed, 350 insertions(+), 5 deletions(-) create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CompositeLicensingAuthorityProvider.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Options/TestLicensingAuthority.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs diff --git a/.claude/launch.json b/.claude/launch.json index 0d6896a..7106e2a 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -7,6 +7,21 @@ "runtimeArgs": ["run", "--project", "src/SharpCrafters.Backstage.LicenseServer.Web"], "port": 44670, "url": "http://localhost:44670" + }, + { + "name": "SharpCrafters.Backstage.LicenseServer (accelerated)", + "runtimeExecutable": "dotnet", + "runtimeArgs": [ + "run", + "--project", + "src/SharpCrafters.Backstage.LicenseServer.Web", + "--", + "--LicenseServer:TimeAcceleration=1440", + "--LicenseServer:BuildServers=server", + "--Authentication:Scheme=None" + ], + "port": 44670, + "url": "http://localhost:44670" } ] } diff --git a/.gitignore b/.gitignore index 6475bbf..7d50f6b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,9 @@ obj *.db *.db-shm *.db-wal -/App_Data +# The audit signing key is generated on first start and is a secret. The pattern is not anchored, +# because the directory is created next to whichever project is being run. +App_Data/ # Generated by PostSharp.Engineering. `./Build.ps1 prepare` writes them from the product definition # and from the resolved dependencies, so they carry machine-local paths and must not be committed. diff --git a/docs/configuration.md b/docs/configuration.md index d9dde14..8da79dd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -124,6 +124,30 @@ rows already written, but it does start a new chain. | Setting | Default | Meaning | |---|---|---| | `LicenseServer:TimeAcceleration` | 1 | How much faster than real time the server's clock runs. | +| `LicenseServer:TestLicensingAuthorities` | empty | Licensing authorities whose license keys a development server accepts besides the production one. | + +Leave `TimeAcceleration` at 1. Any other value exists so that a multi-day licensing scenario can be +replayed in minutes against a test server, and the server warns at startup when it is set. + +`TestLicensingAuthorities` exists so that a load simulation can be run against license keys that +nobody sells. Each entry carries the identifier that the signature of a license key names and the +public half of the key pair, in the XML representation that SharpCrafters.Backstage reads: + +```json +{ + "LicenseServer": { + "TestLicensingAuthorities": [ + { + "KeyId": 200, + "PublicKey": "nistP256" + } + ] + } +} +``` -Leave this at 1. Any other value exists so that a multi-day licensing scenario can be replayed in -minutes against a test server, and the server warns at startup when it is set. +The identifier must differ from the identifiers of the production keys, which are 0, 1 and 2, and +the server refuses to start on a duplicate. It also refuses to start with this setting outside the +Development environment: whoever holds the private half of the pair can mint license keys that a +server configured this way honours. Keep the private half out of source control, and configure the +public half through user secrets rather than through `appsettings.json`. diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CompositeLicensingAuthorityProvider.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CompositeLicensingAuthorityProvider.cs new file mode 100644 index 0000000..c8a55e8 --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/CompositeLicensingAuthorityProvider.cs @@ -0,0 +1,42 @@ +using SharpCrafters.Backstage.Licensing.Licenses; + +namespace SharpCrafters.Backstage.LicenseServer.Licensing; + +/// +/// Accepts the signature of a license key issued by any one of several licensing authorities. +/// +/// +/// The identifiers of the keys have to be distinct across the providers, because the identifier +/// carried by a signature is what selects the key that verifies it. The constructor rejects a +/// duplicate rather than letting the first provider win, since which one that is would depend on the +/// order the providers were passed in. +/// +public sealed class CompositeLicensingAuthorityProvider : ILicensingAuthorityProvider +{ + private readonly Dictionary providers = []; + + public CompositeLicensingAuthorityProvider( params ILicensingAuthorityProvider[] providers ) + { + ArgumentNullException.ThrowIfNull( providers ); + + foreach ( ILicensingAuthorityProvider provider in providers ) + { + foreach ( byte keyId in provider.KeyIds ) + { + if ( !this.providers.TryAdd( keyId, provider ) ) + { + throw new ArgumentException( + $"Two licensing authorities declare the key of identifier {keyId}.", + nameof(providers) ); + } + } + } + } + + public IEnumerable KeyIds => this.providers.Keys; + + public LicensingAuthority GetAuthority( byte keyId ) + => this.providers.TryGetValue( keyId, out ILicensingAuthorityProvider? provider ) + ? provider.GetAuthority( keyId ) + : throw new KeyNotFoundException( $"There is no licensing authority key of identifier {keyId}." ); +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs index 675e8cd..7fa6491 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs @@ -98,5 +98,13 @@ public sealed class LicenseServerOptions /// public LeaseLockMode LeaseLockMode { get; set; } = LeaseLockMode.InProcess; + /// + /// Gets or sets the licensing authorities whose license keys this server accepts besides the + /// production one. It exists so that a load simulation can be run against license keys that + /// nobody sells, and the server refuses to start with a value here outside the Development + /// environment. + /// + public TestLicensingAuthority[] TestLicensingAuthorities { get; set; } = []; + public TimeSpan MutexTimeoutSpan => TimeSpan.FromSeconds( this.MutexTimeout ); } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/TestLicensingAuthority.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/TestLicensingAuthority.cs new file mode 100644 index 0000000..478aad5 --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/TestLicensingAuthority.cs @@ -0,0 +1,26 @@ +namespace SharpCrafters.Backstage.LicenseServer.Options; + +/// +/// One licensing authority that a development server accepts license keys from, besides the +/// production one. +/// +/// +/// Only the public half of the key pair goes here: it verifies a signature and cannot create one. +/// Whoever holds the private half can nevertheless mint license keys that a server configured this +/// way accepts, which is why the setting is refused outside the Development environment. +/// +public sealed class TestLicensingAuthority +{ + /// + /// Gets or sets the identifier the signature of a license key carries. It has to differ from the + /// identifiers of the production keys, which are 0, 1 and 2. + /// + public byte KeyId { get; set; } + + /// + /// Gets or sets the public key, in the XML representation that SharpCrafters.Backstage reads: + /// an ECDSAKeyValue element for an Elliptic Curve DSA key, or a DSAKeyValue element + /// for a finite field DSA one. + /// + public string PublicKey { get; set; } = ""; +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs new file mode 100644 index 0000000..106dab8 --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs @@ -0,0 +1,66 @@ +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Options; +using SharpCrafters.Backstage.Licensing.Licenses; + +namespace SharpCrafters.Backstage.LicenseServer; + +/// +/// Registers the component that parses and validates license keys, and decides which licensing +/// authorities it accepts them from. +/// +public static class LicensingRegistration +{ + /// + /// Registers the license key parser. + /// + /// + /// The identifiers of the test licensing authorities that were added to the production one, for + /// logging. Empty on a server configured the way a customer runs it. + /// + /// + /// A test licensing authority is configured outside the Development environment. + /// + public static IReadOnlyList AddLicenseServerLicensing( + this IServiceCollection services, + IConfiguration configuration, + IHostEnvironment environment ) + { + TestLicensingAuthority[] testAuthorities = configuration + .GetSection( $"{LicenseServerOptions.SectionName}:TestLicensingAuthorities" ) + .Get() ?? []; + + if ( testAuthorities.Length > 0 && !environment.IsDevelopment() ) + { + // Refusing to start is deliberate. Ignoring the setting would leave an administrator + // believing the server accepts those license keys, and accepting it would let whoever + // holds the private key mint licenses this server honours. + throw new InvalidOperationException( + $"{LicenseServerOptions.SectionName}:TestLicensingAuthorities is set, but the environment is " + + $"'{environment.EnvironmentName}' and not 'Development'. A server that is not a development " + + "server accepts license keys from the production licensing authority only. Remove the setting." ); + } + + ILicensingAuthorityProvider authorities = new ProductionLicensingAuthorityProvider(); + + if ( testAuthorities.Length > 0 ) + { + var explicitAuthorities = new ExplicitLicensingAuthorityProvider( + testAuthorities.Select( a => ((int) a.KeyId, a.PublicKey) ).ToArray() ); + + // The key is parsed when it is first used, which would otherwise be in the middle of a + // lease request. Asking for each authority here turns a malformed key into a startup + // failure. + foreach ( TestLicensingAuthority authority in testAuthorities ) + { + explicitAuthorities.GetAuthority( authority.KeyId ); + } + + authorities = new CompositeLicensingAuthorityProvider( authorities, explicitAuthorities ); + } + + services.AddSingleton( + _ => new CachingLicenseParser( new BackstageLicenseParser( authorities ) ) ); + + return testAuthorities.Select( a => a.KeyId ).ToArray(); + } +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs index 19f439c..1526c24 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs @@ -32,8 +32,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddSingleton( - _ => new CachingLicenseParser( new BackstageLicenseParser() ) ); +IReadOnlyList testLicensingAuthorities = + builder.Services.AddLicenseServerLicensing( builder.Configuration, builder.Environment ); builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -191,6 +191,14 @@ "Authentication:Scheme" ); } +if ( testLicensingAuthorities.Count > 0 ) +{ + app.Logger.LogWarning( + "This server accepts license keys signed by the test licensing authorities {KeyIds} besides the " + + "production one. This is a test configuration and must not be used in production.", + string.Join( ", ", testLicensingAuthorities ) ); +} + app.Run(); /// diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs new file mode 100644 index 0000000..08b3059 --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/TestLicensingAuthorityTests.cs @@ -0,0 +1,154 @@ +using System.Security.Cryptography; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; +using SharpCrafters.Backstage.Licensing.Licenses; + +namespace SharpCrafters.Backstage.LicenseServer.Tests; + +/// +/// The licensing authorities a development server accepts license keys from besides the production +/// one, which is what lets a load simulation run against license keys that nobody sells. +/// +public sealed class TestLicensingAuthorityTests +{ + private const byte keyId = 200; + + /// + /// A license key signed by a configured authority is parsed, and the production authority still + /// works alongside it. + /// + [Fact] + public void ConfiguredAuthority_InDevelopment_IsAccepted() + { + (string publicKey, string licenseKey) = CreateAuthorityAndKey(); + + ILicenseParser parser = BuildParser( "Development", (keyId, publicKey) ); + + Assert.NotNull( parser.TryParse( licenseKey ) ); + } + + /// + /// A license key signed by an authority that is not configured is rejected, so the setting widens + /// what the server accepts by exactly the keys it names. + /// + [Fact] + public void UnconfiguredAuthority_InDevelopment_IsRejected() + { + (string publicKey, _) = CreateAuthorityAndKey(); + (_, string otherLicenseKey) = CreateAuthorityAndKey(); + + ILicenseParser parser = BuildParser( "Development", (keyId, publicKey) ); + + Assert.Null( parser.TryParse( otherLicenseKey ) ); + } + + /// + /// Outside Development the server refuses to start rather than ignoring the setting. An + /// administrator who set it is told, and nobody holding the private key gets a server that + /// honours what they sign. + /// + [Theory] + [InlineData( "Production" )] + [InlineData( "Staging" )] + [InlineData( "Testing" )] + public void ConfiguredAuthority_OutsideDevelopment_RefusesToStart( string environmentName ) + { + (string publicKey, _) = CreateAuthorityAndKey(); + + InvalidOperationException exception = Assert.Throws( + () => BuildParser( environmentName, (keyId, publicKey) ) ); + + Assert.Contains( "TestLicensingAuthorities", exception.Message, StringComparison.Ordinal ); + Assert.Contains( environmentName, exception.Message, StringComparison.Ordinal ); + } + + /// + /// A server that configures none of these starts as it always did, and accepts the production + /// authority alone. + /// + [Fact] + public void NoConfiguredAuthority_OutsideDevelopment_Starts() + { + (_, string licenseKey) = CreateAuthorityAndKey(); + + ILicenseParser parser = BuildParser( "Production" ); + + Assert.Null( parser.TryParse( licenseKey ) ); + } + + /// + /// A key whose identifier is one of the production ones is refused, because the identifier is what + /// selects the key that verifies a signature and the production authority must keep its own. + /// + [Fact] + public void ConfiguredAuthority_TakingAProductionKeyIdentifier_IsRefused() + { + (string publicKey, _) = CreateAuthorityAndKey(); + + Assert.Throws( () => BuildParser( "Development", (2, publicKey) ) ); + } + + /// + /// A malformed key fails at startup rather than in the middle of a lease request, which is when + /// the authority would otherwise first be built. + /// + [Fact] + public void ConfiguredAuthority_WithAMalformedKey_FailsAtStartup() + => Assert.ThrowsAny( () => BuildParser( "Development", (keyId, "not-a-key") ) ); + + private static ILicenseParser BuildParser( string environmentName, params (int KeyId, string PublicKey)[] authorities ) + { + Dictionary settings = []; + + for ( int i = 0; i < authorities.Length; i++ ) + { + settings[$"LicenseServer:TestLicensingAuthorities:{i}:KeyId"] = authorities[i].KeyId.ToString(); + settings[$"LicenseServer:TestLicensingAuthorities:{i}:PublicKey"] = authorities[i].PublicKey; + } + + IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection( settings ).Build(); + + ServiceCollection services = []; + services.AddLicenseServerLicensing( configuration, new StubEnvironment( environmentName ) ); + + return services.BuildServiceProvider().GetRequiredService(); + } + + /// + /// Creates a licensing authority and a license key that it signs. + /// + private static (string PublicKey, string LicenseKey) CreateAuthorityAndKey() + { + using ECDsa key = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); + ECParameters parameters = key.ExportParameters( true ); + + string ToXml( bool includePrivateValue ) + => "nistP256" + + $"{Convert.ToBase64String( parameters.Q.X! )}" + + $"{Convert.ToBase64String( parameters.Q.Y! )}" + + (includePrivateValue ? $"{Convert.ToBase64String( parameters.D! )}" : "") + + ""; + + LicensingAuthority authority = + new ExplicitLicensingAuthorityProvider( (keyId, ToXml( true )) ).GetAuthority( keyId ); + + string licenseKey = TestLicenseKeys.Builder().SignAndSerialize( authority ); + + return (ToXml( false ), licenseKey); + } + + private sealed class StubEnvironment( string environmentName ) : IHostEnvironment + { + public string EnvironmentName { get; set; } = environmentName; + + public string ApplicationName { get; set; } = "Tests"; + + public string ContentRootPath { get; set; } = AppContext.BaseDirectory; + + public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = + new Microsoft.Extensions.FileProviders.NullFileProvider(); + } +} From 399389457a988f99f47d326d71474956b85cd769 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 12:08:13 +0200 Subject: [PATCH 23/44] Fix the two failures an accelerated simulation found Neither showed up in the test suite, and both fail on a real server. The audit log export answered with a truncated response and HTTP 500 for any range that held a lease. Lease.Write writes the fields one by one, so the StreamWriter flushed synchronously once its buffer filled, and Kestrel refuses a synchronous write to a response body. The line is now built in memory and written with WriteLineAsync; it is the whole log, not one line of it, that must not be assembled in memory. The usage page answered with HTTP 500 when a user held two open leases on one machine. The timeline kept a list of machines per user, skipped an opening point for a machine already in it, and removed on every closing point, so the second close found nothing and threw. It now counts the open leases per machine, so the closes balance the opens. A lease that ends no later than it starts is dropped before the points are built, because its closing point sorts before its opening one. The tests that missed them are the more interesting part. - Every earlier export test ran against an empty database, where the endpoint returns an empty body without streaming anything. Adding leases is not enough either: three lines fit in the writer's buffer and never flush. The new test writes sixty, and runs against a response body that refuses a synchronous write, because TestServer accepts one whatever its AllowSynchronousIO property says. - LeaseCountingPointsTests recorded two open leases on one machine as data the timeline was entitled to refuse, on the assumption that the lease service never produces it. It does: a server whose clock moves backwards grants a second lease while the first is still open, and restarting a server with TimeAcceleration set is enough. That test is replaced by ones that count the seats. Co-Authored-By: Claude Opus 5 --- .../Data/LeaseRepository.cs | 42 ++++-- .../Endpoints/LicenseServerEndpoints.cs | 7 +- .../AuditLogExportTests.cs | 133 ++++++++++++++++++ .../Infrastructure/AsyncOnlyResponseBody.cs | 97 +++++++++++++ .../LicenseServerApplication.cs | 8 ++ .../LeaseCountingPointsTests.cs | 66 +++++++-- 6 files changed, 328 insertions(+), 25 deletions(-) create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs index 7ca50e1..9dd33b7 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs @@ -222,6 +222,12 @@ public IEnumerable GetLeaseCountingPoints( .AsNoTracking() .ToList(); + // A lease cancelled in the instant it was granted spans no time and belongs on no timeline. + // It has to go before the points are built rather than be tolerated afterwards: Close sorts + // before Open at the same instant, so its closing point would be processed first and its + // opening point would then raise the count for the rest of the window. + leases.RemoveAll( l => l.EndTime <= l.StartTime ); + // Ordering in memory gives a stable sort, so the timeline is reproducible. Close sorts // before Open at the same instant; see LeaseCountingPointKind. List allRecords = leases @@ -234,43 +240,49 @@ public IEnumerable GetLeaseCountingPoints( .ThenBy( p => p.Lease.LeaseId ) .ToList(); - Dictionary> currentUsers = new( StringComparer.OrdinalIgnoreCase ); + // How many open leases each user holds on each machine. The number of distinct machines is + // what a seat is counted from, so a user holding two leases on one machine occupies the same + // seat as a user holding one. Counting the leases rather than listing the machines is what + // makes the closing points balance the opening ones whatever the data: the list form removed + // the machine on the first close and then found nothing to remove on the second. + Dictionary> currentUsers = new( StringComparer.OrdinalIgnoreCase ); int leaseCount = 0; foreach ( LeaseCountingPoint record in allRecords ) { - if ( !currentUsers.TryGetValue( record.Lease.UserName, out List? machines ) ) + if ( !currentUsers.TryGetValue( record.Lease.UserName, out Dictionary? machines ) ) { - machines = []; + machines = new Dictionary( StringComparer.OrdinalIgnoreCase ); currentUsers.Add( record.Lease.UserName, machines ); } int seatsBefore = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); + string machine = record.Lease.Machine; if ( record.Kind == LeaseCountingPointKind.Open ) { - if ( !machines.Contains( record.Lease.Machine, StringComparer.OrdinalIgnoreCase ) ) - { - machines.Add( record.Lease.Machine ); - } + machines[machine] = machines.GetValueOrDefault( machine ) + 1; } - else + else if ( machines.TryGetValue( machine, out int openLeases ) ) { - string machine = record.Lease.Machine; - int index = machines.FindIndex( s => string.Equals( s, machine, StringComparison.OrdinalIgnoreCase ) ); - - if ( index >= 0 ) + // The machine leaves the list when its last lease closes. + if ( openLeases > 1 ) { - machines.RemoveAt( index ); + machines[machine] = openLeases - 1; } else { - throw new InvalidOperationException( - $"Closing lease #{record.Lease.LeaseId} for machine {machine}, which is not open." ); + machines.Remove( machine ); } } + // A closing point with nothing to close is left alone. Both points are produced for every + // lease that remains, and an opening point now always sorts before its own closing point, + // so nothing reaches it; it is written this way rather than as a throw because the page + // that draws the timeline is a report, and an administrator looking at usage should not + // be answered with an error. + int seatsAfter = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); leaseCount += seatsAfter - seatsBefore; diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs index bb64511..1ca5167 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Endpoints/LicenseServerEndpoints.cs @@ -229,8 +229,11 @@ private static async Task ExportAsync( await foreach ( Lease lease in leases.WithCancellation( cancellationToken ) ) { - lease.Write( writer, true ); - await writer.WriteLineAsync(); + // The line is built in memory and written asynchronously. Writing the fields + // straight to the writer makes it flush synchronously when its buffer fills, and + // Kestrel refuses a synchronous write to a response body. One line is a hundred + // bytes; it is the whole log that must not be assembled in memory. + await writer.WriteLineAsync( lease.ToAuditLine( true ) ); } }, "text/plain" ); diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs new file mode 100644 index 0000000..4fc243a --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/AuditLogExportTests.cs @@ -0,0 +1,133 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc.Testing; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +namespace SharpCrafters.Backstage.LicenseServer.Tests; + +/// +/// The audit log export, which streams the rows to the response as they arrive rather than building +/// the whole file in memory. +/// +public sealed class AuditLogExportTests : IDisposable +{ + /// + /// Enough leases that the writer of the response has to flush before the last one is written. A + /// buffers about a thousand characters and an audit line is about + /// ninety, so a handful of leases are written entirely from the buffer and exercise nothing. The + /// export was returning a truncated response in production while passing a test that wrote three + /// lines. + /// + private const int leaseCount = 60; + + private readonly LicenseServerApplication application = new(); + + public void Dispose() => this.application.Dispose(); + + [Fact] + public async Task Export_WithMoreLeasesThanTheWriterBuffers_StreamsThemAll() + { + License license = this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + this.SeedLeases( license, leaseCount ); + + HttpClient client = this.application.CreateClient(); + + // The response body refuses a synchronous write, exactly as Kestrel does. TestServer accepts + // one whatever its AllowSynchronousIO property says, so without this guard an endpoint that + // writes synchronously passes here and fails against a real server. + this.application.ResponseBody.IsEnabled = true; + + HttpResponseMessage response = await client.GetAsync( this.ExportUrl( 1, 12 ) ); + + Assert.Equal( HttpStatusCode.OK, response.StatusCode ); + + string[] lines = (await response.Content.ReadAsStringAsync()) + .Split( '\n', StringSplitOptions.RemoveEmptyEntries ); + + Assert.Equal( leaseCount, lines.Length ); + + foreach ( string line in lines ) + { + // The identifier, the overwritten lease, the license, the two instants, the two hashed + // names and the signature. + string[] fields = line.TrimEnd( '\r' ).Split( ';' ); + + Assert.Equal( 8, fields.Length ); + Assert.NotEmpty( fields[7] ); + } + + Assert.DoesNotContain( "alice", string.Join( "", lines ), StringComparison.OrdinalIgnoreCase ); + } + + /// + /// The download is offered as a file, named after the range that was asked for. + /// + [Fact] + public async Task Export_WithLeases_IsOfferedAsAFile() + { + License license = this.application.AddLicense( LicenseBuilder.Default().WithUsers( 5 ) ); + this.SeedLeases( license, 1 ); + + HttpClient client = this.application.CreateClient(); + this.application.ResponseBody.IsEnabled = true; + + HttpResponseMessage response = await client.GetAsync( this.ExportUrl( 3, 4 ) ); + + Assert.Equal( HttpStatusCode.OK, response.StatusCode ); + + Assert.Equal( + $"attachment; filename=PostSharp_LicenseLog_{DateTime.UtcNow.Year}-3_{DateTime.UtcNow.Year}-4.txt", + response.Content.Headers.ContentDisposition?.ToString() ); + } + + /// + /// An empty range is served as an empty body. This is the path every earlier export test took, + /// because the database they exported held no lease at all. + /// + [Fact] + public async Task Export_WithNoLease_IsEmpty() + { + HttpClient client = this.application.CreateClient(); + this.application.ResponseBody.IsEnabled = true; + + HttpResponseMessage response = await client.GetAsync( this.ExportUrl( 1, 12 ) ); + + Assert.Equal( HttpStatusCode.OK, response.StatusCode ); + Assert.Equal( "", await response.Content.ReadAsStringAsync() ); + } + + /// + /// The leases are written straight to the database rather than requested, so that their number + /// does not depend on the capacity of the license or on the rules of the allocator. + /// + private void SeedLeases( License license, int count ) + { + using LicenseServerDbContext db = this.application.CreateDbContext(); + + DateTime start = DateTime.UtcNow.Date.AddDays( -1 ); + + for ( int i = 0; i < count; i++ ) + { + db.Leases.Add( + new Lease + { + LicenseId = license.LicenseId, + StartTime = start, + EndTime = start.AddDays( 3 ), + UserName = $"alice.{i:D3}", + Machine = $"desktop-{i:D3}", + AuthenticatedUser = "DOMAIN\\tester", + HMAC = $"SIGNATURE-{i:D3}" + } ); + } + + db.SaveChanges(); + } + + private string ExportUrl( int fromMonth, int toMonth ) + { + int year = DateTime.UtcNow.Year; + + return $"/Admin/Export.ashx?fy={year}&fm={fromMonth}&ty={year}&tm={toMonth}"; + } +} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs new file mode 100644 index 0000000..c05af1d --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/AsyncOnlyResponseBody.cs @@ -0,0 +1,97 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; + +namespace SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +/// +/// Makes the response body of the test host refuse a synchronous write, as Kestrel does. +/// +/// +/// accepts a synchronous write whatever its +/// AllowSynchronousIO property says, so an endpoint that writes synchronously passes every +/// test and then fails against a real server with "Synchronous operations are disallowed". This +/// filter closes that gap by wrapping the body in a stream that throws the same way. +/// +public sealed class AsyncOnlyResponseBody : IStartupFilter +{ + /// + /// Gets or sets a value indicating whether the guard is active. It is off by default, so that a + /// test that does not care about the response body is unaffected. + /// + public bool IsEnabled { get; set; } + + public Action Configure( Action next ) + => builder => + { + builder.Use( + async ( context, nextMiddleware ) => + { + if ( !this.IsEnabled ) + { + await nextMiddleware( context ); + + return; + } + + Stream original = context.Response.Body; + context.Response.Body = new AsyncOnlyStream( original ); + + try + { + await nextMiddleware( context ); + } + finally + { + context.Response.Body = original; + } + } ); + + next( builder ); + }; + + /// + /// Forwards every asynchronous write and throws on every synchronous one, with the message + /// Kestrel uses. + /// + private sealed class AsyncOnlyStream( Stream inner ) : Stream + { + private const string message = + "Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead."; + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => inner.CanWrite; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override void Write( byte[] buffer, int offset, int count ) => throw new InvalidOperationException( message ); + + public override void Write( ReadOnlySpan buffer ) => throw new InvalidOperationException( message ); + + public override void WriteByte( byte value ) => throw new InvalidOperationException( message ); + + public override void Flush() => throw new InvalidOperationException( message ); + + public override Task WriteAsync( byte[] buffer, int offset, int count, CancellationToken cancellationToken ) + => inner.WriteAsync( buffer, offset, count, cancellationToken ); + + public override ValueTask WriteAsync( ReadOnlyMemory buffer, CancellationToken cancellationToken = default ) + => inner.WriteAsync( buffer, cancellationToken ); + + public override Task FlushAsync( CancellationToken cancellationToken ) => inner.FlushAsync( cancellationToken ); + + public override int Read( byte[] buffer, int offset, int count ) => throw new NotSupportedException(); + + public override long Seek( long offset, SeekOrigin origin ) => throw new NotSupportedException(); + + public override void SetLength( long value ) => throw new NotSupportedException(); + } +} diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs index d684d54..dd17535 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/Infrastructure/LicenseServerApplication.cs @@ -56,6 +56,12 @@ public LicenseServerApplication() /// public ILeaseLock LeaseLock { get; set; } = new InProcessLeaseLock(); + /// + /// Gets the guard that makes the response body refuse a synchronous write, which a test writing + /// to the response body enables. + /// + public AsyncOnlyResponseBody ResponseBody { get; } = new(); + protected override void ConfigureWebHost( IWebHostBuilder builder ) { builder.UseEnvironment( "Testing" ); @@ -110,6 +116,8 @@ protected override void ConfigureWebHost( IWebHostBuilder builder ) options.DefaultChallengeScheme = TestAuthenticationHandler.SchemeName; } ); + services.AddSingleton( this.ResponseBody ); + services.AddLogging( logging => logging.SetMinimumLevel( LogLevel.Warning ) ); } ); } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs index 4199c3a..a8e358b 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs @@ -161,13 +161,19 @@ public async Task GetLeaseCountingPoints_ExcludesLeasesOutsideTheWindow() } /// - /// The timeline assumes a user never holds two open leases on the same machine at once, which - /// is an invariant the lease service maintains by reusing or prolonging a lease instead of - /// granting a second one. Data that breaks it is reported rather than silently miscounted, - /// because an under-count in a licensing audit is worse than a failure. + /// Two open leases held by one user on one machine occupy one seat, and the timeline still + /// returns to zero once both have ended. /// + /// + /// The lease service normally prevents this by reusing or prolonging a lease instead of granting + /// a second one, and an earlier version of this test recorded the situation as data the timeline + /// was entitled to refuse. It is not: a server whose clock moves backwards -- a restart with + /// TimeAcceleration set, a correction from a time server, a restored snapshot -- grants a + /// second lease while the first is still open, and a load simulation produced exactly that within + /// minutes. The usage page answered with HTTP 500 for as long as the older lease ran. + /// [Fact] - public async Task GetLeaseCountingPoints_OverlappingLeasesOnOneMachine_AreReported() + public async Task GetLeaseCountingPoints_TwoOpenLeasesOnOneMachine_CountAsOneSeat() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); @@ -178,10 +184,54 @@ public async Task GetLeaseCountingPoints_OverlappingLeasesOnOneMachine_AreReport LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) .From( TestClock.Days( 1 ) ).Lasting( 3 ).AddTo( context ); - InvalidOperationException exception = - Assert.Throws( () => Timeline( context, license ) ); + List points = Timeline( context, license ); + + Assert.Equal( 4, points.Count ); + Assert.Equal( 1, points.Max( p => p.LeaseCount ) ); + Assert.Equal( 0, points[^1].LeaseCount ); + } + + /// + /// The same user on two machines still occupies one seat at two machines per user, which is what + /// shows that the counting of duplicates did not turn into a count of leases. + /// + [Fact] + public async Task GetLeaseCountingPoints_TwoOpenLeasesOnTwoMachines_CountAsOneSeat() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) + .From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + + LeaseBuilder.For( license ).User( "alice" ).Machine( "notebook-1" ) + .From( TestClock.Days( 1 ) ).Lasting( 3 ).AddTo( context ); + + List points = Timeline( context, license ); + + Assert.Equal( 1, points.Max( p => p.LeaseCount ) ); + Assert.Equal( 0, points[^1].LeaseCount ); + } + + /// + /// A third machine takes a second seat, at two machines per user. + /// + [Fact] + public async Task GetLeaseCountingPoints_ThreeOpenLeasesOnThreeMachines_CountAsTwoSeats() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); - Assert.Contains( "which is not open", exception.Message, StringComparison.Ordinal ); + foreach ( string machine in new[] { "desktop-1", "notebook-1", "desktop-2" } ) + { + LeaseBuilder.For( license ).User( "alice" ).Machine( machine ) + .From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + } + + List points = Timeline( context, license ); + + Assert.Equal( 2, points.Max( p => p.LeaseCount ) ); + Assert.Equal( 0, points[^1].LeaseCount ); } [Fact] From 6f83755c06f34c17f1b06774ad5de2259607717f Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 12:13:41 +0200 Subject: [PATCH 24/44] Document the license server protocol The contract with the clients was spread between the endpoint that implements it, the client that speaks it and the tests that pin it. docs/protocol.md describes it in one place: the three endpoints, the arguments of a lease request and what each one decides, the format of a lease and the lenient parsing a client applies to it, every status code and denial message, the renewal policy that keeps the load proportional to developers rather than to builds, the accelerated clock, and the format and signature chain of the audit log. The behaviours a deployed client depends on are listed together at the end, because the reason not to change them is not visible from the code that implements them. Co-Authored-By: Claude Opus 5 --- README.md | 4 + docs/protocol.md | 328 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+) create mode 100644 docs/protocol.md diff --git a/README.md b/README.md index 0037f45..75e636e 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ You can download the latest release from https://github.com/postsharp-ops/SharpC * [Installing the license server](http://doc.postsharp.net/license-server-admin). * [Using the license server](http://doc.postsharp.net/license-server). +* [Configuring the license server](docs/configuration.md). +* [Running the license server in a container](docs/docker.md). +* [The license server protocol](docs/protocol.md), for whoever maintains a client or diagnoses a + deployment. ## Trying it out diff --git a/docs/protocol.md b/docs/protocol.md new file mode 100644 index 0000000..722cb73 --- /dev/null +++ b/docs/protocol.md @@ -0,0 +1,328 @@ +# The license server protocol + +A client asks this server for a lease: permission to use a license key for a limited period. The +protocol is the one PostSharp has been speaking since version 5, and every deployed client speaks it, +so the shape of each request and each response is a contract that this server cannot change. + +This document describes what the server accepts and what it answers. It is written for whoever +maintains a client, diagnoses a deployment, or changes this server. + +The client side lives in `SharpCrafters.Backstage.Licensing.LicenseServer`, in the +SharpCrafters.Backstage repository. `LicenseServerClient` builds the requests, `LicenseLease` parses +the responses, and `LicenseServerLoadSimulator` drives the whole protocol against a running server. + +## Conventions + +| Property | Value | +|---|---| +| Transport | HTTP or HTTPS. The server does not require HTTPS; a client warns the user when a license server URL is plain HTTP. | +| Method | `GET` for every endpoint. | +| Content type | `text/plain` for every response body, without a charset parameter. Bodies are UTF-8. | +| Paths | `Lease.ashx`, `GetTime.ashx` and `Admin/Export.ashx`, relative to the base URL of the server. | +| Query arguments | Percent-encoded. Argument names are case-sensitive. | +| Instants | UTC, in the XML round-trip representation, for instance `2026-09-22T07:07:08.3615328Z`. | + +The `.ashx` extension is historical. The server is no longer an ASP.NET application with handlers, +but the paths are kept because clients have them compiled in. + +A client that is given a URL already carrying a query string sends that URL verbatim instead of +appending `Lease.ashx`. This exists for a URL supplied directly to a build, and registration refuses +such a URL. + +### Authentication + +The server answers an anonymous request by default, which is how it has always shipped. A client +sends the credentials of the current user unless it is told otherwise, because an on-premises server +published by IIS with Windows authentication answers 401 to an anonymous request. An installation +that enables Windows authentication therefore records who borrowed each lease. + +Setting `LicenseServer:RequireAuthenticatedLeaseRequests` makes the server refuse an anonymous lease +request. See [configuration.md](configuration.md). + +The protocol carries the name of the user and the name of the machine in the query string, so a +server reached over plain HTTP transmits both in cleartext. A client warns about that and serves the +build anyway. + +## Leasing a license: `GET /Lease.ashx` + +### Request + +| Argument | Required | Meaning | +|---|---|---| +| `user` | yes | The account borrowing the license, as the operating system names it, for instance `CONTOSO\alice`. | +| `machine` | yes | The machine name, a hyphen, and a hexadecimal machine identifier. See below. | +| `version` | no | The version of the client, for instance `2027.0.0`. | +| `buildDate` | no | The date the client was built, in the round-trip format. | +| `product` | no | The product whose pool of licenses the server should allocate from, for instance `MetalamaProfessional`. | + +An example, before percent-encoding: + +``` +GET /Lease.ashx?user=CONTOSO\alice&machine=DESKTOP-ABC-1f2e3d4c&version=2027.0.0&buildDate=2026-01-15T00:00:00.0000000Z&product=MetalamaProfessional +``` + +The server lowercases `user` and `machine` before it stores or compares them, so two clients that +differ only in case are one user on one machine. + +#### The machine argument + +The value is the machine name, a hyphen, and the lower-case hexadecimal hash of the machine +identifier. The hash distinguishes two machines that carry the same name, which happens with cloned +virtual machines and with build agents created from an image. + +The suffix is load-bearing on the server side as well. Before the server compares a machine name to +its list of build servers it strips a trailing `-` followed by hexadecimal digits, so +`buildagent-1f2e` matches the configured name `buildagent`. A machine name that legitimately ends in +a hyphen and hexadecimal digits is therefore shortened as well, which is a limitation of the format +rather than of this implementation. + +#### The version argument + +A client older than PostSharp 5 does not send `version`. The server reads an absent one as `4.9.9`, +which is what places the client before the version that introduced the argument. A client that +declares no version of its own sends `0.0` rather than omitting the argument. + +The version decides two things: whether a license that requires a newer client may be served, and +which wording the server uses when it refuses. See [Denials](#denials). + +#### The buildDate argument + +The build date is compared to the end of the maintenance subscription of a license. A build produced +after the subscription ended is not covered by it, whatever the date on which it runs. + +A client with no build date of its own sends the earliest representable instant, +`0001-01-01T00:00:00.0000000`, rather than an empty value, which the server cannot parse. A server +that receives no `buildDate` at all skips the subscription check. + +#### The product argument + +PostSharp never sent this argument. A server that receives no product allocates from any pool it +holds, which is what those clients rely on. + +A client of the Backstage generation names the product of its family, so that one server can hold the +licenses of several products side by side. + +The value is the name of a member of the `LicenseProduct` enumeration of SharpCrafters.Backstage. +The server matches it against the `ProductCode` column, accepting both spellings of a product whose +name changed between the two licensing libraries: a request for `PostSharpUltimate` also finds the +licenses that an earlier version of this server stored as `Ultimate`. The pairs are listed in +`ProductCodes`. + +### Response + +A granted lease is answered with `200 OK` and a body of four named parts: + +``` +License: 900001-ZEE78XQQ…ZAUPA; StartTime: 2026-09-22T07:07:08.3615328Z; EndTime: 2026-09-25T07:07:08.3615328Z; RenewTime: 2026-09-24T07:07:08.3615328Z +``` + +| Part | Meaning | +|---|---| +| `License` | The license key the client uses until the lease ends. | +| `StartTime` | The instant the lease began. | +| `EndTime` | The instant after which the license key may no longer be used. | +| `RenewTime` | The instant from which the client should ask for a new lease. It precedes `EndTime`. | + +The parsing on the client side is lenient, and a server may rely on that: + +- The parts are separated by `;`. A license key is an identifier, a hyphen and Base32 characters, so + it never contains one. +- Each part is split at its first `:`, so the colons inside an instant are kept. +- The name of a part is matched without regard to case. +- A part that the client does not understand is ignored. A later version of the server may therefore + add a part, but may not rename or reorder one. +- Only `License` is mandatory. A client that receives no `StartTime` uses the current instant, no + `EndTime` gives a lease of one day, and no `RenewTime` gives `EndTime`. +- A client reads the instants as UTC and keeps them as UTC. + +A client reads at most 64 KiB of the body. A real lease is a few hundred bytes; the bound is there so +that a broken or hostile server cannot make a client read without end. + +### Status codes + +| Code | Body | Meaning | +|---|---|---| +| 200 | The lease, as above. | A license was allocated. | +| 400 | `Missing query string argument: machine.` | `machine` was absent or empty. | +| 400 | `Missing query string argument: user.` | `user` was absent or empty. | +| 400 | `Cannot parse the argument: version.` | `version` is not a version number. | +| 400 | `Cannot parse the argument: buildDate.` | `buildDate` is not a round-trip date. | +| 403 | `No license with free capacity. ` followed by one explanation per license. | No license could serve the request. | +| 503 | `Service overloaded.` | The server did not obtain its lease lock within `LicenseServer:MutexTimeout` seconds. | + +A client shows the body of a 403 to the user, because it is the explanation the administrator of the +server needs. Any other status is reported as the status alone. + +### Denials + +The body of a 403 begins with `No license with free capacity. ` and then carries the reason each +license was passed over, separated by spaces. A server holding no license at all answers the prefix +alone. + +The reasons a license is passed over: + +| Reason | Message | +|---|---| +| The key does not parse or its signature does not verify. | `The license key #N is invalid.` | +| The key requires a newer licensing library than the server carries. | `The license #N requires a higher version of the licensing library on the License Server. Please upgrade the License Server to >= X.Y.Z` | +| The key requires a newer client than the one asking. | `The license #N of type T requires PostSharp version >= X.Y.Z but the requested version is A.B.C.` | +| The key may not be served by a license server at all. | `The license #N, of type T, cannot be used in the license server.` | +| The client was built after the maintenance subscription ended. | `The maintenance subscription of license #N ends on D but the requested version X.Y.Z has been built on E.` | + +The last message omits the version for a client that did not send one, because such a client predates +the argument. + +A request that passes every check may still be denied for capacity. The server then reports no +per-license reason, so the body is the prefix alone. + +### What decides a grant + +The server tries the licenses it holds in the order of their `Priority` column, lowest first, and +skips a license whose priority is negative. For each license, in order: + +1. A lease this user already holds on this machine is returned unchanged, if it ends more than + `MinLeaseDays` from now. +2. Such a lease is prolonged if it ends sooner than that. Prolonging replaces the lease with a new + one that overwrites it, so the audit log keeps both. +3. A new lease is granted if the license has a free seat. +4. A new lease is granted beyond capacity if the license is within its grace period. + +A seat covers `LicenseServer:MachinesPerUser` machines of one user, so a user holding two machines at +the default of two occupies one seat and a third machine takes a second. + +`EndTime` is `NewLeaseDays` from now, clamped to the expiry of the license key. `RenewTime` is +`MinLeaseDays` before `EndTime`. The server refuses to start unless `MinLeaseDays` is smaller than +`NewLeaseDays`, because a renew time that lands in the past makes every client renew on every build. + +A machine named in `LicenseServer:BuildServers` is served a license key without a lease being stored, +so that build agents do not consume the seats of the developers they build for. A build agent is +exempt from consuming a seat and from nothing else: the same validation runs, and an expired or +ineligible license is refused as it would be for anybody. + +### Renewal + +A client stores the lease it was granted and contacts the server again only when it needs to. This is +client behaviour rather than something the server enforces, and it is what keeps the load on a server +proportional to the number of developers rather than to the number of builds. + +- The stored lease is used while the current instant is before `RenewTime`. +- The client downloads a new lease once `RenewTime` has passed. A renewal on a machine the user + already holds prolongs a seat rather than allocating one. +- A renewal that fails while the stored lease is still valid keeps the stored lease and reports + nothing. The build has a license, and failing it because the server is briefly unreachable would be + worse than the problem. +- A lease whose `EndTime` has passed is discarded and a new one is downloaded. +- A client discards a lease whose `EndTime` is already in the past when it arrives. Without that + guard, a server whose clock is behind the client's would make every process download a lease, find + it expired and download again, without end. + +With the default of a three-day lease renewed after two, one machine contacts the server about once +every two days, whatever the number of builds in between. + +## Reading the server clock: `GET /GetTime.ashx` + +The server answers its own idea of the current instant and how much faster than real time its clock +runs, separated by `;`: + +``` +2026-09-17T11:00:58.5236731Z;1440 +``` + +The acceleration is `1` on a production server, where the clock is the real one. Any other value +comes from `LicenseServer:TimeAcceleration`, and the server warns at startup when it is set. + +A test harness anchors its own virtual clock on the reading and advances it at the same rate: + +``` +virtualNow = serverTimeAtSync + (realNow - realTimeAtSync) * acceleration +``` + +The round trip is not compensated. At an acceleration of 1440 a round trip of 50 ms is already +72 seconds of virtual time, so a harness synchronizes again whenever the drift shows, which for a +licensing harness means whenever a lease arrives whose renewal instant has already passed. + +The virtual clock of the server is anchored when the process starts and never resets. Restarting a +server therefore moves its clock backwards, and leases granted before the restart are then dated in +the future. Drop the database as well as recycling the process between two simulations. + +## Exporting the audit log: `GET /Admin/Export.ashx` + +The export is not part of the client protocol. It is the file an administrator hands to an auditor, +and its format is a contract of its own, because customers archive the files and compare them across +years. + +### Request + +| Argument | Meaning | +|---|---| +| `fy`, `fm` | The year and month the range starts at. | +| `ty`, `tm` | The year and month the range ends at, inclusive. | + +Years are between 2010 and 2100 and months between 1 and 12. Anything else is answered with `400` and +the body `The range of months is missing or invalid. Years must be between 2010 and 2100.` + +The response carries `Content-Disposition: attachment` with a file name derived from the range, for +instance `PostSharp_LicenseLog_2026-1_2026-12.txt`, and is streamed as the rows are read. + +### Format + +One line per lease, with eight fields separated by `;`: + +``` +40;;900001;2026-09-17T11:02:14.1234567Z;2026-09-20T11:02:14.1234567Z;d97556dbab6becaa;93cf1e71b44530bd;J+QFuzRr4fT+mkwzkYOQ/NZtkVy2EHWj1b9t82h/xB8= +``` + +| Field | Meaning | +|---|---| +| 1 | The identifier of the lease. | +| 2 | The identifier of the lease this one overwrote, empty for a lease that overwrote none. | +| 3 | The identifier of the license. | +| 4 | The instant the lease began, in UTC. | +| 5 | The instant the lease ended, in UTC. | +| 6 | The hash of the machine name. | +| 7 | The hash of the user name. | +| 8 | The signature. | + +Names appear only as hashes, so the file can be shared without disclosing who works where. The hash +is the lower-case hexadecimal of an unkeyed 64-bit hash of the name, trimmed and lower-cased first. +The algorithm is MD5 truncated to its first eight bytes read as a little-endian signed integer. MD5 +is not chosen for its cryptographic properties, which are irrelevant to an anonymising hash, but +because the values have to equal the ones PostSharp has been producing since 2013 and the license +audit of Backstage hashes the same names the same way. + +### The signature chain + +Each signature covers the signature of the previous lease, a semicolon, and the seven fields of its +own line: + +``` +signature(n) = base64( HMAC-SHA256( key, signature(n-1) + ";" + fields 1 to 7 of line n ) ) +``` + +The first lease of a database chains onto an empty string. The key is the one described under +Auditing in [configuration.md](configuration.md). + +An auditor recomputes the chain from an exported file alone, because the signed payload is exactly +the line that is exported. This is new. The previous implementation called the parameterless +`HMAC.Create()`, which produced a hash under a randomly generated key on every call, so no chain it +wrote was ever verifiable. + +A range of months is resolved to a range of lease identifiers, and every lease in that range is +exported. The result is a contiguous run of the log rather than a filtered selection, which is what +keeps the chain verifiable, and it is why a few leases outside the requested months appear in the +file. + +## Compatibility + +The server keeps these behaviours because clients depend on them. Changing any of them breaks a +client that is already deployed. + +| Behaviour | Why | +|---|---| +| The `.ashx` paths. | Clients have them compiled in. | +| An absent `version` means 4.9.9. | It is how a client older than PostSharp 5 is recognized. | +| An absent `product` means any product. | No PostSharp client ever sent the argument. | +| The four part names of a lease, and their order. | A client splits the body positionally in spirit, even though it matches by name. | +| The status codes, and the body of a 403. | A client shows the body of a 403 to the user and treats every other non-200 as a failure. | +| The trailing hexadecimal suffix of a machine name is stripped before matching a build server. | The list of build servers in an existing configuration names machines without it. | +| The field order of an audit line, and the hash of the names. | Customers archive exported files and compare them across years. | From 984704ae36a92087bbde1cb9b3e21f54913285a7 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 12:18:53 +0200 Subject: [PATCH 25/44] Draw the usage graph in users rather than in seats The chart was labelled "Concurrent users" and drew seats. The two differ for a user working on more machines than one seat covers: the allocator charges that person a second seat, and the chart counted two. It now counts the people holding a lease, which is what its axis, its legend and the two reference lines are expressed in -- the capacity of a license key is a number of concurrent users. Each point of the timeline now carries both numbers. LeaseCount keeps the seats, which is what the allocator compares to the capacity when it decides whether to grant a lease, and UserCount carries the people. Nothing but the chart reads the second one today. This makes the chart differ from the "In use" column of the license list, which counts seats. A license can therefore be at its capacity with the chart below the line. Co-Authored-By: Claude Opus 5 --- .../Data/LeaseCountingPoint.cs | 19 +++- .../Data/LeaseRepository.cs | 18 ++- .../Pages/Graph.cshtml.cs | 28 +++-- .../wwwroot/js/graph.js | 4 +- .../LeaseCountingPointsTests.cs | 104 ++++++++++-------- .../PageTests.cs | 2 +- 6 files changed, 115 insertions(+), 60 deletions(-) diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs index 0fdb0f0..10ee086 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs @@ -1,8 +1,8 @@ namespace SharpCrafters.Backstage.LicenseServer; /// -/// A point on the usage timeline of a license: the moment a lease starts or ends, together with the -/// number of seats in use just after that moment. +/// A point on the usage timeline of a license: the moment a lease starts or ends, together with what +/// was in use just after that moment. /// public sealed class LeaseCountingPoint { @@ -15,5 +15,20 @@ public sealed class LeaseCountingPoint /// /// Gets the number of seats consumed immediately after this point. /// + /// + /// A seat covers MachinesPerUser machines of one user, so a user working on more machines + /// than that consumes more than one seat. This is the quantity the allocator compares to the + /// capacity of the license when it decides whether to grant a lease. + /// public int LeaseCount { get; set; } + + /// + /// Gets the number of users holding at least one lease immediately after this point. + /// + /// + /// This counts people rather than what they consume, so it is never larger than + /// and is smaller whenever somebody works on enough machines to take a + /// second seat. + /// + public int UserCount { get; set; } } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs index 9dd33b7..00fa122 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs @@ -248,6 +248,7 @@ public IEnumerable GetLeaseCountingPoints( Dictionary> currentUsers = new( StringComparer.OrdinalIgnoreCase ); int leaseCount = 0; + int userCount = 0; foreach ( LeaseCountingPoint record in allRecords ) { @@ -257,7 +258,8 @@ public IEnumerable GetLeaseCountingPoints( currentUsers.Add( record.Lease.UserName, machines ); } - int seatsBefore = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); + int machinesBefore = machines.Count; + int seatsBefore = SeatCounter.CountSeats( [machinesBefore], this.settings.MachinesPerUser ); string machine = record.Lease.Machine; if ( record.Kind == LeaseCountingPointKind.Open ) @@ -286,7 +288,21 @@ public IEnumerable GetLeaseCountingPoints( int seatsAfter = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); leaseCount += seatsAfter - seatsBefore; + + // A user joins the count when their first machine takes a lease and leaves it when their + // last one gives it up. The entry in currentUsers stays behind, so the users are counted + // from the machines they hold rather than from the number of entries. + if ( machinesBefore == 0 && machines.Count > 0 ) + { + userCount++; + } + else if ( machinesBefore > 0 && machines.Count == 0 ) + { + userCount--; + } + record.LeaseCount = leaseCount; + record.UserCount = userCount; yield return record; } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs index 4e48b2e..68e3f69 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs @@ -8,9 +8,15 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages; /// -/// The usage history of one license: how many seats were in use on each of the last N days, against +/// The usage history of one license: how many users held a lease on each of the last N days, against /// the capacity of the license and its grace allowance. /// +/// +/// The chart counts users and not the seats they consume. The two are the same until somebody works +/// on more machines than one seat covers, and the allocator charges that person a second seat while +/// the chart still draws one person, so a license can be at its capacity with the chart below the +/// line. carries the seats for a caller that needs them. +/// public sealed class GraphModel( ILeaseRepository repository, ILicenseParser licenseParser, @@ -72,11 +78,16 @@ public async Task OnGetAsync( CancellationToken cancellationToken day => new { Date = day.Key, - Peak = day.Max( point => point.LeaseCount ), + + // The number of people holding a lease, not the number of seats they consume. + // The two differ for a user working on more machines than one seat covers; the + // capacity of a license is expressed in users, which is what the two reference + // lines of the chart draw. + Peak = day.Max( point => point.UserCount ), // The timeline is ordered, and grouping preserves that order within a group, so // the last point of a day is the count the next day starts from. - AtEndOfDay = day.Last().LeaseCount + AtEndOfDay = day.Last().UserCount } ) .ToList(); @@ -128,7 +139,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken this.Chart = new UsageChart { Labels = labels, - Used = values, + Users = values, Maximum = maximum, Grace = graceMaximum, AxisMaximum = (int) Math.Ceiling( Math.Ceiling( axisMaximum * 1.2 ) / 10 ) * 10 @@ -144,15 +155,18 @@ public sealed class UsageChart { public string[] Labels { get; init; } = []; - public int[] Used { get; init; } = []; + /// + /// Gets the number of users holding a lease on each day of the window. + /// + public int[] Users { get; init; } = []; /// - /// Gets the number of seats the license allows, or null when it is unlimited. + /// Gets the number of concurrent users the license allows, or null when it is unlimited. /// public int? Maximum { get; init; } /// - /// Gets the number of seats tolerated during the grace period. + /// Gets the number of concurrent users tolerated during the grace period. /// public int? Grace { get; init; } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js index 1ba426c..8f2997d 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js @@ -20,8 +20,8 @@ Chart.defaults.font.family = styles.getPropertyValue("--font-body").trim() || "sans-serif"; var datasets = [{ - label: "Used", - data: chart.used, + label: "Users", + data: chart.users, borderColor: "#973bfc", backgroundColor: "rgba(151, 59, 252, 0.16)", fill: true, diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs index a8e358b..a72a3d4 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs @@ -3,8 +3,8 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// -/// The usage timeline behind the graph: a sequence of lease open and close events carrying the -/// running seat count. +/// The usage timeline behind the graph: a sequence of lease open and close events, each carrying the +/// running number of seats and of users. /// public sealed class LeaseCountingPointsTests { @@ -29,6 +29,49 @@ public async Task GetLeaseCountingPoints_OneLease_OpensThenCloses() Assert.Equal( 0, points[1].LeaseCount ); } + [Fact] + public async Task GetLeaseCountingPoints_ThreeUsersOneMachineEach_AreThreeUsers() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + foreach ( string user in new[] { "alice", "bob", "carol" } ) + { + LeaseBuilder.For( license ).User( user ).Machine( $"desktop-{user}" ) + .From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); + } + + List points = Timeline( context, license ); + + Assert.Equal( 3, points.Max( p => p.UserCount ) ); + Assert.Equal( 3, points.Max( p => p.LeaseCount ) ); + Assert.Equal( 0, points[^1].UserCount ); + } + + /// + /// A user who gives up one machine and keeps another is still counted, and leaves the count only + /// when the last of their leases ends. + /// + [Fact] + public async Task GetLeaseCountingPoints_UserKeepingOneMachine_StaysCounted() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) + .From( TestClock.Origin ).Lasting( 1 ).AddTo( context ); + + LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ) + .From( TestClock.Origin ).Lasting( 5 ).AddTo( context ); + + List points = Timeline( context, license ); + + // The first lease closes on day one and the second on day five. The user is counted + // throughout and leaves only at the last point. + Assert.All( points[..^1], p => Assert.Equal( 1, p.UserCount ) ); + Assert.Equal( 0, points[^1].UserCount ); + } + [Fact] public async Task GetLeaseCountingPoints_OneUserTwoMachines_NeverExceedsOneSeat() { @@ -40,7 +83,10 @@ public async Task GetLeaseCountingPoints_OneUserTwoMachines_NeverExceedsOneSeat( LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).From( TestClock.Days( 1 ) ).Lasting( 3 ) .AddTo( context ); - Assert.Equal( 1, Timeline( context, license ).Max( p => p.LeaseCount ) ); + List points = Timeline( context, license ); + + Assert.Equal( 1, points.Max( p => p.LeaseCount ) ); + Assert.Equal( 1, points.Max( p => p.UserCount ) ); } [Fact] @@ -55,7 +101,12 @@ public async Task GetLeaseCountingPoints_OneUserThreeMachines_ReachesTwoSeats() .AddTo( context ); } - Assert.Equal( 2, Timeline( context, license ).Max( p => p.LeaseCount ) ); + List points = Timeline( context, license ); + + // Two seats but one person. This is the difference between the two counts each point carries, + // and the reason the chart draws the users. + Assert.Equal( 2, points.Max( p => p.LeaseCount ) ); + Assert.Equal( 1, points.Max( p => p.UserCount ) ); } /// @@ -188,50 +239,9 @@ public async Task GetLeaseCountingPoints_TwoOpenLeasesOnOneMachine_CountAsOneSea Assert.Equal( 4, points.Count ); Assert.Equal( 1, points.Max( p => p.LeaseCount ) ); + Assert.Equal( 1, points.Max( p => p.UserCount ) ); Assert.Equal( 0, points[^1].LeaseCount ); - } - - /// - /// The same user on two machines still occupies one seat at two machines per user, which is what - /// shows that the counting of duplicates did not turn into a count of leases. - /// - [Fact] - public async Task GetLeaseCountingPoints_TwoOpenLeasesOnTwoMachines_CountAsOneSeat() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - - LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ) - .From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); - - LeaseBuilder.For( license ).User( "alice" ).Machine( "notebook-1" ) - .From( TestClock.Days( 1 ) ).Lasting( 3 ).AddTo( context ); - - List points = Timeline( context, license ); - - Assert.Equal( 1, points.Max( p => p.LeaseCount ) ); - Assert.Equal( 0, points[^1].LeaseCount ); - } - - /// - /// A third machine takes a second seat, at two machines per user. - /// - [Fact] - public async Task GetLeaseCountingPoints_ThreeOpenLeasesOnThreeMachines_CountAsTwoSeats() - { - await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); - License license = LicenseBuilder.Default().AddTo( context ); - - foreach ( string machine in new[] { "desktop-1", "notebook-1", "desktop-2" } ) - { - LeaseBuilder.For( license ).User( "alice" ).Machine( machine ) - .From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); - } - - List points = Timeline( context, license ); - - Assert.Equal( 2, points.Max( p => p.LeaseCount ) ); - Assert.Equal( 0, points[^1].LeaseCount ); + Assert.Equal( 0, points[^1].UserCount ); } [Fact] diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs index 5a59a52..abae0f3 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs @@ -96,7 +96,7 @@ public async Task Graph_EmbeddedDataIsValidJsonCoveringTheWindow() JsonElement root = document.RootElement; Assert.Equal( 90, root.GetProperty( "labels" ).GetArrayLength() ); - Assert.Equal( 90, root.GetProperty( "used" ).GetArrayLength() ); + Assert.Equal( 90, root.GetProperty( "users" ).GetArrayLength() ); Assert.Equal( 10, root.GetProperty( "maximum" ).GetInt32() ); Assert.Equal( 12, root.GetProperty( "grace" ).GetInt32() ); Assert.True( root.GetProperty( "axisMaximum" ).GetInt32() >= 12 ); From 6f4aad36c6b2ad3009df0e63cdfebe9dbe2e8dba Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 12:29:48 +0200 Subject: [PATCH 26/44] Define the seat and count it from machines rather than from leases A seat is one user together with the machines that user works on, up to MachinesPerUser of them; a user working on more consumes one seat per that many machines, rounded up. The word was used throughout without ever being defined, which is what made it look like a concept the product does not have. The definition now sits on SeatCounter, on the setting, and in the configuration and protocol documents. The licence agreement states the same rule the other way round, as authorized users each entitled to a number of devices. GetActiveLeads becomes GetActiveSeats and LeaseCountingPoint.LeaseCount becomes SeatCount, so that the names say what they hold. GetActiveSeats counted the leases a user holds instead of the machines they work on: a user holding two leases on one machine was charged for two machines, which denies a colleague a lease the licence has the capacity for. The lease service avoids that by prolonging rather than granting a second lease, but a server whose clock has moved backwards grants one, and a load simulation produced it within minutes of a restart. The timeline behind the graph already counted machines. This also reverts the previous commit. The usage chart draws seats again, so the line and the two limits above it are in the same unit and the chart agrees with the "In use" column. Co-Authored-By: Claude Opus 5 --- docs/configuration.md | 10 ++- docs/protocol.md | 7 +- .../Data/ILeaseRepository.cs | 2 +- .../Data/LeaseCountingPoint.cs | 25 ++----- .../Data/LeaseRepository.cs | 37 ++++------ .../Data/SeatCounter.cs | 21 +++++- .../Options/LicenseServerOptions.cs | 6 +- .../Services/LeaseService.cs | 2 +- .../Pages/Admin/Details.cshtml.cs | 2 +- .../Pages/Graph.cshtml.cs | 29 +++----- .../Pages/Index.cshtml.cs | 2 +- .../wwwroot/js/graph.js | 6 +- .../BuildServerDetectionTests.cs | 2 +- .../CancelLeaseTests.cs | 4 +- ...veLeadsTests.cs => GetActiveSeatsTests.cs} | 73 +++++++++++++------ .../LeaseAllocationTests.cs | 2 +- .../LeaseCountingPointsTests.cs | 47 ++++++------ .../PageTests.cs | 2 +- 18 files changed, 151 insertions(+), 128 deletions(-) rename tests/SharpCrafters.Backstage.LicenseServer.Tests/{GetActiveLeadsTests.cs => GetActiveSeatsTests.cs} (62%) diff --git a/docs/configuration.md b/docs/configuration.md index 8da79dd..f008657 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,9 +40,17 @@ into `appsettings.json`. ## Licensing rules +A seat is one user together with the machines that user works on, up to `MachinesPerUser` of them. A +user working on more machines than that consumes one seat per `MachinesPerUser` machines, rounded up: +at the default of two, one or two machines are one seat, three or four are two seats, and so on. The +capacity of a license key is a number of seats, and the seat is the only unit the server counts in. + +The license agreement states the same rule the other way round, as a number of authorized users each +entitled to a number of devices. + | Setting | Default | Meaning | |---|---|---| -| `LicenseServer:MachinesPerUser` | 2 | How many devices one user may use on a single seat. Check your license agreement before changing it. | +| `LicenseServer:MachinesPerUser` | 2 | How many machines one seat covers. Check your license agreement before changing it. | | `LicenseServer:NewLeaseDays` | 3 | How long a new lease lasts. | | `LicenseServer:MinLeaseDays` | 1 | How long before the end of a lease a client starts renewing it. If your developers work offline for weeks at a time, raise this above the number of days they are away. Must be smaller than `NewLeaseDays`. | | `LicenseServer:BuildServers` | empty | The machine names of build agents, separated by semicolons, commas or spaces. A build agent is served a license but is not given a lease, so that it does not consume a developer's seat. A trailing hexadecimal identifier is ignored, so `buildagent-1f2e` matches `buildagent`. | diff --git a/docs/protocol.md b/docs/protocol.md index 722cb73..d8c0689 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -187,8 +187,11 @@ skips a license whose priority is negative. For each license, in order: 3. A new lease is granted if the license has a free seat. 4. A new lease is granted beyond capacity if the license is within its grace period. -A seat covers `LicenseServer:MachinesPerUser` machines of one user, so a user holding two machines at -the default of two occupies one seat and a third machine takes a second. +A seat is one user together with the machines that user works on, up to +`LicenseServer:MachinesPerUser` of them. A user working on more machines consumes one seat per that +many machines, rounded up, so at the default of two a user on one or two machines is one seat and a +user on three is two. The capacity of a license key is a number of seats, and the seat is the only +unit the server counts in. `EndTime` is `NewLeaseDays` from now, clamped to the expiry of the license key. `RenewTime` is `MinLeaseDays` before `EndTime`. The server refuses to start unless `MinLeaseDays` is smaller than diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs index db82c89..bacc64c 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/ILeaseRepository.cs @@ -47,7 +47,7 @@ public interface ILeaseRepository /// /// Counts the seats of a license in use at a given moment. /// - int GetActiveLeads( int licenseId, DateTime dateTime ); + int GetActiveSeats( int licenseId, DateTime dateTime ); /// /// Returns the usage timeline of a license over a period, as a sequence of lease open and close diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs index 10ee086..90bcfba 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseCountingPoint.cs @@ -1,8 +1,8 @@ namespace SharpCrafters.Backstage.LicenseServer; /// -/// A point on the usage timeline of a license: the moment a lease starts or ends, together with what -/// was in use just after that moment. +/// A point on the usage timeline of a license: the moment a lease starts or ends, together with the +/// number of seats in use just after that moment. /// public sealed class LeaseCountingPoint { @@ -13,22 +13,13 @@ public sealed class LeaseCountingPoint public required Lease Lease { get; init; } /// - /// Gets the number of seats consumed immediately after this point. + /// Gets the number of seats in use immediately after this point. /// /// - /// A seat covers MachinesPerUser machines of one user, so a user working on more machines - /// than that consumes more than one seat. This is the quantity the allocator compares to the - /// capacity of the license when it decides whether to grant a lease. + /// A seat is one user and the machines that user works on, up to MachinesPerUser of them; + /// see . This is the quantity the allocator compares to the + /// capacity of the license when it decides whether to grant a lease, so it is also what the usage + /// chart draws against the capacity. /// - public int LeaseCount { get; set; } - - /// - /// Gets the number of users holding at least one lease immediately after this point. - /// - /// - /// This counts people rather than what they consume, so it is never larger than - /// and is smaller whenever somebody works on enough machines to take a - /// second seat. - /// - public int UserCount { get; set; } + public int SeatCount { get; set; } } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs index 00fa122..8f4bce7 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/LeaseRepository.cs @@ -199,14 +199,19 @@ private bool FixLease( Lease lease, DateTime time, bool fixEndTime = true ) return true; } - public int GetActiveLeads( int licenseId, DateTime dateTime ) + public int GetActiveSeats( int licenseId, DateTime dateTime ) { - // The seat arithmetic runs here rather than in SQL, so that the query translates on every - // provider. The result is one row per distinct user holding a lease on this license. + // The machines are counted in SQL and the seat arithmetic runs here, so that the query + // translates on every provider. The result is one row per user holding a lease on this + // license. + // + // The machines are counted as distinct, and not as leases. A user can hold two leases on one + // machine -- which is what a server whose clock has moved backwards produces -- and counting + // the leases would charge them for a machine they are not working on. List machinesPerUser = db.OpenLeases .Where( l => l.LicenseId == licenseId && l.StartTime <= dateTime && l.EndTime > dateTime ) .GroupBy( l => l.UserName ) - .Select( g => g.Count() ) + .Select( g => g.Select( l => l.Machine ).Distinct().Count() ) .ToList(); return SeatCounter.CountSeats( machinesPerUser, this.settings.MachinesPerUser ); @@ -247,8 +252,7 @@ public IEnumerable GetLeaseCountingPoints( // the machine on the first close and then found nothing to remove on the second. Dictionary> currentUsers = new( StringComparer.OrdinalIgnoreCase ); - int leaseCount = 0; - int userCount = 0; + int seatCount = 0; foreach ( LeaseCountingPoint record in allRecords ) { @@ -258,8 +262,7 @@ public IEnumerable GetLeaseCountingPoints( currentUsers.Add( record.Lease.UserName, machines ); } - int machinesBefore = machines.Count; - int seatsBefore = SeatCounter.CountSeats( [machinesBefore], this.settings.MachinesPerUser ); + int seatsBefore = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); string machine = record.Lease.Machine; if ( record.Kind == LeaseCountingPointKind.Open ) @@ -287,22 +290,8 @@ public IEnumerable GetLeaseCountingPoints( int seatsAfter = SeatCounter.CountSeats( [machines.Count], this.settings.MachinesPerUser ); - leaseCount += seatsAfter - seatsBefore; - - // A user joins the count when their first machine takes a lease and leaves it when their - // last one gives it up. The entry in currentUsers stays behind, so the users are counted - // from the machines they hold rather than from the number of entries. - if ( machinesBefore == 0 && machines.Count > 0 ) - { - userCount++; - } - else if ( machinesBefore > 0 && machines.Count == 0 ) - { - userCount--; - } - - record.LeaseCount = leaseCount; - record.UserCount = userCount; + seatCount += seatsAfter - seatsBefore; + record.SeatCount = seatCount; yield return record; } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs index 8f8f227..76c7e18 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs @@ -1,14 +1,29 @@ namespace SharpCrafters.Backstage.LicenseServer.Data; /// -/// Converts machine counts into seat counts. +/// Counts the seats that the holders of a set of leases consume. /// +/// +/// +/// A seat is one user together with the machines that user works on, up to +/// MachinesPerUser of them. A user working on more machines than that consumes one seat per +/// MachinesPerUser machines, rounded up: at the default of two, one or two machines are one +/// seat, three or four are two seats, and so on. +/// +/// +/// The seat is the unit the capacity of a license key is expressed in, and the only place where the +/// number of machines enters the licensing rules. The licence agreement puts it the other way round, +/// as a number of authorized users each entitled to a number of devices; the two say the same thing, +/// and this is the form the server counts in. +/// +/// public static class SeatCounter { /// - /// Counts the seats consumed by users holding the given numbers of machines. A user consumes one - /// seat per machines, rounded up. + /// Counts the seats consumed by users working on the given numbers of machines. /// + /// The number of distinct machines each user is working on. + /// The number of machines one seat covers. /// /// This arithmetic used to run inside the SQL GROUP BY, which no provider other than SQL /// Server can translate. Doing it here keeps the query portable and makes the rounding diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs index 7fa6491..074cea7 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs @@ -19,8 +19,10 @@ public sealed class LicenseServerOptions public int GracePeriodWarningDays { get; set; } = 1; /// - /// Gets or sets the number of devices that can be used by a single user with a single seat. - /// The default value is 2. Check your license agreement for a different value. + /// Gets or sets the number of machines that one seat covers. A user working on more machines than + /// this consumes one seat per this many machines, rounded up; see + /// . The default value is 2. Check your license agreement for a + /// different value. /// [Range( 1, 100 )] public int MachinesPerUser { get; set; } = 2; diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs index 85e1787..f207601 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Services/LeaseService.cs @@ -492,7 +492,7 @@ public int Usage { if ( this.usage == -1 ) { - this.usage = repository.GetActiveLeads( license.LicenseId, time ); + this.usage = repository.GetActiveSeats( license.LicenseId, time ); } return this.usage; diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs index 5985a4c..0d1dccb 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs @@ -41,7 +41,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken .AsNoTracking() .ToListAsync( cancellationToken ); - this.ConcurrentUsers = repository.GetActiveLeads( this.Id, now ); + this.ConcurrentUsers = repository.GetActiveSeats( this.Id, now ); this.IsDisabled = license.Priority < 0; return this.Page(); diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs index 68e3f69..e8c60bd 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Graph.cshtml.cs @@ -8,14 +8,14 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages; /// -/// The usage history of one license: how many users held a lease on each of the last N days, against +/// The usage history of one license: how many seats were in use on each of the last N days, against /// the capacity of the license and its grace allowance. /// /// -/// The chart counts users and not the seats they consume. The two are the same until somebody works -/// on more machines than one seat covers, and the allocator charges that person a second seat while -/// the chart still draws one person, so a license can be at its capacity with the chart below the -/// line. carries the seats for a caller that needs them. +/// A seat is one user and the machines that user works on, up to MachinesPerUser of them; see +/// . The chart draws the same quantity the allocator compares to the +/// capacity, so the line and the two limits above it are in the same unit and the "In use" column of +/// the license list agrees with the chart. /// public sealed class GraphModel( ILeaseRepository repository, @@ -78,16 +78,11 @@ public async Task OnGetAsync( CancellationToken cancellationToken day => new { Date = day.Key, - - // The number of people holding a lease, not the number of seats they consume. - // The two differ for a user working on more machines than one seat covers; the - // capacity of a license is expressed in users, which is what the two reference - // lines of the chart draw. - Peak = day.Max( point => point.UserCount ), + Peak = day.Max( point => point.SeatCount ), // The timeline is ordered, and grouping preserves that order within a group, so // the last point of a day is the count the next day starts from. - AtEndOfDay = day.Last().UserCount + AtEndOfDay = day.Last().SeatCount } ) .ToList(); @@ -139,7 +134,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken this.Chart = new UsageChart { Labels = labels, - Users = values, + Seats = values, Maximum = maximum, Grace = graceMaximum, AxisMaximum = (int) Math.Ceiling( Math.Ceiling( axisMaximum * 1.2 ) / 10 ) * 10 @@ -156,17 +151,17 @@ public sealed class UsageChart public string[] Labels { get; init; } = []; /// - /// Gets the number of users holding a lease on each day of the window. + /// Gets the number of seats in use on each day of the window. /// - public int[] Users { get; init; } = []; + public int[] Seats { get; init; } = []; /// - /// Gets the number of concurrent users the license allows, or null when it is unlimited. + /// Gets the number of seats the license allows, or null when it is unlimited. /// public int? Maximum { get; init; } /// - /// Gets the number of concurrent users tolerated during the grace period. + /// Gets the number of seats tolerated during the grace period. /// public int? Grace { get; init; } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs index cde560f..4d270d1 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Index.cshtml.cs @@ -44,7 +44,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken ) LicenseType = parsedLicense.LicenseType, ProductCode = parsedLicense.Product, MaxUsers = parsedLicense.UserNumber, - CurrentUsers = repository.GetActiveLeads( license.LicenseId, now ), + CurrentUsers = repository.GetActiveSeats( license.LicenseId, now ), GraceStartTime = license.GraceStartTime, Status = license.Priority >= 0 ? "Active" : "Disabled", MaintenanceEndDate = parsedLicense.SubscriptionEndDate diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js index 8f2997d..31ee564 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js @@ -20,8 +20,8 @@ Chart.defaults.font.family = styles.getPropertyValue("--font-body").trim() || "sans-serif"; var datasets = [{ - label: "Users", - data: chart.users, + label: "Seats", + data: chart.seats, borderColor: "#973bfc", backgroundColor: "rgba(151, 59, 252, 0.16)", fill: true, @@ -62,7 +62,7 @@ y: { beginAtZero: true, max: chart.axisMaximum, - title: { display: true, text: "Concurrent users" }, + title: { display: true, text: "Seats" }, ticks: { precision: 0 } }, x: { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs index 26c1d35..03accda 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/BuildServerDetectionTests.cs @@ -95,7 +95,7 @@ private sealed class StubRepository : ILeaseRepository public void CancelLease( Lease lease, string authenticatedUserName, DateTime time ) => throw new NotSupportedException(); - public int GetActiveLeads( int licenseId, DateTime dateTime ) => throw new NotSupportedException(); + public int GetActiveSeats( int licenseId, DateTime dateTime ) => throw new NotSupportedException(); public IEnumerable GetLeaseCountingPoints( int licenseId, diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs index 346ce08..065ad8d 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/CancelLeaseTests.cs @@ -51,12 +51,12 @@ public async Task CancelLease_ReleasesTheSeat() License license = LicenseBuilder.Default().AddTo( context ); Lease original = LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); - Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 2 ) ) ); + Assert.Equal( 1, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 2 ) ) ); context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); await context.Repository.SaveChangesAsync(); - Assert.Equal( 0, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 2 ) ) ); + Assert.Equal( 0, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 2 ) ) ); } /// diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveLeadsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveSeatsTests.cs similarity index 62% rename from tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveLeadsTests.cs rename to tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveSeatsTests.cs index 14479fa..68f0bb0 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveLeadsTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/GetActiveSeatsTests.cs @@ -5,40 +5,40 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// How many seats of a license are in use at a given moment. /// -public sealed class GetActiveLeadsTests +public sealed class GetActiveSeatsTests { [Fact] - public async Task GetActiveLeads_NoLeases_ReturnsZero() + public async Task GetActiveSeats_NoLeases_ReturnsZero() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); - Assert.Equal( 0, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + Assert.Equal( 0, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); } [Fact] - public async Task GetActiveLeads_OneUserOneMachine_ReturnsOne() + public async Task GetActiveSeats_OneUserOneMachine_ReturnsOne() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); - Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + Assert.Equal( 1, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); } [Fact] - public async Task GetActiveLeads_OneUserTwoMachines_StillReturnsOne() + public async Task GetActiveSeats_OneUserTwoMachines_StillReturnsOne() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).AddTo( context ); - Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + Assert.Equal( 1, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); } [Fact] - public async Task GetActiveLeads_OneUserThreeMachines_ReturnsTwo() + public async Task GetActiveSeats_OneUserThreeMachines_ReturnsTwo() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); @@ -48,42 +48,42 @@ public async Task GetActiveLeads_OneUserThreeMachines_ReturnsTwo() LeaseBuilder.For( license ).User( "alice" ).Machine( machine ).AddTo( context ); } - Assert.Equal( 2, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + Assert.Equal( 2, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); } [Fact] - public async Task GetActiveLeads_TwoUsers_ReturnsTwo() + public async Task GetActiveSeats_TwoUsers_ReturnsTwo() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).AddTo( context ); LeaseBuilder.For( license ).User( "bob" ).AddTo( context ); - Assert.Equal( 2, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + Assert.Equal( 2, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); } [Fact] - public async Task GetActiveLeads_LeaseStartingExactlyNow_IsCounted() + public async Task GetActiveSeats_LeaseStartingExactlyNow_IsCounted() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); - Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Origin ) ); + Assert.Equal( 1, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Origin ) ); } [Fact] - public async Task GetActiveLeads_LeaseEndingExactlyNow_IsNotCounted() + public async Task GetActiveSeats_LeaseEndingExactlyNow_IsNotCounted() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).From( TestClock.Origin ).Lasting( 3 ).AddTo( context ); - Assert.Equal( 0, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 3 ) ) ); + Assert.Equal( 0, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 3 ) ) ); } [Fact] - public async Task GetActiveLeads_OtherLicense_IsNotCounted() + public async Task GetActiveSeats_OtherLicense_IsNotCounted() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License first = LicenseBuilder.Default().WithLicenseId( 1 ).AddTo( context ); @@ -91,12 +91,12 @@ public async Task GetActiveLeads_OtherLicense_IsNotCounted() LeaseBuilder.For( second ).AddTo( context ); - Assert.Equal( 0, context.Repository.GetActiveLeads( first.LicenseId, TestClock.Days( 1 ) ) ); - Assert.Equal( 1, context.Repository.GetActiveLeads( second.LicenseId, TestClock.Days( 1 ) ) ); + Assert.Equal( 0, context.Repository.GetActiveSeats( first.LicenseId, TestClock.Days( 1 ) ) ); + Assert.Equal( 1, context.Repository.GetActiveSeats( second.LicenseId, TestClock.Days( 1 ) ) ); } [Fact] - public async Task GetActiveLeads_ReplacedLease_IsNotCounted() + public async Task GetActiveSeats_ReplacedLease_IsNotCounted() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); @@ -105,7 +105,7 @@ public async Task GetActiveLeads_ReplacedLease_IsNotCounted() context.Repository.CancelLease( original, "admin", TestClock.Days( 1 ) ); await context.Repository.SaveChangesAsync(); - Assert.Equal( 0, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 2 ) ) ); + Assert.Equal( 0, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 2 ) ) ); } /// @@ -113,18 +113,18 @@ public async Task GetActiveLeads_ReplacedLease_IsNotCounted() /// match, so that a test cannot pass here and fail in production. /// [Fact] - public async Task GetActiveLeads_UserNameCasingDiffers_CountsAsOneUser() + public async Task GetActiveSeats_UserNameCasingDiffers_CountsAsOneUser() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); LeaseBuilder.For( license ).User( "ALICE" ).Machine( "laptop-1" ).AddTo( context ); - Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + Assert.Equal( 1, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); } [Fact] - public async Task GetActiveLeads_HonoursMachinesPerUser() + public async Task GetActiveSeats_HonoursMachinesPerUser() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync( o => o.MachinesPerUser = 1 ); @@ -134,6 +134,31 @@ public async Task GetActiveLeads_HonoursMachinesPerUser() LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).AddTo( context ); // With one machine per seat, the same user on two machines consumes two seats. - Assert.Equal( 2, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + Assert.Equal( 2, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); + } + + /// + /// A seat is counted from the machines a user works on, not from the leases they hold. A user can + /// hold two leases on one machine, and charging them for a machine they do not have would deny a + /// colleague a lease the license has the capacity for. + /// + /// + /// The lease service normally prevents a second lease on one machine by prolonging the first, but + /// a server whose clock has moved backwards grants one. A load simulation produced exactly that + /// within minutes of a restart. + /// + [Fact] + public async Task GetActiveSeats_TwoLeasesOnOneMachine_CountAsOneMachine() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + License license = LicenseBuilder.Default().AddTo( context ); + + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "desktop-1" ).AddTo( context ); + LeaseBuilder.For( license ).User( "alice" ).Machine( "laptop-1" ).AddTo( context ); + + // Two machines at two machines per seat is one seat. Counting the three leases would make it + // two. + Assert.Equal( 1, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); } } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs index f6eb2ad..62deb97 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseAllocationTests.cs @@ -85,7 +85,7 @@ public async Task GetLease_SecondMachineForTheSameUser_DoesNotConsumeASeat() await context.Repository.SaveChangesAsync(); Assert.NotNull( lease ); - Assert.Equal( 1, context.Repository.GetActiveLeads( license.LicenseId, TestClock.Days( 1 ) ) ); + Assert.Equal( 1, context.Repository.GetActiveSeats( license.LicenseId, TestClock.Days( 1 ) ) ); } [Fact] diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs index a72a3d4..9dccb61 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/LeaseCountingPointsTests.cs @@ -4,7 +4,7 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// The usage timeline behind the graph: a sequence of lease open and close events, each carrying the -/// running number of seats and of users. +/// running seat count. /// public sealed class LeaseCountingPointsTests { @@ -24,13 +24,13 @@ public async Task GetLeaseCountingPoints_OneLease_OpensThenCloses() Assert.Equal( 2, points.Count ); Assert.Equal( LeaseCountingPointKind.Open, points[0].Kind ); - Assert.Equal( 1, points[0].LeaseCount ); + Assert.Equal( 1, points[0].SeatCount ); Assert.Equal( LeaseCountingPointKind.Close, points[1].Kind ); - Assert.Equal( 0, points[1].LeaseCount ); + Assert.Equal( 0, points[1].SeatCount ); } [Fact] - public async Task GetLeaseCountingPoints_ThreeUsersOneMachineEach_AreThreeUsers() + public async Task GetLeaseCountingPoints_ThreeUsersOneMachineEach_AreThreeSeats() { await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); License license = LicenseBuilder.Default().AddTo( context ); @@ -43,13 +43,12 @@ public async Task GetLeaseCountingPoints_ThreeUsersOneMachineEach_AreThreeUsers( List points = Timeline( context, license ); - Assert.Equal( 3, points.Max( p => p.UserCount ) ); - Assert.Equal( 3, points.Max( p => p.LeaseCount ) ); - Assert.Equal( 0, points[^1].UserCount ); + Assert.Equal( 3, points.Max( p => p.SeatCount ) ); + Assert.Equal( 0, points[^1].SeatCount ); } /// - /// A user who gives up one machine and keeps another is still counted, and leaves the count only + /// A user who gives up one machine and keeps another still holds their seat, and releases it only /// when the last of their leases ends. /// [Fact] @@ -66,10 +65,10 @@ public async Task GetLeaseCountingPoints_UserKeepingOneMachine_StaysCounted() List points = Timeline( context, license ); - // The first lease closes on day one and the second on day five. The user is counted - // throughout and leaves only at the last point. - Assert.All( points[..^1], p => Assert.Equal( 1, p.UserCount ) ); - Assert.Equal( 0, points[^1].UserCount ); + // The first lease closes on day one and the second on day five. The seat is held throughout + // and released only at the last point. + Assert.All( points[..^1], p => Assert.Equal( 1, p.SeatCount ) ); + Assert.Equal( 0, points[^1].SeatCount ); } [Fact] @@ -85,8 +84,7 @@ public async Task GetLeaseCountingPoints_OneUserTwoMachines_NeverExceedsOneSeat( List points = Timeline( context, license ); - Assert.Equal( 1, points.Max( p => p.LeaseCount ) ); - Assert.Equal( 1, points.Max( p => p.UserCount ) ); + Assert.Equal( 1, points.Max( p => p.SeatCount ) ); } [Fact] @@ -103,10 +101,9 @@ public async Task GetLeaseCountingPoints_OneUserThreeMachines_ReachesTwoSeats() List points = Timeline( context, license ); - // Two seats but one person. This is the difference between the two counts each point carries, - // and the reason the chart draws the users. - Assert.Equal( 2, points.Max( p => p.LeaseCount ) ); - Assert.Equal( 1, points.Max( p => p.UserCount ) ); + // One user on three machines is two seats: one seat covers two machines, and the third takes + // a second seat. + Assert.Equal( 2, points.Max( p => p.SeatCount ) ); } /// @@ -131,7 +128,7 @@ public async Task GetLeaseCountingPoints_CloseIsProcessedBeforeOpenAtTheSameInst List points = Timeline( context, license ); // With one machine per seat, a transient double-count would show up as 2. - Assert.Equal( 1, points.Max( p => p.LeaseCount ) ); + Assert.Equal( 1, points.Max( p => p.SeatCount ) ); LeaseCountingPoint[] atHandover = points.Where( p => p.Time == TestClock.Days( 1 ) ).ToArray(); Assert.Equal( 2, atHandover.Length ); @@ -176,7 +173,7 @@ public async Task GetLeaseCountingPoints_IsDeterministic() string First() => string.Join( "|", - Timeline( context, license ).Select( p => $"{p.Time:O}/{p.Kind}/{p.Lease.LeaseId}/{p.LeaseCount}" ) ); + Timeline( context, license ).Select( p => $"{p.Time:O}/{p.Kind}/{p.Lease.LeaseId}/{p.SeatCount}" ) ); Assert.Equal( First(), First() ); } @@ -238,10 +235,8 @@ public async Task GetLeaseCountingPoints_TwoOpenLeasesOnOneMachine_CountAsOneSea List points = Timeline( context, license ); Assert.Equal( 4, points.Count ); - Assert.Equal( 1, points.Max( p => p.LeaseCount ) ); - Assert.Equal( 1, points.Max( p => p.UserCount ) ); - Assert.Equal( 0, points[^1].LeaseCount ); - Assert.Equal( 0, points[^1].UserCount ); + Assert.Equal( 1, points.Max( p => p.SeatCount ) ); + Assert.Equal( 0, points[^1].SeatCount ); } [Fact] @@ -259,7 +254,7 @@ public async Task GetLeaseCountingPoints_ReturnsToZeroAfterEveryLeaseEnds() List points = Timeline( context, license ); Assert.NotEmpty( points ); - Assert.Equal( 0, points[^1].LeaseCount ); - Assert.All( points, p => Assert.True( p.LeaseCount >= 0 ) ); + Assert.Equal( 0, points[^1].SeatCount ); + Assert.All( points, p => Assert.True( p.SeatCount >= 0 ) ); } } diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs index abae0f3..02afaf8 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs @@ -96,7 +96,7 @@ public async Task Graph_EmbeddedDataIsValidJsonCoveringTheWindow() JsonElement root = document.RootElement; Assert.Equal( 90, root.GetProperty( "labels" ).GetArrayLength() ); - Assert.Equal( 90, root.GetProperty( "users" ).GetArrayLength() ); + Assert.Equal( 90, root.GetProperty( "seats" ).GetArrayLength() ); Assert.Equal( 10, root.GetProperty( "maximum" ).GetInt32() ); Assert.Equal( 12, root.GetProperty( "grace" ).GetInt32() ); Assert.True( root.GetProperty( "axisMaximum" ).GetInt32() >= 12 ); From a4c241a3fe48f872010e681d522aab7daa230a11 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 12:32:30 +0200 Subject: [PATCH 27/44] State on the details page what a seat is The page reports a number of seats, so it now says what one is. The rule is stated with the number of machines this server is configured with rather than with the default, so an installation that changed the setting is not told something untrue. ConcurrentUsers on the page model becomes Seats, which is what it has always held. Co-Authored-By: Claude Opus 5 --- .../Pages/Admin/Details.cshtml | 8 +++- .../Pages/Admin/Details.cshtml.cs | 13 ++++++- .../wwwroot/css/site.css | 7 ++++ .../PageTests.cs | 37 ++++++++++++++++++- 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml index 8ba0281..cca7ab7 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml @@ -11,10 +11,16 @@

@Model.Leases.Count current lease(s), consuming - @Model.ConcurrentUsers seat(s). + @Model.Seats seat(s). See the usage history.

+

+ A seat is one user together with the machines that user works on, up to + @Model.MachinesPerSeat of them. A user working on more machines consumes one seat per + @Model.MachinesPerSeat machine(s), rounded up. The capacity of a license is a number of seats. +

+ @if ( Model.Leases.Count == 0 ) {

No lease is currently held against this license.

diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs index 0d1dccb..77d41bc 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs @@ -1,7 +1,9 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Options; namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; @@ -11,6 +13,7 @@ namespace SharpCrafters.Backstage.LicenseServer.Pages.Admin; public sealed class DetailsModel( ILeaseRepository repository, LicenseServerDbContext db, + IOptions options, TimeProvider timeProvider ) : PageModel { [BindProperty( SupportsGet = true )] @@ -18,7 +21,13 @@ public sealed class DetailsModel( public IReadOnlyList Leases { get; private set; } = []; - public int ConcurrentUsers { get; private set; } + public int Seats { get; private set; } + + /// + /// Gets the number of machines one seat covers, so that the page states the rule with the value + /// this server is configured with rather than with the default. + /// + public int MachinesPerSeat => options.Value.MachinesPerUser; public bool IsDisabled { get; private set; } @@ -41,7 +50,7 @@ public async Task OnGetAsync( CancellationToken cancellationToken .AsNoTracking() .ToListAsync( cancellationToken ); - this.ConcurrentUsers = repository.GetActiveSeats( this.Id, now ); + this.Seats = repository.GetActiveSeats( this.Id, now ); this.IsDisabled = license.Priority < 0; return this.Page(); diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css index 92fabd0..f108fe0 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css @@ -342,6 +342,13 @@ input:focus, select:focus, textarea:focus { /* --- Callouts ----------------------------------------------------------- */ +/* An explanation of a term used just above it: present, but quieter than what it explains. */ +.note { + max-width: 68ch; + font-size: 14px; + color: var(--text-muted); +} + /* The website's note callout: a coloured left rule on a raised panel. */ .callout { padding: 20px 24px; diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs index 02afaf8..2a74417 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs @@ -1,5 +1,6 @@ using System.Net; using System.Text.Json; +using System.Text.RegularExpressions; using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; namespace SharpCrafters.Backstage.LicenseServer.Tests; @@ -7,10 +8,17 @@ namespace SharpCrafters.Backstage.LicenseServer.Tests; /// /// The pages an administrator uses, exercised through the real pipeline. /// -public sealed class PageTests : IDisposable +public sealed partial class PageTests : IDisposable { private readonly LicenseServerApplication application = new(); + /// + /// Collapses every run of whitespace, so that an assertion on a sentence does not depend on where + /// the markup wraps it. + /// + [GeneratedRegex( @"\s+" )] + private static partial Regex Whitespace(); + public void Dispose() => this.application.Dispose(); [Theory] @@ -59,6 +67,33 @@ public async Task Details_IsServed() Assert.Equal( HttpStatusCode.OK, response.StatusCode ); } + /// + /// The page states what a seat is, because it reports a number of them. The rule is stated with + /// the value this server is configured with, so an installation that changed it is not told the + /// default. + /// + [Fact] + public async Task Details_ExplainsWhatASeatIs() + { + this.application.AddLicense( LicenseBuilder.Default().WithLicenseId( 3 ) ); + HttpClient client = this.application.CreateClient(); + + // The markup wraps the sentence over several lines, and where it wraps is not what this test + // is about. + string body = Whitespace().Replace( await client.GetStringAsync( "/Admin/Details?id=3" ), " " ); + + // LicenseServerApplication configures two machines per seat. + Assert.Contains( + "A seat is one user together with the machines that user works on, up to 2 of them.", + body, + StringComparison.Ordinal ); + + Assert.Contains( + "consumes one seat per 2 machine(s), rounded up", + body, + StringComparison.Ordinal ); + } + [Fact] public async Task Details_UnknownLicense_Returns404() { From 424e4bbba27c94721a47c779c607b2e512fafdc0 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 12:35:58 +0200 Subject: [PATCH 28/44] Sample the series as lines in the chart legend The legend drew a filled rectangle for each series, which matches neither the shape of what is on the chart nor the dash pattern that tells the capacity apart from the grace allowance. Each sample is now a line. The dash pattern and the width are carried over from the dataset explicitly, because the legend item Chart.js generates does not take them: without that the three samples are solid lines differing only in colour, while the lines they stand for are solid, dashed and dotted. Co-Authored-By: Claude Opus 5 --- .../wwwroot/js/graph.js | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js index 31ee564..eee42de 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/graph.js @@ -80,7 +80,31 @@ } }, plugins: { - legend: { position: "bottom" }, + legend: { + position: "bottom", + + // Each series is a line, so the legend samples it as a line rather than as the + // filled rectangle Chart.js draws by default. The dash pattern and the width + // have to be carried over from the dataset, because the generated legend item + // does not take them, and without them the three samples differ only by colour + // while the lines on the chart are solid, dashed and dotted. + labels: { + usePointStyle: true, + pointStyle: "line", + boxWidth: 32, + generateLabels: function (instance) { + var items = Chart.defaults.plugins.legend.labels.generateLabels(instance); + + items.forEach(function (item) { + var dataset = instance.data.datasets[item.datasetIndex]; + item.lineDash = dataset.borderDash || []; + item.lineWidth = dataset.borderWidth || 2; + }); + + return items; + } + } + }, tooltip: { callbacks: { title: function (items) { return items[0].label; } From c2ce4009c13e1ec0bc5d4e09a7481d77624d9201 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 12:37:04 +0200 Subject: [PATCH 29/44] Rewrite the definition of a seat The sentence carried two ideas and a parenthesised plural: "one user together with the machines that user works on, up to 2 of them" and "one seat per 2 machine(s), rounded up". It now states one idea per sentence, in the plainest words the rule allows, and the noun agrees with the number so that a server configured with one machine per seat does not read "1 machines". The same wording is used wherever the rule is stated, so the product explains it one way: the details page, SeatCounter, the MachinesPerUser setting, and the configuration and protocol documents. Co-Authored-By: Claude Opus 5 --- docs/configuration.md | 12 ++++++------ docs/protocol.md | 9 ++++----- .../Data/SeatCounter.cs | 7 +++---- .../Options/LicenseServerOptions.cs | 4 ++-- .../Pages/Admin/Details.cshtml | 6 +++--- .../Pages/Admin/Details.cshtml.cs | 7 +++++++ .../PageTests.cs | 7 ++----- 7 files changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index f008657..fb06674 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,13 +40,13 @@ into `appsettings.json`. ## Licensing rules -A seat is one user together with the machines that user works on, up to `MachinesPerUser` of them. A -user working on more machines than that consumes one seat per `MachinesPerUser` machines, rounded up: -at the default of two, one or two machines are one seat, three or four are two seats, and so on. The -capacity of a license key is a number of seats, and the seat is the only unit the server counts in. +A seat is one user working on up to `MachinesPerUser` machines. A user working on more machines takes +more than one seat: the number of machines divided by `MachinesPerUser`, rounded up. At the default of +two, one or two machines are one seat and three or four are two. -The license agreement states the same rule the other way round, as a number of authorized users each -entitled to a number of devices. +The capacity of a license key is a number of seats, and the seat is the only unit the server counts +in. The license agreement states the same rule the other way round, as a number of authorized users +each entitled to a number of devices. | Setting | Default | Meaning | |---|---|---| diff --git a/docs/protocol.md b/docs/protocol.md index d8c0689..3fae610 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -187,11 +187,10 @@ skips a license whose priority is negative. For each license, in order: 3. A new lease is granted if the license has a free seat. 4. A new lease is granted beyond capacity if the license is within its grace period. -A seat is one user together with the machines that user works on, up to -`LicenseServer:MachinesPerUser` of them. A user working on more machines consumes one seat per that -many machines, rounded up, so at the default of two a user on one or two machines is one seat and a -user on three is two. The capacity of a license key is a number of seats, and the seat is the only -unit the server counts in. +A seat is one user working on up to `LicenseServer:MachinesPerUser` machines. A user working on more +machines takes more than one seat: the number of machines divided by that setting, rounded up. At the +default of two, one or two machines are one seat and three or four are two. The capacity of a license +key is a number of seats, and the seat is the only unit the server counts in. `EndTime` is `NewLeaseDays` from now, clamped to the expiry of the license key. `RenewTime` is `MinLeaseDays` before `EndTime`. The server refuses to start unless `MinLeaseDays` is smaller than diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs index 76c7e18..efd5956 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Data/SeatCounter.cs @@ -5,10 +5,9 @@ namespace SharpCrafters.Backstage.LicenseServer.Data; ///
/// /// -/// A seat is one user together with the machines that user works on, up to -/// MachinesPerUser of them. A user working on more machines than that consumes one seat per -/// MachinesPerUser machines, rounded up: at the default of two, one or two machines are one -/// seat, three or four are two seats, and so on. +/// A seat is one user working on up to MachinesPerUser machines. A user working on more +/// machines takes more than one seat: the number of machines divided by MachinesPerUser, +/// rounded up. At the default of two, one or two machines are one seat and three or four are two. /// /// /// The seat is the unit the capacity of a license key is expressed in, and the only place where the diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs index 074cea7..3b435a5 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs @@ -19,8 +19,8 @@ public sealed class LicenseServerOptions public int GracePeriodWarningDays { get; set; } = 1; /// - /// Gets or sets the number of machines that one seat covers. A user working on more machines than - /// this consumes one seat per this many machines, rounded up; see + /// Gets or sets the number of machines that one seat covers. A user working on more machines + /// takes more than one seat: the number of machines divided by this value, rounded up. See /// . The default value is 2. Check your license agreement for a /// different value. /// diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml index cca7ab7..0c04f78 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml @@ -16,9 +16,9 @@

- A seat is one user together with the machines that user works on, up to - @Model.MachinesPerSeat of them. A user working on more machines consumes one seat per - @Model.MachinesPerSeat machine(s), rounded up. The capacity of a license is a number of seats. + A seat is one user working on up to @Model.MachinesPerSeatText. A user working on more machines + takes more than one seat: the number of machines divided by @Model.MachinesPerSeat, rounded up. + The capacity of a license is a number of seats.

@if ( Model.Leases.Count == 0 ) diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs index 77d41bc..16315f4 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml.cs @@ -29,6 +29,13 @@ public sealed class DetailsModel( ///
public int MachinesPerSeat => options.Value.MachinesPerUser; + /// + /// Gets with its noun, so that a server configured with one machine + /// per seat reads as "1 machine" and not as "1 machines". + /// + public string MachinesPerSeatText + => this.MachinesPerSeat == 1 ? "1 machine" : $"{this.MachinesPerSeat} machines"; + public bool IsDisabled { get; private set; } public async Task OnGetAsync( CancellationToken cancellationToken ) diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs index 2a74417..1c5627a 100644 --- a/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/PageTests.cs @@ -83,13 +83,10 @@ public async Task Details_ExplainsWhatASeatIs() string body = Whitespace().Replace( await client.GetStringAsync( "/Admin/Details?id=3" ), " " ); // LicenseServerApplication configures two machines per seat. - Assert.Contains( - "A seat is one user together with the machines that user works on, up to 2 of them.", - body, - StringComparison.Ordinal ); + Assert.Contains( "A seat is one user working on up to 2 machines.", body, StringComparison.Ordinal ); Assert.Contains( - "consumes one seat per 2 machine(s), rounded up", + "the number of machines divided by 2, rounded up", body, StringComparison.Ordinal ); } From fb26834d2db3c77d4a2398979c372e705d4217de Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 12:40:22 +0200 Subject: [PATCH 30/44] Stop the manage panel from rendering as an empty box The panel was a paragraph containing forms. A paragraph cannot contain a form, so the browser closed it at the first one: the panel rendered as an empty bordered rectangle and the buttons and the sentence below it landed outside, unstyled. It is a div now. The actions class it carries had no rule at all, so the two buttons of a disabled license stacked. It now lays them out in a row with the sentence that explains them. Co-Authored-By: Claude Opus 5 --- .../Pages/Admin/Details.cshtml | 8 +++++--- .../wwwroot/css/site.css | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml index 0c04f78..2a6a895 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml @@ -60,7 +60,9 @@ else

Manage this license

-

+@* A div and not a paragraph: a paragraph cannot contain a form, so the browser closed it at the + first form and left an empty panel behind, with the buttons outside it. *@ +

@if ( Model.IsDisabled ) { @@ -78,6 +80,6 @@ else - A disabled license serves no new lease, and can then be deleted. +

A disabled license serves no new lease, and can then be deleted.

} -

+
diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css index f108fe0..cc3eac2 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/css/site.css @@ -342,6 +342,18 @@ input:focus, select:focus, textarea:focus { /* --- Callouts ----------------------------------------------------------- */ +/* A row of buttons that act on the thing the page is about, with the sentence that explains them. */ +.actions { + display: flex; + flex-wrap: wrap; + gap: 16px; + align-items: center; +} + +.actions .note { + margin: 0; +} + /* An explanation of a term used just above it: present, but quieter than what it explains. */ .note { max-width: 68ch; From 644434952ff53d0097768b5004834b34b92b9398 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 12:59:24 +0200 Subject: [PATCH 31/44] Answer the review on the two documents Authentication: a lease request is anonymous by design, and authentication is there to gate the administrative interface. The protocol document had it the other way round, as though authentication existed to attribute leases. Securing the administrative pages: the configuration document now says that it is the administrator's responsibility and how to do it, with the roles setting and with a URL authorization rule under IIS for whoever does not authenticate against a domain. The installation steps point at it. The machine argument: the claim that a machine name ending in hexadecimal is shortened was wrong. Exactly one group is stripped and every client appends the hash, so such a name survives. What is true is that the name and the hash travel in one argument and the server has to find the hash by its shape. Co-Authored-By: Claude Opus 5 --- README.md | 9 ++++++--- docs/configuration.md | 38 ++++++++++++++++++++++++++++++++++---- docs/protocol.md | 27 ++++++++++++++++++--------- 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 75e636e..4a13b11 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,12 @@ anything other than a trial. 3. Unpack `SharpCrafters.Backstage.LicenseServer..zip` into the directory of an IIS application. 4. Edit `appsettings.json`: set the connection string, the notification e-mail addresses and the SMTP server. The settings are described in [docs/configuration.md](docs/configuration.md). -5. In IIS Manager, enable **Windows Authentication** on the application and disable - **Anonymous Authentication** if you want every lease request to be attributed to a user. -6. Browse to the application and add your license key. +5. In IIS Manager, enable Windows Authentication on the application if you want every lease request + to be attributed to a user. Lease requests are served anonymously either way. +6. Restrict the administrative pages. They are open by default, and closing them is your + responsibility; see + [Securing the administrative pages](docs/configuration.md#securing-the-administrative-pages). +7. Browse to the application and add your license key. ## Installing elsewhere diff --git a/docs/configuration.md b/docs/configuration.md index fb06674..cec4eda 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -93,10 +93,40 @@ Windows, and `None` elsewhere, and logs which one it chose. Set the value explic care about: guessing wrong is quiet rather than loud, because a server that authenticates nobody still serves leases perfectly well — it just cannot say who took them. -**Both default to open**, which is how the license server has always shipped, so that an upgrade -cannot lock an administrator out of their own server. The administrative pages are the only way to -add or revoke a license, so setting `AdminRoles` is worth doing; until it is set, the server says so -in its log every time it starts. +### Securing the administrative pages + +Securing the administrative pages is the administrator's responsibility. The server does not do it on +its own: `AdminRoles` and `RequireAuthenticatedLeaseRequests` both default to open, which is how the +license server has always shipped, so that an upgrade cannot lock an administrator out of their own +server. The server writes a warning to its log at every start until `AdminRoles` is set. + +Closing them is worth doing. The administrative pages are the only way to add or revoke a license, +and the export at `/Admin/Export.ashx` hands over the whole audit log. There are two ways to close +them, and they can be combined. + +The first is `LicenseServer:AdminRoles`. The check covers every page under `/Admin` and the export +endpoint. It needs an authentication scheme that supplies the Windows groups, so it works with +`IISIntegrated` and with `Negotiate`, and not with `None`. + +The second is to restrict the path at the web server, which works whatever the scheme. Under IIS, +enable Windows authentication on the site and add a URL authorization rule for the `Admin` path to +the `web.config` of the application, which is in the published output: + +```xml + + + + + + + + + + +``` + +This needs the URL Authorization role service of IIS, which is not installed by default. Behind any +other web server, and in a container, restrict the path in whatever sits in front of the application. ## Concurrency diff --git a/docs/protocol.md b/docs/protocol.md index 3fae610..c91088a 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -31,13 +31,18 @@ such a URL. ### Authentication -The server answers an anonymous request by default, which is how it has always shipped. A client -sends the credentials of the current user unless it is told otherwise, because an on-premises server -published by IIS with Windows authentication answers 401 to an anonymous request. An installation -that enables Windows authentication therefore records who borrowed each lease. +A lease request is anonymous by design. The server answers one that carries no credentials, and that +is how the protocol is meant to be used. A client sends the credentials of the current user all the +same, unless it is told otherwise, because a server published by IIS with Windows authentication +answers 401 to an anonymous request. When credentials do arrive, the server records who borrowed each +lease. + +Authentication is there to gate the administrative interface. The pages under `/Admin` and the audit +log export are the part that needs closing, and securing them is the administrator's responsibility; +see [configuration.md](configuration.md). Setting `LicenseServer:RequireAuthenticatedLeaseRequests` makes the server refuse an anonymous lease -request. See [configuration.md](configuration.md). +request as well. It is off by default. The protocol carries the name of the user and the name of the machine in the query string, so a server reached over plain HTTP transmits both in cleartext. A client warns about that and serves the @@ -71,10 +76,14 @@ identifier. The hash distinguishes two machines that carry the same name, which virtual machines and with build agents created from an image. The suffix is load-bearing on the server side as well. Before the server compares a machine name to -its list of build servers it strips a trailing `-` followed by hexadecimal digits, so -`buildagent-1f2e` matches the configured name `buildagent`. A machine name that legitimately ends in -a hyphen and hexadecimal digits is therefore shortened as well, which is a limitation of the format -rather than of this implementation. +its list of build servers it strips one trailing `-` followed by hexadecimal digits, so +`buildagent-1f2e` matches the configured name `buildagent`. Exactly one group is removed and every +client appends the hash, so a machine whose own name ends in hexadecimal keeps it: `build-01` arrives +as `build-01-1f2e` and is compared as `build-01`. + +The server has to find the hash by its shape, because the name and the hash travel in one argument. +Sending them separately would remove the guess, and could only be added beside this argument rather +than in place of it, since deployed clients send what they send. #### The version argument From 64043545b5cc011fea761f644af706d8bb579459 Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 13:15:33 +0200 Subject: [PATCH 32/44] Make the container a deployment, and keep its data out of it The compose file described itself as a test deployment and behaved like one: the database password sat in it in plain text and the audit signing key was a fixed value, so every deployment made from it shared one. It is now an ordinary deployment. The password comes from MSSQL_SA_PASSWORD in the environment and compose refuses to start without it, and the server generates its own audit signing key. docker-compose.test.yml is the opt-in: applied beside the main file, it puts the server in the Development environment, sets LicenseServer:SeedTestLicenses and accelerates the clock. SeedTestLicenses has the server issue itself the license keys it serves, which is what a trial and a load simulation need and what no production authority will ever sign for them. The server generates a licensing authority into its data directory, trusts it and adds one license key per product family. It refuses to start with the setting outside the Development environment, as it already did for TestLicensingAuthorities. The data directory is what has to outlive the container. It holds the audit signing key, whose loss restarts the signature chain, and now the test authority, whose loss stops the license keys already in the database from verifying. The image declares /app/App_Data as a volume so that a container started without a mount does not keep them in its own writable layer, and LicenseServer:DataDirectory moves the directory elsewhere. Verified against Docker under WSL: the default stack comes up with no license and a clock at 1, the test override seeds both licenses and serves a lease, and both keys survive the container being replaced with the volume kept. Co-Authored-By: Claude Opus 5 --- Dockerfile | 12 +- README.md | 11 +- docker-compose.test.yml | 28 +++ docker-compose.yml | 42 +++-- docs/configuration.md | 24 +++ docs/docker.md | 62 +++++-- .../Licensing/TestLicenseAuthority.cs | 113 ++++++++++++ .../Licensing/TestLicenseSeeder.cs | 87 ++++++++++ .../Options/LicenseServerOptions.cs | 20 +++ .../LicensingRegistration.cs | 70 ++++++-- .../Program.cs | 23 ++- .../SeedTestLicensesTests.cs | 163 ++++++++++++++++++ 12 files changed, 603 insertions(+), 52 deletions(-) create mode 100644 docker-compose.test.yml create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseAuthority.cs create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseSeeder.cs create mode 100644 tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs diff --git a/Dockerfile b/Dockerfile index e8c6349..587f8b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,8 @@ # ./Build.ps1 build # docker compose up # -# See docker-compose.yml for a ready-to-run deployment with a SQL Server database. +# See docker-compose.yml for a deployment with a SQL Server database, and docs/docker.md for what to +# change before running it for real. # # The build is not done inside the image on purpose. The licensing component comes from a private # feed and the build needs the generated global.json and nuget.config that `./Build.ps1 prepare` @@ -15,8 +16,6 @@ FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime WORKDIR /app -# The audit signing key is written here on first start. Mount a volume over it, or the signature -# chain restarts whenever the container is replaced. RUN mkdir -p /app/App_Data && useradd --uid 64198 --create-home licenseserver \ && chown -R licenseserver /app USER licenseserver @@ -24,6 +23,13 @@ USER licenseserver # Written by the PackAndZip target of the web project; it is what the release archive contains. COPY --chown=licenseserver artifacts/app/ ./ +# The server generates the audit signing key here on first start, and the audit log signature chain +# restarts if it is lost. Declaring the volume means a container started without an explicit mount +# still keeps the key somewhere outside its own writable layer, rather than losing it silently when +# the container is replaced. Name the volume in production: an anonymous one is easy to prune by +# accident. LicenseServer:DataDirectory moves the directory elsewhere. +VOLUME ["/app/App_Data"] + ENV ASPNETCORE_HTTP_PORTS=8080 EXPOSE 8080 diff --git a/README.md b/README.md index 4a13b11..c8cdc4c 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ The quickest way to see the license server working, on any machine with Docker, deployment. It starts the server, a SQL Server database and a job that creates the schema: ``` +export MSSQL_SA_PASSWORD='...' ./Build.ps1 build docker compose up ``` @@ -42,8 +43,14 @@ docker compose up The image carries the contents of the release archive, so the build comes first and the container runs exactly what is released. -Then open http://localhost:8080 and add your license key. See -[docs/docker.md](docs/docker.md) for what it contains and what to change before using it for +Then open http://localhost:8080 and add your license key. If you have none to hand, the test +override has the server issue itself the keys it serves: + +``` +docker compose -f docker-compose.yml -f docker-compose.test.yml up +``` + +See [docs/docker.md](docs/docker.md) for what it contains and what to change before using it for anything other than a trial. ## Installing on IIS diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..d22089a --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,28 @@ +# Turns the deployment in docker-compose.yml into one that can be exercised without buying a +# license. Apply it beside the main file, never on its own: +# +# docker compose -f docker-compose.yml -f docker-compose.test.yml up +# +# What it changes: the server runs in the Development environment, issues itself the license keys it +# serves, and runs its clock 1440 times faster than real time so that a fortnight of leases fits into +# a coffee break. The license keys are signed by a licensing authority the server generates into its +# own data volume, and no other server accepts them. +# +# The server refuses to start with these settings outside the Development environment, so this file +# cannot quietly turn a real deployment into a test one. + +services: + + licenseserver: + environment: + ASPNETCORE_ENVIRONMENT: Development + + # Generates a licensing authority in the data volume and adds a license key per product family. + LicenseServer__SeedTestLicenses: "true" + + # One real minute is one virtual day. See the license server protocol for how a client follows + # the accelerated clock through GetTime.ashx. + LicenseServer__TimeAcceleration: "1440" + + # The machine names that LicenseServerLoadSimulator gives its build agents. + LicenseServer__BuildServers: server diff --git a/docker-compose.yml b/docker-compose.yml index 677b9f9..8cb469c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,20 @@ -# A complete PostSharp License Server for testing: the server, a SQL Server database, and a one-shot -# job that creates the schema from Database/CreateTables.sql. +# The license server and a SQL Server database, with the schema created from CreateTables.sql. # -# docker compose up --build -# open http://localhost:8080 +# export MSSQL_SA_PASSWORD='...' +# ./Build.ps1 build +# docker compose up # -# This is a TEST deployment. The password below is in plain text, the database is thrown away with -# its volume, and nobody is authenticated, so every lease is recorded without a user name. See -# docs/configuration.md before running it for real. +# This deployment serves the license keys you add to it, and holds nothing that is not yours. To +# bring it up with license keys it issues to itself instead, which is what a trial or a load +# simulation wants, add the test override: +# +# docker compose -f docker-compose.yml -f docker-compose.test.yml up +# +# Read docs/docker.md before running either for anything that matters. The database password is +# taken from the environment and the server has no administrator credentials of its own, so securing +# the pages under /Admin is still yours to do; see docs/configuration.md. -name: postsharp-license-server +name: backstage-license-server services: @@ -16,7 +22,7 @@ services: image: mcr.microsoft.com/mssql/server:2022-latest environment: ACCEPT_EULA: "Y" - MSSQL_SA_PASSWORD: "LicenseServer!2026" + MSSQL_SA_PASSWORD: ${MSSQL_SA_PASSWORD:?set MSSQL_SA_PASSWORD in the environment} MSSQL_PID: Developer ports: - "1433:1433" @@ -38,6 +44,8 @@ services: depends_on: database: condition: service_healthy + environment: + MSSQL_SA_PASSWORD: ${MSSQL_SA_PASSWORD:?set MSSQL_SA_PASSWORD in the environment} volumes: - ./src/SharpCrafters.Backstage.LicenseServer.Web/Database:/schema:ro entrypoint: @@ -46,13 +54,13 @@ services: - | set -e SQLCMD=/opt/mssql-tools18/bin/sqlcmd - $$SQLCMD -S database -U sa -P "LicenseServer!2026" -C -b \ + $$SQLCMD -S database -U sa -P "$$MSSQL_SA_PASSWORD" -C -b \ -Q "IF DB_ID('PostSharpLicenseServer') IS NULL CREATE DATABASE PostSharpLicenseServer" # Re-running compose must not fail on an existing schema. - TABLES=$$($$SQLCMD -S database -U sa -P "LicenseServer!2026" -C -h -1 -W -d PostSharpLicenseServer \ + TABLES=$$($$SQLCMD -S database -U sa -P "$$MSSQL_SA_PASSWORD" -C -h -1 -W -d PostSharpLicenseServer \ -Q "SET NOCOUNT ON; SELECT COUNT(*) FROM sys.tables WHERE name = 'Licenses'") if [ "$$TABLES" = "0" ]; then - $$SQLCMD -S database -U sa -P "LicenseServer!2026" -C -b -d PostSharpLicenseServer -i /schema/CreateTables.sql + $$SQLCMD -S database -U sa -P "$$MSSQL_SA_PASSWORD" -C -b -d PostSharpLicenseServer -i /schema/CreateTables.sql echo "Schema created." else echo "Schema already present." @@ -69,13 +77,15 @@ services: environment: LicenseServer__DatabaseProvider: SqlServer ConnectionStrings__SharpCrafters_LicenseServerConnectionString: >- - Server=database,1433;Database=PostSharpLicenseServer;User Id=sa;Password=LicenseServer!2026;TrustServerCertificate=True - # No domain controller in a container, so nobody is authenticated. + Server=database,1433;Database=PostSharpLicenseServer;User Id=sa;Password=${MSSQL_SA_PASSWORD:?set MSSQL_SA_PASSWORD in the environment};TrustServerCertificate=True + # A container has no domain controller to authenticate against, so leases record no user name. + # Whatever fronts the container has to restrict the pages under /Admin. Authentication__Scheme: None - # Keeps the audit signature chain stable across restarts. - LicenseServer__AuditHmacKey: "dGVzdC1vbmx5LWF1ZGl0LWtleS1kby1ub3QtdXNlLWxpdmU=" Smtp__Enabled: "false" volumes: + # The audit signing key lives here. It has to outlive the container: losing it does not + # invalidate the rows already written, but it does start a new signature chain. Back this + # volume up with the database. - licenseserver-data:/app/App_Data volumes: diff --git a/docs/configuration.md b/docs/configuration.md index cec4eda..b9913a0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -157,16 +157,40 @@ on first start and written to `App_Data\audit-signing.key`. Include that file in your backups and preserve it across upgrades. Losing it does not invalidate the rows already written, but it does start a new chain. +## Storage + +| Setting | Default | Meaning | +|---|---|---| +| `LicenseServer:DataDirectory` | `App_Data` | Where the server keeps the files it generates and must not lose. | + +The directory holds the audit signing key, and the test licensing authority when there is one. A +relative path is resolved against the application, so the default works wherever the archive is +unpacked. Set an absolute path to put the directory on storage of its own. + +In a container this directory has to be a volume. The image declares one at `/app/App_Data`, so the +files survive the container being replaced even when no mount is given; name the volume in a real +deployment and back it up with the database. See [docker.md](docker.md). + ## Testing | Setting | Default | Meaning | |---|---|---| | `LicenseServer:TimeAcceleration` | 1 | How much faster than real time the server's clock runs. | +| `LicenseServer:SeedTestLicenses` | `false` | Whether the server issues itself the license keys it serves. | | `LicenseServer:TestLicensingAuthorities` | empty | Licensing authorities whose license keys a development server accepts besides the production one. | +The server refuses to start with either of the last two set outside the Development environment. + Leave `TimeAcceleration` at 1. Any other value exists so that a multi-day licensing scenario can be replayed in minutes against a test server, and the server warns at startup when it is set. +`SeedTestLicenses` exists so that a trial or a load simulation has something to lease without anybody +buying a license first. The server generates a licensing authority of its own into +[`DataDirectory`](#storage), trusts it, and adds one license key per product family if the database +has none. No other server accepts those keys. The authority has to survive a restart, because the +license keys it signed are in the database and stop verifying when the key pair changes; in a +container that means the data directory has to be a volume. + `TestLicensingAuthorities` exists so that a load simulation can be run against license keys that nobody sells. Each entry carries the identifier that the signature of a license key names and the public half of the key pair, in the XML representation that SharpCrafters.Backstage reads: diff --git a/docs/docker.md b/docs/docker.md index 68a39b8..8757ea4 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -1,9 +1,10 @@ # Running the license server in a container -`docker-compose.yml` brings up a complete license server: the application, a SQL Server database, -and a one-shot job that creates the schema from `Database/CreateTables.sql`. +`docker-compose.yml` brings up a license server: the application, a SQL Server database, and a +one-shot job that creates the schema from `Database/CreateTables.sql`. ``` +export MSSQL_SA_PASSWORD='...' ./Build.ps1 build docker compose up ``` @@ -11,6 +12,12 @@ docker compose up The build comes first because the image carries the contents of the release archive rather than building the sources again, so the container runs exactly what is released. +The deployment serves the license keys you add to it and holds nothing else. The database password +comes from the environment rather than from a file in the repository, and the server generates its +own audit signing key into a volume. To bring it up with license keys it issues to itself, which is +what a trial or a load simulation wants, see +[Trying it out without a license key](#trying-it-out-without-a-license-key). + The server is then at http://localhost:8080 and the database at `localhost:1433`. Add a license key on the **Add a license** page and point a PostSharp client at `http://localhost:8080/Lease.ashx`. @@ -35,26 +42,51 @@ docker compose down --volumes | `database-schema` | Runs once: creates the database if it does not exist, then runs `CreateTables.sql` if the tables are not already there. Re-running `docker compose up` does not fail on an existing schema. It reuses the SQL Server image, which already carries `sqlcmd`, rather than pulling a second one. | | `licenseserver` | The application, from `Dockerfile`. Waits for the schema job to finish. | -The application keeps its audit signing key in the `licenseserver-data` volume, so the signature -chain survives the container being replaced. The database keeps its files in `database-data`. +The application keeps the files it generates and must not lose in the `licenseserver-data` volume, +mounted at `/app/App_Data`: the audit signing key, and the test licensing authority when there is +one. They have to outlive the container. Losing the audit signing key does not invalidate the rows +already written, but it does start a new signature chain, and losing the test authority stops the +license keys it signed from being accepted. The database keeps its files in `database-data`. + +The image declares `/app/App_Data` as a volume, so a container started with `docker run` and no +explicit mount still keeps these files outside its own writable layer. Name the volume in a real +deployment: an anonymous one is easy to prune by accident. `LicenseServer__DataDirectory` moves the +directory somewhere else. + +## Trying it out without a license key + +A server with no license key cannot serve a lease, and every key the production licensing authority +signs is one that was sold. The test override lets the server issue itself the keys it serves: + +``` +docker compose -f docker-compose.yml -f docker-compose.test.yml up +``` + +It puts the server in the Development environment, sets `LicenseServer__SeedTestLicenses`, and runs +the clock 1440 times faster than real time so that a fortnight of leases fits into a coffee break. +The server generates a licensing authority of its own into the data volume, trusts it, and adds one +license key per product family. No other server accepts those keys, and the server refuses to start +with either setting outside the Development environment, so the override cannot quietly turn a real +deployment into a test one. -## This is a test deployment +This is also what [LicenseServerLoadSimulator](protocol.md#reading-the-server-clock-gettimeashx) in +the SharpCrafters.Backstage repository expects. Drop the data volume between two simulations: the +virtual clock is anchored when the process starts, so a restart moves it backwards and leaves leases +dated in the future. -It is meant for trying the license server out and for development. Four things make it unsuitable as -it stands for anything else. +## Before you run it for real -- **The `sa` password is in the compose file in plain text**, and `sa` is what the application - connects as. A real deployment uses a login with rights on the one database, and supplies the - password from a secret rather than from a file in the repository. +- **The application connects as `sa`.** A real deployment uses a login with rights on the one + database. The password comes from `MSSQL_SA_PASSWORD` in the environment, and compose refuses to + start without it. - **Nobody is authenticated.** There is no domain controller in a container, so `Authentication__Scheme` is `None` and every lease is recorded without a user name. To attribute leases, run the container on a host joined to your domain with a Kerberos keytab and set the scheme to `Negotiate`. -- **The administrative pages are open**, as they are in the default configuration everywhere. Set - `LicenseServer__AdminRoles` once you have an identity to check against. -- **The audit signing key is a fixed value in the compose file**, so that restarting the stack does - not start a new signature chain. Remove it for a real deployment and let the server generate one - into the volume. +- **The administrative pages are open**, as they are in the default configuration everywhere. Nothing + in the container closes them. See + [Securing the administrative pages](configuration.md#securing-the-administrative-pages). +- **Back up the `licenseserver-data` volume with the database.** It holds the audit signing key. ## The image on its own diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseAuthority.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseAuthority.cs new file mode 100644 index 0000000..b53fe45 --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseAuthority.cs @@ -0,0 +1,113 @@ +using System.Security.Cryptography; +using Microsoft.Extensions.Logging; +using SharpCrafters.Backstage.Licensing; +using SharpCrafters.Backstage.Licensing.Licenses; + +namespace SharpCrafters.Backstage.LicenseServer.Licensing; + +/// +/// A licensing authority of its own, which a development server uses to issue the license keys it +/// serves to itself. +/// +/// +/// +/// A server that has no license key cannot serve a lease, and every key the production authority +/// signs is one that was sold. This authority fills that gap for a trial and for a load simulation: +/// it signs keys that only a server holding the same key pair accepts. +/// +/// +/// The key pair is generated on first use and kept in the data directory, next to the audit signing +/// key. It has to outlive the process, because the license keys it signed are in the database and +/// stop verifying when the pair changes. In a container that means the data directory has to be a +/// volume. +/// +/// +public sealed class TestLicenseAuthority +{ + /// + /// The identifier the signature of these license keys carries. It is outside the range of the + /// production keys, which are 0, 1 and 2. + /// + public const byte KeyId = 200; + + private readonly LicensingAuthority signingAuthority; + + /// + /// Gets the provider that verifies the license keys this authority signs, holding the public half + /// of the pair. It is what the license parser of the server is given. + /// + public ILicensingAuthorityProvider Authority { get; } + + private TestLicenseAuthority( ECParameters parameters ) + { + this.signingAuthority = CreateAuthority( ToXml( parameters, true ) ); + this.Authority = new ExplicitLicensingAuthorityProvider( (KeyId, ToXml( parameters, false )) ); + } + + /// + /// Reads the key pair from , generating and storing one when the + /// file is absent. + /// + public static TestLicenseAuthority LoadOrCreate( string keyFilePath, ILogger logger ) + { + ArgumentException.ThrowIfNullOrWhiteSpace( keyFilePath ); + ArgumentNullException.ThrowIfNull( logger ); + + using ECDsa key = ECDsa.Create( ECCurve.NamedCurves.nistP256 ); + + if ( File.Exists( keyFilePath ) ) + { + key.ImportECPrivateKey( Convert.FromBase64String( File.ReadAllText( keyFilePath ).Trim() ), out _ ); + + return new TestLicenseAuthority( key.ExportParameters( true ) ); + } + + Directory.CreateDirectory( Path.GetDirectoryName( keyFilePath )! ); + File.WriteAllText( keyFilePath, Convert.ToBase64String( key.ExportECPrivateKey() ) ); + + logger.LogInformation( + "Generated a test licensing authority in {Path}. The license keys it signs are accepted by " + + "this server alone, and stop being accepted if the file is lost.", + keyFilePath ); + + return new TestLicenseAuthority( key.ExportParameters( true ) ); + } + + /// + /// Signs a license key that this server accepts. + /// + public string CreateLicenseKey( + int licenseId, + LicenseProduct product, + LicenseType licenseType, + short userNumber, + byte graceDays, + byte gracePercent, + DateTime validTo ) + { + LicenseKeyDataBuilder builder = new() + { + LicenseId = licenseId, + Product = product, + LicenseType = licenseType, + UserNumber = userNumber, + GraceDays = graceDays, + GracePercent = gracePercent, + LicenseServerEligible = true, + ValidTo = validTo, + SubscriptionEndDate = validTo + }; + + return builder.SignAndSerialize( this.signingAuthority ); + } + + private static LicensingAuthority CreateAuthority( string keyXml ) + => new ExplicitLicensingAuthorityProvider( (KeyId, keyXml) ).GetAuthority( KeyId ); + + private static string ToXml( ECParameters parameters, bool includePrivateValue ) + => "nistP256" + + $"{Convert.ToBase64String( parameters.Q.X! )}" + + $"{Convert.ToBase64String( parameters.Q.Y! )}" + + (includePrivateValue ? $"{Convert.ToBase64String( parameters.D! )}" : string.Empty) + + ""; +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseSeeder.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseSeeder.cs new file mode 100644 index 0000000..51fa5e6 --- /dev/null +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Licensing/TestLicenseSeeder.cs @@ -0,0 +1,87 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.Licensing; + +namespace SharpCrafters.Backstage.LicenseServer.Licensing; + +/// +/// Puts license keys in the database of a development server, so that it can serve a lease without +/// anybody buying a license first. +/// +public static class TestLicenseSeeder +{ + /// + /// The licenses that are seeded. One per product family, so that a client naming a product + /// exercises the matching rather than falling back on "any product". + /// + private static readonly (int LicenseId, LicenseProduct Product, short Users)[] licenses = + [ + (900001, LicenseProduct.MetalamaProfessional, 25), + (900002, LicenseProduct.PostSharpUltimate, 25) + ]; + + /// + /// Adds the license keys that are missing, and leaves the ones that are there alone. + /// + /// The identifiers of the licenses that were added. + /// + /// Seeding is skipped for a license that is already present rather than replaced, so that a + /// server restarted against the same database keeps the leases it has granted. The keys are + /// signed by , whose key pair lives in the same data directory, + /// so both survive a restart together or are lost together. + /// + public static IReadOnlyList Seed( + LicenseServerDbContext db, + TestLicenseAuthority authority, + TimeProvider timeProvider, + ILogger logger ) + { + ArgumentNullException.ThrowIfNull( db ); + ArgumentNullException.ThrowIfNull( authority ); + ArgumentNullException.ThrowIfNull( timeProvider ); + ArgumentNullException.ThrowIfNull( logger ); + + DateTime now = timeProvider.GetUtcNow().UtcDateTime; + HashSet existing = [.. db.Licenses.Select( l => l.LicenseId )]; + List added = []; + + foreach ( (int licenseId, LicenseProduct product, short users) in licenses ) + { + if ( existing.Contains( licenseId ) ) + { + continue; + } + + db.Licenses.Add( + new License + { + LicenseId = licenseId, + ProductCode = ProductCodes.ForStorage( product ), + CreatedOn = now, + LicenseKey = authority.CreateLicenseKey( + licenseId, + product, + LicenseType.Business, + users, + graceDays: 5, + gracePercent: 20, + validTo: now.AddYears( 5 ) ) + } ); + + added.Add( licenseId ); + } + + if ( added.Count > 0 ) + { + db.SaveChanges(); + + logger.LogWarning( + "Added the test licenses {LicenseIds}, signed by this server's own test licensing " + + "authority. They are not licenses anybody sold, and no other server accepts them.", + string.Join( ", ", added ) ); + } + + return added; + } +} diff --git a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs index 3b435a5..dcb0063 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Core/Options/LicenseServerOptions.cs @@ -108,5 +108,25 @@ public sealed class LicenseServerOptions ///
public TestLicensingAuthority[] TestLicensingAuthorities { get; set; } = []; + /// + /// Gets or sets a value indicating whether the server issues itself the license keys it serves. + /// It exists so that a trial or a load simulation has something to lease without anybody buying + /// a license first, and the server refuses to start with it set outside the Development + /// environment. + /// + /// + /// The server generates a licensing authority of its own, keeps it in + /// and trusts it. The license keys it signs are accepted by this server alone. + /// + public bool SeedTestLicenses { get; set; } + + /// + /// Gets or sets the directory holding the files the server generates and must not lose: the audit + /// signing key, and the test licensing authority when there is one. Relative to the application + /// by default. In a container it has to be a volume, or the audit signature chain restarts every + /// time the container is replaced. + /// + public string DataDirectory { get; set; } = "App_Data"; + public TimeSpan MutexTimeoutSpan => TimeSpan.FromSeconds( this.MutexTimeout ); } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs index 106dab8..35f65a5 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/LicensingRegistration.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Logging.Abstractions; using SharpCrafters.Backstage.LicenseServer.Licensing; using SharpCrafters.Backstage.LicenseServer.Options; using SharpCrafters.Backstage.Licensing.Licenses; @@ -11,36 +12,40 @@ namespace SharpCrafters.Backstage.LicenseServer; public static class LicensingRegistration { /// - /// Registers the license key parser. + /// Registers the license key parser, and the server's own licensing authority when it issues + /// itself test license keys. /// /// /// The identifiers of the test licensing authorities that were added to the production one, for /// logging. Empty on a server configured the way a customer runs it. /// /// - /// A test licensing authority is configured outside the Development environment. + /// A test licensing authority is configured, or test license keys are asked for, outside the + /// Development environment. /// public static IReadOnlyList AddLicenseServerLicensing( this IServiceCollection services, IConfiguration configuration, IHostEnvironment environment ) { - TestLicensingAuthority[] testAuthorities = configuration - .GetSection( $"{LicenseServerOptions.SectionName}:TestLicensingAuthorities" ) - .Get() ?? []; + IConfigurationSection section = configuration.GetSection( LicenseServerOptions.SectionName ); - if ( testAuthorities.Length > 0 && !environment.IsDevelopment() ) + TestLicensingAuthority[] testAuthorities = + section.GetSection( "TestLicensingAuthorities" ).Get() ?? []; + + bool seedTestLicenses = section.GetValue( "SeedTestLicenses", false ); + + // Refusing to start is deliberate. Ignoring either setting would leave an administrator + // believing the server accepts those license keys, and honouring it would let whoever holds + // the private key mint licenses this server serves. + if ( !environment.IsDevelopment() ) { - // Refusing to start is deliberate. Ignoring the setting would leave an administrator - // believing the server accepts those license keys, and accepting it would let whoever - // holds the private key mint licenses this server honours. - throw new InvalidOperationException( - $"{LicenseServerOptions.SectionName}:TestLicensingAuthorities is set, but the environment is " - + $"'{environment.EnvironmentName}' and not 'Development'. A server that is not a development " - + "server accepts license keys from the production licensing authority only. Remove the setting." ); + Refuse( testAuthorities.Length > 0, "TestLicensingAuthorities is set" ); + Refuse( seedTestLicenses, "SeedTestLicenses is on" ); } ILicensingAuthorityProvider authorities = new ProductionLicensingAuthorityProvider(); + List testKeyIds = []; if ( testAuthorities.Length > 0 ) { @@ -56,11 +61,48 @@ public static IReadOnlyList AddLicenseServerLicensing( } authorities = new CompositeLicensingAuthorityProvider( authorities, explicitAuthorities ); + testKeyIds.AddRange( testAuthorities.Select( a => a.KeyId ) ); + } + + if ( seedTestLicenses ) + { + TestLicenseAuthority ownAuthority = TestLicenseAuthority.LoadOrCreate( + Path.Combine( ResolveDataDirectory( section, environment ), "test-authority.key" ), + NullLogger.Instance ); + + services.AddSingleton( ownAuthority ); + + authorities = new CompositeLicensingAuthorityProvider( authorities, ownAuthority.Authority ); + testKeyIds.Add( TestLicenseAuthority.KeyId ); } services.AddSingleton( _ => new CachingLicenseParser( new BackstageLicenseParser( authorities ) ) ); - return testAuthorities.Select( a => a.KeyId ).ToArray(); + return testKeyIds; + + void Refuse( bool condition, string what ) + { + if ( condition ) + { + throw new InvalidOperationException( + $"{LicenseServerOptions.SectionName}:{what}, but the environment is " + + $"'{environment.EnvironmentName}' and not 'Development'. A server that is not a development " + + "server serves license keys of the production licensing authority only. Remove the setting." ); + } + } + } + + /// + /// Gets the directory holding the files the server generates and must not lose. A relative path + /// is resolved against the application, so that the default works wherever it is unpacked. + /// + public static string ResolveDataDirectory( IConfigurationSection section, IHostEnvironment environment ) + { + string configured = section.GetValue( "DataDirectory", "App_Data" ) ?? "App_Data"; + + return Path.IsPathRooted( configured ) + ? configured + : Path.Combine( environment.ContentRootPath, configured ); } } diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs index 1526c24..5c32ed4 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Program.cs @@ -43,8 +43,9 @@ services.GetRequiredService>(), services.GetRequiredService>(), Path.Combine( - services.GetRequiredService().ContentRootPath, - "App_Data", + LicensingRegistration.ResolveDataDirectory( + builder.Configuration.GetSection( LicenseServerOptions.SectionName ), + services.GetRequiredService() ), "audit-signing.key" ) ) ); builder.Services.AddSingleton(); @@ -166,6 +167,24 @@ scope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); } +// A development server can issue itself the license keys it serves, so that a trial or a load +// simulation has something to lease. The registration has already refused to start if this is set +// outside the Development environment. +{ + TestLicenseAuthority? testAuthority = app.Services.GetService(); + + if ( testAuthority != null ) + { + using IServiceScope scope = app.Services.CreateScope(); + + TestLicenseSeeder.Seed( + scope.ServiceProvider.GetRequiredService(), + testAuthority, + app.Services.GetRequiredService(), + app.Logger ); + } +} + // The administrative pages are the only way to add or revoke a license, so an open default deserves // more than a comment in a configuration file. { diff --git a/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs new file mode 100644 index 0000000..17f129c --- /dev/null +++ b/tests/SharpCrafters.Backstage.LicenseServer.Tests/SeedTestLicensesTests.cs @@ -0,0 +1,163 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging.Abstractions; +using SharpCrafters.Backstage.LicenseServer.Data; +using SharpCrafters.Backstage.LicenseServer.Licensing; +using SharpCrafters.Backstage.LicenseServer.Tests.Infrastructure; + +// Aliased because SharpCrafters.Backstage.LicenseServer.Licensing, the namespace of this product, +// shadows SharpCrafters.Backstage.Licensing, the namespace of the licensing component. +using LicenseProduct = SharpCrafters.Backstage.Licensing.LicenseProduct; +using LicenseType = SharpCrafters.Backstage.Licensing.LicenseType; + +namespace SharpCrafters.Backstage.LicenseServer.Tests; + +/// +/// The license keys a development server issues to itself, so that a trial or a load simulation has +/// something to lease without anybody buying a license first. +/// +public sealed class SeedTestLicensesTests : IDisposable +{ + private readonly string dataDirectory = + Path.Combine( Path.GetTempPath(), "licenseserver-tests", Guid.NewGuid().ToString( "N" ) ); + + public void Dispose() + { + if ( Directory.Exists( this.dataDirectory ) ) + { + Directory.Delete( this.dataDirectory, true ); + } + } + + private string KeyFile => Path.Combine( this.dataDirectory, "test-authority.key" ); + + private TestLicenseAuthority CreateAuthority() + => TestLicenseAuthority.LoadOrCreate( this.KeyFile, NullLogger.Instance ); + + /// + /// The key pair has to outlive the process, because the license keys it signed are in the + /// database and stop verifying when the pair changes. In a container that is what makes the data + /// directory a volume rather than part of the container. + /// + [Fact] + public void Authority_IsReusedAcrossProcesses() + { + string licenseKey = this.CreateAuthority() + .CreateLicenseKey( 900001, LicenseProduct.MetalamaProfessional, LicenseType.Business, 25, 5, 20, DateTime.UtcNow.AddYears( 1 ) ); + + Assert.True( File.Exists( this.KeyFile ) ); + + // A second instance reads the file rather than generating a new pair, so a key signed before + // a restart is still accepted after it. + BackstageLicenseParser parser = new( this.CreateAuthority().Authority ); + + Assert.NotNull( parser.TryParse( licenseKey ) ); + } + + [Fact] + public void Authority_OfAnotherServer_IsNotAccepted() + { + string licenseKey = this.CreateAuthority() + .CreateLicenseKey( 900001, LicenseProduct.MetalamaProfessional, LicenseType.Business, 25, 5, 20, DateTime.UtcNow.AddYears( 1 ) ); + + string otherDirectory = Path.Combine( this.dataDirectory, "other" ); + + TestLicenseAuthority other = + TestLicenseAuthority.LoadOrCreate( Path.Combine( otherDirectory, "test-authority.key" ), NullLogger.Instance ); + + Assert.Null( new BackstageLicenseParser( other.Authority ).TryParse( licenseKey ) ); + } + + [Fact] + public async Task Seed_EmptyDatabase_AddsLicensesThisServerAccepts() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + TestLicenseAuthority authority = this.CreateAuthority(); + + IReadOnlyList added = TestLicenseSeeder.Seed( + context.Db, + authority, + TimeProvider.System, + NullLogger.Instance ); + + Assert.Equal( 2, added.Count ); + + BackstageLicenseParser parser = new( authority.Authority ); + + foreach ( License license in context.Db.Licenses ) + { + LicenseInfo? parsed = parser.TryParse( license.LicenseKey ); + + Assert.NotNull( parsed ); + Assert.True( parsed.IsLicenseServerEligible ); + Assert.Equal( license.ProductCode, parsed.Product ); + } + } + + /// + /// Seeding twice leaves the first set alone, so a server restarted against the same database + /// keeps the leases it has granted. + /// + [Fact] + public async Task Seed_Twice_AddsNothingTheSecondTime() + { + await using LicenseServerTestContext context = await LicenseServerTestContext.CreateAsync(); + TestLicenseAuthority authority = this.CreateAuthority(); + + TestLicenseSeeder.Seed( context.Db, authority, TimeProvider.System, NullLogger.Instance ); + string[] first = context.Db.Licenses.Select( l => l.LicenseKey ).Order().ToArray(); + + Assert.Empty( TestLicenseSeeder.Seed( context.Db, authority, TimeProvider.System, NullLogger.Instance ) ); + Assert.Equal( first, context.Db.Licenses.Select( l => l.LicenseKey ).Order().ToArray() ); + } + + /// + /// Outside Development the server refuses to start rather than quietly serving license keys it + /// issued to itself. + /// + [Theory] + [InlineData( "Production" )] + [InlineData( "Staging" )] + public void SeedTestLicenses_OutsideDevelopment_RefusesToStart( string environmentName ) + { + IConfiguration configuration = new ConfigurationBuilder() + .AddInMemoryCollection( new Dictionary { ["LicenseServer:SeedTestLicenses"] = "true" } ) + .Build(); + + ServiceCollection services = []; + + InvalidOperationException exception = Assert.Throws( + () => services.AddLicenseServerLicensing( configuration, new StubEnvironment( environmentName, this.dataDirectory ) ) ); + + Assert.Contains( "SeedTestLicenses", exception.Message, StringComparison.Ordinal ); + Assert.Contains( environmentName, exception.Message, StringComparison.Ordinal ); + } + + /// + /// A server that asks for none of this starts with the production authority alone, which is what + /// the container image does by default. + /// + [Fact] + public void NoTestSettings_OutsideDevelopment_Starts() + { + IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection( [] ).Build(); + + ServiceCollection services = []; + + Assert.Empty( services.AddLicenseServerLicensing( configuration, new StubEnvironment( "Production", this.dataDirectory ) ) ); + Assert.Null( services.BuildServiceProvider().GetService() ); + } + + private sealed class StubEnvironment( string environmentName, string contentRootPath = "." ) : IHostEnvironment + { + public string EnvironmentName { get; set; } = environmentName; + + public string ApplicationName { get; set; } = "Tests"; + + public string ContentRootPath { get; set; } = contentRootPath; + + public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = + new Microsoft.Extensions.FileProviders.NullFileProvider(); + } +} From 474780ec8b6d1c2a8c8f50b5d64d76d0a23c64ff Mon Sep 17 00:00:00 2001 From: Gael Fraiteur Date: Thu, 17 Sep 2026 14:13:39 +0200 Subject: [PATCH 33/44] Put the management actions in a menu, behind a confirmation The actions that change a license were three buttons in a panel at the bottom of the page. They are now one Manage button at the top, left of Back, which opens a menu, and each action states what it does in a dialog before it runs. They did not work. The page is reached as /Admin/Details?id=900001, and a form does not inherit the query string of the page that contains it, so every action posted to /Admin/Details?handler=Disable, matched no license and answered 404 as a blank page. The forms name the license now, and a test posts each of them the way a browser does, with the action and the antiforgery token the page itself supplies. The menu is a
element and the confirmation is added to forms that submit without it, so a browser that does not run admin-actions.js keeps every action and loses only the confirmation. The dialog clears its return value before it opens, because a dialog closed with the Escape key keeps the value of the previous close in some browsers, which would confirm an action nobody confirmed. A disabled license now says so in a callout. The state used to be legible only from which buttons the page was showing, and those are behind a closed menu now. Verified in the browser: the three actions reach the license, Keep it and the outside click leave it alone, the menu opens under its button at phone width instead of off the screen, and the delete button is dark on orange, which orange on purple was not. Co-Authored-By: Claude Opus 5 --- .../Pages/Admin/Details.cshtml | 88 +++++++++---- .../wwwroot/css/site.css | 117 ++++++++++++++++++ .../wwwroot/js/admin-actions.js | 75 +++++++++++ .../LicenseServerApplication.cs | 1 + .../Infrastructure/TestData.cs | 5 + .../PageTests.cs | 105 ++++++++++++++++ 6 files changed, 367 insertions(+), 24 deletions(-) create mode 100644 src/SharpCrafters.Backstage.LicenseServer.Web/wwwroot/js/admin-actions.js diff --git a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml index 2a6a895..9503dca 100644 --- a/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml +++ b/src/SharpCrafters.Backstage.LicenseServer.Web/Pages/Admin/Details.cshtml @@ -6,9 +6,57 @@ +@if ( Model.IsDisabled ) +{ +
+

+ This license is disabled: it serves no new lease. The leases it has already granted run + until they expire. +

+
+} +

@Model.Leases.Count current lease(s), consuming @Model.Seats seat(s). @@ -58,28 +106,20 @@ else

} -

Manage this license

+@* Filled in from the action the administrator chose. Without a script this dialog is never shown and + the action runs on the first click, which is how the page behaved before it existed. *@ + +
+

+

-@* A div and not a paragraph: a paragraph cannot contain a form, so the browser closed it at the - first form and left an empty panel behind, with the buttons outside it. *@ -
- @if ( Model.IsDisabled ) - { - - - +
+ + +
+ +
-
- -
- } - else - { -
- -
- -

A disabled license serves no new lease, and can then be deleted.

- } -

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 diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..49d50c1 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,15 @@ + + + + latest + enable + enable + true + + PostSharp License Server + PostSharp Technologies + Copyright (c) PostSharp Technologies + 2025.1.0 + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..ca4c868 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,32 @@ + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PostSharp.LicenseServer.sln.DotSettings.user b/PostSharp.LicenseServer.sln.DotSettings.user deleted file mode 100644 index 1f8a406..0000000 --- a/PostSharp.LicenseServer.sln.DotSettings.user +++ /dev/null @@ -1,3 +0,0 @@ - - ForceIncluded - ForceIncluded \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 0000000..01cb658 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "10.0.400", + "rollForward": "latestFeature" + } +} diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..4d736c1 --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/PostSharp.LicenseServer/.vs/config/applicationhost.config b/src/PostSharp.LicenseServer/.vs/config/applicationhost.config deleted file mode 100644 index 2ad21f2..0000000 --- a/src/PostSharp.LicenseServer/.vs/config/applicationhost.config +++ /dev/null @@ -1,1038 +0,0 @@ - - - - - - - - -

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