0CsV;D(1l98*ZQRSRIGXkpb&MrSqS@1#F1U_{QuU*SdYXVppeklF#*;BkdA}T<=U6jq zc|^Bv3zL-M4V1cZ^PB^{ZFG8YL?qa-k4@EC%PIGr&p@2DnbOF!e4G-ZHv^qR78}G? zAyo;;&yAxewF*ZBAcmL3;h&=~rl(zf^C)cV;qqww{zh3-Gch4l )ZpurryY)<9n0@P{c-4f%n?KB|8T^OhaLnlVeq8WQeKDkCgS)& zT{VZXx`+6M-z-EMg?&hCMVS^P4hB7){h3C1q|ET=yErPw78|35wcIX89~bve{p1)} z(KVc+;%!{u&mGO%gT|4cR0ok`caYP5dUCchU}hGwpNh)7idIQ~0+Tj|%w+yFPL $36U3hOdv|xPm@7K4&v~@QSl-5mC#rpZhxgzx;{BPIThRg&V z(ALQQ-QE4vYs#uW_FdH~>4Oow${oZkS^-tlZnCTd1gfzu4+QUn=(=?@VM;BRBJ@^1 zicx;hGVrM`HdW?wU#f0DeoQZbUZ9NT8n8 n}`uLO8?1ZT>(|wLcGA zZp0b1;*G9l`6Rp_iT9efYKXQwV>srJB7nCVgwsKSgRqtItY%7a@Stau>9$q_FS7N_ z!a;mMh`k@d^;JB?A<0VEN-3i@!;(||gaNC>-Z@0b3QsdFRo~tt@`I|1AB_nCPak41 zr~jPQ+b6_d$7z`>7?ovIEToug#K*t)Eh~aH$Sl1UWS9d=f*3lCJS8@K@osILFdZ&~ zM;1tZsZhG~z%7tJC1hoBvH=3u49JaUlZyE)#d>CFGpr%~7k1*AgzkN-sCx*F)3~7p zb#!*g(3CHPcu0@wSqk}PjYCT#13LSqnUa0PzYyymZO-W?(WcKEghTET92~XJ7H|wH z)@$05(mw&IKg7(6i&5mY?Gwf|az{<(ZY=#?($~tSR9&iSw_%vWLIg3zQHoG95?zl{ z9lCU-W*SvGx`xZh53?Ls#hp^Gn9IZRu@%rYk|>X{ioa5(79&};EhRD|X?0tgQr2F@ zixpFyAf}Ef)r0W(k3-mh90QW+j>nz5KTLK#6?QPebiYX@qaaTVBXh%3n`&4f#8a$n z6INlFX-EL)AFqB#k7_)(sXtrKuk{U(X yEPUopYXCTx7i4yC-SPeVMs} zcQWgZcQBnbd%bg|Z)y q2jQmWsq8bSyv(b&~0+;Cg|U8qg$gep>Ro> z-@n?a@t1U4g(NM$%j_{380npqm`Zfa?z+JWc`8Ck9yl=IA0?{hEqW?beF C1RuSW?1Ir59v=GtYNg-xx@cx^r}W09;91SOcyHi@L+MYQ8%GEoue^;@+<>PD zEtjr}KRLNghcNQExCz&PBqUX+(?C9^?V>&1^8Z-%JGycrmyzyd|BgZL;mHciQm2hK z&$Vm0iuGkat3ak)Hh+4DLH$=LG#$S%lad>S)1?Qs(uk{&ir-V>o$=r#X4Od=V~Z)G zi+SMvh(MJCz2={G>g^wHj2X5D5vf;NeI@5aYeOobK}Utc_mqKn1lPA%?*3XE1A;r( z HvYiC+JPUOewnu^KL4CC+i z4VY`a+Uv*B0Rh;B|0Nj)amC>s_Oku#Jqx_lxBlbbi*n|3Cm$Hk&0+b*LVargq*aI) zj!mNN7?DDst^`tHD}GEuei6p1uCMClOL-(rUWw%-p&{UmVt`;!o{sS4k47Pup!YWE z!@baRtMA{x>rH+)N3T8Uf66*px{|% 3BO&n%4WEf}f02h_0YUk* zqU*7mHe_-5L{k@3#H<%qqsko-c<&Bce#6-!quJHG)5Mw4b*(%fpzW3^{Oj2VPU^_l zAAFD<$j=0K+ QGFmUJ$DwqLU~NNsy?Z1SY-!{N)g zP1AZE*Tkr4!vrh^=&6yG$8M`zV1SG`7(!RobOrvZhmt+Iv4<)a-8Kz6^hW_$nNOFj zOT4<<@U^mJ8PcHiM|^N&&v+SMY1L;5O8yTgrnU1$z6<9%Vdy`Wan(rF%Y2&4s220i zXu-QqTTLb5j~!qBh_hIxuxY|Z^g LNpgp^5tlSNkK)X^pSg?a9Elr=V nn8(oz0IMw^UP@TIed^oJUK1Ne%MRvxi22&v?&6N2V{5EJJ9~R`e-Rc)?EyMgB zThw)cC8oe6XZKUVRFrhw>tS4%@0d3|>DC50HTpC2^_KS|U#-4gup=BP)xSMnF1&)@ z_U_U|D#DN_Lu6np_W5vHJ9KS7fB!}UL@0iYL^r36dCjIHc;)xJQ(*8)rL04J5=x%# zN8ZA)Oko#`UvBw?R!-Z;x5aUcpL=^1HpffFSP9^H%qO;6F+uzQmQ~M8M%hM<95HxS zq{<|L%z-9_e%)q+^PFh9!+e{AhGDeYdeYwAfHX0)BF%)PT-7{WyH(zJicU@Mn~Q|$ z(y@OP34`4A7xzw;=zHsQrZT;$`tk1MlN1YZ-x YQWwn)Lu&-Qi2L2|3n-|NhwgHMDG(qt|LCr-pLi&8b5YM3O%|m z>VE;ofD8VJ0Wi~%GSNOy^W wl=p4?C@pMiGmZ6ocR+NkHway}J|Dl3edpTq z-r}lcp`mTh*dhg-o6#@*PF@)nJc==6(*3bXavi1e10l+(nNysUrY)TCv;^wY?wWY# zfQSB6E<8L}tCkk~AtKe3{=Mhv7EAz_2Byu5S{$R0#_&J{)csyIwezDZ$G(_&j)m7& zsLMc$nTubFb|)RsO;@HMl5IX}!I`OoNtecJMxEM?TO&}ny_WB1an~cm_flm68aa}! zDzr}S^6w39?{=gFt&U92|0YE;Vp1-(eM&vSv~n{e%kC9=6m^^!Sm^EbdSjZ}^+8k) zwU4=f@2w*g+&Z?w-H# *cX}h zyqB`g_t>_eiNaR+l9St3zU=wd=-x?WxZ$?5tPYQg>{#-|i}9~zUcHTBIE}q9UW0r| zYd!K^J}_-Y?KCl;9g$mq_OcUZ>MV1}U7CyCZRlz=3CnhTBw8x1;NV-Y-@#R-Hbg5MSifbnEc#ZhtG(_#Scg zV@IEas^4Yb^G|PwvAt4*ez0i6;@C3b&P7uAHZgqMbek~1a$d8hzj@&E*zQd}Q64Ug zz=1a_mv18U;kp>9f=cOA5M|K>-YVGJco$S&E~jDa#fM|LM;{(y_6n=^XhuDkt?TY` z*3{V;a}O0aD0y#ah&k_@4u6+sk(tAq`)MU)_0|3kh*8k*5@F;$nm$1ak5?SNuv$Hf zB)@$)r%6yhdFjM`J$ZXfuYHDxY(KT-%eB)3zyjX3g bb;>kOGQ!?qwvdmVyq4BY|?av z^4eh}N_<%46?fN=g-w1PzWWI`xQp~8Kw7TJB?Qwqoow?hMy0kc=WMsnF{OlUZQs|M z5kvGWMX~%4WW6FUzTSJIw{6q#kN|EzyMTu#9X}8MH%$(gLHMA_8){phY@AZBLsK^6 zWm+&ukLyM7+Y%L>D7->V8G-<}_%FJ%;!w799&_VB=~Yj?MYqViN*x(=vMR?0{Xd!v z$YqnElTuYx)wi>I&AKG Cd+_fx477mee>{{eKZMReu%)c0qMg`q WceDpu zkl{r4h;WA7lNG6~7TYcG??TlP7?$}|tCR=SzolU@!RSkeFiM5IsT3g)1F#cdYwV2N zz6iQ6pe`U+Z4OtL(zF^C99>*~t|;NGeqZj=W@rG{ln@R|oy2{1Q_rB+bE4j3X)Cx9 zH#EpK@dH+u*rV{(GTAt@$CQ3;Fw)Ttf-R-Eu!n~k7;Y=R_%p}$(=7sArVM5~AVBkq zhUq6qZ=1TmI3pLtSK4AZ5lH;RVjg#>)KFSE5<|I0WqJI{;!UX@KHRn^eCx@l8YKKA z#E=}<%s&4%#ny*-dHIrrZk~niO2f{ZM!NfM&(Fv&E_2PxdXav_PNFS+6y)|o{POgR zT!CZ47Zvw~i~smLtNYfJ%;0flRol=7Esz2Xt|yMj^yw#Y&KM*7Ii>fDNe5`-4 zX!{zi6HkNYH~}=raBqPy&;-Dw$T`<|EN@*P5JP}CsmghRw5a8HG88~WO@c^rbC#2E z*8z)g9x_G4&@`d;6-X4YIqSV!mkn$Aa5|2F+?FpGNTSufk(^>KRl&Icadw)0L}!ME zf0QA)q%< qv(JHS2)3`5l-`ko8cq+GpeEi5s+lU8^s{{Yxk>`Q`OP z?ifxun)>$7;k6)*oc*~oMDi^ABtq}8PG(INOaz+M(=jZRImRWpg~q&-<6h3wcDb#w z)$x}PJ+!#5viH&P%X^`m#x@)72au7 @CIvSN%7^RwR$2h7W!J$Fmy2q&QZWKDPej{aTy@?~EyaK)uCmK; z;plKbeLadMvK%)6>ac;LbHXdg2I$Gqh^$qm@-H9&${^?*Rg5VzZEBW?{horYAP6wB zzF6=nkv}{}gmuoSU!hyn2Fq7o{$dXVbuO8^>Rh&zHqbb4{^?XZb+9T=m`M1J
JLLa|NcuWWFwIp@;x+rt1eXsBqYiLKzLz1Q{oTDcN+R^IX@KC{-I#f6eajj z(&NI2J2f#)mSrZma^Eit&NJ#?`w$to&!et zC%_x`A#uzT+VXg_)2&S^F*$M5uJ8(Lw_~T_-plBOxT1!p2hg--$@aUt zC^jhU_R&((FZ{>-<>JeT{F`TL`l=$YmD?4az>Ip`SE4UbOMi1A@a^gm=aJyczMIs7 z<9S!nhd_6Q?`Qn0J5G~UEg(dG2%Kiuw%%D|5i(p?$-*?Z{Y>$|b!yAHveRcPJ{g*7 zt!;m|rO~Rls?wSKd<})59g8i(>AD+};YJ^DfOulOQ90axJYL+ck!YT*y~mBW#aMxg zTh|!2HX+H_jH^}*3bblTMw z5HLELO^)LS^BLBBr#^4p@&`&G%A? =gSc~!{Zc=}1^Uo~r(^|o6nX_g)!b-~+2xBwAs^m^LR-ftb zH*`*GJ1Oajz1?dTSE}p(j+kEo_bkLAeg$*g-=oZukaz%(Ub0zAsZ9&lk2ouM>@}X_ z+8Czoe*ajUvo?hA(Ik9m#zZHI{cTL+YyU=t8eW?<14_44a{uT%;w;A>o|qr)a{Zt< z+}MysC_v~NOe@c$m$yH;dD5r&2LzO$9^^<4K5YyglUynl~4E$da{V*8Az_O)QUz zOYGU5CL2$hcy)hK^EL$^Lp>BLgm+hR2;7$LU3?Jxp)TTQD#f3aJ6hgSjhN+vVlNQo zOWhFGr| sIX}N8e(mksoxHI}M0IBYh#1XRRkj zdO%I$pjw55N1xR(^H4>wBNafmXwmRsZD=G6H%u~KqkgBnCHrAme%oae79NgzO@fdy zHfqiN5oRCX9dFyF(1+SKz*oVLE!R;>$dLK!p+iQcRh|0JosD(B1SJ( &J;kRZCJ2Oj}X<|oH5+@iXc6rb6VI*{P-Q3EYal7taq@+y|9SIPbXX1AVp zk}{u`&h26;AAK{Dzj7a=KWigyXdUk$&5gSn_x9UP3N5}~wD-u`kh|aJR@%4N;r2xf zsABh8kkJQeCLTNIM*Kjbvl8txD|QKE%eHu8FXvD42L5{~ze?2%13zQ}!ICn0Fhb#J z&0M$tyvz%b3D3-0y@Q2?1PE{U`-hNqeP_A*Sk7Ntx#Ho2d>qxsk`*RARL39gM7+eA z431urV_`D(=##KreJ@nG`(|x9FyQH1w7#N%)>mFd*lxd7*6|TIIH5p_Gb(fT=R-@x zR!fSN4I_;_2GvR14-)6(LnneiX!648#`E?^WO|q%?@#LG^Du}obt-R2JfAK!jX*V! z{@L{*6dN{wBUL-XM{~XbE{bU3O!FdOvPfE6$&BdpPP+DFLb8N9S5@p-KglREs-=^2 z;obI7fQz6^D@#JUCC89^tn}ap#jX%&F$zD6On7WfR`C5VwMY+WEQl9s94KkX>j`W> z49Sba21lh!4>?3TLp0Iak%ddTBrXjrSDqVyZn5j{rN55K+lhPj zk`$h#pE-06vwLV3oB2Uyb3qZ+P5DqXro=pt-pe9ru!4yw*TX;e720hPBLIE_LFAcI zoy#`Ml+0hhls0&~ztTq?htHtM6X$PlvT-1bG8RWG63@`~g&i7I2%uD3fD51K3mO2U zxmI*ZwQmnnA)s{Z7X{9Or(V;Z$J)Z}L66{RY*Ij`p+S?idT1xce@j8DcI)js;w@h2 zO+9#Ce*_ge?Pf$8% nYGY2n6b^f!3n2qWQ%#Hjj>4@LWQ>ok@_r;|4 zd@#UUxk~)d>wElv@Fr*p Bq`7%H>(@{a3WYiuUUOI@TKK&rX6nL({SE@| zK&A!U+Y=Z!KY^7c%rHaFdbQY``kD<$ GS(Pa)@GOyT9G&rZl1gA>i1IH789&}S!F#2}yI@T>={c6jH zpIX6^u4eiea3>MSKbqQ~0cDzEAMBZ-E#Z!%F^IB_J5x`CPBo@z@EHO<{rv+qZCreL z&n0kyF`=Re6+U!=6l?8HB3u_6*UpE)2wg3bMGGRaV}Ytd&-Q-Gi3muFl$`=f_b(d+ zAX!s0EXY!j;P~@Z4!e{$U|xz%PnZoS?smX;9e^w=&jzN&AaSN;-Z!BH4* z{+(HSkxr_@&)}mfnuEZv@i??QI-W#G>z|C=a1wTU!t^A<+^g IAvkiG?>9yA+0b__8Qt7{kGeQ-wMM^6s@idDQSD>Q&^O z6N&)?JRt gak28UPW^V=QGBe_U z4g{rW8mIjpUPEAo1G}w3l-3?`f @@4Vq;(oK|EBi0PdZzjYgI5)vB45n3ue}Qb4%fKxo1h4 =Y(aLK3Q|tf2kG3A} zkPuMgu^L `5Z6)r z)7RUTsv)x=dreEKRSXL>%# S^TuoNaY?z38gBy2I9ZKMD zAG+x8ll{Mh%PVu?i9h6vg}#@SG6Vfi`b|_KPj3I1YLGA@ ?Lp0M zB+?S@@O8e=r))G!SPim#IwW%SqtPm{m}Ps#j39>mUyJhp9&f(T2_gP!`~etn69Dm& zjGjS6quy$wtlni`S{(9zZ>H{pIyx5UI{g9femXF}i>(aq2gKxe+b!vM^n+K}%ABUX z!-ZVVd(WSu^A7>htAWqJh(Y{Z{ToD?;~y6O&wr)S!8Qaj8Q^&T1lAoe`k<(r{EfMw z*E=gxJ*gsITCbvoCjOvh26LmV! LoJxp~#8nXk=jtvIDpa4+ov(OjBH&vBuU(C~ItBs&HgX zm72a?b5cJ8o?d`F0fetnu!`i=1VVEgWt;4uqT8VN{5p!9-U)3s>QqLowcv>iOa2xI z)ZGy2XVKV2@49 KzJ?Gv`JHmV47`8>!9*UL7>!KMdM~8bjhrV-mZ#EdB z(7I>pyWItQK;!4WCe2iGT_}7}Jv)OEzuqs-H(r~LTB@2py-|g9rfA%YDb7sNdA~$d z;aPO%{@HHyoUN!e(1M8)Z7WLm2*>QC368G#^wa>Qp}ExOt>Ho%nE{Skud4FT6CCGT zRXNFM<7s!Ypqg#+L@85JLG7-QTy!wI>+xmsdV5T}Q*iUk8^UnbgrURzT$^uc+V+Fi z(YXc;sy;^Ni-c+*?@1;&+~@yLYg4|+!fyHY%Ia_X8*(|)%Ur~?w0r2>2#Z@NDJuC6 zH1o;IIF@pSXTj?WZiFlH?tci13M(px%*K%4n5X@tr{2;vtcKOVExFw+<{lh3cf-G; z;Gx^>U}4Jv@Fw VLcD*9+Pl2-56g5RuiUy} z*p)CN;FiAHX}bezSydn5NcQO$wQm(Ivjk{KzxxLO!wO2GHY7 dF&Oxnt 9@+;- z`{YFuYxwGQep;FPMf2`!<~lu6B3CrI%Ui=##O<#ECcoE=JC@V4jr>#EGg4U dhz*XoXQuY?g(Ns*s!{cBmLXV zDXd)cBLDKHG4)7eCJTz-U*BS*Ts YG;YaNSKtBj6*LdVA^3%B87>F;_pkMGj zu#$L7EkaT&t9bS^A+n9BnP3~k`wpIDkVEM5 Art5IFd7(S1QnfH2R749^mT2EsHEagL`SL1$EheF+*j-S5j8mx^2U2`#;7e?wH!e zyeq(eb2r&62#<$^9hTg4mc#-0QGQ7W>$MsGh~MXgUHzAFYvUPiVSxd;dXV*w<-Hyu z_lmsiwv(5eywHzI;W<|(XMypS#SL$8b*MvJrr%3?a)!&GohLv)-sP9)+THov|3!#e zbVf6u{9=NBTIz~3?1*GI8A`qK(XMmO3yM}vCTEs{Gw6k<2mWonAZ3<)EBkq!;ddb5 z0SMo}pe0~0jm~>G% O@~=Ch7k=*)rHody~@d=6OF5km>{ey1u&Y#{3nuD&h+Sn%rCHX06SK zoF2`)Mk@js3SRuc28XVN@Yaxs&VwHG(@us H!DHQdacB%`uSO_}Kp RzXmxh7V7oCwB`L*uuLt-0ddgr%D@5UB}#8IEEohbdZ}?2vB$}r|9D+hePj6Q ze_ PMc)h40OnU8+BmtPVZ4?*7U2tLUWh;%BUqrF@3tsyS)ykh50Y6qp~m+ z2sU{puo>?zv3il2COeImm_J89x?X=YX}P+w(Sm+REhqJZ#tYf=BJH|^H(5Ps)@<=9 zy|ieM(lmzf>L1m+rq2UZX&u-JS4GE@dg?<1AZ@_g7wC?vkwm*Ho=)oRdtm#%;zLz0 zlz_I!YN`_VdSB!^BQ{IB_EaVn6ITb5a@u)Bm`S;=^u9}Bsb2tNZ3qP~0&<>T+=u{9 zMHLlT_@1kfE2?HirNZ*;V9m_T>~+^5%8pOP#b8fGNvOiE{j-@RSKUf?9&R&myGCXL zm?=;nK$7cm7>KHq+X(o_hf;mIoH!C^7La?O^2>JXtzjlz1Qy!Ni*`BN)o4{;>~*Og z>p-#&GFtaGbI;5uG;W!+r?f6@!0O5|LmtykU?JsAU_p1i1Kcd}tt~%IG7Jd-o$s$F z1kNs^@myc#RTOYqGjwVw uNy87l-XRHPRP+@Ft=Rv1_g7z`u3|fk zSZ`{%KA+^ERN_{v0xdiNt}0MRr2Z@JG<4}+@jR(h-eBLxfg(Z9fN`lzU^3^yUZ|J6 zRL!omK_WEUQKyoqR%)*y?-lCCe;ue2AYJDz4K0TrZUi9%tqeptgMAXT%_r b=Kt#9AVK5G^Z|rUgwy<0vC!!WUA5Ubho1H}+m{3|k?CXqp-n z L6`W+H(d(CmIEkJQ$5 zLmdsRj1+^#5GSorS^8V~+kICO#ifaNj^jwyPXE9%Udn3^N;c*YqwGla8`OjTBDod9 zH(EQ~?gWQ;g(p4&0&MjxafwpjF!d#IBW<6dQ*5+#c AbMqiz6MnB&SUrDhI%flkSlk@D-F_mv2O zY_};i)=CREP&O6XYLTCAxTWiG g@v>iN2dhI`gyDT|wz5Rger|& %9Si1Q4)aAineZi0+6_f!P?Flc-;&4RRwbw3w zh2^=w>w3;in;5Srk>X6#D_ns-|9%9F_iIk`$!nA}`|6~jYVxKx!k2*M&6?u;>r?`s zq+BKH9Y2+GfZl@d>TjjjHg~^uNLD?JxJ0}EI776s(^>$&(~o8#k~9!fdD3ry@vuSY z%(CRkk@>O2Tos;{;(O}uPIbiuf+g-Ali%b5DNw~@2b!SxI&-PRa6pw{daH!~%)h4S z6Y;>~xnAQJI{rdEKjHsoo!juynz)F?>y_QK>8~fRL!(f%s+1Af5@@17MVjUOseVIP ziUcIi=M61i!l#u8QP`F~YZhF`JCf9Ddfbe{HfG2({L@uw+D(?!V@|@CU)X1SO^8ay z(GQjBkY<*JBPI|02)=DjpDb{`N63iduLk~{TD*Ja?yr{d*UGPth)mbul&uQ8Lk8%6 z0%<4jujK-Z)tt^NxTad}Y5+^SXITO8c6*nS&U@vsK&A9eN7ee|P;gV{8aaoQJ9Y35 zxvWbH=~ S;XjH(aBOWSb5{r{C7I?w+I?7u zU2Ijv9So968#Bdb=Sz<^e{o);vT!D+6|XXlbf#Vl#X&3Eyg=C=AY#}UF26nLL=wwx ztjq#TQrwv^`ewFwp(u^Kt|z%DA(4Kg ac1D= D7CcwX7BKo-aN#XRuZVRxq%RU+66Zms zT~g2wL_9uL#M#|(xgr`MNgdRTrd=xo%zpdyV~LuD%S^g?wEy3mDDV02hh1aSZbUrS zr!}s}$sS_G%U#z&tXdA3NfP~#bA03Q=B-_pXXKpIigf9nYH*@F3Y+Si{*?9sI^;Iz zM_cMO_6(p2jDcUn ^qTtPY5N;3?XFSq9U?0#8_guEo0xecz&1e=YF2|{p0<-f6j*)=9;*C*9cNULhJKs~3=SPOlHYus-LI*y4O(zE11p5MaaS`DEyAaBoc{A= P2hhQVoHW?( zU}Cp1OjD>q9RI8kyfC^9VFhABg0fQ~7Y3i{FF_7p5Qvcj5lhRx7KXL)RX-y{nfyk4 zEcy|mopVYfbHJtTvitqxqkS)X&=1qpVWR5GKT3%qG6Iwti(J38JEA6tt2 hNivecHYYs!@VOT;o#K5Om^8d z&O+7%iZ;Eg!@dI&%f3wC_&6{WN+q%GKfZ`Rn3ec82t@gi`s=>q8^n#zJ*YB=zMx#I zn2qj#^KimR*l>02D0hpDB5mH@-37Y@RqI>7;87W_->t!j!CzcWWO>k^vZd$dxAl(w z0k#0)^;mo+xQeZtv3C+1qzISdFDs@1-Wvx^3nQW#;>PhvbQ9g>q1X;4M`%OJv58XI zbQ!
$ vI zpOtS_Qs8>4$X KAGtyrTWfD|zk8B=b zf9)2&v1U5_EU!HOXPvG+Rk92AE$OfM%Nv1?4%ogIt*WU< ~;fmHf zIY*he< q2Vcb zPc!>Z!#z*2rGDlMCxMglBOwq6_S3JiUb)eq!!H)4THkaF9lp;)&$R7j)gRoEOim=I z8YkQmSEb^f&eKWN z1`_aogKxU=$_--cf$ThIvMJUI3zYxq&i&R_hEjt`X47%@(IYV1ccLw&Y7avaU+x{Z z)3XGZHh%YbyP|73##qA71=++T@5xWH`0CX5z9%wF6AC;}r^_ayp1Tfmg^M+B{f_!A z*J`jjg$wS^egT=8Z1mX^eus`w!rxiq5{Ok~uSt3a9#2eRc@G2Z77hmR4@5PpbgXD~ z!x*Rzh=*qjbqwHE8Kp@z_L{{Kl Z}gnBEIPa7}9LiSki99+PM4 z+<>4GW#?xyKF&JX&$@w+5)<~`aS*VmXFLJTBO1h~6rDv>LBg-KWu8eUZG@BXQ>D&z zL0p!uD=#SCEsIv>$c8rIoT}Ktg>kbo?dWBZHWmyYBK+DQ-KuJ&^DyO>jlhhR=Msn2 z(W{kijVec5TJ6HR!aDP424 4Nu}J~^*G3l>rSm1pVD2^%NeyUNQ9!ERXq2H;});g)Y@?@b@IM%=9LaAFCjX zkL_$7ZaPLu$b-CktQuH%WcnREZa^_5I^{)8wp`10X$HPfu+cc}tpx|SQ|`E}P6Tr` z_!|&+I!oOe Bp&2Cv55E+-Qbe=4(g6Ml4O%ddJU}u%@m8s8p zURQ^cVIwLZT1k0=A7-!Lr32`zV47AC5?G6N_93yX{&*^VqEoGUElB=>@Ij+J)HYTgP(xnR3*uGt(wVDQI<&Bc;%xl*18cD9(?bVpT zz%$#X9=J+J{RiJ$=0Mbv3xBF|fW23oFc;0dYmJ#5?Lo`)Ki4x>eRz)96Yl6p@?3@e z?N{!J*VtL9Tb%|G8AA>Ywtl|go?KBbN73X 0%@B3rdBr2F6L5|GE2r9i>ErYo!77+kuG_Ke2@P#x%pM;i%`Mg4?lt_)wyaUlu zbhK>}Mlhn_sg@r8A}Kg;P8+z!!KC_l@9L*)Ei7Z6%d)B(3u&wWBT5pHucah|-y;E9 zDA*}MQ(&bNKky=GlNfC;SSRLV7pKeI$rfP|LVah49`j>zCfcrGGxv5H-zGESYR75^ zq6n)WcUD!8KJvxdTlm`-x+1wV_y@y2e2)CffGZnx_{A~4ZV?Cnkvs|~kg${*9gKNw z0T2#a9)P3371H>DrDBMz0$3ctd `19@cm21pOM;>SF<6Oj(HS> z+q$3$Z_Lh~gAi3-weagm(YN4AVmQ=EEVHWUE*C^3EmTwAfSe0qPkEO95k})~y9-dr zFQXTnHD#(kWL?7^6Svkxd516t^ zgUO;hJF|%+mzF!jIm?TDjzA@JglDWzHEH;FXws8u8{A{!!paxJZXlJ6>)=Fk;n(5! z9wDr)nz*G{0nbEK^S9s$L#D>EDEqtEMMVN3l_XUXe=NQroV;RuUJ+P 3GKFQgSn@<6M_EwkgxfcjB=d_Wk`1}Q6{w6ru~_Z71SHHwI>Y<3Vo-MhPbC*<{w zg@_26dM9P!Je*j&H9Oa9#-D-%AM`r^$Zl`bo8pX6YKH+kc+@Y@4T3bX6U6(UiV4Sz zSl9pIUhg0kisZYul(mBbs5iLL-+np!SIprzlQui}O9nM_c|kVd;sm!)@1$%!vo*U% z5lpB+qTKZmPD+RJ=z9|!_}wTAZP+e@ R^MfFR!g0}i*ALbUxERu_2dfL ze6iN3F-}$V9fy@$8S6w3b@A97*_R=G#+T+8XDcW@p-&<7cgh|)Qf93}qt7My@m#ZU zS~1@}GT=7mFBp-oznY{m>U(Q`Eo{n3w5~F(FKd1@5#^}#$v#F?Pd`HM4*H$oQwOA- z3u&JX0_D)-XSb`mO=w@KRt_PZ`dB+zi^F>4zKm*1a8d*w Q#DKY9+3OAP z{^{ta+W&&Q$X!yC@7ZE%{ scX)#9If}EFdg-sVTeV$GCYmDhL zp0@v8J?-GiS+RpN)So94rcHyu1xeETFO^nLi8&G~t4JSkxG-qKfciN%weel3ZD$#` zeJLr;FGumn)PAWSD38q1K)3~9iSPtku4GJUEhxYupnq-?e{I_DayGy*J$fDZU*$IG zN;1GuQOob$N7Xy~;91tZ8Va;vrz$KS^HWKqRd7}e9 2bOJkVh~uAW5W>OIT7W&&># zDRBc`ocHA1F)QX4@U0m%SdAr-NO{^|D;;s6H(!*$>WNPvT)Wy)1;|83J?qkt^CTW@ z8;#@1Tx-p)OOX3cwLIVJc5l3JLUS85SSVNV+g1jXRHxv-)2=L&o}P{>d<-)DKrb*T zJ&jmo2ODrhkJ?Wk+0x8#f 3jh?e!nqWky25rQv2H#ZYoyF@Ms0cCjsXwHi;ojr?BHcu _dtL`koI%&u!MVrW^Da=Vg~axU*;F~>E)vPr*h&OxjEp$M{?b-o0l zGGOqUD-+U9J&oglZ31P^v}+3m&rkiVgNw&Ua|%d+|5^;62cABK2!Uk2h3Q1c6) &CBHSs)>j%%Sk2KAgz* zai;`4dYAzEIiMgUhf;E 9+x zy)`t7C2lQrkq;GxI>>zA^ya?Ns}9wT0N^OM!8b8)!Bve2G53D_O=>e|gA o48%nABg>h+wYKW_oRazZEs&sL_6^cvDzus;tuZG zuTFqj?uUr@I+*O@=iinO)%_$mgbm+}>l=IVwl24pv|MoC3==Da8)Eq$fGg5@sEbo9 zifyeM=v?lymRns_(@NYoL1I?E#`z|sEYHQJjVCCtWSE6+rzOUSEwP!Jr?u~P(ui9( z?!t3}0BHt FMg8zAkOC$|793&BDH08_EBb%oDBb zEQG5ebUE?r5ZCMCgDu_1sU`)n66(0MXSp{Urvt=BlZ{iRn8TiZvY&Z#ePnp4oTc=z zbG9%{b|@BPbj!Mhl+;%;hRGitm_#FG()Cd7xwUO&@l4rv2e>Uly!XokKT!0)d B&LKNx^Y450*FeWZBujaHw6ftY|5*4opQuWR29Kpx<%b z|L9(b{I2*UtbBCr<^?A4>o{ED?bB{I-}y>g&rBJHi7ENgo24%C&MG9aIN<5o&{O~P z#atgu_+j5lbjxJ+uCBwJ(eCvqd458UtWB-=(rKDJ^8rExd~ry?B(>78Ycb|UDEd*0 z;y+#sQ}_w0#+RQzuQYCEtfH?s0P^K!IXO9xzfvE4@I7?Havw&et_M!iM>U(<=Q1wX ziSl2yW5%2lwd(a?iBe9D!dEgnn%f!%ayY7=|6JV6J-)u#0Y#ZFs~QCl?r&}vwdhVD z!Ob771Pcb}35*(RJy2@wD1pr(?&)1{V@H*H0VQ#5Yd{BIP*~jWJ^Kk?j*_mqYlK%6 zkhxEirUaiFGS|9SM#F4`pvN+wwm)@+?t&a-UR|iwHCBa&GjL>OL|nckCT&(RTEfoE zD0mv1V>a*2&=aKk$9wnu8{jgq0<)(>(k=+7a 6E6UT)TWTPTxqUvG#qtzLctBrR;tv3I}CMu6clc9o85FQ9}K} zU^8v*y#e18G_6Dnf2Bp( Oj({JOJ8XUR5OuXs3;Z44Rp8}Q z{T;0Bm{A+V#jF^{TByMB_42QG92KR(- z-SwtAC@NM^xXZ31N9{oXF+Jx$i6;IMAc|-0fiPYczcF5D@eH4h_UgUq$ZdcGI0wLd zmy}&U?R^I3J(4^(^LRTKz;}_9iZ4Z&pMhIRLWF_9y#m)6kJ2lK&pW?DI?}V3FEj@K zTV}l<2SY$&53uA?07#b+ao_gfTm4>I$iF8*hlm1->NJ>mTK}!7g8728kWfSnyRJe? z_Z&9_GH4K*%V>%}O1@&?k8BZBREK{0d}AVB8gRLYi+;oVlF|8@UgscCN2S9^HbjxR zlLlZ$7IF%2!M>FY!i8GEB$>!3to{_+;0?J%GK~e(FSS%lva- lbMnr6oSgSLuh*M J5C+D_>J&*KF_4)X|#tUw_ z?%kFTJh@{Rz{hvEiTB}e_5JdYkMER$(Jg(e5GN`b{#X=*rL4rSMWw?}ax24w57m5a zYgR9CbqT9WI@S17-c_#Rc;I)s8xjci`I#_WOnCP6uENg(!&e7ntB;i5{7!#ot>U=f z>A8}@a{#Jo{o5re(~gwh@YgWtIu0T*6Q4c@%PvB66$ac7GLxfx$a`1s>|YV7OY2{= z8fdG{M$G##mbo-8?Bceme0&KPo&C7GdA+BHhWq;aZwYGg1ilkD|JAwm?J#fPy1>zj zop}lRdwB8S<9m37|DU=4qbUv<85!8Xrf(UH833-Z97gqK&fDVV3wWx(%@_!34UP_$ zI?!T$BcfcSc!Ov54}C50>0sJozq{_|4dlO0t|-8AXyusGJn{M;{91#5lrHf4N8%qJ z;I&T=opIU6>zs<(Kg7@L{5-WQnAh^Dynec)_TMn&Ygf=*`{;+6vOv^|8;0czf-I#n zp)6Un=waf`1_IBZiozom?z c*pCW&tw+#W+0` )JQe3d+6V_VKT(c&zVlboR|fl817#54_;C-9jUv6B}<%r`e%DR)c~Y!3sdN5 z3A{&FFRyFHObCzh^SL1?A>C2
@-TKa >w-6>aUW1gEQuPg-CLo=Lkn^G+7rQ z1DkV9HX4URbnd{HTAXyQNoxx`+h7;iA0X?$?=SL}< A=KiNq}_o z4*)K@>|zvl^rfrzO=pg1^p#jn>_Xn={I-;$p6si~MV4#BTi$jt=K>c-a+89ss8l)y zqpcGI @Z{*pdE`!>*W1A zzu6bV(l$peb%LXswW*_O<)0Ey)W}?@u7S!tCU(9)FbL*@Rox`G+A^#9+9Cj9=CS78 z@8`1SO&s;)bxYr15`!?pU#^@FI(ZP;>;L6um_$y1PXt{qhyhvN*3Or}Un4x!kTo(b z5HtNeBxJJ8b40jqX@#uWbFHWtX@SW%iamFExna)92KtqJR 4M=5Wa@?tzcphQ68fAl}zX;l@U!!#ph@SerXn9 z63{xXtx&Hte9nn8PU9wGS0jF~4ba?A3fkM+pZXN~31SvjBUduK`D=YP)}27>UK|vi zK|*?9%wba9SC3({Ae=YZg*tsgx^|09i#H&AhcnIp_$+C~Kx&o*$|Q>Hg7f0RXX8x8 z{4oEpnsWm8o-0L<=^OS8GT7~n%f|qz-Ae{eW`IwYhE&pTUo@?gVh^%x5Wz%R`P)ZU zEQ_LXq?H!B0lJCjF`r64()Z22)QhUy&u#JP+d|ks_2Y}g|Efy-zo|9 J9qnD3KVtFthws4IONqJP`58X?x%0zvR`cB^EF-|9{nn z(j>tGhMV>b-V0g%c2RuhV_;#8btyeS8$n#XGWNM8ejgz|-dIQc9cO=qhu-YI@V0}X z;qEyj&}!hdNTaGmhM||O#$M}z@OC|28#f(d47YVqDAYZg=Fn}luLB}Hmn?1T8Bt+c zm1c56=7fz@S3*iLL#-xN#S!wQ3U9OFU5`^h9e&6h;eRO1uciLN(QG8V;%wmD2m)y* zas<_j_u6P|=!;rFb3W$1&G>tliycw!aTTdfu|F Nn#x2E5@@aaf5~Qb*p xPO!W*%46X9&xzdl@rP)YHhoR`w z$K+XmIfjyia|Y23Gu;{u6{~*I8h D_|j(s3o^y5n0* z`?TN@|G4xz+EhWaU#r8SpF_!N(DdEj5Z$c)Y0OpS;Nf2upIo$Q?iWn`SW|t%hO!KK zY=P!i(2v4 =JoF>8?HCxvuIUhpPy~AL1$1irSE|S`F2O^oz5zYYNrR0jmWg^P4 Cp=JsQ3NsVRfpYvb6e_p55so7oMR`k@wpl07J(D zJm=H_=X*{mL%TY)!1tBr 0=kdC&*;D2{>lDy)U&} zZaPfq>~G>;8O~na3E>yFJjw2T8n)-;M+^dSgjDDIQp3uhBu7}kLN1dd%* AEtyp5$YE-=b5kLi8lLk9$FZ}t^>Vkm#W>HQm%lA zX^AovNwvf_hUhA%bI9Y) zT5JnxYt1ui!pKya^ v?U}w*eT!J{5R8LLi;YfBw54OvfsMM9oEJ?u$D^Gd2L^0`EBg;vqHnL z^`Ec;fzD)FnFCSU6m$w=h%OhU(|Xma9tu?-dMpnLf2|fub_@iH|0)xidA_jk`+;Db zr&8P$dPzw=a%0k^$8Oz0Z|%D{dJ|eHGIW)CTj|hq0aE`jHB@>WSO-j^D2)O~C@AgC z1p|9U@t11q-dMi7Hu1Lah_64AXi~O^1uJ7wI_Ez-OA1Qp9&=34S(~=me^`*^mwO&z zESeLe3cu>uo{}f!-}nmtES|Od)5)qOjasUl$Fbq%>ghBq!?J;pl8k-xsTQvmuuc-% za?%;19iOVl <$lLSXXmrZbf!qw za(sB0Vx{rQUBYX-)|uk(YHI4S)2Xh*?gfG4vh#M3H?+rn`vvnJ=ZM7hR ?G)r6OxM27_(J<@$!rIUE z65+~w0Lu6G-ch)NJ2mm%?m%XICGo|VyeUXJD6b6jV)bIJ4!oh7nEitN<}QqvG(x|1 zhP%$p@i$rG&@OlDeqEQ1-T0kT*o^0fhOAVP(H{ Dmr&*F6hIROa37C~l%AD48pyYY4$e_NIrer#*vt=NiP;+qn{Q+oNuf^5hnN z;j0tN#^XfGfaR#hSNWn#xfug?Cc3LS1&czAeP~rstB%_3aBIa(Sm=^lyQD~sRq^s@ zY~D-Cqj`5Zym~P^`=Tu|G1NP_E4PI|JVroiV}5wpGb`gs(j}3I!?#-j?|@p<)7kT1 zCCNRkj8J4psF_ZbPU2w49zmRK*(XESrrhy{YsVesna3`3%W!%@7c&ct^6SFK?l-D- zoDV!xA2n}&Xb`_of96bhiDLckty`$(pIPQC6&=8XR3OC9u{BpbjQgC5Bn-&RaUL$u zsz3fZ>EMn2Gp9=4t)2S;eALOn+x4~(L&?oG#+XwUVl})PbEimF{Dn;srm}Cwl}wD$ z>3+WS^0xD!C!9qM8S$@pH!o|H5pV?zEci*`9sdFFiIBvC#5$01{xt}}$@Z=yBEmKa zZ#&}cC4a!9Zq)spRk^=|k pDe2>6_a~`X32Ph3bw<`+Q82yZ0WY#jO^@{ znvEMFH^<__N1)$tX-|yHi7u4(f0q8B_1XCDa3yMSJ@;K1P+?Q+$4u^s_YK9q3)W>3 z?cnZG+v2sBv+tP?-i&+JjH^x$D(3{RY`CCsdiNA+goYdr7dB<~lKuIM$9Fl;D!~=U zC8+k;eFw=Mh*kWr>6sI^K?aySaVQ=)2qvJu1c%NyfC{1|&F%G6U|y#tx@LX=@gWW9 zlf;KInUD%)Vwq_=$TCBPp^*H{6+>jjA4#U~xj;^P;y ER}r7$9vK(qYRHMesUym z2=hXG{rVeni4X6FQ#Crew>tuanmZTEswyjfCxd!_mHh~IP!Z9r{K`-4rCO@Dr32*B zYSOtzG?G%jS089~!aRpRg6mT4WElFRrb`7(*O~l=l$bv>4%`u7F~cBqzYgI1=lP z>ckF a4fsv1BhH9IdeRhgA0{El7*qLdgo3eNLSW}5#L|s(GSU{=E9o7-pRVI0}kS*#z6H>A0sj=(&znV)t1X{{zhoR5ea{%$ZOUCd2*r@mGBp z3&CvR51Y!=0CB*}IsrEL-~u5>_HA>}_tG@&<-BYrXvox&U`4P_zx4hitw6INK4aoB z Sr%0HR0wwPZel+j|;yg- 3A@slGPXa?Zuc)?cu9p$n>;o zWg}9^mKBN~)7)_6UMkE8h$Y=?7+$iiiD;3YW&nF^3n3P}fg@>X);X_Sx(Bw1*e?{j z#=qYW*yw3ZdJ5wp3j(N`74yJ~M+cQ*RXHBDmuxXuRlPjbT#Xuv;3-qI2wHRGOVYVI z$cbx(Epwy;4I=M;_%y*?fmL0zt1}D7ax&O&l0Q1cvkztHy_U!MVeBE9NS5^`-LyZx zLBw|f+M`fU{bS)$`&^ zwy&5MkM(5EohK=1uQ|OjCJ5Bu9>qt=5sc-^d<()G<^w`&8Uw>GKPVwBIz$eLNzMP2 z9L~I^K|a1#i^z}Dr3GonEFVp%Ru)BG(d#eF&DBs7Lti}Att%rAn Hh&qf*8yV z=~)$DP}oqnY?mx9Zl4M!7fq`LRi(NrTD% NtnSE& zTRx_@a!ad8X)Yd8v@vYA@gej+`ijqW$gXAjBPib9@7;!6up}K70*I^vOq}E0efdri zyjXo2F6k<3eHuLZ=lhY`zp>J%MgKj}^Z^&Uf#nd)5u+3s^+^)3KQ3v6OTZBqXzeEd z;D$4sQo$vKMetW-figWv&s5F_VgP`{tTkMp^A( 9IdyHyLmOg7dc82JO_~q2|mp zX+BykxWsoH(o^enX EaG1}I>jwnUJqPw@N8dOG1G~pE!W^=A8f2e z=-##e!OrGu)XXtApN)x=sSE9;D$fJ-G?W$F5N`BVvl#R7N(M6)KJ|UtJ c%I zo?yr`#*T;jqO{KaWGCv9)jH=*w{B4|G)>09ThC`bc*+QrGJx+)dBZsY6({8@}8# zn8wY$T>h}ZO>3PQIp>a9+$^ujeRS8iz0|t}H?ZRDmi-B!Uv;1WcUiw0xa7|M_%Qtm zOTU(r&v-kY^L%ohJ3$c{84-bD$-hHcdVAN7t?_{6Q{KG$_g0K98+823Oa1V-TOD&} zN(ZnM<|4NM?5D23#+B%$1pigb{k^Z35yV~*04v76gl>g{VOKu2mKAd-+)W(b-$IUL z-uY-(CN{FP`@123cKqzh)?qLkDG8W^GvSU2ImtUtr{DY*7u&R_NXA@`7?&Cax$%Ok zGA7Gz@kMbGbzu_)kMLn_=eMnTik5Y0Wzrjx63fQnxyy^gpSqoOgQkx5rV*$^h~@f8 zbD&f=*V?(huM}7|&2`|eylPXVD6}C=o#DmHFLVS=dT_;Z|G=QYGdx`+_`smdD_kQ- zuz@rAXertYx<0&hxB)_;Nnhe&+(nlF-HF| zJUdh6h&5p@`SIaSMsDqf(E~PAp}af}DvlkCSF>h2OcGCXW0w-g<90xy fjWi5P=9+*$Hsj?x%F?QR9y)bAEod*WQ`!Ok`3B=Pt1Bp1!>aA@c)X^j zJk3e^9)6O-I31)8^?E!OyMt_1=nvEwZ*BFw_N+fF^|@J+XZKSNyXHqj#+c&PK<9A} zO#MFc9KwxNV@L$`ezW?-V%I?6pHI@}~0rM4|;26?dN>$j%(3S^ HH>BQUYPgQZ_N9zo|5-;0-^?~nGM~6$bdU@ z(i+{M^|ubek#}TOvxApbAN>g5sVY|#XA=X@EqNKldVNmZaj&AQpOznh$0L$`H=R<& z)YQ<8XE}ni89@eVX~BPo(k07b+YgxqkW!!580 %n<_I@0V z$CvR9T}NJ3Q-jUDd}dt>CaDAV@CIMoH;esE!X$|OyU{_1u~15uGWl`>ba_k%VCB4x zItSe38S(Uj{lAS7@}4h;vx_Bp91Y*Cfom?l0~p)#`65O=Y5v@F^xBx(&6m&zl=5t% zYj=)aIL%B(Z6yu2)hx8bP<@^s{EnqynvY_jn;+?Q6U@H?H@B>_&fjph<*Xz0dfRvS zxGVF=DsT~uK7&PlV{|tm)7OXC^h%gqm&i&PTMl=|wWC(-i=oUf2P8|lEAjCD9PZX5 z0FR#Ja{+Hh1iDSVskvYpaQU`NWI_y^l^@SspZJjOgF)anik2eIHqcVDwm!{lfqZ9^ zgr9#L(TZ?drj9QmR{I3#;Z6r;s3Eqt<6+HvSe|{^;-2L3IgF l$T*2H==|xBNmB*NfcX?Z1!2{$UN++Db15c09@C(C*8?A zi_C@EUueA5%FGg~0M>>q;{ny|iRFhke?2(&IFfX$S=jXHIFX2O=1ioVdeTKIv*r9{ ztlGVYIWzvs^906meBZ{pKPyto3qNPO4F4Fe9Q6^s%yurkP=eG)9T;p{r-qGioq7Z4 z;bRGrAPVfEYGe o;F4|c35X+Y~V=|V|+2$8iRL@5sR%|zId9$Y %1JTyu%Ujxuo nTU!EOe-b~2ZWpBTvd349h~&rkGdGjcMzAo9(- P8l0xo0$YY@9D|@qJUC_zv*Xowky?v)4ax2m-sBms`JD>wXokxkZlz z!x?Yx)BldPOH =ljk&_g2URPJW&Fo4s`bd*m7ONRaJ4 z @7e diff --git a/docs/images/migrate_wizard.png b/docs/images/migrate_wizard.png deleted file mode 100644 index aaac87e9225756e720da691812947376af438ed0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10362 zcmeHtX;f3$y6pzLqENA*1Q8`53f7Uvq7@J*R1}4Pr6oNCq$h+%BoG6^7POE~1PqN4 z8&Dy1=`cWopfu7+7a$=ZB1D8FKp+7^;B7qj-8b$T@7{6Wx#!OtqkbeAdygc0uW#)+ zzd7ex_s%<<+41Av9{~W^an| gNXHEcRo%^Q1gUx}~ zP-_6FOp{&p*#aKRMA*7U0l?12jh9q&_)9+kI4pPel=UC6UR*ZprRS8V$!u24SNe^q zzNbI0Hmdg!vB}CmA94Eeki*Ho&$i0uN_9bS-ODe7)Sf6cc`cmKczJY>wX$^LwL+zC z4@0GfT_^V)dGYf(?p=diM*4-ZOLmcFN1p@ SPu`jNCW2|sL2AqoxQ0I0EcA$-%nP+JM&F@ z*|jJFKdl2Do|`l>ml_inFuxZ7jK3wrRxgXbK1)N=7wqVL@f`EfskJr!It{Yk>WHZl zFT&Orc?6s!RD`CB(2{DK&8`4|?3dN6{g^b=SF72+){?erE1GglpvqO!7v1SbzmNtC z@p!y><^TXxz~#LzDyWns4t_kX2wdNGfK=HryI5tY0|4FwJ6P|E3TCQ1X7s=R=Ot#7 z=DW2q$ePc(w==U{)$Hm^NDfBz2lqs9@bzV9?@Ib%l77o~Ueysk`a)j0^1*kLb^eW| zNp;}twKjVFYiji$OFat=gH 4osUQtMd>nn~C26rbcJ8xZ`v_^EGqa9p$qo0PZb}dXkHVZo{2XzX z7AR7_L13r7SAWlmEqRnq8tvL*$f#B1=ToD&iB>J#jClEkkzHQ%L|cw{?N$$Ce6Z3t z9w+85cKL~WFw+b?uO&l6L~U6@anyjCpZzFga`xJ-{3`_A51cL>f4!xcu0)dkXf04n zZ8H ^~F#lM_d-t3mgaP5$txA_})w*mbT8H)D| zk^VAh){Gn)H+d*ZA~m$dHR+FD>cYCjP%CXXt+nHphIkKmdH`17*S%uTVz}aNv_~B) zpF q6a}e1NxtHlK(Wn2ne|6(mNnCtJYykS+4VIW(n0Sn}cHq5hHE`x~BxpQ>Q-= zgL{net?v&Pu1po vVtYNz=!Hb0;SccV5zI5ULqB`)TX)8>cT zlpf5PJ{Y?Xp%iTSgsGAD)8H;$W^5_HbJr uf^~YVz|Vl&;FxT#s1;ZgrDsTZ;|6|7d!$FLNM;lQX8R#dmaXMzMy4{)}L4rT*F+ zu1nKvR$g*U!ywT=EscvjzP>7mPw-I4`N$0GKSPdQJ@k^>Wt im5NT$TK*ymV?II4Pdz=AxjN>}_N zX?ianiFDJsLg49BqRKi5-y(6Ow1H)N2LijRzLf669VGe=_*LW4oy`oJrnI(A5p+-4 zs_vU_iB^-!R%H`zP1TBs+`h1O{hgcIf~ }-wEX4k9*K>{7`eOYH z0l!D3T%yDb`SWjU`__iLxgc(6Fe6pB5<_E(_PhOk&d={pqLMuYyx(RPOncjN!!ioY zc++v3#Y4P6?~&2m%KRwP#8>$JCh9E_O C5^hGhQ=;MX42!MP4wQm%%i-o}jAk=gtlUD?E-8p2G=!+o zzw)-#Ta&^2=;_pzdg>XZa8N&g4h==mo3dxe9=LMtl+G^g8L`Y~aJA4Vw0j4 +E;@mOY z1SX#8@6Yrga$JkKBc*$(E}R<=d^$!PurY57lsW@i@1TAM+>o@AFus1 ra2K>qM|@rj^?~+LPoTGr7FV=nyI^@{x#tJEwsE znpM+Y72_s*0G}e%mT@#y3b>OKoyG+(x}}#~>*~fD35I=QSOg+hJm5|iyzi8uWwj|; z+bo>9WYetjNe#TL@fC5@Ak0~pi9N&Ix-(&y#TE6IEnx-zL~`wTvVE9Q*K2ibj887* zvCI!x{%g`XEKIj{{CdXdg#D}wr?|9-akMuz;faM4mT$%~hY!iz7>{hb-E8Bwt2Oem za~pOxck$0)$L4Z$sIvtIn;`v1HpP~##WH# _u=_kLaJY9;+(0!*~ys;uM2%WeU%B*=YRj +^aIqqS zEdb4o_9u0=BxnoxQ^PKdEiWlQbM_&lxF_}@W^=f14QdVDlh_%3|9A(L*dM}IaX zjHDfoZIK0HckO&vt_Z5T^?i3Y)Yo9bhJZUK? 5^t#x& z%cCj&!zB2C=E-uE>@IQFae@8%H9b>MZ1o|YChn`rGYNM{6lRmd@aWaKW-UGd2tp6G zSb{BqUFq8l{>nUVDS|>GEhfrFHwEtf_8})N7 L(>$#V?6 zyM+3pO{r+3VU9=vlS8)Y@!xs-XvJjoi5V`7QuFLgnnM%Kk_O$htz%ih(JH6%<|o?X zVR&soo1CF0{+z5Qys`MI_;K$2sg;2Zp#;Y{ ~GL CxuF*-{8 zORsKC3IZ%7s@GBus8>PE_>@l%l#8HSb88*$LKSX~E!}3`hn(AU3@r>zC@~rnfr_r6 zHp*nz@#Qa>QQ_|&TgFq-pyUvXe6hia^1oH3;#WbfHc)UJb!DFCi9ORq(x1}rI9j_s zg<@U>5>%-f-KMDxE`?k=bbwFM+`s&^Tj)yNqlachwj#u(3DPbjZL@==`qN=CMZwco zrg~mr*j1JYRoBj1)R#K#;w&8UzHgjv2g^}Fj>HP|#>iY>z`HVK%TS-$4p!^rVt k2&FP*Ms%xo0Am#4ya#qc}8kCJn32-xxATD*lutm z;Bc=!TxH)l*TJe$8B)?2o?+O(M^{hESRB14%z)2$t|1jt-6cPh= T_VJ7aI`5_YrGAeW;3TmCloi%LTehJmiheX~SQF z)WKdMf3x`l*>frCEo7u<|F+j-3vCX)#OS?A_k8e?d7Nr{l`z2`yYnF(I&K$}U1Bf- zKPDNc@m5E@^bTw8*L!_GlO??kW9OUF@Px1qBpL0I3~3`1!UX&EVwlH^5rU@@L*mSG zj=*QhMhb8V^|dyZ?_+ZNd~9uXoI`$ynsa#Mb}5fjp^`LK>0hjDd6(RKL6Gm>p0QS~ zd99cv$^XO NlI(3bP~u=vvN|iAE7eEDI^o@J+J$_+V3&iD)t~MR|6R@QVrpp&?I=-_ zKb{_u=lfuM7s9kT4Z;^^JRt>7hl!9nQTK~o1FbsT7%m}5rGB0?AYuijL(|WB+YRV` z?Uwdv2>qaa f3T|Rjou7Y;MHx|boi`q7wnlfqY)FZtP z?99h*uNyJGn%!<9wnP}RFrAUM-YQ$CitgYS3_4!xciANRlL-)tMq39hKjuEim59q8 zYMNWl$z_k}sR2MT!mx#0pRQY; Wm5lvh2tN(-AXk%RF}#i+6(;z3WEWIqEo*+lGl^=2$J$yK z2(*LtM+iL3aG6sHGuxEACryWd3qXv&28&!VShx;(&9nC+$X*cS>>V281`886cWt(( zEJ-~{OkuRlpO&dCA<72C5R0~)&<6SnPa|`lll82Bp*(W$-J+V0Jf4UgmofpoIp1@D z=fP6{ocsTej`n}#k`dZ1mV$Nzc1NBGKRWq&`*@m_r7|<#Rj4`O(<|5p91Aj*_fueW z;;csZn_hX&e~ej~vK$Qi;IR3f5-hF~Hn0%$A)?OMVjZf`Aa%ViK*L%HdObGCzgj0_ zP7Z@USL-C$R^nA;s$}qB_hY|_;%NGM<+#=L@kn90DE~6g8f17JoVLrk%wtN|t9FpC zx;GnMPVhp+R;Mb5ky0C@gC >Z<1jaJVjFlmc%Z~18 zL5+x96wBDFJV-(=Erq9U0_txOD~+q(q^y0(bQG!w?dg$E?CDeEYn_711`|FUJnl)y z)Io5%H%ukA%b*=zEN^zeS;mKZ1<&t7EPZLsl+3y?)cKU RJY=NBoHafQLefUMS~zWIRIOMzXFxnex3af2W+Dymy)EfKk=iK1d1LWf2b!gatY z>W!_tS6)J{*Kl>@v+xJq9@P X~(F(zwqX5`equrtdab-CRl6WS%*f!UKhZ-9BkVA8MM>6d%iG` z=*w1=l0fVw&2fd!NodW*s?xqSvgS{MY}2?39ajF7VlZoP5;xWJ^OODuWxhj;k0=O& zP~uY552iq|AB}|-&NUVVy6R+hgTuvJ#N|-_4#>F>aG>0ou_eW(EAEzUdB)|@huZxM z2>B?tCHpslxOgEg_akb$Ki9*L`mlBOb)o=AQS}v&5e(YsJIk%WVCui=C9JwpzD(qs ze%j~^2eAndQc@@xY&kFiGoU0AswA}tpnk3BG%}GWwV!Ojb~MTsqgcT8TPME@7pN>4 zCjGZG%s;A~od^VijjbJd6jL|t$rdH7i(*Cv7NfoU!5mB6wZ7WL8(rg$Qo}KVyi9rU zA^-Oj?pkx*6g2!BX5mZ%5xp)R9hEfbt)37Gtk%El9IB|6W5LcFU7h6N!{gy1@U29Q zTVKYt{`@`CLw~-fMJ;hIcW4^Un%mGNYm(9Rc>RFzSWq<8(8V=$R_63ppaK?347P6I zY_nQ=%QL}@n@| @LZ0SG1MDz^YgB z7P4v$o78|T+rr%Q(y1-qMTKSjHT}&%y)~s$Ezh`8<|RLlV_svmoM| g(vJ7Uf7%53+NL`{-=-Hs31feQUnxGjBOpZIT}h^^(QViV8n}W= z(Dv7d=5A&So$~ams`&s3$Esa%9 z&I}Ratq>l9dPeuNGvcwJUZTHdTobYCS2$ZA1BaJDwgTSKphg*86H;oz @4BW*mx50R{g%@B2 zXe;0R{@kW_L;o$I|9ROF!C3=oFe>+TUb8`j94nS2GyC3wz!@!g$W%cdXekOXqQ|B0 zb^iX9o1uqTGF8D^d74s{3M483v} &na!+>k?_~o z!bO1z!a%m5%W71@1&PXU=Bmt9^JQEW@)`!+Z9RAz_~gxUNeRtWc&;fNhpaEDoCrO> z@bM)3jOBa=Pr^?~h*_E)Yjpttmrk&aG2dxEp$Q83`S(%J&ZwfEi|e;pe$4Ug2i@LE z(T^ELTb08SDPIgM8>cSlN~*6+oQs+J{C;h*BF-z2hR7w~52A>Z{EOHtT}$LrCvs~< zb{=Ls2OBmRdY}=pHh$$~Tc`$5k0F3h(zcor2`|PmF~IH-bjU{jL!vrwX>Od|nyDoo z*a*Rsn&rQRv8nyv^$3W{VUY%f-TTESck}E+I{?vkxAbHa3Dq98vM@#S$j(G<-xTg% z40?Nif=+Kk <-cPkzEC% zFl=Tb2gXa?Z*DA*xNdkBk;0D}Z}m3>4nM{2Q$FYKc>W`NxVn44#Wf-%6(nw Zr@BW^OG{i>X+Gj`OfnIt2VsI#OA> pL&r)b@oE}g9*PEnoF__Et)aApnyG3d3jo&52Ie4@_Olc zhG9U$>1@|~JrV!yRq+Z2ge$4+BsP$UDhdrrZ}xY8GpBE03ES+1j|3a5+y-lQ?evNr zyTgkDA?w$!62B(E@o6jl Q~Mrk9(E|1 zfIhLG4K;c-{UMHgF?l;sf#ZIlzl3tX5EFjS%N^Y4c9|DQ&x;ILM%)4U{|()vh&2G1 zxscA*>xon6dgi&3UVsqB7nvZRIw=9(^zWDr_LW0R0ri^y#PZv}P=Nn+_5b;5=wH+E zPcJ0@Rl2`Q_ZJ2Ie@a37uYpbx%%e5|zbS|Ox1;+1T(JJ5OEz10r{Fk@t=;-b3cb*M zMyuTAOO~@2ww8*EuRbo05YIGn^GI?z>MhbU>Tj0ngiLs*dH`x^ENRQ#{YS$gW^zhV zs=WK$&948Y2S0XQ6+Kfarh=-{Z@%-JzGTCDwtjp&H;^D1U~eS%C(GNGL^M%drg~Eo zXGDbIi-epmeNMOjL^jqjIsuf+OkCj9{52CUN#LHo<=7L@3a-6CgSu&DO>XUzoI_L9 zpEDGkWC&NV9WiPx^Oieo_jlJ_zP?hv7QG_Sb`PU}c#{$JWy99jd^cPo7G1(JzqZp! zqeXgOgwD@1BDYeg?USX!W=mt`n;k11qpKafjZmP5zEVO5*AS>1H$n|xdh(N>)Xr+d zBTJnzv?#rgYxQ!g^>r*7%3=X@Lye-5JjOmBrlHt1M3KJS5sut20AN@%5c1JY>CNup z55<~z_TDi5dN)MU-KQxl_UtSW`v0xG_sf$4IEuZ!t-}lgCI=>t1>~xHyXDXhS-fdD z^NsQGB>nuTvN{t>SQ2!%8<$6niqQjSdgn_;=_}|}aLW%&%eQ7Nx`rzjQs@%e$MkyE z2zs&KSv&_FaeS%LIjwdqhUX#bPQzRc$-I8MXTwhO!ljBeb)-Hk;FeCoEHT^x3?7pD z@ZQ6&<066$?Ct@MXD1>5;;1No)I?cKwI9gcd%vssA J(@1kQY|$mZH2zLoCn>%s^@8@r`HA2+4nWwkbL#cMVabh7JCgU`vQ3nf{)6yK<~ zjyn*t%J?^L^@VmWE@v9vmTsfFL7k}*uU=}N8zQv5WC&D}ipEm$j9Z>EIGKE|2kV}> z#_mo}YsF93?Eq-b-m=?6ie9XxAfFf~bR)8RSdaVDKt^%1!ol3lX^)V0 q-f8N|}W Qz6V 4pRUC diff --git a/docs/remote-app-setup.md b/docs/remote-app-setup.md deleted file mode 100644 index 499b23ed6..000000000 --- a/docs/remote-app-setup.md +++ /dev/null @@ -1,49 +0,0 @@ -# Remote app setup - -In some incremental upgrade scenarios, it's useful for the new ASP.NET Core app to be able to communicate with the original ASP.NET app. - -Specifically, this capability is used, currently, for [remote app authentication](remote-authentication/remote-authentication.md) and [remote session](session-state/remote-session.md) features. - -## Configuration - -To enable the ASP.NET Core app to communicate with the ASP.NET app, it's necessary to make a couple small changes to each app. - -### ASP.NET app configuration - -To setup the ASP.NET app to be able to receive requests from the ASP.NET Core app, call the `AddRemoteApp` extension method on the `ISystemWebAdapterBuilder` as shown here. - -```CSharp -SystemWebAdapterConfiguration.AddSystemWebAdapters(this) - .AddRemoteAppServer(options => - { - // ApiKey is a string representing a GUID - options.ApiKey = ConfigurationManager.AppSettings["RemoteAppApiKey"]; - }); -``` - -In the options configuration method passed to the `AddRemoteApp` call, you must specify an API key which is used to secure the endpoint so that only trusted callers can make requests to it (this same API key will be provided to the ASP.NET Core app when it is configured). The API key is a string and must be parsable as a GUID (128-bit hex number). Hyphens in the key are optional. - -### ASP.NET Core app - -To setup the ASP.NET Core app to be able to send requests to the ASP.NET app, you need to make a similar change, calling `AddRemoteApp` after registering System.Web adapter services with `AddSystemWebAdapters`. - -```CSharp -builder.Services.AddSystemWebAdapters() - .AddRemoteAppClient(options => - { - options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]); - options.ApiKey = builder.Configuration["RemoteAppApiKey"]; - }); -``` - -The `AddRemoteApp` call is used to configure the remote app's URL and the shared secret API key. - -With both the ASP.NET and ASP.NET Core app updated, extension methods can now be used to setup [remote app authentication](remote-authentication/remote-authentication.md) or [remote session](session-state/remote-session.md), as needed. - -## Securing the remote app connection - -Because remote app features involve serving requests on new endpoints from the ASP.NET app, it's important that communication to and from the ASP.NET app be secure. - -First, make sure that the API key string used to authenticate the ASP.NET Core app with the ASP.NET app is unique and kept secret. It is a best practice to not store the API key in source control. Instead, load it at runtime from a secure source such as Azure Key Vault or other secure runtime configuration. In order to encourage secure API keys, remote app connections require that the keys be non-empty GUIDs (128-bit hex numbers). - -Second, because it's important for the ASP.NET Core app to be able to trust that it is requesting information from the correct ASP.NET app, the ASP.NET app should use HTTPS in any production scenarios so that the ASP.NET Core app can know responses are being served by a trusted source. diff --git a/docs/remote-authentication/remote-authentication.md b/docs/remote-authentication/remote-authentication.md deleted file mode 100644 index 49c2a82c9..000000000 --- a/docs/remote-authentication/remote-authentication.md +++ /dev/null @@ -1,68 +0,0 @@ -# Remote Authentication - -The System.Web adapter's remote authentication feature allows an ASP.NET Core app to determine a user's identity (authenticate an HTTP request) by deferring to an ASP.NET app. Enabling the feature adds an endpoint to the ASP.NET app that returns a serialized `ClaimsPrincipal` representing the authenticated user for any requests made to the endpoint. The ASP.NET Core app, then, registers a custom authentication handler that will (for endpoints with remote authentication enabled) determine a user's identity by calling that endpoint on the ASP.NET app and passing selected headers and cookies from the original request received by the ASP.NET Core app. - -## Configuration - -There are just a few small code changes needed to enable remote authentication in a solution that's already set up according to the [Getting Started](../getting_started.md). - -First, follow the [remote app setup](../remote-app-setup.md) instructions to connect the ASP.NET Core and ASP.NET apps. Then, there are just a couple extra extension methods to call to enable remote app authentication. - -### ASP.NET app configuration - -First, the ASP.NET app needs to be configured to add the authentication endpoint. This is done by calling the `AddRemoteAuthentication` extension method to set up the HTTP module that will watch for requests to the authentication endpoint. Note that remote authentication scenarios typically want to add proxy support, as well, so that any auth-related redirects will correctly route to the ASP.NET Core app rather than the ASP.NET one. - -```CSharp -SystemWebAdapterConfiguration.AddSystemWebAdapters(this) - .AddProxySupport(options => options.UseForwardedHeaders = true) - .AddRemoteApp(options => - { - options.ApiKey = ConfigurationManager.AppSettings["RemoteAppApiKey"]; - }) - .AddRemoteAppAuthentication(); -``` - -### ASP.NET Core app configuration - -Next, the ASP.NET Core app needs to be configured to enable the authentication handler that will authenticate users by making an HTTP request to the ASP.NET app. Again, this is done by calling `AddRemoteAppAuthentication` when registering System.Web adapters services: - -```CSharp -builder.Services.AddSystemWebAdapters() - .AddRemoteApp(options => - { - options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]); - options.ApiKey = builder.Configuration("RemoteAppApiKey"); - }) - .AddRemoteAppAuthentication(true); -``` - -The boolean that is passed to the `AddRemoteAuthentication` call specifies whether remote app authentication should be the default authentication scheme. Passing `true` will cause the user to be authenticated via remote app authentication for all requests, whereas passing `false` means that the user will only be authenticated with remote app authentication if the remote app scheme is specifically requested (with `[Authorize(AuthenticationSchemes = RemoteAppAuthenticationDefaults.AuthenticationScheme)]` on a controller or action method, for example). Passing false for this parameter has the advantage of only making HTTP requests to the original ASP.NET app for authentication for endpoints that require remote app authentication but has the disadvantage of requiring annotating all such endpoints to indicate that they will use remote app auth. - -In addition to the require boolean, an optional callback may be passed to `AddRemoteAppAuthentication` to modify some other aspects of the remote authentication process's behavior: - -* `RequestHeadersToForward`: This property contains headers that should be forwarded from a request when calling the authenticate API. By default, the only headers forwarded are `Authorization` and `Cookie`. Additional headers can be forwarded by adding them to this list. Alternatively, if the list is cleared (so that no headers are specified), then all headers will be forwarded. -* `ResponseHeadersToForward`: This property lists response headers that should be propagated back from the authenticate request to the original call that prompted authentication in scenarios where identity is challenged. By default, this includes `Location`, `Set-Cookie`, and `WWW-Authenticate` headers. -* `AuthenticationEndpointPath`: The endpoint on the ASP.NET app where authenticate requests should be made. This defaults to `/systemweb-adapters/authenticate` and must match the endpoint specified in the ASP.NET authentication endpoint configuration. - -Finally, if the ASP.NET Core app didn't previously include authentication middleware, that will need to be enabled (after routing middleware, but before authorization middleware): - -```CSharp -app.UseAuthentication(); -``` - -## Design - -1. When requests are processed by the ASP.NET Core app, if remote app authentication is the default scheme or specified by the request's endpoint, the `RemoteAuthenticationAuthHandler` will attempt to authenticate the user. - 1. The handler will make an HTTP request to the ASP.NET app's authenticate endpoint. It will copy configured headers from the current request onto this new one in order to forward auth-relevant data. As mentioned above, default behavior is to copy the `Authorize` and `Cookie` headers. The API key header is also added for security purposes. -1. The ASP.NET app will serve requests sent to the authenticate endpoint. As long as the API keys match, the ASP.NET app will return either the current user's `ClaimsPrincipal` serialized into the response body or it will return an HTTP status code (like 401 or 302) and response headers indicating failure. -1. When the ASP.NET Core app's `RemoteAuthenticationAuthHandler` receives the response from the ASP.NET app: - 1. If a ClaimsPrincipal was successfully returned, the auth handler will deserialize it and use it as the current user's identity. - 1. If a ClaimsPrincipal was not successfully returned, the handler will store the result and if authentication is challenged (because the user is accessing a protected resource, for example), the request's response will be updated with the status code and selected response headers from the response from the authenticate endpoint. This enables challenge responses (like redirects to a login page) to be propagated to end users. - 1. Because results from the ASP.NET app's authenticate endpoint may include data specific to that endpoint, users can register `IRemoteAuthenticationResultProcessor` implementations with the ASP.NET Core app which will run on any authentication results before they are used. As an example, the one built-in `IRemoteAuthenticationResultProcessor` is `RedirectUrlProcessor` which looks for Location response headers returned from the authenticate endpoint and ensures that they redirect back to the host of the ASP.NET Core app and not the ASP.NET app directly. - -## Known limitations - -This remote authentication approach has a couple known limitations: - -1. Because Windows authentication depends on a handle to a Windows identity, Windows authentication is not supported by this feature. Future work is planned to explore how shared Windows authentication might work. -1. This feature allows the ASP.NET Core app to make use of an identity authenticated by the ASP.NET app but all actions related to users (logging on, logging off, etc.) still need to be routed through the ASP.NET app. diff --git a/docs/session-state/remote-session.md b/docs/session-state/remote-session.md deleted file mode 100644 index 19aebbc2f..000000000 --- a/docs/session-state/remote-session.md +++ /dev/null @@ -1,112 +0,0 @@ -# Remote App Session State - -Remote app session state will enable communication between the ASP.NET Core and ASP.NET app and to retrieve the session state. This is enabled by exposing an endpoint on the ASP.NET app that can be queried to retrieve and set the session state. - -# HttpSessionState serialization - -The `System.Web.SessionState.HttpSessionState` object must be serialized for remote app session state to be enabled. This is accomplished through implementation of the type `Microsoft.AspNetCore.SysteWebAdapters.SessionState.Serialization.ISessionSerializer`, of which a default binary writer implementation is provided. This is added by the following code: - -```csharp -builder.Services.AddSystemWebAdapters() - .AddSessionSerializer(options => - { - // Customize session serialization here - }); -``` - -## Configuration - -First, follow the [remote app setup](../remote-app-setup.md) instructions to connect the ASP.NET Core and ASP.NET apps. Then, there are just a couple extra extension methods to call to enable remote app session state. - -Configuration for ASP.NET Core involves calling `AddRemoteAppSession` and `AddJsonSessionSerializer` to register known session item types. The code should look similar to the following: - -```csharp -builder.Services.AddSystemWebAdapters() - .AddJsonSessionSerializer(options => - { - // Serialization/deserialization requires each session key to be registered to a type - options.RegisterKey ("test-value"); - options.RegisterKey ("SampleSessionItem"); - }) - .AddRemoteAppClient(options => - { - // Provide the URL for the remote app that has enabled session querying - options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]); - - // Provide a strong API key that will be used to authenticate the request on the remote app for querying the session - options.ApiKey = builder.Configuration("RemoteAppApiKey"); - }) - .AddSessionClient(); -``` - -Session support requires additional work for the ASP.NET Core pipeline, and is not turned on by default. It can be configured on a per-route basis via ASP.NET Core metadata. - -For example, session support requires either to annotate a controller: - -```cs -[Session] -public class SomeController : Controller -{ -} -``` - -or to enable for all endpoints by default: - -```cs -app.MapDefaultControllerRoute() - .RequireSystemWebAdapterSession(); -``` - -The framework equivalent would look like the following change in `Global.asax.cs`: - -```csharp -SystemWebAdapterConfiguration.AddSystemWebAdapters(this) - .AddJsonSessionSerializer(options => - { - // Serialization/deserialization requires each session key to be registered to a type - options.RegisterKey ("test-value"); - options.RegisterKey ("SampleSessionItem"); - }) - // Provide a strong API key that will be used to authenticate the request on the remote app for querying the session - // ApiKey is a string representing a GUID - .AddRemoteAppServer(options => options.ApiKey = ConfigurationManager.AppSettings["RemoteAppApiKey"]) - .AddSessionServer(); -``` - -# Protocol - -## Readonly -Readonly session will retrieve the session state from the framework app without any sort of locking. This consists of a single `GET` request that will return a session state and can be closed immediately. - -```mermaid - sequenceDiagram - participant core as ASP.NET Core - participant framework as ASP.NET - participant session as Session Store - core ->> framework: GET /session - framework ->> session: Request session - session -->> framework: Session - framework -->> core: Session -``` - -## Writeable - -Writeable session state protocol starts with the the same as the readonly, but differs in the following: - -- Requires an additional `PUT` request to update the state -- The initial `GET` request must be kept open until the session is done; if closed, the session will not be able to be updated - -```mermaid - sequenceDiagram - participant core as ASP.NET Core - participant framework as ASP.NET - participant session as Session Store - core ->> framework: GET /session - framework ->> session: Request session - session -->> framework: Session - framework -->> core: Session - core ->> framework: PUT /session - framework ->> framework: Deserialize to HttpSessionState - framework -->> core: Session complete - framework ->> session: Persist -``` diff --git a/docs/session-state/session.md b/docs/session-state/session.md deleted file mode 100644 index 79f612099..000000000 --- a/docs/session-state/session.md +++ /dev/null @@ -1,46 +0,0 @@ -# Session State - -Session state in ASP.NET Framework provided a number of features that ASP.NET Core does not provide. In order to migrate from ASP.NET Framework to Core, the adapters provide mechanisms to enable populating session state with similar behavior as `System.Web` did. Some of the differences between framework and core are: - -- ASP.NET Framework would lock session usage within a session, so subsequent requests in a session are handled in a serial fashion. This is different than ASP.NET Core that does not provide any of these guarantees. -- ASP.NET Framework would serialize and deserialize objects automatically (unless being done in-memory). ASP.NET Core simply provides a mechanism to store a `byte[]` given a key. Any object serialization/deserialization has to be done manually be the user. - -The adapter infrastructure exposes two interfaces that can be used to implement any session storage system. These are: - -- `Microsoft.AspNetCore.SystemWebAdapters.ISessionManager`: This has a single method that gets passed an `HttpContext` and the session metadata and expects an `ISessionState` object to be returned. -- `Microsoft.AspNetCore.SystemWebAdapters.ISessionState`: This describes the state of a session object. It is used as the backing of the `System.Web.SessionState.HttpSessionState` type. - -## Serialization -Since the adapters provide the ability to work with strongly-typed session state, we must be able to serialize and deserialize types. This is customized through the `Microsoft.AspNetCore.SystemWebAdapters.SessionState.Serialization.ISessionKeySerializer`. - -A default JSON implementation is provided that is configured via the `JsonSessionSerializerOptions`: - -- `RegisterKey (string)` - Registers a session key to a known type. This is required in order to serialize/deserialize the session state correctly. If a key is found that there is no registration for, an error will be thrown and session will not be available. - -To use the default JSON backed implementation, add a package reference to [Microsoft.AspNetCore.SystemWebAdapters.SessionState](https://www.nuget.org/packages/Microsoft.AspNetCore.SystemWebAdapters.SessionState) and add the following to the startup: - -> The Microsoft.AspNetCore.SystemWebAdapters.SessionState package is currently in preview. Remember to check the box to 'include prerelease' packages. - -```csharp -builder.Services.AddSystemWebAdapters() - .AddJsonSessionSerializer(options => - { - // Serialization/deserialization requires each session key to be registered to a type - options.RegisterKey ("test-value"); - }); -``` - -## Implementations - -There are two available implementations of the session state object that currently ship, each with some trade offs of features. The best choice for an application may depend on which part of the migration it is in, and may change over time. - -- Strongly typed: Provides the ability to access an object and can be cast to the expected type -- Locking: Ensures multiple requests within a single session are queued up and aren't accessing the session at the same time -- Standalone: Use when you're not sharing session between ASP.NET Framework and ASP.NET Core to avoid modifying code in class libraries that references SessionState - -Below are the available implementations: - -| Implementation | Strongly typed | Locking | Standalone | -|-------------------------------------------------------------|----------------|---------|------------| -| [Remote app](remote-session.md) | ✔️ | ✔️ | ⛔ | -| [Wrapped ASP.NET Core](wrapped-aspnetcore-session.md) | ✔️ | ⛔ | ✔️ | diff --git a/docs/session-state/wrapped-aspnetcore-session.md b/docs/session-state/wrapped-aspnetcore-session.md deleted file mode 100644 index 601c83609..000000000 --- a/docs/session-state/wrapped-aspnetcore-session.md +++ /dev/null @@ -1,18 +0,0 @@ -# Wrapped ASP.NET Core Session State - -This implementation wraps the session provided on ASP.NET Core so that it can be used with the adapters. The session will be using the same backing store as `Microsoft.AspNetCore.Http.ISession` but will provide strongly-typed access to its members. - -Configuration for ASP.NET Core would look similar to the following: - -```csharp -builder.Services.AddSystemWebAdapters() - .AddJsonSessionSerializer(options => - { - // Serialization/deserialization requires each session key to be registered to a type - options.RegisterKey ("test-value"); - options.RegisterKey ("SampleSessionItem"); - }) - .WrapAspNetCoreSession(); -``` - -The framework app would not need any changes to enable this behavior. \ No newline at end of file diff --git a/docs/usage_guidance.md b/docs/usage_guidance.md deleted file mode 100644 index 8722f1ff8..000000000 --- a/docs/usage_guidance.md +++ /dev/null @@ -1,102 +0,0 @@ -# Usage Guidance - -`Microsoft.AspNetCore.SystemWebAdapters` provides an emulation layer to mimic behavior from ASP.NET framework on ASP.NET Core. Below are some guidelines for some of the considerations when using them: - -## `HttpContext` lifetime - -The adapters are backed by `Microsoft.AspNetCore.Http.HttpContext` which cannot be used past the lifetime of a request. Thus, `System.Web.HttpContext` when run on ASP.NET Core cannot be used past a request as well, while on ASP.NET Framework it would work at times. An `ObjectDisposedException` will be thrown in cases where it is used past a request end. - -**Recommendation**: Store the values needed into a POCO and hold onto that. - -## Conversion to `System.Web.HttpContext` - -There are two ways to convert an `Microsoft.AspNetCore.Http.HttpContext` to a `System.Web.HttpContext`: - -- Implicit casting -- Constructor usage - -**Recommendation**: For the most cases, implicit casting should be preferred as this will cache the created instance and ensure only a single `System.Web.HttpContext` per request. - -## `CultureInfo.CurrentCulture` is not set by default - -In ASP.NET Framework, `CultureInfo.Current` was set for a request, but this is not done automatically in ASP.NET Core. Instead, you must add the appropriate middleware to your pipeline. - -**Recommendation**: See [ASP.NET Core Localization](https://docs.microsoft.com/aspnet/core/fundamentals/localization#localization-middleware) for details on how to enable this. - -Simplest way to enable this with similar behavior as ASP.NET Framework would be to add the following to your pipeline: - -```csharp -app.UseRequestLocalization(); -``` - -## `System.Threading.Thread.CurrentPrincipal` - -In ASP.NET Framework, `System.Threading.Thread.CurrentPrincipal` and `System.Security.Claims.ClaimsPrincipal.Current` would be set to the current user. This is not available on ASP.NET Core out of the box. Support for this is available with these adapters by adding the `ISetThreadCurrentPrincipal` to the endpoint (available to controllers via the `SetThreadCurrentPrincipalAttribute`). However, it should only be used if the code cannot be refactored to remove usage. - -**Recommendation**: If possible, use the property `HttpContext.User` instead by passing it through to the call site. If not possible, enable setting the current user and also consider setting the request to be a logical single thread (see below for details). - -## Request thread does not exist in ASP.NET Core - -In ASP.NET Framework, a request had thread-affinity and `HttpContext.Current` would only be available if on that thread. ASP.NET Core does not have this guarantee so `HttpContext.Current` will be available within the same async context, but no guarantees about threads are made. - -**Recommendation**: If reading/writing to the `HttpContext`, you must ensure you are doing so in a single-threaded way. You can force a request to never run concurrently on any async context by setting the `ISingleThreadedRequestMetadata`. This will have performance implications and should only be used if you can't refactor usage to ensure non-concurrent access. There is an implementation available to add to controllers with `SingleThreadedRequestAttribute`: - - -```csharp -[SingleThreadedRequest] -public class SomeController : Controller -{ - ... -} -``` - -## `HttpContext.Request` may need to be prebuffered - -By default, the incoming request is not always seekable nor fully available. In order to get behavior seen in .NET Framework, you can opt into prebuffering the input stream. This will fully read the incoming stream and buffer it to memory or disk (depending on settings). - -**Recommendation**: This can be enabled by applying endpoint metadata that implements the `IPreBufferRequestStreamMetadata` interface. This is available as an attribute `PreBufferRequestStreamAttribute` that can be applied to controllers or methods. - -To enable this on all MVC endpoints, there is an extension method that can be used as follows: - -```cs -app.MapDefaultControllerRoute() - .PreBufferRequestStream(); -``` - -## `HttpContext.Response` may require buffering - -Some APIs on `HttpContext.Response` require that the output stream is buffered, such as `HttpResponse.Output`, `HttpResponse.End()`, `HttpResponse.Clear()`, and `HttpResponse.SuppressContent`. - -**Recommendation**: In order to support behavior for `HttpContext.Response` that requires buffering the response before sending, endpoints must opt-into it with endpoint metadata implementing `IBufferResponseStreamMetadata`. - -To enable this on all MVC endpoints, there is an extension method that can be used as follows: - -```cs -app.MapDefaultControllerRoute() - .BufferResponseStream(); -``` - -## Shared session state - -In order to support `HttpContext.Session`, endpoints must opt-into it via metadata implementing `ISessionMetadata`. - -**Recommendation**: To enable this on all MVC endpoints, there is an extension method that can be used as follows: - -```cs -app.MapDefaultControllerRoute() - .RequireSystemWebAdapterSession(); -``` - -This also requires some implementation of a session store. For details of options here, see [here](./session-state/session.md). - -## Remote session exposes additional endpoint for application - -The [remote session support](session-state/remote-session.md) exposes an endpoint that allows the core app to retrieve session information. This may cause a potentially long-lived request to exist between the core app and the framework app, but will time out with the current request or the session timeout (by default is 20 minutes). - -**Recommendation**: Ensure the API key used is a strong one and that the connection with the framework app is done over SSL. - -## Virtual directories must be identical for framework and core applications - -The virtual directory setup is used for route generation, authorization, and other services within the system. At this point, no reliable method has been found to enable different virtual directories due to how ASP.NET Framework works. - -**Recomendation**: If you are using virtual directories, ensure your two applications are on different sites with the same application/virtual directory layout. From 2a839ca5335a83492bd76384aca7a7ccbadcfc6e Mon Sep 17 00:00:00 2001 From: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com> Date: Fri, 2 Dec 2022 14:28:30 -1000 Subject: [PATCH 3/4] readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5c35c13d3..e424785a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# System.Web adapters for ASP.NET Core +# `System.Web` adapters for ASP.NET Core This project provides a collection of adapters that help migrating from `System.Web.dll` based ASP.NET projects to ASP.NET Core projects. The adapters currently include: @@ -7,15 +7,15 @@ This project provides a collection of adapters that help migrating from `System. - `Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices`: Support for adding services to ASP.NET Framework applications to enable migration efforts - `Microsoft.AspNetCore.SystemWebAdapters.Abstractions`: A collection of abstractions shared between the ASP.NET Core and .NET Framework implementations, such as session serialization interfaces. -These adapters help enable large scale, incremental migration from ASP.NET to ASP.NET Core. For more details on incremental migration from ASP.NET to ASP.NET Core, please see the [documentation](docs). +These adapters help enable large scale, incremental migration from ASP.NET to ASP.NET Core. For more details on incremental migration from ASP.NET to ASP.NET Core, see [Incremental ASP.NET to ASP.NET Core Migration](https://learn.microsoft.com/aspnet/core/migration/inc/overview). ## Get started -Use the [Getting Started](docs/getting_started.md) guide in the docs to start using the System.Web adapters as part of an incremental migration from ASP.NET to ASP.NET Core. +Use the [Get started with incremental ASP.NET to ASP.NET Core migration](https://learn.microsoft.com/en-us/aspnet/core/migration/inc/start) guide in the docs to start using the `System.Web` adapters as part of an incremental migration from ASP.NET to ASP.NET Core. ## Set up -Below are the steps needed to start using the System.Web adapters with your ASP.NET project: +The following steps are needed to use the `System.Web` adapters with an ASP.NET project: 1. *Optional for nightly adapter builds*: Set up `NuGet.config` to point to the CI feed: ```xml From 5ad2572436dc80c69ec738c96d3258115a772604 Mon Sep 17 00:00:00 2001 From: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com> Date: Fri, 2 Dec 2022 14:32:26 -1000 Subject: [PATCH 4/4] Delete README.md --- docs/README.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index ae8b177e5..000000000 --- a/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -See [Incremental ASP.NET to ASP.NET Core Migration](https://learn.microsoft.com/aspnet/core/migration/inc/overview)