From 83d8e26ad52c1f80aa1f765eab4c8f64d80e488f Mon Sep 17 00:00:00 2001 From: "LAPTOP-SB56SG4Q\\86185" Date: Mon, 14 Mar 2022 15:54:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=BC=80=E6=BA=90=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=9D=90=E6=96=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 5591-5860-6693使用文档.docx | Bin 0 -> 14415 bytes JSD-7963-需求确认书V1.docx | Bin 0 -> 59852 bytes README.md | 5 +- plugin.xml | 29 + src/com/fr/plugin/ChangeConfigHander.java | 38 + .../fr/plugin/ChangeDesignConfigHander.java | 38 + src/com/fr/plugin/DataFilterPlaceHolder.java | 108 +++ src/com/fr/plugin/FR2loginFilter.java | 347 ++++++++++ src/com/fr/plugin/FRloginFilter.java | 165 +++++ src/com/fr/plugin/FilterMeConfig.java | 82 +++ .../GlobalRequestFilterPlaceHolder.java | 110 +++ src/com/fr/plugin/HttpApi.java | 130 ++++ src/com/fr/plugin/IpUtils.java | 62 ++ src/com/fr/plugin/JHHttpHander.java | 14 + src/com/fr/plugin/JHUrlAliasProvider.java | 17 + src/com/fr/plugin/JSONUtils.java | 28 + src/com/fr/plugin/LastloginFilter.java | 647 ++++++++++++++++++ src/com/fr/plugin/LoginFilter.java | 150 ++++ src/com/fr/plugin/LoginOutEventProvider.java | 25 + src/com/fr/plugin/PostionImportgHander.java | 139 ++++ src/com/fr/plugin/RemoteFilter.java | 348 ++++++++++ src/com/fr/plugin/RequestWrapper.java | 128 ++++ src/com/fr/plugin/XContentRequestWrapper.java | 97 +++ src/com/fr/plugin/error.html | 16 + src/com/fr/plugin/util/LogUtils.java | 90 +++ src/com/fr/plugin/util/RequestUtils.java | 26 + src/com/fr/plugin/util/ResponseUtils.java | 35 + src/com/fr/sso/cas/FRAccessFilter.java | 464 +++++++++++++ 28 files changed, 3337 insertions(+), 1 deletion(-) create mode 100644 5591-5860-6693使用文档.docx create mode 100644 JSD-7963-需求确认书V1.docx create mode 100644 plugin.xml create mode 100644 src/com/fr/plugin/ChangeConfigHander.java create mode 100644 src/com/fr/plugin/ChangeDesignConfigHander.java create mode 100644 src/com/fr/plugin/DataFilterPlaceHolder.java create mode 100644 src/com/fr/plugin/FR2loginFilter.java create mode 100644 src/com/fr/plugin/FRloginFilter.java create mode 100644 src/com/fr/plugin/FilterMeConfig.java create mode 100644 src/com/fr/plugin/GlobalRequestFilterPlaceHolder.java create mode 100644 src/com/fr/plugin/HttpApi.java create mode 100644 src/com/fr/plugin/IpUtils.java create mode 100644 src/com/fr/plugin/JHHttpHander.java create mode 100644 src/com/fr/plugin/JHUrlAliasProvider.java create mode 100644 src/com/fr/plugin/JSONUtils.java create mode 100644 src/com/fr/plugin/LastloginFilter.java create mode 100644 src/com/fr/plugin/LoginFilter.java create mode 100644 src/com/fr/plugin/LoginOutEventProvider.java create mode 100644 src/com/fr/plugin/PostionImportgHander.java create mode 100644 src/com/fr/plugin/RemoteFilter.java create mode 100644 src/com/fr/plugin/RequestWrapper.java create mode 100644 src/com/fr/plugin/XContentRequestWrapper.java create mode 100644 src/com/fr/plugin/error.html create mode 100644 src/com/fr/plugin/util/LogUtils.java create mode 100644 src/com/fr/plugin/util/RequestUtils.java create mode 100644 src/com/fr/plugin/util/ResponseUtils.java create mode 100644 src/com/fr/sso/cas/FRAccessFilter.java diff --git a/5591-5860-6693使用文档.docx b/5591-5860-6693使用文档.docx new file mode 100644 index 0000000000000000000000000000000000000000..2eaa15234218d3b5633646f7bd6b7adb22225a93 GIT binary patch literal 14415 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 DZB9!+ literal 0 HcmV?d00001 diff --git a/JSD-7963-需求确认书V1.docx b/JSD-7963-需求确认书V1.docx new file mode 100644 index 0000000000000000000000000000000000000000..f1daa21d3fd1d6ec255ca6944e7b1d2f50162bb9 GIT binary patch literal 59852 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 jP2_O9d~&#$`=Gxx9i z{hRFQp_%O)m*<;{kBg^8Yi`;5tB&lU?56O;>vw1L-7Otm9QfDp*Rq<4_x z-=1cUALd(MD@}NGc=pfD#Y1d;{MS{iq5YSi!{zO7X?ynH-JPdMe0{F>r+B`D#hK?R z=lq$c_&lGVt-0{SV|v=3%zazfo4D}k@9!S8vy+c=XL~pGwmui*FLm5+rsckQl|DbO z*UK7eR`$h1eSI-M|8G@qjq5ViubYc)M|U4BGsb?lsO;!Rd;TrFxGwiqqsqC_{BJp& z=f{1A$BW1J?H`Wkj@a_mAs*k9vH#vO2sk57!QV@3y|)6&W^Hk8Xdwd1Uf$2bJvT z*yyLA?;4G7qdv#A?56aB+ud8dT6zDS9&c~1t)7^{-v`sNZS&_Z{IRMjew;XUetbLm zuFE#}F6i&KhkC{rPpn>^FaFQN^NWK^ab*v$rye)tJ=dN& z{@j=6yFbs{KOehQQ;TEt((l`Tua^1l==?kSw>SK?*%qE8d^*4H?Ok49KV|rM=)K?1 zazJO-V^yzZ{MYhHaPo6Ix89$2uZ#QpPj9#6i4QN56z9Zt?(9Z2W%~L5^xg2O?@S5! zv8uXu_wjG-U~)8XozT_%x%`hUG6AE_=O#9i%=xWZh$W*9q~PnN182o1O3r$EBSz&%b1~rhmznBl}o1 zkTZpaC|m_1{q{K{>>{VZ89~OZvr0X&6l_*Gf7&tqSyv}MUH_8FI3KbZR`Rr>n2p(* zF`MRHno{Mx4j!0|<+RHwpW=3ZsJu(BGQhuVWt9Ss)lIYs+E>84`LfwDaIbfyisuGn zOGM>-85}!Fg{l*ldG3EFFJ`sNX6F3+!`aN!B1d99%G%eN@l!1R-5CxyI%Hqu8p2iV zpzmn1V$A7l1;YmK1nG+Plnm&V8`9FiTaAaQca547Ojuuqa`uu%qHo2{WDIgwWs<^f ztTBP#%&?9*|Mj{5Dxr-rv5#JNw%-m7tiIrbf036r9Auu7S=?}xojKAGc{hux06i5* znf)>&*(ZJ|f7-Wu=Uq|Zwm7_)ENWE}PJoZ00DRxzXsX>BT@Rh3In1F>d+X<@Lq?@c z5{G-A0a?`KXCSQYE+v!WQn=@xZ>?Y;-57jKley;spBRnma;%1pQUWK=ke!_7ZknR5 zo4ZP%^{HR$xLM(XnV2a04s&=35A(ct%XbVa3*l}U{r%cRTyz?%Pj{4{ZTTg?tehmB2 zN*&VIZ1CZo{t4ZA^VYJ;qx5A;e3fUvoF+b-fK{uN79V62O5F-#rs5eQ`@-Xv6cv(d zPNspxX~`Yj$S~TvXlFlUnu}xYeSox(J?oaQk*vM*UWzbHZ7D!GLbxQecQ~$=b~cU5 z2inwJ$p%zkH_#E0Lm?IMi~skEPN7<4*}yl5#zgO$z#02B9=3wz1b1BqI-XWG_L?U9 zK*zJz+*S;4lV*NOr=@+_IeSDsZPG2dzFS(2zO}H&R-M1Dh%NS6ud9@X0p3O+n>{p~ z($8@koD(dqhixp=)l~6e(mB^S^KPkG6amaCK?$%p;h?fnBBhc1T=z|_P6&qcig?t{lCU|{>-X1pE*}mRqov1oN|l2YVvJ2*u~t3|LN zx;x8d%z@`fFIw3ns16iSiSN+VpdJV4SEG5jt}esX$z$BwXW@P6y9WCTb&%;z?U_-mobB!X3 zNJh7IV&LXE)n;9JxOQeiBo=;{5E*o}QeU9SmBDQd=spJyr95tNjO=z-gX98!K`V(O z+@p9`lUlr}kKXk_XZR5AW`@u+F}n2Z)nay%0^~lF#U+j8wyC-?yRE68d)+B%n-9E- z-`0>vnnvo1rvDYxt&Yjm3M3}ZBtBzvI5Qfz-wF8flb8k7A7`5Ct0bfn@#Y!t=7f^; zv>0Y}@ElWXP)lf(JS+j|rbBKC;-;2MrA&9uNX0icRMAxa+GoDS!mlg*513EG$7q%X zM&^c)D@=G?7nnYnrK6sn%#>>}#t*Z4`KDx-??t}B-`=sXdB1vPK(%Gi-a_rgIALx> zSWH@_y;J`2TxCUcdW9_eXUWRyEU&tm>Vb~p;jmdm+GzlPLgDbZm1dH+0vGRK72X9} zFZ7@N-OM)Yo)3x_JU{t7?UP+&Y-UB0k*@l042m@kFO=rS#yXz#i6(l|NUT^>%5kLA zFH%8MqCa5c>H^Fn6A1Dd`iq0@()CM|osE0=_fWB#4#2<;m=W$@Xgj0ssW_vIZs3bEqafp1gEsfllS*$c?xweO^a^BY0-|@Hcr-Hi3qq%|BO~7%M z!X9(r8aC0S6LxEFeDD^Us@r9S&SLz+jTy+;=h?6y@uKI9%*uX*8AyrsTFnT*r3?&e z6Q#3V8kn-ib3f^6{C$!Om0L^Ej)|zC-#igutq_D-vGYs~8Wf2e+C(8>EvYj7Q(PMA zq4b5%2{8gHgIyZ|l~_GK<+M0{&p0 zePt}3Tu(A4E(-aODWy#d6mTM#JPPH>*4lK-gu_@3=Cu?d@Jf&rd5pYHZwln4BmzxJ zzK}V~%cZ%c@hrw|iZOSKe7T1Z%P|u%Mj?|jmv^=;SQW9z?hYBHxN#7N%QfyzbV+$n zW3D$dP3(vbe$T-_L^5%7vcj!cX0oF=&C@N{)F)A9#c$+6G>kby_ug{I6 z2LAdGUE9i?) ziAqBql|Rw|^Xtc+X1nL52(S5JTK+GWDs(> z9K z+n_ESZ9T(*833U!tEg;Zg54;W!Cp&dXMOqvNFCHqgR-0L-mGdiUsed_I$Iz1m>Qj( zR@uOa4V2fIq@Sw1!WknhuKt@f0CgbvZ=b~H!Z(xn7wMU`#E|BYw=pW&GteoY3J}{Y zAzFaUw?@7($Wojcft;c2v#+jGQVG^NlXzvb8Z4rL{e(pB1Q$vb)4(RGPsIyuRwkRX zz~}M0*3Y_GEE-3ZQG>TJCS30UW{X6efmSi>3ui%`dcUhNxfFQ|kZ?54a!vK^ClJnH zNtv?=31tj=p)${Y(^t)>sXMRUqsVR3fWWeHbv&ofR;vea$s0bGnk3i52x1~hU$WZm zuTde2Ad|vvLSY$24u;~Hp@c%VeJw^Tj8^+y=TjE z;bB!|p(PDD{Cy&tPy9HGf+H?58|814Z0M8=vxVi9`W)QaM3o|+p@HokEjPP3j8&0$ ztaI{NEe>-_=OM4*lpb|n-8VCG8DyWP%*_FbHWKlmt#%$!t~70fBhowZ0aSH**&5RauZ6dmOdIS1`cbs29!j`FUBHu)4YAe?Fx8QS11bbp?sZxn`XBK^$t zp^IXXR}V_Q?DI&qv#Q49Y_Dz%x=&8H^xH#LHwGY>Hp@gB3cr=2#vk&ek+`8;5KBAw zFtr}2jU@_e_*HY$iN2COngh5%<2t4O7zmV4x(_+oRydA6M8nvb+U4bUM*w~UHuGZ| z1t1c;-~RPBvINSzF+0HnFMk0$;Vy~fv5E3@Jn?pgY@j#*k7G$6N zFcspxvy&i3j1^QZd)V1Mvr0#t+yoHj?$6Y$Qu)K`8=hR2$KVY*iEZ6(e3&8tb(WXX zrT_dyX20YXZNrxDn5sw_Sg9!$ITCS!+@s}@b5ByvUXq&8t)Wgb(^v19ru@<;EK&SG zLk%(dCxW}lGdtu;)l5!tIp*sULh!<{<=*2Qfo>qr#iUIg128@S9=219L_@ z6<^8&OIM{?8I62etC+yIrt?ItKsckb05*PalF4V(2GjS9{}Mp8^wgzDlIZ`g-!E--}s`JJk5T1G)$lj zuekm`dFG1@VxcgKnXy%^ght5@^d_4;*qR~bOyz*NCN9~YZBuO+AH#fmP=v>{oi$CZ z%bizT3cm?g$ckpk_=>efg%j=oL!Kjl^_xMeb8o#+946PFCb7LyrM{&8v4T3%n}f?z zMM*zeJ#=9jTcQwc^(%_RkNZ$CnJ*n5+|hw82&l=Vzo^j_yN^NZ3;zU^TZT&jU?CJ! zL^DN_AINPS6T&(?$-^T(nVCA>B?9QitI|3uI>O6JZ1ZN+UaF=;A#hR6A1u!HU?85njK7C zP=yI@p#(tSsHv1hOka|^^^EbWp)-%|`T-zvZ!42|Q;%qViqO=vb7BUKQ2U3wdMmZz z$MnNl!kLq#3z-+yk68{jWj7yK$Hv1O?US>x%;KYTQVu6r8f;a#0kM&a>zpd5RA!j7 zXC&&8Z#K*({Si?|z!#G$>;iAwwUrgKo1IjSy@%_n=u+pPlard7I>);w8wp|YhdPzs zs@n1rXyCN_|I9=ji$&2x9y2PH4+hd;C=GtHVZLLzdZ`XV*OYTDYbP~aW*yi^HC3jQ zkF&fPvupHPP}dm%!00R4gC16@NDzw35ownzROs5k_S<3{Jkn$Q( zwwoU+HnM359PKt?svp@sOt7BfgB%U?w3LEjKG;Po{tY%3J?v=vtL&pqw+cl7s3<1; zpZWk6D|Y(w6Os$!Lbg_ftPAct1Pl{}nBodSBBLOXs-y2Q_LYrsbu*<+-YqAas(yd7 z`fZjBA3FMu(Zs@uSwq|+>g;TQB>=|AqOg1r*0&;25lhIfDU$c$WgOxdYm)=kLZpLq zvVUloBaZTM01uK&G|S}}`j!rSn1x{R=vNCLWTO_giB;qxwn8#8%R1~O1%pGaidn$n z+h!%*yAbXGZ~0>j`=rF_3)#xJV5GKX_I@EeXT75cGiXOq&b7w#eV-5`-bcE93&9 z*e@+B!mX&X-*Hq3bvNtYOIg_Zz^PUq<_}&ozz0T~5ZyPGG-p*D&4iRmbfIN@EfbHM zr9Ng^q&97}?HzI)l4YoD`qsf$0kzc5=Q`mB=r4zILR2gZ$q=F59LlfM{R_bMpdTv za|&zx2HB_^=xY<{3bzII2vv##LGH`|({3$MF-hz2^ogV*OBjM)@wBw!Ko8G{U5Jn# z;jF@*`Gr|UQJ}gDY`r$XsC8S$ozj_4l$*E z(mle5vVYe>uDO^Bqt*%H1YqTnb|Fcw0P4~@r5e~NKZG@1rSzpr^%t>IrAVDqDp?4_ zfue<|uy1u^U~)MmZ5dhFEON)9OJp<7dQ9cvYVrVl(`9UyLog?mam}~u#Z2aU8Fm#nAxCT4oezp>^ZUt^Je}#J? zi68Z^sVQ9mvEUwnajY$d&9V;AmC=loqP`B3?k}T^anf9hDZJoh;#gU_?8E_Sj)~t=ZGwE2RS&nMLAJ$~!Vs2z`W+}}xnm7- zx~+;RBhF;MqAIDS$rddepa^(bY+-=i}$~mx2E41c|0(l?QYhb71~zyaQ~^T{N8_kP+RNi ztFz6$X{YbXavQ7Ksk&{Os&lx2}0| zS=$+83?3uz;qF@opLO~9ZrHlKbsIy}d-uWnoOwg`>)G!6nbUjUv<=-Q{Q1qv`-i{h z`q9Dd<@&nB_xspoE=_kkTW=?~x2x0R?dyAgXDdy$tuy}JPNI|Iz3&%(8-ku+M(*4x z<&IG;Z0yR^&6LaZ$ClmqgKHPMdtcYDnisV$PP1QrpQWD;Ry9)rb#ZoWmvb+FZf|aK z8FYMK2D-Vsdf)HvjayAAzogUDeqLYW+z78&X9O1i*2O%(#_fzeH+a6k#a#!K{d!t{ zB-HbDV(jZ+>^o1qEj#^8`!hIYw?Acff6S>f^!@gEDBl^N^`i4rE}f|Nz3uDg*WN4O zy{q@Ve>}!%ruX%i4LWX(!_Ujh;VXZtiEvkkfZ?XE$H!}t{4UPP`NzSn4U^Iu(E&nn@M)AQ?AR@0Iz_SgMg5reD1`RJYe=~hu)Yu2Tx zN8Qwp%l_LOiQLckxI*4+Uas%c;@02SyfKsS6=^^05L>LZn8iAQoZFvD>F$>_wXG$SNaes&DhbK9%FpV8~IvZ{((zZbv%xkSu(&drDi1p*TO z=lA#@H`)JMBAS~R7@IgU|HrzqL{--IkOOTOGo2S|b1c!2S2o-MVyPG!1Wc5Pl^;kJ zfI*2O8Q+|86=@6M6kxk4yMX!^q(@fiFUDiSdjl&%woGIFU$UN;p4+d*x!a>@Xx(Il}9z76>%Hi=ED(VCDdR2sGAQGD&5m{PX1I~6J= zB{T6!y2Q-P%aUEt&QiH)QSxp2`lTQOv6+LCgksWWPNWBlV)Er3PuM2(5l~;PDW#EA z_A~}}8MwNBAxSNm9)!aVbEt+Z>Ka$@hRQ14x`hqnsXs^$N|yGP=0S?pz-@a35@NeJ zD=Jx#dVgG0`oyLebq1W#?~y>m$`smfo6HoIh#}KYOH4^SHk_j-X18y`zITD<(@WFf z#ybGg{KG{fQ}YZ*PyWYY@3`qDkXi*Bb|CASUoYe<-uXq>6N@}5d5YqY+ocv_F|DK& zU80T5J{w#EWi0@_m1u3cVispz;#P_*5m}CyPygXDbuAw4TAB79w>I=RoW{Zxy4_!q zs6VR<9oIKsl%arAiqRs~a5ol#Ot{XTSmdZhcpCAKyS1Swn(6~*Q1Yx;LI5|OQ64b4 zM$`MaXLBTG?ML~RyRgEJ3<-#AaCPtzfT8+>DXL++=kE}a4#FZYBRHz>A(Hj5tv#M` zl0)WTy2pZ)zQcN6fF9%l&mSmy?>mL41B6Fi`Lh+ttLqob9M2 zW%S3T%Ew5aUn#jB|4XCa`b;Ro2hph`{=u>C9m*$Dl{UdN91lqSUW|FmNSrldp9`sNiz^)Pel<4?G(+cC=V3vB+9>VV4ILeQ2J7i0}4?GieU+ihG6#c zi)T7=L++K?pqTBbWYxf#(M5~YE}Fac>fqmq*}0gCgB7QNF!xf)NUNen0LKGsZmKIk z$VM-GLXRTx97{-uotQt_Oq??1Y(}k zzw`Y)aiM$n+rKCybvHe?W*4`sZ$3`DGZ`wLok+&4=PdJY&x8M3bWi>`^~{DGc!9}I zA24kb`kySz!Uf?31n;XYfmB>~hLs&MWM=0&mMeh6SE#Gd)&Jf2XV7YJ52&7Rvq`(|$<% z?DI7r8=M25yyMr!p4e@ma4cvg+!x5dlovJBfwR&6lphDK z=jz(}e*N+N`Fna8m&HiPPm9?OOxR~T2%V=C^uI373oiC?r|Y&nMJ50#kov*v@;qGOH~<3Q7t;Ie_pWr z57>N61bumwwx|hd`^3EF=TNKgFA%w;Yt1SK!ho7K{OB^!lkj1;aJm7!CmX}?#KQx^ z(@*vZ*|K`?K?e)e;UbY{h>$jTUX@I3@wJ8Vx}w+q@$kA@tsig4oyQA=w8$aV1}bu? zR>E646))|_M+JYmHENQ4*Pe)PcFhspXZ@wGhH{eN%9xCgb3z}@VqMM=d8xW(SU{0< zTj|TvZl3?in`qiM9jg^&htXO$B~b? zW+Ej7u~`HH2_ci61Di*-u7|@)Y)XAc778w~HoKXB=>28Wv0ET+S+9jR`(R=(G~Mmi z*x%`~(gjnhgij)YPm;rfP)5pNW|kXLOB2W4GSH{Pz-B1Uw{ZV0+s^6!Zrmo~Q3_g* z0g)0}Y+zd>3^10EYI*v4m^q={Jm&RUf)H zVXG(y(j%?lZY?E!;yO^*lU;_$r?uXQN1xsWa_wH}A7{%p53m%*r6)EEq!E59YM7S> zRNrZ75J<7?E34l!FE#@qD5hsuXka0V1|dC$E;{QVDe1C#mPmO@e{aU$DhB z5hd;7{S!H?j;*!g4s5A(k@l$m>c%@$m$jf;VHq@;c_O1x-BRLec}jk}k|k9hw?xi@ zH4F}M!CHfTiXL0MAoLE&A=_JxwzU^Lw8A1}@@LT!n4`p;c0|wB!+}B&k2AS3^#Ykh zR{VN8c!MwKY2v#)!oHvHJAw5|_VKCnhEW=`BZ(n5On9-wjw~g~TA7{zvkJS_x9cW7 z+3a{oZVaPNJG6PoO_g^v&C{O--BV!)A5@vY5B}Q({)WSHY$5-6dGEh?dAO_02w7HU z4okT_FjTpk7DK($dIe&gxx%uEr!DFt!As|kgD#!NP;@`>=tz)sG7L>qG|#w~4-31h zg1kcsJK%yldDi7?8#0bJ$Wb?%5IVY6Jd;;ieGj;qjg8`Oqn%82{)*c3+iI&C5MoTr z;wXXA1SlmCXFgC$Ka{+@tZXR&*p&50H&{)+?Z#{k?D3YUE$PB|8G&hBA^D_S{8IwP zN+0}41_(7WW``3_EhgHz#2C`=SI^ti({P`~al;8HP{8V#c^pT!1duMq6$zHHUPzwVCvn zrkh;AhYY^u-13GkU1lLtC#GUg38hfUvnDf%8jP%TiZIT(HPI%ki!Vn){dyj}qM(w?WIae@-y_v*Cx=5YqlI{d z9zfqvJ~lxO&!$AI1}=tTro?1L7vW*|mu{Zj;WGvHh9e+H2LX!;V5GbNn;8>mDA9nv z%=kMvqy3fmmctRcIYFj_fs{iA3_jm_=4p0R&tkxkU&0bG!@LBm_mSsU-VrW6;Zjfu zu|TsSWcHpf)u<&d*+?rwLtj^|FXhPwF{_U_iQ%$+bZ{LvBzdAeyWzKLX3xP1CMr3k z74Pl$Ex$j)(%(Gb4$k?rg}9TSBL4E11oW_X%*n>72=))!e&gpn%fbyR@vW^oL9%(@kaGRC20LP^$lsT9PKnvhc+@tw^HK-ku zfga1RfMb^@QJ@<6J(naI0F}%z5TT!FGAWC7ub#RO=8+k(jQbJr=c-*FI&o2go~R}t zj2<7mkvX<}VKSXaK{733n^b}C`gOeCsr(KjP5m0a?bo%rI+$xrBHFROZkV_*~Ope06&_HR#JA++kHG(KTB6RsNv=^4=sz>NL(2RIk{EW z?e0I~VB0cFUJ%Cg>#;9y_MZZ+TJOtFE5E;B%)XuDGriFV^q&b~#{qU>QzLRTVyOxP zxoTF|mJB<=t(X%Ff59Z?=|gn8->?^OjOt@V)3DyM2XZ2M4HB;Oir^2m3tilqEq;;O>z1d+ zYDpAjrkof04+gh%&E(>?X(ea9tX6V5tdgWiS?0lm&)}9T=ONd2(9Hq zceifdnX6e#)9hu=WrBowB=9Fe^5GCr5SkVgiDbPbMdirlzJ6ZTuFD*E*+h;&n9Sc- zIX)*}zIktVBhqe$q0bTV$&@yAzP%^JiLZS5lIzd5aPOz8d}QcyAk*%`o4dghw~Lz} zw>raBG$hPIOurJU+OI{WN~&OC7sCwb<{p#D?reueqtW>y?TL|IfsRJ44H{InbisI8G;|_E&K{- zcR|1%MSR2c%6Em=E7N&x^{x)Ub*4$_nXGMWbz9dp&L11y*248|=FXA!>Qpr^xHYi2 zA>N?+mKbZ7Pn|uU#B)IPw?SpAsg2WOekHpTCaHqYm(^FcH5yj!uZf=}*jZXRa%CHn z)(yh&jA$?(X*OH23k^8N>^dsi_BEDVEs8ML-({hhxkj(kMH~a_=~afzA^geI!r$I>)nGLY17Pd_K}hB>-%Wo5i+m$^~M+6 zTW8MzTOgk~;S7gcke3f)Oh#FIYC<8~c9dwana_wHnq01aRgt)_j0DohCQu_4e=HWk zfz(|I$VVBNG0u(`;#SPC`kxIyE#L~Mjmr$5fu_h_^pYmTgGp}C^4L&&^jg1LucRvq zm9nqL0j4`6t>n<1lO#4{3yu;djEorO+|4^K9_H2-a53x>^&TiKpm>HMiZ4U^n~OnUU~|sFM3t^ z-U&LZjuZ3-EVpqAxh8kxHpgl&m2pHQG*9q6fN@u9)pzk8SW_ zGfbCsQhD^3;Ehm(Ve^a%x_49;eqkOlB3DM%O;$43xj-(w?fg8mg(lJajQW{2hzc(p z-qO+SSvp|PNDI_Vd`!@PuFq2)pkYUgTsM@dyaJsie&$ASLv`-j0d=-D*MaMvZH-Fx zxDj%Sf81N7efvwko^>dtk)43!e3Ae* z>%Cw9P0+Aq1eCYCIPB3$h(xKZ!=GGaeeeO;21y2iL{W#>MF0lRR;) zeU>BpRdy`8LTFqhuq}-62Nt~f7E7*leK8Pxx31?C3uXr~|VE0YO)do|)8fL6Jy)$Q2Z_>BGT-r5pbhwGA+1aLLeZsZ72tj{(UHVCTa4LAdTA)T*b0rz2w7>4 zjYNlS(eUz#aij3z&euv{zQL3SSAq?|zJOlLc{Wc{q-QmjQtayBW@C*~#kh*Jb_zWx zwc5`Q2O%DnSrEOens>O|Jk?~T@)Br|S-$f+3fEBEsnubeA-vni(6P>aZM?M0!$Dz7 zke@z4I1oa4^ELMvFm!(d25cJN(2Vu`HglU;WVD6?^Q>IfY*u5m4p>fR-v{$XV!bqrGhA6P%v9=f9o${Ixa6&zklx{e~en+;f zJ@^0Wd2E=0lk4EK=3w*qghi!QRpI?3Gq<+eNcukFoV7CE1(KYS8mPMZTxT&zou)aZ z&Pu?@;dDq8)kH9)-#{$2-wS!}SN{#zXEmHuXDkQp1>KI^3xlACn}n!*&)#t-l8tw@ zhr*@gHII^vYAl#(mE*B|GxQnd-4VV`(6^eBOT1Nr=vvS2xX9M!o6M z@oE+|Fi$2lmNA^R(>&J8dA;Kx^8~$l8IzHu-SS`k2Kk@eR^iEe-2({_5ZlTBM_}|X zmCnV+#Mb%0bDcDtwnkI$KGfKM0!k_}F=J$bz&MXLmA2g!w}(wwTwuY3P{<_wf!YPQ z{}GFWA?2FJwHK;c-Uqqs4OtSG?!gZWsp6v2_h7ay5E^wtE7?@*ReXe~;d#(f+1xJV zj^BHmuM(3Bx5oa+Y6kiPTZ7?&X0`f>)(R`_M)Xgz(MWN*Ow}}$VH9r@%U*JdAMc0! z&#ina=M{jf1Qh_s)@qhVpQvXC``et`}7HLQrNd{XM%*uqFz#v&Hf_8#3^XL&`rCVHI& z8A(D5=An`uG1^j!E{w`nkqm0+`8EQ{EYISVU@+rjy0h9ub%K05=pT)=g)|aoy1U+r zHj2A&A~xVj1zrgkB(&PO`in^H9WAqcsWjx->iT?OZtNU(P%d~B2+I3v&u94^_6#Ex zR6T*F#;0??*c%_)#@cidcMP7#Qvu+-U$kUG0fWM3D)R#p`v~)oWPtqyVGSdxNGfOL z8}NXta;xCtULeo9$u_xtqse_Po3@)=Wl@Wm=4>%DOFX@r3M&pA!l&0p%^>Ns$vd<^ zG)`aalTw@2u93|>^UA|Yudzy*B$tj>C`!k79`Lidu(TAaU~dYq#CtGEPzQSVddo?c zC3>SlX;0L;ZM^4>YC6Q2-olcID)S`H$QryYwxJAfHv%U?IB9gpA@^ z>@EKu>?dk;ah|w-{2b>=9)ME<%)C+*7_wV7l4>jUX+^@>_K|~t-AI?>M4|$E*-^_~ zE?2?Oi54rgwed2qV^zmCj6UG~=#c6*H*<`6Eu}k{1ce(gth=qRE9C~HL%nS6%G1I_ zNQJ1WBpPS1E0TkrgnsQLzd4sMY0s2ubvy;`tW*sDmf<#xdK+x$T_X3GDZ~Vofx^~E zw(^j=`*1IqpeR@7u3hC{?OnS~=F6GR-*eTH?ygBLP6@zjRnF-kV+MM{z*ntFXryEm zDSTqca^j-L(&x!CKlQ39SfN$Z5@OFjr%W~;!Gt!s+AZbGd1Cs?J1+)y7}k!sH|8udZ9@U1gj-C8Rvs)9UQ}sQ z%7d(4!TmaycZft1o>TZ*4D#jGvICSmL^Oh!OBAeNb9MV^#ur+fhB0x!v;*xhnFJ$g z?yxfoa6;6>&?MN;TaZo`J(m_Q(GNuOoRy`US5)K%$tN^9X?;0Uh(9I0BF#+>df}ya zD0pbK9jT(o;(#d}84{m4)shfGjq$2(Y{#@Y(u~MywCi18pA_l!ZOiF1Z6F&>on`5;Sl$+FzAyWEtxu zdS1*e^@4j+Ac{@nx^MzF5y`8RG}(K?lx7na@yZkhY-)lLI?>bFi6i;JqYX&K3Cd0; zCbTC*J}7vHNx3A$+UZ`_1c~8a)M|K$Tsd!A~g_$MjU=^2wX$PcG(7jz2PRBZHPlJ(Y&4A z-$y#Ka^0y)RE=-3%@cOVP(L2iq<{!w`Bki6#TM?mpf?loLmS=#Dcrb1FsKc&Bbk1M zWlf`x5~U3gYuuK&Ko(*bznjIrifs-f&c0xF*5NQbTbiqPbR2b?`V^jPP{)kR0r zx$g9-TL%aO<|3drb1JXIhnX=9flq}@M53ny*P0e0*g;b&zj2Z;vCy~r=8lr7pJrcb zl_%CzErOGE!GO7MKThIYD4WX68(jEYv$7;mIsvs;6+n{S7@}14WMFUJlF2({P-|z# zn$X$vCymE9EFAbK+N4Rysk;H-7+zJ9ywN1}jGwV-$yt`!VK7boVK{&VAwj7I`Z-TQiN6}G zd=)}vKNia(_IR?(iFDw@c~UB;L1(M#cJ&>BD6tRfl@s4g2s{aDs& zz@%@#NB{{yzc08gLKGezR#M~q5GEG zQNnJ4-tUpup*+7nFNeR+aa(JBJMRx1+f1F_4xbk#Gg@|5Ut@0HJiWdKmq}`4cG7B@EJ^- zHs!XSMXcpMoX5oyTbJ0j3r4IlGWle$VYl$mWbtUMmkLME!{rW)(fVtJCa=KJ+(>+Y z11wCix9E+!Osw6a8*+FrW+Pn?Bd=Ghbn6eJjCjNPehcC4$Y#P`^6z4hTU&FlMw5GY zcuPo{Blv4yQe3aNld2hhqM;&)NCgyZyFnQ=S~P`|Q=?TrR9TNE#>Vet&d{`<$B3*f zG^Ta~h!1UpOJox8CJ~x;ES9`8M3`pTzmN;}=EyD(I^?F z6lJRrJk!q@rrVZYM#R=>K$%&M==SbS@MIbnVPm|(1n8i37)Wl>P*VbAk3Qk`wmLP- zXlka3hDy7T1#Zb~7WIk7h<=h8R>F1!IW?MSg;{AnB3t_XwHYxiPdJLXs8*E5fWBHY zbL38>3qzihI!x1J-64B(6fcZ!_Tn2!nCB>N2=pO+#6iRQ`7p;95Ts<0svV<$9i8hYg_so}P48)W=|h#E+m z-;F5nTxX+}u@Ra}iXC(4!3sS~lNu~l@H~m-CTplM-RcD!-teGT$GF~&_}1*J{4y!V z573mN$kk(FqSC~5ST-0t$9&gCCFP{kuV)I~7&FCz+?j+Zw(QyF5U?bK6K;u(XV)dn zg01lvS*YDP;jh~#>tf7C(nlIj9$4f%K8>PE`|b1+$HEnc{8M+TDiUf@c;eEIK4+3q z1Ll+o7uBbsC&5b=ON~U1vMn2vL2`wJt`e>4s?)=lxH2aF$)4RMcynH>htDWe5tlxc z$7i%k`$M_)^x3o)Y%&w;POB(Hh_O3FTgfuy*~Cs%LZyeG2}DXLDv!;ZHo2xg1n@%& zbm^6t&^%=7(v{25&eW??_6`lADKy5-TGgCk=L(5(?Mpr0M28VmxXg7;IGQaIu~OO& zk1?)229I5ZRStQnnKZN*>`4^HR-1Ct*N!`?R<>v)L7}wS5>vl@br?RJe3*a1_4m6v z+6q;#ST|xh7#GE`s5QgDnP664V!QD)Y7XesLRz2jj7&Q74Uj2e#Ki<#)XG(fJBOcn z#$7+J(I4B+@1u5FtZu_C4(H3TG;|t=@JW}KN&pgDA}8!)3x|N==G*pf@4n25XF9DH z{%v-erBYB-v_$vQH?HnFPe$n<9~-fVB6&@J-6$qHx=sef$ZgzCJ9@PBCyIo@#={&T z?J(rp&ww58cCsonpCZA*DcB1E9f7#6KSmVxkKf{yP|#0)9vH%Zw9Mn2!Ul z?KA{}5zT4Jqf%@SSoX)~3Q=D=rv)^OsDmI1>pv|g) z>g-H_^kRej%Co{JKE5&XN17;{!}hi>f1L?7H60+Wit?6zVae(iIGxww)zmkWVa_l1 z3sI9dPhHXL60DOD9)Bg9JqAG%h_f>~BV>YJOi5d(KQY26%h_G{J-*kX2f(-wA9 z4#k!Bnrxxzq|Lwi)EO4mgg6HQ)qQ>4pXt)V;Hf%wd$-%q=W}reyUn64s#n!RcsQf?R(Jh(@(%2}Pd4Nv%ht#aM*A(Q z*zlZ^G{|#{bl?d{|NZpck}Et`HY5jxHee=B42@^Qlr)#9rm?7Xl#Sog2x{e(1=G9v1XtV&0S;n_RrWg($ppd>5?K+q4Cc@2B!l|oii^)>i6$?bTvpE2}CsD^=3)F%pHl4=5`(PSL;YP;i z&_zImxqP)N9;4}>CYqL-ffvrQr&B`O>Ck-9E`=izD%P^JtR~A;QZ_{2qIeeStQfS4 zs%em_YIEv9&(emRqZ2C;u*6OZgN#Pu`DRuUHdZZFAY&)faMsy*)q6EFqiy?EEDxy>9!n{{Ae`j_1FKC-b*> zy?Ee9`xOi)rtinl`DHNxw*W)U4@d9qiTIje)utNi#q9&S9>P9XdatSm8dfe%{+CcG zwvMmYWfo!VINP}HRoSi(Z)sEiw)ZYz7^sISC~8gUn+(ZC?#7YUOqOD4w#Dhtue+Li z0QU$lcXF}U4s-4^Uub2+g$2jX>9Je}K0&d?G>Ig;8vX8(oT#_cWgU(=hhw?MgJMLDGyLxQI zNLZhasL!A6s}j?%Wf{DXH<>{vW#DF-DZ2T^AkOw$|9@ z8r$|7+qP}nwr$(CZQEz|caxp$ea`(+Nq1FsCvSJUs_U%>O5Y;icBEpOls^V?-{>Rg zw7)hi=AKszWY$2=2O@`dG{_PU#!M-A9}Nd}ycXJT^7bRUwB9`3w-W`116Hh$^EhIk zmVN-~deV}U#B(tgM6vqxq`}GO`+Gz1l1_C>=!y^Jzq-wq56^4`J)MnD!=SE&ucmOr zdW%DSA0C0T3-A$ti`c~zmE+l^Gq33kW{qIp|BmGPK)4v6yw_-|+We((Io5qQJ3FPu z%@RgU60HdJY#*!SjV0QfAR8K6D-n_-FG`A*OeRHacFX*lClGm9m)#Qh?x9tdiBZc9K+^5NNe+#$~i5^bvJB~ z{B$jXSoD27){x_wHei41MDkUX8q!n9Q1S(oyu=+3%5olR@?ru3e<|t;@t89_3Z6#X z4<@zn+%Zv)YbAX$Huzq6d1gCoy=+W);eH!VnY&u>7Y0+ziqzE382DP9-cT%uIz&p7 zc`poZ?O8U*`sZ=z_-aEGgQf5~CEytaOxi&xhF*Uag)Zch0%q-bDrXqKtHE_NHcxX} zk2l9*)!{RJi+9}q@#XvYO)1nQ>TC)55CrfSQV|S___LRZ3rueBK&~$m zjo!QSK)aW>Rgp#(UjlJKG0yt%uXPW=#%fp2s%AW-Sl0NEnMkUO$`+y=cwVH9x3cZY zvRmEv(DMff>+n$n6>tuguFZTe*zVyP3C^}6If%1YZsuVmdsPfyU$6H;kS=;1;id&kim#9<$zBC2Tme|GI=S_gwP|-TC ze)_?k$47d0-)R;BP8|~i#b3B^ZW_z%`^wE_Cvvqn`;Xz@71IsfgbKkMy%A?j zqevm5ENan-hI(W%keIDTH@a~RjkgMm5H z-Q~8>7vZw(zvL>MN&?4aEJ2882qUWhdgGU7QYE|+Yfz;hN4eZV{9N{HP^?1}gdR|W z&D5HJ%^tL$pW^btGbI9L@g}*JN4gXoIP;gvQ8UW$@{Z5HijUP!T)~8%I26p0 z%k_f*uXcUkgk&^b4N4zW?zn>tVvCUJ7U}42JgVIP%D<4AV}2Xp*I3QVo=*(r5m%&? zxlBVm&_#Hr$Lot)e1x*>>|r((uJG+3@OxTn#^^-gW%H*K8SE>|mmrqe)%c}k>`-Nn zHK!}OaCTO)ZEtR)4|owNrLvVL_5oC8nc$M=QaC>1a zkKgDjcS2xgZQM74W_i?Wp(69%Yg(7jV@ucq*NYJV9YR}A4s3k(HmXk9f*r02(xMI>K2~@7oVyQQkjC@u`c!n49M@*o$+&2&MQCj*#jQ$}#1%}Y{>(;eVEfEtdUpyt%3&4Fz}VFG z-f&3Ni{F3iYgsQ#%rk*;6IArfMcPKD&V~%CaiZ+5Qjz#l**xzuu&#?DvX|4+I+=V4 zaqO`G;15LRUwSN~R3yt%N);(nGn7?3i4)eZV@v2{>OBA!j9@cUQ{PBLU;JCR(F43F zCMwMCF^XdL6{ey=(gpYZfjrp}UR!qNcBxUo!nv_?R}yLo|8fF2X?0?6~@i;=Fb19+hZ%F#Q6ij zcY-%}`>`sgY7sda6wvO#nBq%)fr>I)i-92wnYU=jsq9eYgJH+Ss8sf)KrNIV1H*V% z*o|jd1KMe!+I4}I9}O!PjSStJgT)o|!KS;K!sVWQ>-;9(YhaqCNvg*UqWrIg{&2f2O&ZkE z_&(5bB1Duiu2;ME$vCA2jy&d@zNa{gD(Mi&syE^nqgxcChZLi8k%E{52?Bqv)`jE; zlF<`Ekufk9*o%=HgB{8JVb7ZadIy8P=vHDziAmK)ailD;>W{i=a7aW!x=rlifFI2{<$4rHc@4@ z=>qoW`rbG~CaR7uYUN?})ks${_&5jWs+vCG`)AXo7kUx3(`@q(+-~dpcm z6nn2VDI@rvpC4Nh2QJ-MTJQP~-%odvY%SeBz~4V#*Vtr2>19RCbVYBeg=?v=0+rl% zE%2;1wuxz}BMEfgMk47HvB!5lJxyK@UsY5)*5A))qt~wiD>Nmz>JqmK{xRbI zXD5AlKR=j|&AuQvpI`g0<1+_rJy^dQNUH|bGxq(j%WrcVwVqQ$Rb2QN84x~iXRier z1rs_qnIDE9-xp+}{bwhl{U^sEdJM-q)|NXRZ4OVL(^)QL!b~5s*r-e%)xDir&0xT^ z?!BV$6+h|O^4y5*G?}YeX+;@qPtUEhY{!kx6@T+m&E#pW9|is_G}S{#Sd_34WLVqI zLzxztcPN0#J*nfP>=$!eCKCi6mjKJ-%bHTZz)dM?!B8?|SAGew zNO%<@&G_t*K>Ba4f{hY#cIp+Qk<$?&9 zks;4+(ZE@m;c^|h-O2O*_`Lk&kkPWe+3n->@h*IIGu_6u(bEO3DHmPy$qC=PN3Y(9 zMSw?JMSOj^JmQi&>~eB$aR8k%lCm9;ICflh(+9ut{Z29n&PEs2(*b8Tb0_Mv=61Aw z+D~@?@9n#4;klPmC9945^OTeoLl-ms)A}`!N|STnbE3WNeiAa*WaAfw5XiQ(=*eD1 z=m?d~*zV|X^0}e;VhsS|ihyIi{bufY#AaI5vQ)j@+U@k#G}nP|<@2!pjQs=3cGLMe zbX?VAVMTZetRz~Kh5vO6?+T(Za=4Cd>*RQLboaq~`l9ve`werq->yxM=tO(J(YZ1s=v|C(F$o4g zf~i!d;+D|#D|AgO0|a}E{~3Ubk79;-HheBq{kqC(!#XgmSOH&|$3R(KsudCcj8DK@ zk~2Ve!6!ST68sfZf9z$9`N}j*l(fhUL`}G!mgX>F4uaNo(aH`$FqTM-``UQFzL-0L z{!TQOZiZzRb(x8x?W$NRwA^mb$AqBw&uH1>qx=OOz1W^mx?=`Lum$lc5?`U z@5kmd{sbF^ytB>&d*e$&2b;O1)VH*MPJJ|By_})S!5GX$uXU@HK&e z6La1Dx7`UERF91(WY*zx&*T7q=hxcw#@6g}+T%dB2kYzocgds^sr_O1xi>Sa(|QlT z^^2|XewfxQc+invKleK;n#kzU!rkJ9*9K|D+jyz5^7=q~J*ID9@3wdFydwc%AAYga z>I%~5f^hy_(>eYK(p(Gmk&`X}eLtNa+qAQphAl=I==0p0Sv{zl41j*u%(TGpEO+ z#hIzx`Wt)1-m65-$iDEKdry~;L##D5qRf<w) zW{tw#v~L)YVqz4gB(UCW*1z98!wC+xNgq;N0DtQK{PNlRUmO?%K@Z`h-ylVQgT?s2 zf|awkv32-Q#6~gl7QcY}pdIiJc>5g|k&8ur>PDHmjIsDPhQ?ZJv^vZ%pZt< z6q1d?lIKJOb`;GdI-Ont*}i!RoP@vYRJ|TNzF_|+pvoG%k@Wi)2aJ3R0D$!SX#PXC zwsx}8H?sfLZ2k|TN=q{4k2vCgVSzQ0vFbw3oVl&wABkR|VX>-IxFXm)wwAWQdJH>C zT%HFnB_JRJ19yD~L1iijp~8Uodk;ncsCE~9{6;6o%O}e#OUoxL^zpJB zwS_OHTeiQ>RlDDA9ld!w!c}g686ONfyk2V2vYaj%Q}<_gckZ6(Qz91yYlfW~mFR34 z1$X++ovm$Ykp%}KL+9_SSDaekJ`+p&I8`gN(`%;R_KnwrQ&aa5`-KiY>AX=or{eblmE($c0(T*mb>mO{ zw;lP%Z~C6R5y#ly5hE3*P&c)yH8-Rajo8gq{OC7_R9%CdclNAFGu+g;ye}T&DHV}@ zZ|08xf^n>=7SjO3O{L5kP&liv#RchBb7%TlVWNR7ah-xmsK&Y1kgh&8w7*rbNvvT5$@Y>sMN9GT*T)$E1vMDj?Ke}6p8 zz-Rq36FOGJNhI7a~V3XY9E%>d)W^Qg}e{ z?6JL;_*+uU%D|mopXr`{Y<0T=Lgzk#k^6SH=g1Ub#2rOURhTjE>&5n#^nbj*%Z|N} zs-*xe!x9W3_{+k&zZ$dwfZl3t|K+2~$(&5%tG`1mvJ?|}(8Tb1I%{=%*wqqc`xC28 z>QM0H3MBEvT{^w2sz#LCMkvfIKpGE*=`su9*Q z58jS$p(_#tmsP;VGn<@#awIYYIRH)9$;T3NagZqOnZI)mQ?gQmk$7SHH!W&EXCA!p zhNQP5U6h%CJ4N;pG<`%udFnyn&5>k^$rL5$Z=QG^0sF5W1}J|36`Yvz;`am0>Gmc8-e!;FdaO?^ZpJ)hA7|auB9A$qf zss%<->b&k@OT^rq9$!6etC!NsrfzQ&VIBbYt7a%3tQ7wTJ#H|uSRYEHS=I`!FJN_S zkso?I(U7K%!FaGbWMVWV3W3vV0iZ=JwOTPnra_s%Fpuz4Gm)zAvR)1dJ+IKytV{bZ zQ0;Nn?pI@`|5KzObobFb1krE^8h{HYRu}EHRK#A4h0ua$1bEZQQg#nqF3NKSx?S(| zp)7B7bOfVTJI5a|C6&ah;fn*rz}TDn?Q~aCI;125e5-ImK|e)kmZPA8|CMnW2!f^) z-2u4tUVp;gEr(ZaQsg19#OyfE;-h*rld+9H7bwD*ir<&rm;rHP~UfWC+g6huV z-eMoB?2J8TpscIa_3gn-Fa9R7qR1eItm22JS)`S?Y1zMp*cnENAq>?ko(;xv#x~aa z(RceRkJ7!Fh$S8_<->MVYq0^409y0Pd;O-Kq{H>(t)y`l*fePO)6Jm>iqF>Qqlgb928lo?&|D zAY-?Ijf+Rvhd=W-I*xSH1})KULUQ93OC=bw`&(#)1DWJyg~xYK_{jAOy>=RDB=-}mG7g}N1G1h1fM!}M8`)sJ zJU)$qQzzvxNw3n1vX~;@`&Olg$)fU2xnr6ty!h==zk(! z2Ly?eElo-5%jAN(Bns#4&#TXg?Zsg$Oc*F9-Yy84;YX{(xpEhZ7$|A%ZSXf}FBT94 zXmq8Tp3wv7bs;4z{iokwA3I8Uc;#JF(qS26=3Q=Zgr3yz zJnFnXS^XO-L8rCp30@JWtM@J_S?rC)>8k*wOZw8yq2V-SqVi$D=G2D4H|{s78fywR zDEX^*`p}3VV3!yY%~I}gBOF4izmCCltOyP`BdjKwh+NBdnn0h+E}O|+-A}kJM=3UX zBNp@h-qC3Nb79#o4D84fnmK6aPZXwktTZOY$pg%r9e$#iQZCC{z0-&C#zm8Uaf3Hx zvITfYY!bi=5b8^SI&lZ$2)l~ybxqRFwXBG(YGqKW@63q*h&$=2M2B4?*7v~8V44Qy zjDpu}A0N$#3^jIxDJzCBH0HUF0yyyQ$y^%?|6)&b5RDwxPM zmhgYx6~hTH^Z2DIm2i90c^nvi2s)OV_fU%Ttgg9GcG(fIhSn)`xjNN?Mr4U_+gt@% z5Mo-WVb=O)jBfYWy-4*s){+v)VK80M7>8-8s<(7B(={kp0K@)u@Dr65H$|hbTsP2k zP(!Z}LkT*Ao2t>$Rd1cBU=T`U)Tc}@6*C+UHGNo2oJkzD%Mz{Nb*JC`-n6;M$jDwA zB^bs6j@9RGAIhs*yh2wtrOM)*pYEm~)+41PMSrhpS6RagO1o$n=d`gLzoBO`UI1w~ zu034JJ;jN-NF$t4znyQ=i`8FRtah zPG?!(p?MHBjwfCmH4#Pg$hEG0{3=yp!`=Wyg{Wvdq@Pv!+>5-@y&b)iMBE>2%x4$b zk;w=|6V#FV}=YOXFPeKTlxmO-%icCz9C5D>;g- zWIwX@n4E8h!QM065C70{4S60x%^n`#Q4A{IPMI_$k=N$Sw^q~1cp@gi)h*=C|hJ`S|hdSk7YRTQI%T=0z=Y3(3BA?G5RU*fe>{=NU zoO5L0OMe-wipKrd3upYnmmrnDS4O1{>$ljZ;r&)QnTPmZdvkJ=<;bt&yZWsph|gPH z;#UFMTURP}(MK`Om*I_h>-+&06ERubdK)ichxu$EXgw{kdo2|&XX9faEpSG4+4vO9#ToS zKVNn+igcK|k}@12YB1nv-?^m$^~9cHt(Q{Hq%LKq?B}D3ridVvmaQd@<75G>sv_FN z1OFz`$J|&AE^O;7(38`SNQjKit%{3~APC#3)eGHOMkrj3Ttw#;Bg&eLpu{KC_;Rh) zt+P%ULb8TkWo`arme`?ZxWA0h2>WTF(}q3FVGYZ_At!*sj|8_tZ*Ok_u?|L>5AjWr zhL1hi#vD0diP+aZtj*JALVpUUMHU)klzL@jX(3S~s|W7s(};6KS2aQH>Fq8iq4oM& zKRC)H=b7|9MNPj}R z;lr=Iwl9lH*%3`rRfdIL`a) z*k&?3Mm7cw#t-q`#=hU%Nq?ZNRs|TI)FzVrw}${W^kD|SyPDA~`3fF(_ElY_yj&bB z!rwiswv)mMoH=Kk6hk}s`wWvamwaLVTrskQmCFcXxmT)|avQC%JTilWW$Ignxi}Qp zARsRRS#yVc{VP8T53N~%u-AS$*1ubM(DDq&U(M4pA|_HK-VjF;b+#D_2$E#6Y51yG zJTHTI3X(U-IzhC!R@;cGgZz3{3YP;nno}u!C+@vll8X6ZntbthyD`&6jau z<$F=8W|p_5ItL|-@ES|Kg5uZqM3uh2bw9wL2ayCYK$ zIw?E%mHYPXKPd#`55S^~-*Kir?YMFAY4vWZ!Z@i+dY`4IKzMyOk1@@=8R?XUPxEM`LBaN0Fd#-q*&C`Pi+NnCw7QacbAdAa*w10lj7Q3yB{cj1iSx^$SVA2T35*n`=lL zZAHe7DVD1fqRKG{Gx&|r6;V|^I9X^vQhT>+WDp7oKZ|i)UjDgW{sb)45XYD#VyA0Q z;^38gooTzEdv6FV$g}LZv+~%vwaDg!fI)WC88CO;h2g;Ehp5u5#`7grUHQ5xR0hLf zl5Y$2xo?Jic`j@4TX|0U>RmiTuI3viZE@xG#-)hYjaj?NrsQGv{gIOIT!rgbUpYy*r%5viGaC3OX zbfHA@?5~qT)>4QR?W6fZMWVlc79r`r`!#DBCLd?n%~fdmQE)e~?73=q=C#o^KWZ#U zIZExOmxjCGn{Ay1xaF_o!!5`X>W>NuB1tLaaiP+YDR`i`#%{`8AZp@EcnBduDO8D~ z+Q2Jtq4qx=xtF+$^VPFT5qLrIl9xcgfl%n3{&NnA<%pM!emY=(eD{Tj6mIzNqQsrG z4`0^K2e@Val}Z^lX(ac;BJpOV@j6S~2Gd0J4)X+{k?ypkGQ&DlQ0oS3?h728*2km2 z!Of#0xPXw4)d$Y8&s!l1(U_i)qttIkfp!4!dZ$7%OxG$?Lb;_&f*1J{J#W>jXE4zr zF~hCQ#i~lGOJkx+j#|rly14=`h;-!OPs^~%&DxlZt{y+KS!GNfx@Hubp$!S zY(`N^YKCEqQBwCu!rmcQS@XEEAW8^V78FceU zy#YS^Q4$a2_cqY9$nzJ9F#3Ca)7o>Sy}aJ9lCLTH0JtSlp98B9_6JDC0Ci#d7Pd+5 zGUZ0j2@23Tw-Rq$XIO1t{3`r^>*KJRot4sd3iWAQ>+#qgH!?mfE#6EQG>wTWQlSeQ zd!{SCfPE<_vccGJP5Vk(r zR$yqBO2!>-hypdoSv;4a8b`@CFP>yN`pjN%3`WRne&?cFO6dtl`^(44IeVSY_Zw8j z#-u5>vIKdaGB{`|VUZ%!7?+uf?GTI8!pU=~`D9fyh4>?k?E@Vj5rKw-PwU%rQaf9< zI!gFO?0PP#-t(gTLa^cFGQDh+bb7?kaW0i|C@IdZ++r5D*wO5#EXTn$$E*AeSEsa{ z8Cnx;3^Hr=e5R@Frz>?G*y0c4eC&I5H;*o+6711WFtlyvDjv`aMU!KgGlwa4LVPpQ z%a$WN63vthD9d0nuQ6Jw`l57lf8q@~*e)DlXK)Uy+M|Ns^NdoPi6C(+1l4qGSZsH_ zX3~J>MbspIB%19l=mf`vjpeT}BC-4-UAQr1H&Txb^c(-T#)&+HeLiWwk$(8AziNk6 zW3(XxxtbM!;YTy#`r4X+W z%!FokMKy;-pmlaNq-?KgHm|kr2;oc1a()u%WJR?E7uT%i*-Y5JWSq98!A;L&J2Tv) zgGy9`N z@dq$DY0{3X006@NKlPVNL>x)?gAEpCMvMi(f5)6}o6Lzo*Suh!7&53=_-FP6DAf)TZJ%Hl?3H zz*(0a>UJCkuVdSYfXEChb>np_$^KMI-<%Qb$E!IGho^Rm8LX=zB4WVELx9m<5j_KM9%vFp#p;KHys1F>$XZgMgpw2AkyoY$3SAwS9ZR)Jl>|DC;#L<@M7V?QonbThyqY%Ct5-P$2>PJX(7-zK5PEJMBJK)^{{%`k zbVxeYznF1>+BQ8ih-saMkJzsAB7uI|HN;{5qTa{AN8G9EHS28qf5x&^UsB!w8^`5u z`TyAz_)kDuGSU`63@DxG@A&;4BZG~DT5FZ1c6}CReHNqLOF_~^ANpn#UpLZs$7ZRR z#|3MiL1A9|QU>8*$n2I$=z$98~*sS^`5UNzI;OX&GOeonHz2mFLKLPr9w{+CB%GTLJ0 z5eNVP$Zyd9o$)uev369_)3-GGuZ;haTA%fX2tpV7DKA3j#(EKX7#b0Hyd@Z5I)SMr z3I{4xcs&|v8)ZskKD;R%H!4T-iuQs5BC(k_DQ$Ys=_M_N;(R<3=$f#2YJ70V!O=km z8`H<>-32|p^=GOg+LM%Lq|`vnU)JI|<(bY-+ZddMUC6LuV%@kMCUhP){2cGEr2;pX z{`^2}dfxr5*^9IV;xs30*lX#p*joeEhd-?xdUj$+ub4V40!TU~4S!;SnBr#%2be1Vjmt3@9`U4X&#?n+=-rUqX0-Qv#52NJFj&5fg}zRnW)RRR;m@w(gz zm!EY15#kY%3?+(kCKJMU#WIzzodtN+od7`O|AiWXE$8)~Bsw@5unA-WuP?XlGYLWU z(t}8jn8C6_*eM7@^no=Zq0{2@ldCAAq3dk_DBz-#`3?yV26;v-^948g=bgc9P0wD{ z4EWv<(gssUmEl8=QSS+XKkfBV9o2E-j|3`DG!%22@f#V(@Elg@ck2I}`v zB%$t5R3s5X(^iJmLl8}pz~Q5Y2LYybL=Fx?ng}QhM2q~%kof%i$cg3b_F=3|UyWxF z@IT~O%!%zX3M&IMtZz~Ak{GYjZsg3TYd;rE`l+B!Q-Xk;9um>C^u)j;R9 z&@D5zm|<6#@I5vyir9d!;lEnho>#pqytmlyC6n{X0UwiWP5x5dlYTGi*fgnL$1?OdEQe+c>dI0uL(8g@P{|jp7ED{$ zcYy2Mp?+RBZD4Aq%pef^<;tVEMDjrz_E2aWsGE3xTn}8?@Fwm05*j{VMZS;*Dgi8E z{{bHp(xwvy^m`!~Y)An+t!6XgT=PmYZrg12ERVs?LnZ~1Ter2ns@nU$W25d*EQwMmeMksS8)0vw1-2?`WQ%f7}Ya38M3og6vd=2mzI z8u`!`1xgCi=2jI-%KrQP$9ImK&9R}4wTrBYi%qr|3#yxCWXpd|Nt%Gv1%Lnqxxbb; zvvS&!L|a$`D^Pvvg4$5>`4cTaNqq0rWz>)Wi^F<6=|nyAr_s7zCIG&@M?I0X#^OhJ z>*B{m6({*;x@Y^=Pys-RRa1I!(={kf02;Tl#CJ(Pie|2)pC_!A zsiy1f72++k)MJzSwRbaR`z+Th#TYJfTA`7-nOX8q3Fo z50fkhBl5CoxmEKJOZl`G0ona}L6$zEc#I$M=c@FUI&U~OyfZ;EozZho=vNt0d0DI5 z?qT^9>1QkUrY6f%?|T*%F+ zZ@Q(^>F@jH(Nxr=Urodak-02L_TwOCjcwvZCMk4;Fw7raP>rMjDxNoyGGbTqvlG|= zG8xFI6qGq@>}UP0I=dIEh45jhfA0tSaV>1ql}Adti)AKJrPl*&51mlm8rL5xP_NuW zu6GoDeO+1!eJE-85V~EmJ=E25JqQ_{4h%=$TlP@k>`tvvIwmL5_(dfLbTwZ&t#NqT z@-;XodTO;BB}R)|$BGiLOe`7Jo@X-jT1bAB!scR=vpHf^TNItpkne=>Y69s0gjI?A z&TwBl-Zxg?vrDEHneq0b=QcT;!a7lOYp2E7Yx`WH{UcN_|F)pJG8mTK!q3l?qN>9K; zO2eB$>43KBu)#!Qa}9n3BNMz#90o}X@VjuwV9?N#A!-yuXysYC1mrule?Nb3HW)fC z?6{PbZ`TS~(v~88Q4=0fRn6Qv8t=dqG7ym%QXhm~_x~iJ&KEs2njyW~|itgR+;pAJ8{_sdtBvW-J(K;(Moc5wLOF zVYHI6%Bw*@$sMAg2;7 zE+anH8VfgqIr*BIKsZd(Ytz$DeCZvJGc;G{O9t#G#KOE&dG>R9$||1_;RX%Lq$nTi zT6*;QkG#O-8SMIlXH$DsmrNWZ6b{}#gJk#9kfmP_fqMN0evl{Hh@J{Pp=yvrw~ zcdZR*CEt9V1HqUhwj+zgt!ndUl+*rp$3DRRXz5&fchBuXd|a~PK4FD~(|5#~A0 z6LsWCH_7Uj*VlrxAM?@i`jip-C;uvuIO0I5j+rTKs)Ze>&GNs4yz+Gs0v9k>sxtn0 z$3LkbktKX0QJ%rMwZ)wqb`+Bj^GPVTE@WL|#nqxqdL)lx*O)t81iMvT3mrNxls0 z76Qf?{6~U)swiR9n(FJsDJk`1y)9700#$=3ly51*554xk9U0t|VsGN;O1yqf{e}tk*PF{X+2^YE$+IuGE?2BIPL)AZcfcX-3l$byfbGb z{PC@REnCHWgP!b@b3&=}b7_Te&%JN;4%djGm-+x9L*Ot6IyjG3+WYDtHeH$*zZLiL zUZYwnz5$R#QLDjrObPSBwlp)-bS198)}rnk z#K?aS+V+P92L^hYA@}8lY~G0=4RZaA(P^$rES`-ctg@eE4$E+cIrLd!76AkcL$d|r zAzM&`>$nY(9^Htmu`UNji_OL<7x||BOzVlUlmlMEZWByES=Qd>WV{nN_BX*c z(eWl_-JXtzr$8{);~W3r53pu%ymgdKODdsoN-}qIJ{v?D4POPH3WumhV;i(3inf}z zO^SfrV@=~;bQ>S|_Dbh5zp`JgQ6yXuHA9mm_DHUJfOT2zfFg&qKo6--LaCqQzP_|2 z^GO?mJ_LE9ebh_K3k<5@wT=dOw1RyFqmlPyGwT3bNIlQ^Ao*KHHXeA1P+I+!LQl+p zy6Dj~-qf~_XtV7QAJp6nge;r;nGJe|EEO6&CD{G{sOSJJGLJ+aFMiwL9w1@5=-|IZ zhh3V*mkaXer-f@+o(OdG^ug3G^`e}NqUhVc*tO7(@>VVE_vXo2HZ+ zT~J+wdJG;9(TDT~P$)?Q@S~2A9-6>0$?1Or`fY;q0>c;=5bNPkp{xrP>f>WZt~GyU@q9V zyM2wehAo)O59qAqQ{&}PTv4xA$*03ZI_s9`=6qM4fl{Xq0FrwVJOaw>2&#i1+xU() zhhCE-1@aem)+!nb{TwLL@O%JU+Gl$S|Eec-$xe@o3YvaAu{^|k4EFP&>FZRGO-2Oc z)gws{%t*AM*3pRct_EiL*8p2az@c-wE8TM9`N8a<5L=wAbNJZ`0#e_fzaEDBrOKzu z!m;20EvlIG^AxrrL2oA20Q;&dXHeIGbdN?UR3< zGSk`U;+XE>lSDF!r9Q>++^jx+o4u|)!fV9$b|ThqF=Hp;As%@{RBkY&@lp}`+w#dk z-e@HKt;h{(Hvg4{xOPx=(mgOjKR4ZS>rH!AG^;njrbRQZy;D_Rw2S<5DD4@%9Z_{S8w^ zZ|hYeqJi3L-OUw{ajg)z`yUj8R;8OJrm8Bn(q-i6>nEt z?*PZ~p_={0z3KByS7-a0kt~#&4i>!2d2r|-D+FN5kaTW&OJBqv9ndE@eU&UA2sVE? zZ9e?FozVv?$LTtpIU~(Cg`hAznxByezb(^j2AQnt=ZvPKafpJj)nlg~BW`U{Lgz$l z&;9z&PLHl7&33~XP7YOn+3#01L_hSqXPW0bUkIx)O>Kd+199ogQqi=KHL>&@D&wCYS#0i{={6M_E6Zb=*j8ZJSgwUsQ(6V$6hT*m`K99{- z5L4txC~KtyaW75y=dFKrku4&PU{=gZDjp;0w4W@mX33k!>?k7ECuxS4IIoanj>W5N zP?Cwe%(+EAdj7>+$Po8Bko}B4Zk453rl*7RkBrFIvmdh62N~9v@AJxY0@ZVsmXK^0Z12EREu9f5zYfN+!Yi%)WUZJpqO`4TZ?)I2M_~@XkHoE8L-5&d1nS1vpY$ zv;;N=ZuQyJ5ECc-BA$rWP0s9yPUXYvyuI0YNiapvB>&oPEVuD}?aYxp%Pf4UhU6rH$fd zW12F>2u!(bt5TaCH*UH=-hroE3oL1nOdQ;i9+3^0_b%z}?rkub=T+E+>_PVQaZ`y>Cz{UXPgea81S6b-#cC?U|B4bJL=frYj z)7QDMVD%lc5)%DkZc{^Mj3+)WOX8jXsN@8-Z+};pi;wZ5H5p=f)-s^gw^gT{)7S77 z4ZqY~IFyAa6gMT^B_JRrpro{fl%yctvFR4hMtmLxJ^%N- zU(Z<|_QkbXznNLHX3ae}teL5GU^)h52tS0pvXB_#C7spPX5TkGfZBRz55xx;X*6s@HZRV2oe z`a&;IN(cYHZ z(?lf`D(nkE&fbqQs&i7EU%5v@wQ0YhhfE6Gsw+}~H`=8z$Y#X+EPgy)8M2kGJb7RW z(HKkjAbarj5|SC4p$7ee<7OQ`Ol!iCc4gQjJro3uKC+&EFU8)Z-mmglF|0sAJCop; zgTWVFaI5U8PMkciEgY@uUa2Lt*{E{Wgk)c|{;`2_eO)FG#Z08jm_I)}utlFJF7JI^ zh+IE`+at*x--u?bomtbTil97QWAc&0r|u}H&x_TmMO9BvL7ZBFr4)^`vAq+XsmW41 z`=p|rq_Z1taJZ=nE6*q;U6ajM!iJ6o=3$dxbO=zT3Diw*IjUWv#Hcl0r058m&U%H| zk##K`7|*?>5qFkeFj96(c36839l^?OyJnTh1}`+2ACYq}?(LW3_wSAIT$^;dfQLi8 zl|prPoZdA|7y&Q~Z|pDM$>7R|J(Ovqu6howO{1SfBGI4NgJDQ=)oc*=83Dy!XO@3w z!o7iH+Bs&xsoCB{LcA$ADLx=5BSl1xtN%@@W;-d&)(aj~N|YWq37MT)T$C}tB>@&q zay~zFJ%flxTe??Dw94D!xpZ>;z3pGem@PZD9%WF9a_oomg`SE`l8>Fe#`H6;ppS;; zRpLqagP`@*Z;?Wbi|$g*@w%Uoe*_lE9cgdVk_Z|TQN=%OxPRdh9F(dH2I zF<(PT~?&#YNkVM7;yoXuG;<`HIx(l_UEu+7o-(VEsk^tc;CK z@A@{E@J`2v{WYbhB~99bRbSo7q*-%Go7vhXEH9)Q4b&*nJI!*d2=OvMSH_UANl}kG z?`mTh$<7)UUU~HR1%-v66}AzFc4$Fjr%AWET*!fM`W`WAb0yK~3pwH zF@+yDuWk1XhtP~|WPyuTPK1-K-$YyxKGy7LZW28ZLj3WF$`(=v#CERl4qkv2c#t=t;Am9={A2Pr&0V-p7&?^A zx|HAdm3C|{FV|dXc%*WfFYiix3HicKJs2F<3qZ;L%;KS3H~!fUZj*b?gUPZA@6e>7hdy&2@0V&#T;f6KgT zZjAh)n1hHv90SCK7=o2d!75{ZXqG%C<7-0H!Kf`INazFiFr_Xk*Uh-E1Imh`vKqwu z*iiUpcI7nWyrqO`j-!R>O;!4HV@b?o8qhrJJ3=XJ$esmR0dsbb@0o_aA>l0n?W(}d zJk!_Aje5S`N$W_N=&3NG{;kdNJYDFO)X^TD(uhLnXWphw*FN$`yM;`#{vnA0l?P?B zx>x;hLoIRZj>-=i5ipX?0TOx#9a$a%iY}QCj?C2964-^)5MF6gu&1}fcaui6>2PB$ z`XSpV1kXK#CRlngQf3W`jUO4ZM5hXF5UI0PeWvfqvu|XTSBwO!`^aKIL|rjCCHeBP>5_p(A?2>R+Xko_z)Y#g`MzoB zxE-{~WJ?h4G6#}}zS1st5I~Mg*xELAMB2)4%otktOwsf)PH9^(c$p&U3YjF>Mpz^l zA^-H^-No5hBM|Az-wNFjYWW-AiGu;4n_n@DM-{2;xNy$xuu}Na3(mvw!v^yr{iGH4 zN-|Cnde-t&>JdG!aLmG3TLF3uB>O%mpP;_==``$4np_s(MNC(@`ubG5Sn!nTJaZNvV{SJ9$gps+=PX((4bvbN zlyua)+lQ}UhhGgnelSJoHH_#uZnGBM|AO!9j8wI$1#>h22_|n1&qHM4p5}ROPT=Y4 z*5s;QD}}K4!X+H#t_*uYMh}}wAjZ)<`MmOij0zfOS##@0)ze<*XvYoIG&N9HLR>H+ zz%#%F&Z?Fov03IfI=3cEta35f7q$h|Syqe+4b2igx?7INr19O5#XttD5B%x2OJTy? ztRG}Y2U(r#R7B=JRE7G0i0I?Fo3Q+5bdtSVReTub%H|OIsM}6Nix-z+#5c|?`~vH% zg>woF12mWg1g#tG_ndW6yOkGzd@4&+djOE>uAv_&0u?uw;KDh0Y;u%E(2yRG4VN`_zOi|cBB!G zt*IJyc+E!kB?@@>`R&W(m~r($qqqKvuY+9!Xy_(!OffRe6%%nt>mfFHrCUoOD5&2d z&UYu?zi(VzBMR(|M9PI9B-o35QD=B$X!Pr<%N0@KsoPj#U$}rr%v6?>8*IW*)B>Xy3ttOW;Fgc zlIP4BuCOGdTK--V;o}x zBgF%q8iGr^MYem!)jJ{W`yy?ptWQ}`MJJ<}XopHs%t7sHep#!Z?aoBTHwVo?mUS)@ zXkA#Eud-w-nZ~nC?WCe*6!4xZe+@s1WkI4QTG14SkQ~ikFM)q!#*r8&qF|KmF5veh z2XHa{2nVQ3@KLLWgp=^$H$$9Y-jl(gh#-`x7s-CEdXWCvn=~U;=yEv3)ajK=q~>3y zFci4i{qolfR#fNT8r2qvw+Th90YhCw6v2P!J7`ruuy6C0)yzXwaIl|FB-)aaiELZR zdiv>g<*TG@z)_R;o<{xlsGZyN zKj|J3nvsRw8PsaQV7;!d+0(r%`2n{zP@+OT*|2PT@MUIgfmZ1p|DN@RwhvVL&^mmr zPmaL2`#`8QFab}IXP_!2q)s2AF1azW&*QVo=Bpy6=?vJX?=85|PKtceC|fRC$rsd~ zWimZtBiKathj))1j_Ew5Q&!w1!BYFAGbXLEhKz$$rmU1llwFvfH$Z|f})0KkICmH{Fu3zCb8{jNf{6BFBe+4)^RM3Pm<(i9RgTY?nV+whL_gdqOd~hi9%tYTT%{>U*)C&bHGItNQ(1ERd+ zAccb`jFw+IsXh(B`q(0A$Mh~mn5@t;x2#&a>6UJ%7Q)tgmms2X4)me%OZu#}FV$Xr z?#(9Z$M!Kh+zp5RvOY}1hd6f8LxNysE-LtaN}G{;&e)P@(R8KN#vo1r7-6YKn&w3F zp)9?)-r%^eCPd@8N+4OD+qaA`*^turnyAkv`k2s=nr1c;XR`13)vj(=zr+nSr8;B)^tsy?Ts}hjZ+eU#yP2r@;;0&N?dK=b*i<1C@OU__L#e)TyhyiY z&<(%3YPjC+)okv>mXcO|F=mA+nWo3?8^5VOmyj}_8TJ~{k0#QF!M?O?*on41n~UyM z09!x4!0Lrm==}GWFJzksa7+vza-)O)4{1L`9~}oLk?laK)30`BV+9rUZc5nSrGXyBz)f^U0VmLn#%A zA=18Re2i~|2eiWRB=fuLz8o=n(%#)bQy@iX;n8c}-rCG-}t zdG*sDsw9a-^m8KX%L3ypWb>E93ZsjB2?c1FgEu$>KwilKO&`w$ms&u1ZXi9mknloZ z-s*?zB|~kenlmdXwjISpt?>$|4l(|P1{r?%PhL~J#YbYSp3cwSSSx)etHP8JL08v< zwFtjDSd2fL*YJWX^(;+m^1BS5b7jXUQ_(i6D?=Zbrs*6D=E|JEGINj~MHS`3C`loq zOS9;xR6DJR*|G3$wX|v-Ka)P?Rh0&`Ws;zX2fPly0@~6%3ybEcT3fvLd0u@dy~m zLM6RaPw1(iT?ZzV5Ngk@G8Rd^DjQE87!vL~}p>iG`3e-~>F8bOO4uGPzHnFD(3Rb!>rkituH zgpWntJyX$vovnL;d6h#=@wMwqz};S}NW*U^&kXvysC=d8kEFppC#0Bz7{tQEya(wE zy0yR8G;I(_sn<);FMj42kQpL6G1c(Nr3&C26)~5Nt(3Y&yRMnT) zNN~M#gdzM!?2?jH=nSNcA{bLpbY)sNkRTGIz>I1bLOeUm)3)>N6LsB-{Ww$g<;6Kd zN!s&+MfbV#iZ%xev#vou6R^Ab*=NN~9NX41}NG6+Wo)%6_|pyFB_)gGAZQ4YLbv35yFe;-U#c z^oqC}o_c<8W*4X3N%8D>Tf-hpNLXDwU#6R4^`=Yb->-gw`{F?uIGR~I-MRGzNk&F| zedl=&-01Q8df3ezrt6s{G1D?otl%msFf*jRKHOjF$cC)##8%U!eEdn1rctIZqU7HB zcm?{^mX$W_II=zY?CsR$>Es;fo)u@agBY)d5>S7?<4ob>{7a~c;#h;H|nPobVb!cS^?GhGM6P|4A+ z$MIk+txAPA70RtIzw%auA1Faa z@&znz-u*Ca)>o18m5e+X`cL&(;i~wEExa#O`J7Gnf_wSUJ03|oA7+T1MLn+d zNJs6%i6QXmUH(x^MZ!!^SS~J4Y()^(O+i&TZiU{nxFT748O;U z3_Dvl%NsY9_SyYG^qRXCc70gw>^o<0`w>f(WSX-hY(;v8O;&`{%iL+fmg4Qi2^Ee3 zRZf~0FFlDRp#)bzBiNV`kqv3I+0UriHtUwM`RA@m$GD2`(T@@ToeBVTvXvva<*5jr+1615{CK7;*onk2J##f1{+b|Suw&C*SbHaV?H zqub)$8&})K1*CzkH*~K_E}BC6V0GB(H;r`r-nni(HCe;|W8FV4p8vD%r>!jDb$exj z44n;9k62{FhGlUVxg?Mp@Fb<>OnNkBEN6W4Ut`1a%81M`bB1^&j-ar6g~M{Gp&;hu zeCr%+K0P=rhiW%Av3nA3G5ZWT#I@n6uYENj-hD4jO_>ZoQEW)x$`u2)2aEEB8Jp65 zb(ti|wBou+w8QFBK^I@nOdoc&G5jvQCP)|0+?b0d`PNxNqEC#FITvVI(=8%%=*u9v zun6n@P~&E#;3rl(ue^k7B7M)_euueU_}|Ur-#eCnvy8L#=P0URtB{9-fFJ;C>PJnQ zn~A@-`ud@tcWUx>fh@=kij#gq?|~50A5gJ8lc)}PT!u<%6Hy~6Xjq5OKu;pE3W6(S zJ*$+zdHHf3nV(aBoyB-zQzfHCAi)6fY9m`&t^3;%GdC(j8I3Bbb_t1@WSMn}o`H>i z7M2vydfJ(b1k+4Ko!nJ1^=myWt#tvf(m~!A3B=q1)q0H6kOF^d<*H@#mme$Gdkr;m zdpE-+u{VK)tXII2o{CR%S}P6E&yg?*q;Vc(vS1l@`?iqchET-qnxQN{6GX`yg*QiM zZKKDz`o46EfpaxU?T3<`I40Qk`3&T}$9xXjyed(=Tslj;e9P1iDi+-RWCs~t9YNVU zGqV%t5NWtM5(uE5iV)@f3S-h;*zyL$!%n%!ZIU+!%c!f9!R|q>6Vv?o6BAWtRKo(! z*i_afj_0<)O^1)-1q)A1!ksi5JdJn9huZPa+1seOPbScM8}H zsGbQW>_dh%Ev4JI@6EO!6PT?vxXatafK?TJKKF?qcO2+k`VnyKCY?%-{t;xHvcay_eT@s z|24Z?(%@jx05;@dgqs8=9Z_)N3%GTlfvShSnUmgi_De&&yy61OgJ6)!GpyE;Oqph|X1A zK?_pBGjF=W*bk>B7D=4~QvqRtP}LeLOs~%`wHG@DqA>Gm0$sHTazZNg`+M|&HK<3P zD(4n3LpHj5B#VOO(7pm`m-9os&>9PCJlUI+m3|eORy9PL*68^mhb2c{X(}Sr zFNktQw-m$k+z@39{z%{~>T@@cceDBI!zg-Of|cyts}hURz))}l$wybVsEZAj=_<`R z6Hy`J&ys`mzt>o=kt2tIM)@>QsknLsQzzcf^3U}P(0%*dJ+9UB6tglYZne`;eu7RX zYg#gA=A^)qn7J+IRBYP|x1N1S=9py;ZT`e(%!m}}co8_`v~VIQ37mU!O8;P;=G5*v z>T=WfP54vPd1BJ5bnlvK-~-EZ-|x^>LvT2xkYrj?1|Xx?-sAUdkqKIba=ab@oyG2b z)9t*9z)K>G;g+m8^;43CyoU*a33gNP5FrZlLU;EY{0j*1je79s;5YF7`agI7hQQw+ zTG!Fx&A5(k7DxYy1Mf1u8!cXAZkWQ4{e$^&qW%-J8xgLd*UT!Se-Eer6YhHU&A9%F z37pUPXBc){M|T6UYxIq{x_?364$JN?{cdpciyJ#N(1DgjJ zT@FU0{bYHA{s{i|U$lvxvYlUaRax6`W^nGM&&R5e_u4r z4Icb?y^ch12IG&o@IUG4I?B8m*AV&1Tfxg+Vedwr*T5TLU2g;bh(YgCx*IY5q69s4 zON6`e)Bl{%{@)1w_ebf!muP=ycm_Ws~~l%5m?HhK}Tao|0?;QF6-en+UO5-tHCYwZ<7E04BkFaYh^#t|0K%Yc0G4f<$i58digEjuf(~# z>%Lo^^cRWw*qi6<_#V + + com.fr.plugin.sln5591 + + yes + 1.8.0 + 10.0 + 2019-03-10 + fr.open + + + + + com.fr.plugin + + + + + + + + + + + + + + + diff --git a/src/com/fr/plugin/ChangeConfigHander.java b/src/com/fr/plugin/ChangeConfigHander.java new file mode 100644 index 0000000..5c1aabe --- /dev/null +++ b/src/com/fr/plugin/ChangeConfigHander.java @@ -0,0 +1,38 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.BaseHttpHandler; +import com.fr.stable.StringUtils; +import com.fr.third.springframework.web.bind.annotation.RequestMethod; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class ChangeConfigHander extends BaseHttpHandler { + @Override + public RequestMethod getMethod() { + return null; + } + + @Override + public String getPath() { + return "/change1"; + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public void handle(HttpServletRequest req, HttpServletResponse res) throws Exception { + String config = req.getParameter("config"); + FilterMeConfig instance = FilterMeConfig.getInstance(); + if (StringUtils.isBlank(config)) { + instance.setAdminFlags(""); + } else { + instance.setAdminFlags(config); + } + WebUtils.printAsString(res, "success"); + } +} \ No newline at end of file diff --git a/src/com/fr/plugin/ChangeDesignConfigHander.java b/src/com/fr/plugin/ChangeDesignConfigHander.java new file mode 100644 index 0000000..ce8cc75 --- /dev/null +++ b/src/com/fr/plugin/ChangeDesignConfigHander.java @@ -0,0 +1,38 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.BaseHttpHandler; +import com.fr.stable.StringUtils; +import com.fr.third.springframework.web.bind.annotation.RequestMethod; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class ChangeDesignConfigHander extends BaseHttpHandler { + @Override + public RequestMethod getMethod() { + return null; + } + + @Override + public String getPath() { + return "/change2"; + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public void handle(HttpServletRequest req, HttpServletResponse res) throws Exception { + String designConfig = req.getParameter("config"); + FilterMeConfig instance = FilterMeConfig.getInstance(); + if (StringUtils.isBlank(designConfig)) { + instance.setDesignFlags(""); + } else { + instance.setDesignFlags(designConfig); + } + WebUtils.printAsString(res, "success"); + } +} \ No newline at end of file diff --git a/src/com/fr/plugin/DataFilterPlaceHolder.java b/src/com/fr/plugin/DataFilterPlaceHolder.java new file mode 100644 index 0000000..b2ebcc2 --- /dev/null +++ b/src/com/fr/plugin/DataFilterPlaceHolder.java @@ -0,0 +1,108 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.Iterator; + +public class DataFilterPlaceHolder extends AbstractGlobalRequestFilterProvider { + @Override + public String filterName() { + return "a1data"; + } + + @Override + public String[] urlPatterns() { + return new String[]{ + "/*" + }; + } + + @Override + public void init(FilterConfig filterConfig) { + FilterMeConfig.getInstance(); + super.init(filterConfig); + } + + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse httpServletResponse, FilterChain filterChain) { + if (request.getMethod().equalsIgnoreCase("POST")) { + if (request.getPathInfo() != null && request.getPathInfo().endsWith("v5/design/widget/data")) { + String header = request.getHeader("X-Context"); + if (StringUtils.isNotBlank(header)) { + RequestWrapper wrapper = new RequestWrapper(request); + String bodyString = wrapper.getBodyString(); + JSONObject entries = new JSONObject(bodyString); + try { + delJSON(header, entries); +// FineLoggerFactory.getLogger().info("设置到body中 :{}", entries); + wrapper.setBody(entries.toString().getBytes()); + filterChain.doFilter(wrapper, httpServletResponse); + } catch (IOException e) { + printException2FrLog(e); + } catch (ServletException e) { + printException2FrLog(e); + } + return; + } + } + } + try { + filterChain.doFilter(request, httpServletResponse); + } catch (IOException e) { + printException2FrLog(e); + } catch (ServletException e) { + printException2FrLog(e); + } + } + + private void delJSON(String head, JSONObject req) { + try { + head = URLDecoder.decode(head, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + FineLoggerFactory.getLogger().error("收到的 Context:{}", head); + JSONObject jsonObject = new JSONObject(head); + Iterator keys = jsonObject.keys(); + if (req.has("parameter")) { + JSONObject parameter = req.getJSONObject("parameter"); + while (keys.hasNext()) { + String key = keys.next(); + String value = jsonObject.getString(key); + if (!parameter.has(key)) { + setData(key, value, parameter); + } + } + } + + } + + private void setData(String name, String data, JSONObject parameter) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("type", 97);//URL参数 + jsonObject.put("value", data); + parameter.put(name, jsonObject); + } + + public static void printException2FrLog(Throwable e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", s); + } + + +} diff --git a/src/com/fr/plugin/FR2loginFilter.java b/src/com/fr/plugin/FR2loginFilter.java new file mode 100644 index 0000000..1bcf0f9 --- /dev/null +++ b/src/com/fr/plugin/FR2loginFilter.java @@ -0,0 +1,347 @@ +package com.fr.plugin; + +import com.fr.decision.authority.AuthorityContext; +import com.fr.decision.authority.base.constant.type.operation.ManualOperationType; +import com.fr.decision.authority.controller.CustomRoleController; +import com.fr.decision.authority.controller.UserController; +import com.fr.decision.authority.data.CustomRole; +import com.fr.decision.authority.data.User; +import com.fr.decision.fun.impl.AbstractEmbedRequestFilterProvider; +import com.fr.decision.privilege.encrpt.PasswordValidator; +import com.fr.decision.webservice.utils.UserSourceFactory; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.general.ComparatorUtils; +import com.fr.general.IOUtils; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.IpUtils; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.stable.StringUtils; +import com.fr.stable.query.QueryFactory; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.UUID; + +@FunctionRecorder(localeKey = "fr2") +public class FR2loginFilter extends AbstractEmbedRequestFilterProvider { + @Override + public void init(FilterConfig filterConfig) { + super.init(filterConfig); + + } + + private static String[] filterUrls = new String[]{ + "v5/design/report/", + "view/report", + "view/form" + }; + + private void sendRedirect(HttpServletResponse res, String url) { + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + private boolean needFilter(String url) { + if (StringUtils.isNotBlank(url)) { + for (String filterUrl : filterUrls) { + if (url.contains(filterUrl)) { + return true; + } + } + } + return false; + } + + private void login(HttpServletRequest req, HttpServletResponse res, String username) { + HttpSession session = req.getSession(true); + String token = null; + try { + token = LoginService.getInstance().login(req, res, username); + req.setAttribute("fine_auth_token", token); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + FineLoggerFactory.getLogger().error("login failed"); + } + FineLoggerFactory.getLogger().error("login success"); + } + + private boolean ipblackCheck(String ip) { + String[] blackIps = new String[]{}; + try { + InputStream inputStream = ResourceIOUtils.read("/config-all/whitelist.properties"); + if (inputStream != null) { + String lines = IOUtils.inputStream2String(inputStream); + blackIps = lines.split("\n"); + } + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + if ("0:0:0:0:0:0:0:1".equals(ip)) { + return true; + } + for (String blackIp : blackIps) { + if (!blackIp.contains("-")) { + if (ip.equals(blackIp)) { + return true; + } + } else { + String trim = blackIp.trim(); + String[] split = trim.split("-"); + if (split.length > 1) { + if (com.fr.plugin.IpUtils.ipExistsInRange(ip, split[0], split[1])) { + return true; + } + } + } + } + return false; + } + + private void writerOurError(HttpServletResponse httpServletResponse) { + try { + WebUtils.writeOutTemplate("/com/fr/plugin/error.html", httpServletResponse, new HashMap()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private boolean isLogin(HttpServletRequest req) { + return LoginService.getInstance().isLogged(req); + } + + @Override + public void filter(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException, ServletException { + String uri = request.getRequestURI(); + String ip = IpUtils.getIp(request); + if (!ipblackCheck(ip)) { + writerOurError(httpServletResponse); + return; + } + if (request.getMethod().equalsIgnoreCase("GET")) { + //直接return 就开始执行原来的逻辑 + if (request.getParameter("sendTwo") != null) { + return; + } + + //需要特殊处理的url按照特殊处理 + if (needFilter(uri)) { + sendRedirect(httpServletResponse, getUrl(request)); + return; + } + + String loginUrl = request.getContextPath() + request.getServletPath() + "/login"; + String home = request.getContextPath() + request.getServletPath() + "?sendTwo=true"; + if (loginUrl.equals(uri)) { + String manualOut = request.getParameter("manual"); + if (!ComparatorUtils.equals(manualOut, "true")) { + String header = request.getHeader("X-Context"); + if (StringUtils.isNotBlank(header) && !isLogin(request)) { + tokenLogin(request, httpServletResponse); + sendRedirect(httpServletResponse, home); + } + } + } + } + return; + } + + private void tokenLogin(HttpServletRequest req, HttpServletResponse res) { + String header = req.getHeader("X-Context"); + try { + header = URLDecoder.decode(header, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + FineLoggerFactory.getLogger().info("拦截器捕获一个请求 x-content :{}", header); + JSONObject entries = new JSONObject(header); + String usrNm = entries.getString("usrNm"); + JSONArray rlNoList = entries.getJSONArray("rlNoList"); + try { + User user = UserService.getInstance().getUserByUserName(usrNm); + UserController userController = AuthorityContext.getInstance().getUserController(); + CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); + if (user == null) { + FineLoggerFactory.getLogger().info("拦截器新增一个用户 :{}", usrNm); + PasswordValidator passwordValidator = UserSourceFactory.getInstance().getUserSource(ManualOperationType.KEY).getPasswordValidator(); + user = (new User()).userName(usrNm).realName(usrNm).password(passwordValidator.encode(usrNm, UUID.randomUUID().toString())) + .creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); + userController.add(user); + } + String userId = user.getId(); + List roles = customRoleController.findByUser(userId, QueryFactory.create()); + boolean isAdminRoleFlag = false; + List adminFlags = getAdminFlags(); + FineLoggerFactory.getLogger().info("当前的管理员标识符 :{}", adminFlags); + List remoteRoles = new ArrayList<>(); + try { + int size = rlNoList.size(); + for (int i = 0; i < size; i++) { + String name = rlNoList.getString(i); + remoteRoles.add(name); + FineLoggerFactory.getLogger().info("传送过来的角色:{}", name); + if (isAdminFlags(name, adminFlags)) { + isAdminRoleFlag = true; + } + } + + //先判断传过来的的角色是不是本地都有,没有要加上 + for (String role : remoteRoles) { + boolean find = false; + for (CustomRole customRole : roles) { + if (ComparatorUtils.equals(customRole.getName(), role)) { + find = true; + } + } + if (!find) { +// FineLoggerFactory.getLogger().info("传送过来的角色在本地不存在,添加到本地:{}", role); +// List roleList = customRoleController.find(QueryFactory.create().addRestriction(RestrictionFactory.eq("name", role))); +// FineLoggerFactory.getLogger().info("通过角色名称{} 查询出来的本地角色:{}", role, roleList.size()); +// for (CustomRole customRole : roleList) { +// FineLoggerFactory.getLogger().info("给用户新增角色:{}", customRole.getId()); +// userController.addUserToCustomRole(userId, customRole.getId()); +// } + FineLoggerFactory.getLogger().info("传送过来的角色在本地不存在,添加到本地:{}", role); + CustomRole addRole = new CustomRole(); + addRole.setName(role); + addRole.setAlias(role); + addRole.setId(UUID.randomUUID().toString()); + addRole.setDescription("通过xcontent添加"); + customRoleController.add(addRole); + userController.addUserToCustomRole(userId, addRole.getId()); + } + } + //在判断本地的角色是不是远程没有了,要移除掉 + for (CustomRole customRole : roles) { + if (!remoteRoles.contains(customRole.getName())) { + FineLoggerFactory.getLogger().info("远端没有的角色本地移除:{}", customRole.getName()); + userController.removeUserFromCustomRole(userId, customRole.getId()); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + //如果是管理员标志就直接登录admin账号 + if (isAdminRoleFlag) { + FineLoggerFactory.getLogger().info("本次为管理员登录"); + login(req, res, "admin"); + } else { + FineLoggerFactory.getLogger().info("本次为普通用户登录:{}", usrNm); + login(req, res, usrNm); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static boolean isAdminFlags(String flag, List flags) { + return flags.contains(flag); + } + + private List getAdminFlags() { + InputStream inputStream = ResourceIOUtils.read("/config-all/adminflag.properties"); + if (inputStream != null) { + Properties properties = new Properties(); + try { + properties.load(inputStream); + String adminflag = properties.getProperty("adminflag"); + if (StringUtils.isNotBlank(adminflag)) { + String[] split = adminflag.split(","); + return Arrays.asList(split); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return Collections.emptyList(); + } + + private String getUrl(HttpServletRequest request) { + String url = "/"; + try { + url = "http://" + request.getServerName()//服务器地址 + + ":" + + request.getServerPort() + request.getRequestURI(); + Enumeration parameterNames = request.getParameterNames(); + Map reqParams = new HashMap<>(); + while (parameterNames.hasMoreElements()) { + String key = parameterNames.nextElement(); + reqParams.put(key, request.getParameter(key)); + } + Map parmas = header2url(request); + reqParams.putAll(parmas); + String header2url = map2String(reqParams); + FineLoggerFactory.getLogger().info("转换之后的url参数:{}", header2url); + if (url.contains("?")) { + url += header2url; + } else { + url += "?a=1" + header2url; + } + + } catch (Exception e) { + e.printStackTrace(); + } + url += "&sendTwo=true"; + return url; + } + + + private Map header2url(HttpServletRequest request) { + Map params = new HashMap<>(); + String context = request.getHeader("X-Context"); + if (StringUtils.isNotBlank(context)) { + try { + context = URLDecoder.decode(context, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + JSONObject jsonObject = new JSONObject(context); + String userNo = jsonObject.getString("usrNo"); + String userNm = jsonObject.getString("usrNm"); + String pstNo = jsonObject.getString("pstNo"); + String instNo = jsonObject.getString("instNo"); + String accInstNo = jsonObject.getString("accInstNo"); + String admnInstNo = jsonObject.getString("admnInstNo"); + params.put("usrNo", userNo); + params.put("usrNm", userNm); + params.put("pstNo", pstNo); + params.put("instNo", instNo); + params.put("accInstNo", accInstNo); + params.put("admnInstNo", admnInstNo); + } + params.put("sendTwo", "true"); + return params; + } + + private String map2String(Map params) { + StringBuffer buffer = new StringBuffer(); + Set strings = params.keySet(); + for (String key : strings) { + buffer.append("&").append(key).append("=").append(params.get(key)); + } + return buffer.toString(); + } + +} diff --git a/src/com/fr/plugin/FRloginFilter.java b/src/com/fr/plugin/FRloginFilter.java new file mode 100644 index 0000000..82ba782 --- /dev/null +++ b/src/com/fr/plugin/FRloginFilter.java @@ -0,0 +1,165 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.AbstractEmbedRequestFilterProvider; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.stable.StringUtils; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +@FunctionRecorder(localeKey = "frfilter") +public class FRloginFilter extends AbstractEmbedRequestFilterProvider { + + private void sendRedirect(HttpServletResponse res, String url) { + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + @Override + public void filter(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException, ServletException { + String uri = request.getRequestURI(); + if (uri != null && uri.contains("v5/design/report/share") && request.getMethod().equalsIgnoreCase("GET")) { + FineLoggerFactory.getLogger().info("拦截器捕获一个请求,需要跳转share {}", uri); + if (request.getParameter("sendTwo") != null) { + return; + } + String url = getUrl(request); + FineLoggerFactory.getLogger().info("拦截器捕获一个请求,跳转出来的 {}", url); + sendRedirect(httpServletResponse, url); + } + } + + // +// private String getUrl(HttpServletRequest request){ +// String url = "/"; +// try { +// url = "http://" + request.getServerName()//服务器地址 +// + ":" +// + request.getServerPort() + request.getRequestURI() ; +// String queryString = request.getQueryString(); +// String header2url = header2url(request); +// if (StringUtils.isNotBlank(queryString)) { +// url+= "?"+queryString+header2url; +// }else{ +// url+="?a=1"+header2url; +// } +// if(url.contains("?")){ +// url+="&sendTwo=true"; +// }else{ +// url+="?sendTwo=true"; +// } +// } catch (Exception e) { +// printException2Frlog(e); +// } +// return url; +// } + public static void printException2Frlog(Exception e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", s); + } +// +// private String header2url(HttpServletRequest request){ +// StringBuffer buffer=new StringBuffer(); +// String context = request.getHeader("X-Context"); +// if (StringUtils.isNotBlank(context)) { +// try { +// context = URLDecoder.decode(context, "UTF-8"); +// } catch (UnsupportedEncodingException e) { +// e.printStackTrace(); +// } +// JSONObject jsonObject = new JSONObject(context); +// String userNo = jsonObject.getString("usrNo"); +// String userNm = jsonObject.getString("usrNm"); +// String pstNo = jsonObject.getString("pstNo"); +// String instNo = jsonObject.getString("instNo"); +// String accInstNo = jsonObject.getString("accInstNo"); +// String admnInstNo = jsonObject.getString("admnInstNo"); +// buffer.append("&userNo=").append(userNo).append("&userNm=").append(userNm).append("&pstNo=").append(pstNo).append("&instNo=").append(instNo) +// .append("&accInstNo=").append(accInstNo).append("&admnInstNo=").append(admnInstNo); +// } +// +// buffer.append("&sendTwo=true"); +// return buffer.toString(); +// } + + private String getUrl(HttpServletRequest request) { + String url = "/"; + try { + url = "http://" + request.getServerName()//服务器地址 + + ":" + + request.getServerPort() + request.getRequestURI(); + Enumeration parameterNames = request.getParameterNames(); + Map reqParams = new HashMap<>(); + while (parameterNames.hasMoreElements()) { + String key = parameterNames.nextElement(); + reqParams.put(key, request.getParameter(key)); + } + Map parmas = header2url(request); + reqParams.putAll(parmas); + String header2url = map2String(reqParams); + FineLoggerFactory.getLogger().info("转换之后的url参数:{}", header2url); + if (url.contains("?")) { + url += header2url; + } else { + url += "?a=1" + header2url; + } + + } catch (Exception e) { + e.printStackTrace(); + } + url += "&sendTwo=true"; + return url; + } + + + private Map header2url(HttpServletRequest request) { + Map params = new HashMap<>(); + String context = request.getHeader("X-Context"); + if (StringUtils.isNotBlank(context)) { + try { + context = URLDecoder.decode(context, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + JSONObject jsonObject = new JSONObject(context); + String userNo = jsonObject.getString("usrNo"); + String userNm = jsonObject.getString("usrNm"); + String pstNo = jsonObject.getString("pstNo"); + String instNo = jsonObject.getString("instNo"); + String accInstNo = jsonObject.getString("accInstNo"); + String admnInstNo = jsonObject.getString("admnInstNo"); + params.put("usrNo", userNo); + params.put("usrNm", userNm); + params.put("pstNo", pstNo); + params.put("instNo", instNo); + params.put("accInstNo", accInstNo); + params.put("admnInstNo", admnInstNo); + } + params.put("sendTwo", "true"); + return params; + } + + private String map2String(Map params) { + StringBuffer buffer = new StringBuffer(); + Set strings = params.keySet(); + for (String key : strings) { + buffer.append("&").append(key).append("=").append(params.get(key)); + } + return buffer.toString(); + } + +} diff --git a/src/com/fr/plugin/FilterMeConfig.java b/src/com/fr/plugin/FilterMeConfig.java new file mode 100644 index 0000000..acacdc2 --- /dev/null +++ b/src/com/fr/plugin/FilterMeConfig.java @@ -0,0 +1,82 @@ +package com.fr.plugin; + +import com.fr.config.*; +import com.fr.config.holder.Conf; +import com.fr.config.holder.factory.Holders; + +@Visualization(category = "系统接口地址配置") +public class FilterMeConfig extends DefaultConfiguration { + private static volatile FilterMeConfig config = null; + + public static FilterMeConfig getInstance() { + if (config == null) { + config = ConfigContext.getConfigInstance(FilterMeConfig.class); + } + return config; + } + + @Identifier(value = "baseUrl", name = "接口地址", description = "", status = Status.SHOW) + private Conf baseUrl = Holders.simple("http://localhost:8080/"); + + @Identifier(value = "adminFlags", name = "管理员标识符rlNoList", description = "使用分号分隔", status = Status.SHOW) + private Conf adminFlags = Holders.simple(""); + @Identifier(value = "adminFlagsNm", name = "管理员标识符usrNm", description = "使用分号分隔", status = Status.SHOW) + private Conf adminFlagsNm = Holders.simple(""); + @Identifier(value = "designFlags", name = "设计用户标识符", description = "使用分号分隔", status = Status.SHOW) + private Conf designFlags = Holders.simple(""); + @Identifier(value = "needflush", name = "是否需要刷新权限", description = "使用分号分隔", status = Status.SHOW) + private Conf needflush = Holders.simple(true); + + public String getDesignFlags() { + return designFlags.get(); + } + + public void setDesignFlags(String designFlags) { + this.designFlags.set(designFlags); + } + + public String getAdminFlags() { + return adminFlags.get(); + } + + public void setAdminFlags(String adminFlags) { + this.adminFlags.set(adminFlags); + } + + public String getBaseUrl() { + return baseUrl.get(); + } + + + public void setBaseUrl(String baseUrl) { + this.baseUrl.set(baseUrl); + } + + public String getAdminFlagsNm() { + return adminFlagsNm.get(); + } + + public void setAdminFlagsNm(String adminFlagsNm) { + this.adminFlagsNm.set(adminFlagsNm); + ; + } + + public Boolean getNeedflush() { + return needflush.get(); + } + + public void setNeedflush(Boolean needflush) { + this.needflush.set(needflush); + } + + @Override + public Object clone() throws CloneNotSupportedException { + FilterMeConfig cloned = (FilterMeConfig) super.clone(); + cloned.baseUrl = (Conf) baseUrl.clone(); + cloned.adminFlags = (Conf) adminFlags.clone(); + cloned.designFlags = (Conf) designFlags.clone(); + cloned.adminFlagsNm = (Conf) adminFlagsNm.clone(); + cloned.needflush = (Conf) needflush.clone(); + return cloned; + } +} diff --git a/src/com/fr/plugin/GlobalRequestFilterPlaceHolder.java b/src/com/fr/plugin/GlobalRequestFilterPlaceHolder.java new file mode 100644 index 0000000..5db0f34 --- /dev/null +++ b/src/com/fr/plugin/GlobalRequestFilterPlaceHolder.java @@ -0,0 +1,110 @@ +package com.fr.plugin; + +import com.fr.decision.ExtraDecisionClassManager; +import com.fr.decision.fun.GlobalRequestFilterProvider; +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.event.Event; +import com.fr.event.EventDispatcher; +import com.fr.event.Listener; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.context.PluginContext; +import com.fr.plugin.injectable.PluginModule; +import com.fr.plugin.observer.PluginEventType; +import com.fr.stable.StringUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Set; + +/** + * @author fr.open + */ +public class GlobalRequestFilterPlaceHolder extends AbstractGlobalRequestFilterProvider { + private static final String CURRENT_PLUGIN_ID = "com.fr.plugin.sln5591";//需要求个这两个配置 + private static final String CURRENT_FILTER_NAME = "lastLogin"; + private static GlobalRequestFilterProvider PLACE_HOLDER_IMPL_FILTER; + + @Override + public void init(FilterConfig filterConfig) { + Set providers = ExtraDecisionClassManager.getInstance().getArray(GlobalRequestFilterProvider.MARK_STRING); + if (providers != null) { + for (GlobalRequestFilterProvider provider : providers) { + String filterName = provider.filterName(); + if (StringUtils.isNotEmpty(filterName) && CURRENT_FILTER_NAME.equals(filterName)) { + PLACE_HOLDER_IMPL_FILTER = provider; + break; + } + } + } + + com.fr.stable.Filter filter = new com.fr.stable.Filter() { + @Override + public boolean accept(PluginContext context) { + String pluginId = context.getID(); + return context.contain(PluginModule.ExtraDecision, GlobalRequestFilterProvider.MARK_STRING) && CURRENT_PLUGIN_ID.equals(pluginId); + } + }; + + EventDispatcher.listen(PluginEventType.AfterRun, new Listener() { + @Override + public void on(Event event, PluginContext context) { + Set providers = context.getRuntime().get(PluginModule.ExtraDecision, GlobalRequestFilterProvider.MARK_STRING); + if (providers != null) { + for (GlobalRequestFilterProvider provider : providers) { + String filterName = provider.filterName(); + if (StringUtils.isNotEmpty(filterName) && CURRENT_FILTER_NAME.equals(filterName)) { + PLACE_HOLDER_IMPL_FILTER = provider; + break; + } + } + } + } + }, filter); + + EventDispatcher.listen(PluginEventType.BeforeStop, new Listener() { + @Override + public void on(Event event, PluginContext context) { + PLACE_HOLDER_IMPL_FILTER = null; + } + }, filter); + } + + @Override + public String filterName() { + return "GlobalRequestFilterPlaceHolder"; + } + + @Override + public String[] urlPatterns() { + return new String[]{ + "/*" + }; + } + + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { + try { + process(request, response, filterChain); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e, e.getMessage()); + try { + filterChain.doFilter(request, response); + } catch (Exception ex) { + FineLoggerFactory.getLogger().error(ex, ex.getMessage()); + } + } + } + + public void process(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws Exception { + if (PLACE_HOLDER_IMPL_FILTER == null) { + if(FineLoggerFactory.getLogger().isDebugEnabled()) { + FineLoggerFactory.getLogger().debug("[GlobalRequestFilterPlaceHolder] placeHolderImplFilter 为 null"); + } + filterChain.doFilter(request, response); + } else { + PLACE_HOLDER_IMPL_FILTER.doFilter(request, response, filterChain); + } + } +} diff --git a/src/com/fr/plugin/HttpApi.java b/src/com/fr/plugin/HttpApi.java new file mode 100644 index 0000000..ec13789 --- /dev/null +++ b/src/com/fr/plugin/HttpApi.java @@ -0,0 +1,130 @@ +package com.fr.plugin; + +import com.fr.base.ServerConfig; +import com.fr.json.JSONObject; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class HttpApi { + + + + private static String getParam(Map var0, String enc) { + String var1 = ""; + Set var2 = var0.keySet(); + Iterator var3 = var2.iterator(); + + while (var3.hasNext()) { + String var4 = (String) var3.next(); + String var5 = var0.get(var4) + ""; + + try { + var1 = var1 + (var1.length() == 0 ? "" : "&") + URLEncoder.encode(var4, enc) + "=" + URLEncoder.encode(var5, enc); + } catch (Exception var7) { + ; + } + } + + return var1; + } + public static String post(String path, Map param) { + String var3 = getParam(param, ServerConfig.getInstance().getServerCharset()); + PrintWriter var4 = null; + BufferedReader var5 = null; + String var6 = ""; + + try { + URL var7 = new URL(path); + HttpURLConnection var8 = (HttpURLConnection) var7.openConnection(); + var8.setRequestProperty("accept", "*/*"); + var8.setRequestProperty("connection", "Keep-Alive"); + var8.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); +// var8.setRequestProperty("Accept-Charset", "UTF-8"); + var8.setRequestMethod("POST"); + var8.setDoOutput(true); + var8.setDoInput(true); + var4 = new PrintWriter(var8.getOutputStream()); + var4.print(var3); + var4.flush(); + + String var9; + for (var5 = new BufferedReader(new InputStreamReader(var8.getInputStream(), "UTF-8")); (var9 = var5.readLine()) != null; var6 = var6 + var9) { + ; + } + } catch (Exception var18) { + var18.printStackTrace(); + } finally { + try { + if (var4 != null) { + var4.close(); + } + + if (var5 != null) { + var5.close(); + } + } catch (Exception var17) { + ; + } + + } + + return var6; + } + + public static String sendJsonPost(String var0, JSONObject var1, String var2) { + PrintWriter var3 = null; + BufferedReader var4 = null; + HttpURLConnection var5 = null; + String var6 = ""; + + try { + URL var7 = new URL(var0); + var5 = (HttpURLConnection) var7.openConnection(); + var5.setRequestProperty("Content-Type", "application/json;charset=utf8"); +// var5.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf8"); + var5.setRequestProperty("accept", "*/*"); + var5.setRequestProperty("connection", "Keep-Alive"); + var5.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + var5.setRequestProperty("Accept-Charset", var2); + var5.setRequestMethod("POST"); + var5.setDoOutput(true); + var5.setDoInput(true); + var3 = new PrintWriter(var5.getOutputStream()); + var3.print(var1.toString()); + var3.flush(); + + String var8; + for (var4 = new BufferedReader(new InputStreamReader(var5.getInputStream(), var2)); (var8 = var4.readLine()) != null; var6 = var6 + var8) { + ; + } + } catch (Exception var17) { + var17.printStackTrace(); + } finally { + try { + if (var3 != null) { + var3.close(); + } + + if (var4 != null) { + var4.close(); + } + } catch (Exception var16) { + ; + } + + var5.disconnect(); + } + + System.out.println(var6); + return var6; + } + +} diff --git a/src/com/fr/plugin/IpUtils.java b/src/com/fr/plugin/IpUtils.java new file mode 100644 index 0000000..6bfe7ba --- /dev/null +++ b/src/com/fr/plugin/IpUtils.java @@ -0,0 +1,62 @@ +package com.fr.plugin; + +import javax.servlet.http.HttpServletRequest; + +public class IpUtils { + /** + * 将字符串形式IP地址127.0.0.1转换10234564321 + * + * @param strIP + * @return + */ + public static long ip2Long(String strIP) { + long[] ip = new long[4]; +// 先找到IP地址字符串中.的位置 + int position1 = strIP.indexOf("."); + int position2 = strIP.indexOf(".", position1 + 1); + int position3 = strIP.indexOf(".", position2 + 1); +// 将每个.之间的字符串转换成整型 + ip[0] = Long.parseLong(strIP.substring(0, position1)); + ip[1] = Long.parseLong(strIP.substring(position1 + 1, position2)); + ip[2] = Long.parseLong(strIP.substring(position2 + 1, position3)); + ip[3] = Long.parseLong(strIP.substring(position3 + 1)); + return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3]; + } + + public static String getIp(HttpServletRequest request) { + String remoteIp = request.getRemoteAddr(); + + // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 + if (remoteIp != null && remoteIp.length() > 15) { + if (remoteIp.indexOf(",") > 0) { + remoteIp = remoteIp.substring(0, remoteIp.indexOf(",")); + } + } + return remoteIp; + } + + /** + * 将字符串形式IP地址转换long类型 + * @param ip + * @return + */ + public static long getIp2long(String ip) { + ip = ip.trim(); + String[] ips = ip.split("\\."); + long ip1 = Integer.parseInt(ips[0]); + long ip2 = Integer.parseInt(ips[1]); + long ip3 = Integer.parseInt(ips[2]); + long ip4 = Integer.parseInt(ips[3]); + return ip1 * 256 * 256 * 256 + ip2 * 256 * 256 + ip3 * 256 + ip4; + } + /** + * 判断一个ip地址是否在某个ip段范围内 + * @param ip + * @param startIP + * @param endIP + * @return + */ + public static boolean ipExistsInRange(String ip, String startIP, String endIP) { + return (getIp2long(startIP)<=getIp2long(ip)) && (getIp2long(ip)<=getIp2long(endIP)); + } +} diff --git a/src/com/fr/plugin/JHHttpHander.java b/src/com/fr/plugin/JHHttpHander.java new file mode 100644 index 0000000..454cde1 --- /dev/null +++ b/src/com/fr/plugin/JHHttpHander.java @@ -0,0 +1,14 @@ +package com.fr.plugin; + +import com.fr.decision.fun.HttpHandler; +import com.fr.decision.fun.impl.AbstractHttpHandlerProvider; + +public class JHHttpHander extends AbstractHttpHandlerProvider { + + private HttpHandler[] actions = new HttpHandler[]{new ChangeConfigHander() ,new ChangeDesignConfigHander(),new PostionImportgHander() }; + + @Override + public HttpHandler[] registerHandlers() { + return actions; + } +} diff --git a/src/com/fr/plugin/JHUrlAliasProvider.java b/src/com/fr/plugin/JHUrlAliasProvider.java new file mode 100644 index 0000000..cd673ad --- /dev/null +++ b/src/com/fr/plugin/JHUrlAliasProvider.java @@ -0,0 +1,17 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.AbstractURLAliasProvider; +import com.fr.decision.webservice.url.alias.URLAlias; +import com.fr.decision.webservice.url.alias.URLAliasFactory; + +public class JHUrlAliasProvider extends AbstractURLAliasProvider { + @Override + public URLAlias[] registerAlias() { + return new URLAlias[]{ + //第一个参数就是就是别名地址,第二个就是HttpHandler中设置的地址,第三个参数就是设置该请求是否公开(需要和对应的HttpHandler.isPublic方法返回一直,要不然会报错404),返回的是一个非通配 + URLAliasFactory.createPluginAlias("/config/admin", "/change1", true), + URLAliasFactory.createPluginAlias("/config/import", "/depAndPosImport", true), + URLAliasFactory.createPluginAlias("/config/design", "/change2", true) + }; + } +} \ No newline at end of file diff --git a/src/com/fr/plugin/JSONUtils.java b/src/com/fr/plugin/JSONUtils.java new file mode 100644 index 0000000..fc66f61 --- /dev/null +++ b/src/com/fr/plugin/JSONUtils.java @@ -0,0 +1,28 @@ +package com.fr.plugin; + +import com.fr.third.fasterxml.jackson.core.JsonGenerationException; +import com.fr.third.fasterxml.jackson.databind.JsonMappingException; +import com.fr.third.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; + +/** + * JSON序列化和反序列化相关操作类 + */ +public class JSONUtils { + private static ObjectMapper objectMapper = new ObjectMapper(); + + public static String serialize(Object object) { + Writer write = new StringWriter(); + try { + objectMapper.writeValue(write, object); + } catch (JsonGenerationException e) { + } catch (JsonMappingException e) { + } catch (IOException e) { + } + return write.toString(); + } + +} diff --git a/src/com/fr/plugin/LastloginFilter.java b/src/com/fr/plugin/LastloginFilter.java new file mode 100644 index 0000000..2141ea9 --- /dev/null +++ b/src/com/fr/plugin/LastloginFilter.java @@ -0,0 +1,647 @@ +package com.fr.plugin; + +import com.fr.decision.authority.AuthorityContext; +import com.fr.decision.authority.base.constant.type.operation.ManualOperationType; +import com.fr.decision.authority.controller.CustomRoleController; +import com.fr.decision.authority.controller.DepartmentController; +import com.fr.decision.authority.controller.PostController; +import com.fr.decision.authority.controller.UserController; +import com.fr.decision.authority.data.CustomRole; +import com.fr.decision.authority.data.Department; +import com.fr.decision.authority.data.Post; +import com.fr.decision.authority.data.User; +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.decision.privilege.encrpt.PasswordValidator; +import com.fr.decision.webservice.impl.user.type.UserProductType; +import com.fr.decision.webservice.utils.UserSourceFactory; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.general.ComparatorUtils; +import com.fr.general.IOUtils; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.plugin.util.LogUtils; +import com.fr.stable.StringUtils; +import com.fr.stable.query.QueryFactory; +import com.fr.stable.query.restriction.RestrictionFactory; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.*; +import java.net.URLDecoder; +import java.util.*; + +@FunctionRecorder(localeKey = "fr2") +public class LastloginFilter extends AbstractGlobalRequestFilterProvider { + @Override + public String filterName() { + return "lastLogin"; + } + + @Override + public String[] urlPatterns() { + return new String[]{ + "/oookkkk" + }; + } + + @Override + public void init(FilterConfig filterConfig) { + FilterMeConfig.getInstance(); + super.init(filterConfig); + } + + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse httpServletResponse, FilterChain filterChain) { + String uri = request.getRequestURI(); + String ip = com.fr.plugin.IpUtils.getIp(request); + String header = request.getHeader("X-Context"); +// try { +// FineLoggerFactory.getLogger().info("Context:{}", URLDecoder.decode(header, "utf-8")); +// } catch (UnsupportedEncodingException e) { +// e.printStackTrace(); +// } +// printCookies(request); +// FineLoggerFactory.getLogger().info("请求url:{}", WebUtils.getOriginalURL(request)); + if (isRemoteDesignRequest(request) || isResourceFileRequest(request)) { + try { + filterChain.doFilter(request, httpServletResponse); + } catch (IOException e) { + printException2FrLog(e); + } catch (ServletException e) { + printException2FrLog(e); + } + return; + } + + if (!ipblackCheck(ip)) { + writerOurError(httpServletResponse); + return; + } + + if (request.getMethod().equalsIgnoreCase("GET")) { + + if (StringUtils.isNotBlank(header)) { + XContentRequestWrapper wrapper = new XContentRequestWrapper(request); +// wrapper.getParameterMap(); +// String a = wrapper.getParameter("a"); +// wrapper.getParameterNames(); +// FineLoggerFactory.getLogger().error("获取的A :{} ", a); + if (isLogin(wrapper)) { +// String usrNm = wrapper.getParameter("usrNm"); + //从2021-01-29开始的需求, SLN-6585开始修改用户名使用userNo代替 + String usrNo = wrapper.getParameter("usrNo"); + String instNo = wrapper.getParameter("instNo"); + String realUserName = usrNo + "_" + instNo; +// FineLoggerFactory.getLogger().info("登录信息:{}", realUserName); + User logindUser = null; + try { + logindUser = UserService.getInstance().getUserByRequestCookie(request); + UserService instance = UserService.getInstance(); +// FineLoggerFactory.getLogger().info("当前登录的用户名称:{}", logindUser.getUserName()); + //如果登录过并且不是管理员就判断是否是同一个用户 + if (!instance.isAdmin(logindUser.getId())) { + //如果登录的用户和content的中不一致则切换登录用户 + if (!ComparatorUtils.equals(logindUser.getUserName(), realUserName)) { + FineLoggerFactory.getLogger().info("当前登录的用户和xcontent的不一致,切换用户为:{}", realUserName); + tokenLogin(request, httpServletResponse); + } else { + //如果一致则判断角色是否变化 + try { + FineLoggerFactory.getLogger().info("当前用户已经登录,进行权限适配:{}", realUserName); + dealCustomRole(wrapper, logindUser.getId()); + } catch (Exception e) { + printException2FrLog(e); + } + } + } + } catch (Exception e) { + printException2FrLog(e); + } + } else { + FineLoggerFactory.getLogger().info("xc1当前未登录开始登录"); + tokenLogin(request, httpServletResponse); + } + try { + filterChain.doFilter(wrapper, httpServletResponse); + } catch (IOException e) { + printException2FrLog(e); + } catch (ServletException e) { + printException2FrLog(e); + } + return; + } + } + try { + filterChain.doFilter(request, httpServletResponse); + } catch (IOException e) { + printException2FrLog(e); + } catch (ServletException e) { + printException2FrLog(e); + } + } + +// private void printCookies(HttpServletRequest request) { +// Cookie[] cookies = request.getCookies(); +// if (null != cookies) { +// for (Cookie cookie : cookies) { +// FineLoggerFactory.getLogger().info("收到的cookies name {} value:{}", cookie.getName(), cookie.getValue()); +// } +// } +// } + + /** + * 判断是否是远程设计请求 + */ + private boolean isRemoteDesignRequest(HttpServletRequest request) { + String pathInfo = request.getPathInfo(); + return StringUtils.isNotEmpty(pathInfo) && pathInfo.startsWith("/remote/design"); + } + + + private void adminLogin(HttpServletRequest request) { + + } + + /** + * 判断是否是资源文件请求 + */ + private boolean isResourceFileRequest(HttpServletRequest request) { + String pathInfo = request.getPathInfo(); + if (StringUtils.isEmpty(pathInfo)) { + return false; + } + + return pathInfo.startsWith("/resources") || pathInfo.startsWith("/file"); + } + + public static void printException2FrLog(Throwable e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", s); + } + + private void sendRedirect(HttpServletResponse res, String url) { + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + + private static void login(HttpServletRequest req, HttpServletResponse res, String username) { + HttpSession session = req.getSession(true); + String token = null; + try { + token = LoginService.getInstance().login(req, res, username); + req.setAttribute("fine_auth_token", token); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + FineLoggerFactory.getLogger().error("login failed"); + } + FineLoggerFactory.getLogger().error("login success"); + } + + private boolean ipblackCheck(String ip) { + String[] blackIps = new String[]{}; + try { + InputStream inputStream = ResourceIOUtils.read("/config-all/whitelist.properties"); + if (inputStream != null) { + String lines = IOUtils.inputStream2String(inputStream); + blackIps = lines.split("\n"); + } + } catch (IOException e) { + LogUtils.error(e.getMessage(), e); + } + if ("0:0:0:0:0:0:0:1".equals(ip)) { +// LogUtils.info("IP 地址为服务器本机 IPv6 地址: {},直接放行。", ip); + return true; + } + if ("127.0.0.1".equals(ip)) { +// LogUtils.info("IP 地址为服务器本机 IPv4 地址: {},直接放行。", ip); + return true; + } + for (String blackIp : blackIps) { + if (!blackIp.contains("-")) { + if (ip.equals(blackIp)) { + return true; + } + } else { + String trim = blackIp.trim(); + String[] split = trim.split("-"); + if (split.length > 1) { + if (com.fr.plugin.IpUtils.ipExistsInRange(ip, split[0], split[1])) { + return true; + } + } + } + } + return false; + } + + private void writerOurError(HttpServletResponse httpServletResponse) { + try { + WebUtils.writeOutTemplate("/com/fr/plugin/error.html", httpServletResponse, new HashMap()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private boolean isLogin(HttpServletRequest req) { + return LoginService.getInstance().isLogged(req); + } + +// public void filter(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException, ServletException { +// String uri = request.getRequestURI(); +// String ip = IpUtils.getIp(request); +// if (!ipblackCheck(ip)) { +// writerOurError(httpServletResponse); +// return; +// } +// if (request.getMethod().equalsIgnoreCase("GET")) { +// String header = request.getHeader("X-Context"); +// if (StringUtils.isNotBlank(header)) { +// XContentRequestWrapper wrapper = new XContentRequestWrapper(request); +//// String a = wrapper.getParameter("a"); +//// FineLoggerFactory.getLogger().error("获取的A :{} ", a); +// if (isLogin(wrapper)) { +// String usrNm = wrapper.getParameter("usrNm"); +// String userNameFromRequest = LoginService.getInstance().getUserNameFromRequestCookie(request); +// //如果登录的用户和content的中不一致则切换登录用户 +// if (!ComparatorUtils.equals(userNameFromRequest, usrNm)) { +// tokenLogin(request, httpServletResponse); +// } +// } else { +// tokenLogin(request, httpServletResponse); +// return; +// } +// +// String loginUrl = request.getContextPath() + request.getServletPath() + "/login"; +// String home = request.getContextPath() + request.getServletPath() + "?sendTwo=true"; +// if (loginUrl.equals(uri)) { +// String manualOut = request.getParameter("manual"); +// if (!ComparatorUtils.equals(manualOut, "true")) { +//// String header = request.getHeader("X-Context"); +// if (StringUtils.isNotBlank(header) && !isLogin(request)) { +// tokenLogin(request, httpServletResponse); +// sendRedirect(httpServletResponse, home); +// } +// } +// } +// } +// } +// return; +// } + + public static void tokenLogin(HttpServletRequest req, HttpServletResponse res) { + String header = req.getHeader("X-Context"); + try { + header = URLDecoder.decode(header, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + FineLoggerFactory.getLogger().info("xf拦截器捕获一个请求 x-content :{}", header); + JSONObject entries = new JSONObject(header); + JSONArray rlNoList = entries.getJSONArray("rlNoList"); + int size = rlNoList.size(); + List adminFlags = getAdminFlagsRolList(); + boolean isAdmin = false; + for (int i = 0; i < size; i++) { + String adminPost = rlNoList.getString(i); + if (isAdminFlags(adminPost, adminFlags)) { + FineLoggerFactory.getLogger().info("本次使用rlNoList管理员登录 "); + isAdmin = true; + break; + } + } + List adminFlagsNmList = getAdminFlagsNmList(); + String usrNm = entries.getString("usrNm"); + for (String adminName : adminFlagsNmList) { + if(StringUtils.equals(adminName,usrNm)){ + FineLoggerFactory.getLogger().info("本次使用usrNm管理员登录 "); + isAdmin=true; + break; + } + } + if (isAdmin) { + FineLoggerFactory.getLogger().info("本次为管理员登录 "); + login(req, res, "admin"); + return; + } +// String usrNm = entries.getString("usrNm"); + //从 SLN-6585开始usrNm更新成userNo + //如果usrNo的用户名存在则把这个用户的userName改成realUserName + String usrNo = entries.getString("usrNo"); + String instNo = entries.getString("instNo");//部门id + String realUserName = usrNo + "_" + instNo;//更改后的用户名 + String fullName = entries.getString("fullName"); + String alias = fullName + "(" + realUserName + ")"; + try { + User user = UserService.getInstance().getUserByUserName(usrNo); + UserController userController = AuthorityContext.getInstance().getUserController(); + if (user == null) { + //如果usrNo的用户不存在则检查realUserName的用户是否存在 + user = UserService.getInstance().getUserByUserName(realUserName); + if (user == null) { +// FineLoggerFactory.getLogger().info("拦截器新增一个用户 用户名称 :{}", realUserName); + PasswordValidator passwordValidator = UserSourceFactory.getInstance().getUserSource(ManualOperationType.KEY).getPasswordValidator(); + user = (new User()).userName(realUserName).userAlias(alias).realName(fullName).password(passwordValidator.encode(realUserName, UUID.randomUUID().toString())) + .creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); + userController.add(user); + } + } +// FineLoggerFactory.getLogger().info(" 拦截的用户更新:{}", fullName); + user.setRealName(fullName); + user.setUserName(realUserName); + try { + userController.update(user); + } catch (Exception e) { + } + String userId = user.getId(); + dealCustomRole(req, userId); + //在判断本地的角色是不是远程没有了,要移除掉 + CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); + + List roles = customRoleController.findByUser(userId, QueryFactory.create()); + List designFlags = getDesignFlags(); + boolean isDesignUser = false; + if (!designFlags.isEmpty()) { + for (CustomRole role : roles) { + String name = role.getName(); + //如果是有 + if (isAdminFlags(name, designFlags)) { + isDesignUser = true; + break; + } + } + } + try { + UserProductType userProductType = UserProductType.fromInteger(6);//数据处理用户 + if (isDesignUser) { +// FineLoggerFactory.getLogger().info("查找到当前用户有对应角色的设计标志:"); + userController.addUserProductType(userId, userProductType.transProductKey()); + } else { +// FineLoggerFactory.getLogger().info("查找到当前用户有对应角色的设计标志:"); + userController.removeUserProductType(userId, userProductType.transProductKey()); + } + } catch (Exception e) { +// FineLoggerFactory.getLogger().error("数据处理配置出错:{}", e); + } +// if (isAdmin) { +// FineLoggerFactory.getLogger().info("本次为管理员登录 "); +// login(req, res, "admin"); +// } else { +// FineLoggerFactory.getLogger().info("本次为普通用户登录:{}", usrNm); +// login(req, res, usrNm); +// } +// FineLoggerFactory.getLogger().info("本次为普通用户登录:{}", realUserName); + login(req, res, realUserName); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void dealCustomRole(HttpServletRequest req, String userId) throws Exception { + UserController userController = AuthorityContext.getInstance().getUserController(); + Boolean needflush = FilterMeConfig.getInstance().getNeedflush(); + String header = req.getHeader("X-Context"); + CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); + try { + header = URLDecoder.decode(header, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + FineLoggerFactory.getLogger().info("开始同步角色信息x-content :{}", header); + JSONObject entries = new JSONObject(header); + List remoteRoles = new ArrayList<>(); + String pstNo = entries.getString("pstNo");//角色id + String pstNm = entries.getString("pstNm");//角色名称 + String realRoleName = pstNm + "(" + pstNo + ")"; + remoteRoles.add(pstNo); + try { + //先判断传过来的的角色是不是本地都有,没有要加上 + List controllerOne = customRoleController.find(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", pstNo))); + if (!controllerOne.isEmpty()) { + try { +// FineLoggerFactory.getLogger().info("传送过来的角色已存在开始更新,添加关联用户:{}", pstNo, userId); + CustomRole customRole = controllerOne.get(0); + customRole.setName(realRoleName); + customRole.setAlias(pstNo); + customRole.setEnable(true); + customRole.setDescription("通过xcontent添加"); + customRoleController.update(customRole); + } catch (Exception e) { + } + } else { +// FineLoggerFactory.getLogger().info("传送过来的角色在本地不存在,添加到本地:{}", pstNo); + CustomRole addRole = new CustomRole(); + addRole.setId(pstNo); + addRole.setName(realRoleName); + addRole.setAlias(pstNo); + addRole.setEnable(true); + addRole.setDescription("通过xcontent添加"); + try { + customRoleController.add(addRole); + } catch (Exception e) { + } + } + //在判断本地的角色是不是远程没有了,要移除掉 + if(needflush){ + List roles = customRoleController.findByUser(userId, QueryFactory.create()); + for (CustomRole customRole : roles) { + if ("superusers".equals((customRole.getName()))) { + continue; + } + userController.removeUserFromCustomRole(userId, customRole.getId()); + } + } + + try { + userController.addUserToCustomRole(userId, pstNo); +// FineLoggerFactory.getLogger().info("添加{} 到新角色:{}", userId, pstNo); + } catch (Exception e) { + + } + + + DepartmentController departmentController = AuthorityContext.getInstance().getDepartmentController(); + //移除原来的部门 + if(needflush){ + List departmentList = departmentController.findByUser(userId, QueryFactory.create()); + for (Department department : departmentList) { + userController.removeUserFromDepartment(userId, department.getId()); + } + } + String instNm = entries.getString("instNm"); + String instNo = entries.getString("instNo");//部门id + Department department = departmentController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", instNo))); + if (department == null) { + department = new Department(); + department.setName(instNm + "(" + instNo + ")"); + department.setId(instNo); + department.setEnable(true); + department.description("通过x-content创建"); + departmentController.add(department); +// FineLoggerFactory.getLogger().info("新增部门,dep: {}: name {}", department.getId(), department.getName()); + } else { +// department.setName(instNm + "(" + instNo + ")"); +// department.setEnable(true); +// departmentController.update(department); +// FineLoggerFactory.getLogger().info("已存在部门,dep: {}: name {}", department.getId(), department.getName()); + } + + PostController postController = AuthorityContext.getInstance().getPostController(); + List byUser = postController.findByUser(userId, QueryFactory.create()); + //移除原来职位 + if(needflush){ + for (Post post : byUser) { + List byPost = departmentController.findByPost(post.getId(), QueryFactory.create()); + for (Department department1 : byPost) { +// FineLoggerFactory.getLogger().info("---->>>>移除用户职位 职位id {} 部门id{}", post.getId(), department1.getId()); + try { + userController.removeUserFromDepartmentAndPost(userId, department1.getId(), post.getId()); +// FineLoggerFactory.getLogger().info("^^^^^^移除用户职位成功 职位id {} 部门id{}", post.getId(), department1.getId()); + } catch (Exception e) { +// FineLoggerFactory.getLogger().info("vvvvvv移除用户职位失败 职位id {} 部门id{}", post.getId(), department1.getId()); + } + } + } + } + + Post post = postController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", pstNo))); + if (post == null) { + post = new Post(); + post.setId(pstNo); + post.setName(pstNm + "(" + pstNo + ")"); + post.setEnable(true); + postController.add(post); +// FineLoggerFactory.getLogger().info("新增职位,post: {}: name {}", post.getId(), post.getName()); + } else { +// post.setName(pstNm + "(" + pstNo + ")"); +// post.setEnable(true); +// postController.update(post); +// FineLoggerFactory.getLogger().info("已存在职位,post: {}: name {}", post.getId(), post.getName()); + } + try { +// FineLoggerFactory.getLogger().info("添加职位到部门,post: {}: dep {}", pstNo, instNo); + postController.addPostToDepartment(pstNo, instNo); + } catch (Exception e) { + } + try { +// FineLoggerFactory.getLogger().info("添加用户到职位 到部门,post: {}: dep {},userId:{} ", pstNo, instNo, realRoleName); + userController.addUserToDepartmentAndPost(userId, instNo, pstNo); + } catch (Exception e) { + } + } catch (Exception e) { + printException2FrLog(e); + } + } + + private static boolean isAdminFlags(String flag, List flags) { + return flags.contains(flag); + } + + private static List getAdminFlagsRolList() { + String adminFlags = FilterMeConfig.getInstance().getAdminFlags(); + FineLoggerFactory.getLogger().info("本地role管理员角色获取:{}", adminFlags); + if (StringUtils.isNotBlank(adminFlags)) { + String[] split = adminFlags.split(";"); + return Arrays.asList(split); + } + return Collections.emptyList(); + } + private static List getAdminFlagsNmList() { + String adminFlags = FilterMeConfig.getInstance().getAdminFlagsNm(); + FineLoggerFactory.getLogger().info("本地管理员Nm角色获取:{}", adminFlags); + if (StringUtils.isNotBlank(adminFlags)) { + String[] split = adminFlags.split(";"); + return Arrays.asList(split); + } + return Collections.emptyList(); + } + + private static List getDesignFlags() { + String adminFlags = FilterMeConfig.getInstance().getDesignFlags(); + FineLoggerFactory.getLogger().info("本地设计角色获取:{}", adminFlags); + if (StringUtils.isNotBlank(adminFlags)) { + String[] split = adminFlags.split(";"); + return Arrays.asList(split); + } + return Collections.emptyList(); + } + + private String getUrl(HttpServletRequest request) { + String url = "/"; + try { + url = "http://" + request.getServerName()//服务器地址 + + ":" + + request.getServerPort() + request.getRequestURI(); + Enumeration parameterNames = request.getParameterNames(); + Map reqParams = new HashMap<>(); + while (parameterNames.hasMoreElements()) { + String key = parameterNames.nextElement(); + reqParams.put(key, request.getParameter(key)); + } + Map parmas = header2url(request); + reqParams.putAll(parmas); + String header2url = map2String(reqParams); +// FineLoggerFactory.getLogger().info("转换之后的url参数:{}", header2url); + if (url.contains("?")) { + url += header2url; + } else { + url += "?a=1" + header2url; + } + + } catch (Exception e) { + e.printStackTrace(); + } + url += "&sendTwo=true"; + return url; + } + + + private Map header2url(HttpServletRequest request) { + Map params = new HashMap<>(); + String context = request.getHeader("X-Context"); + if (StringUtils.isNotBlank(context)) { + try { + context = URLDecoder.decode(context, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + JSONObject jsonObject = new JSONObject(context); + String userNo = jsonObject.getString("usrNo"); + String userNm = jsonObject.getString("usrNm"); + String pstNo = jsonObject.getString("pstNo"); + String instNo = jsonObject.getString("instNo"); + String accInstNo = jsonObject.getString("accInstNo"); + String admnInstNo = jsonObject.getString("admnInstNo"); + params.put("usrNo", userNo); + params.put("usrNm", userNm); + params.put("pstNo", pstNo); + params.put("instNo", instNo); + params.put("accInstNo", accInstNo); + params.put("admnInstNo", admnInstNo); + } + params.put("sendTwo", "true"); + return params; + } + + private String map2String(Map params) { + StringBuffer buffer = new StringBuffer(); + Set strings = params.keySet(); + for (String key : strings) { + buffer.append("&").append(key).append("=").append(params.get(key)); + } + return buffer.toString(); + } + +} diff --git a/src/com/fr/plugin/LoginFilter.java b/src/com/fr/plugin/LoginFilter.java new file mode 100644 index 0000000..b6d09ae --- /dev/null +++ b/src/com/fr/plugin/LoginFilter.java @@ -0,0 +1,150 @@ +package com.fr.plugin; + +import com.fr.decision.authority.AuthorityContext; +import com.fr.decision.authority.base.constant.type.operation.ManualOperationType; +import com.fr.decision.authority.controller.CustomRoleController; +import com.fr.decision.authority.controller.UserController; +import com.fr.decision.authority.data.CustomRole; +import com.fr.decision.authority.data.User; +import com.fr.decision.fun.impl.AbstractEmbedRequestFilterProvider; +import com.fr.decision.privilege.encrpt.PasswordValidator; +import com.fr.decision.webservice.utils.UserSourceFactory; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.stable.query.QueryFactory; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.*; + +/** + * 废弃 + */ +public class LoginFilter extends AbstractEmbedRequestFilterProvider { + + private boolean isLogin(HttpServletRequest req){ + return LoginService.getInstance().isLogged(req); + } + @Override + public void filter(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { + String header = req.getHeader("X-Context"); + if (StringUtils.isNotBlank(header) && !isLogin(req)) { + try { + header = URLDecoder.decode(header, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + FineLoggerFactory.getLogger().info("拦截器捕获一个请求 x-content :{}", header); + JSONObject entries = new JSONObject(header); + String usrNm = entries.getString("usrNm"); + JSONArray rlNoList = entries.getJSONArray("rlNoList"); + try { + User user = UserService.getInstance().getUserByUserName(usrNm); + UserController userController = AuthorityContext.getInstance().getUserController(); + CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); + if (user == null) { + FineLoggerFactory.getLogger().info("拦截器新增一个用户 :{}", usrNm); + PasswordValidator passwordValidator = UserSourceFactory.getInstance().getUserSource(ManualOperationType.KEY).getPasswordValidator(); + user = (new User()).userName(usrNm).realName(usrNm).password(passwordValidator.encode(usrNm, UUID.randomUUID().toString())) + .creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); + userController.add(user); + } + String userId = user.getId(); + List roles = customRoleController.findByUser(userId, QueryFactory.create()); + List localRoles = new ArrayList<>(); + boolean isAdminRoleFlag = false; + List adminFlags = getAdminFlags(); + for (CustomRole role : roles) { + String name = role.getName(); + localRoles.add(name); + if(isAdminFlags(name,adminFlags)){ + isAdminRoleFlag = true; + } + } + List remoteRoles = new ArrayList<>(); + try { + int size = rlNoList.size(); + for (int i = 0; i < size; i++) { + String name = rlNoList.getString(i); + remoteRoles.add(name); + } + //先判断传过来的的角色是不是本地都有,没有要加上 + for (String role : remoteRoles) { + if (!localRoles.contains(role)) { + userController.addUserToCustomRole(userId, role); + } + } + //在判断本地的角色是不是远程没有了,要移除掉 + for (String localRole : localRoles) { + if (!remoteRoles.contains(localRole)) { + userController.removeUserFromCustomRole(userId, localRole); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + //如果是管理员标志就直接登录admin账号 + if(isAdminRoleFlag){ + login(req, res, "admin"); + }else { + login(req, res, usrNm); + } + String home = req.getContextPath() + req.getServletPath(); + sendRedirect(res, home); + return; + } catch (Exception e) { + e.printStackTrace(); + } + } + return; + } + + private static boolean isAdminFlags(String flag,List flags){ + return flags.contains(flag); + } + private List getAdminFlags() { + InputStream inputStream = ResourceIOUtils.read("/config-all/adminflag.properties"); + if (inputStream != null) { + Properties properties = new Properties(); + try { + properties.load(inputStream); + String adminflag = properties.getProperty("adminflag"); + if (StringUtils.isNotBlank(adminflag)) { + String[] split = adminflag.split(","); + return Arrays.asList(split); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return Collections.emptyList(); + } + + private void sendRedirect(HttpServletResponse res, String url) { + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + private void login(HttpServletRequest req, HttpServletResponse res, String username) { + String token = null; + try { + token = LoginService.getInstance().login(req, res, username); + req.setAttribute("fine_auth_token", token); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + FineLoggerFactory.getLogger().error("login failed"); + } + FineLoggerFactory.getLogger().error("login success"); + } +} diff --git a/src/com/fr/plugin/LoginOutEventProvider.java b/src/com/fr/plugin/LoginOutEventProvider.java new file mode 100644 index 0000000..935a957 --- /dev/null +++ b/src/com/fr/plugin/LoginOutEventProvider.java @@ -0,0 +1,25 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.AbstractLogInOutEventProvider; +import com.fr.decision.webservice.login.LogInOutResultInfo; +import com.fr.decision.webservice.v10.user.UserService; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +public class LoginOutEventProvider extends AbstractLogInOutEventProvider { + + @Override + public String logoutAction(LogInOutResultInfo result) { + HttpServletRequest request = result.getRequest(); + + HttpSession session = request.getSession(); + if (session != null) { + session.removeAttribute("fine_auth_token"); + } + String servletPath = request.getServletPath(); + String contextPath = request.getContextPath(); + String url = contextPath + servletPath + "/login?manual=true"; + return url; + } +} diff --git a/src/com/fr/plugin/PostionImportgHander.java b/src/com/fr/plugin/PostionImportgHander.java new file mode 100644 index 0000000..afbab61 --- /dev/null +++ b/src/com/fr/plugin/PostionImportgHander.java @@ -0,0 +1,139 @@ +package com.fr.plugin; + +import com.fr.decision.authority.AuthorityContext; +import com.fr.decision.authority.controller.CustomRoleController; +import com.fr.decision.authority.controller.DepartmentController; +import com.fr.decision.authority.controller.PostController; +import com.fr.decision.authority.data.CustomRole; +import com.fr.decision.authority.data.Department; +import com.fr.decision.authority.data.Post; +import com.fr.decision.fun.impl.BaseHttpHandler; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.stable.query.QueryFactory; +import com.fr.stable.query.restriction.RestrictionFactory; +import com.fr.third.org.apache.commons.io.IOUtils; +import com.fr.third.springframework.web.bind.annotation.RequestMethod; +import com.fr.web.utils.WebUtils; +import org.json.JSONArray; +import org.json.JSONObject; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class PostionImportgHander extends BaseHttpHandler { + @Override + public RequestMethod getMethod() { + return RequestMethod.POST; + } + + @Override + public String getPath() { + return "/depAndPosImport"; + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public void handle(HttpServletRequest req, HttpServletResponse res) throws Exception { +// 传入的json格式 + /** + * [ + * { + * "fInstNo": "", //父级id + * "instNo": "A01", + * "instNm": "部门一", + * "pstNo": "X1", + * "pstNm": "职务一" + * } + * ] + */ + String body = IOUtils.toString(req.getReader()); + FineLoggerFactory.getLogger().info("批量添加部门信息接口收到:{}", body); + try { + JSONArray jsonArray = new JSONArray(body); + int length = jsonArray.length(); + for (int i = 0; i < length; i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + String fInstNo = jsonObject.getString("fInstNo"); + String instNo = jsonObject.getString("instNo");//部门id + String instNm = jsonObject.getString("instNm");//部门名称 + String pstNo = jsonObject.getString("pstNo");//职务编号 + String pstNm = jsonObject.getString("pstNm");//职务名 + saveDep(fInstNo, instNo, instNm); + savePosition(instNo, pstNo, pstNm); + } + } catch (Exception e) { + WebUtils.printAsString(res, "json format error"); + } + WebUtils.printAsString(res, "success"); + } + + private void savePosition(String did, String pid, String name) { + PostController postController = AuthorityContext.getInstance().getPostController(); + Post post; + try { + post = postController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", pid))); + if (post == null) { + post = new Post(); + post.setId(pid); + post.setName(name + "(" + pid + ")"); + post.setEnable(true); + postController.add(post); + FineLoggerFactory.getLogger().info("从接口新增职位,post: {}: name {}", post.getId(), post.getName()); + } else { + post.setName(name + "(" + pid + ")"); + post.setEnable(true); + postController.update(post); + FineLoggerFactory.getLogger().info("已存在职位,post: {}: name {}", post.getId(), post.getName()); + } + postController.addPostToDepartment(pid, did); + + CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); + CustomRole customRole = customRoleController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", pid))); + if (customRole == null) { + customRole = new CustomRole(); + customRole.id(pid).name(name + "(" + pid + ")").enable(true); + customRoleController.add(customRole); + FineLoggerFactory.getLogger().info("从接口新增角色,id: {}: name {}", customRole.getId(), customRole.getName()); + } else { + customRole.name(name + "(" + pid + ")").enable(true); + customRoleController.update(customRole); + FineLoggerFactory.getLogger().info("已存在角色,id: {}: name {}", customRole.getId(), customRole.getName()); + } + } catch (Exception e) { + FineLoggerFactory.getLogger().info("添加职位异常: 职位id :{} 部门id:{}: name {}", pid, did, name); + } + } + + private void saveDep(String pid, String id, String name) { + try { + DepartmentController departmentController = AuthorityContext.getInstance().getDepartmentController(); + Department department = departmentController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", id))); + if (StringUtils.isBlank(pid)) { + pid = null; + } + if (department == null) { + department = new Department(); + department.setName(name + "(" + id + ")"); + department.setId(id); + department.setParentId(pid); + department.setEnable(true); + department.description("通过x-content创建"); + departmentController.add(department); + FineLoggerFactory.getLogger().info("新增部门,dep: {}: name {}", department.getId(), department.getName()); + } else { + department.setName(name + "(" + id + ")"); + department.setParentId(pid); + department.setEnable(true); + departmentController.update(department); + FineLoggerFactory.getLogger().info("已存在部门,dep: {}: name {}", department.getId(), department.getName()); + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error("添加部门:{} 异常", id, e); + } + } +} \ No newline at end of file diff --git a/src/com/fr/plugin/RemoteFilter.java b/src/com/fr/plugin/RemoteFilter.java new file mode 100644 index 0000000..a5289e1 --- /dev/null +++ b/src/com/fr/plugin/RemoteFilter.java @@ -0,0 +1,348 @@ +package com.fr.plugin; + +import com.fr.data.NetworkHelper; +import com.fr.decision.authority.AuthorityContext; +import com.fr.decision.authority.base.constant.type.operation.ManualOperationType; +import com.fr.decision.authority.controller.CustomRoleController; +import com.fr.decision.authority.controller.DepartmentController; +import com.fr.decision.authority.controller.PostController; +import com.fr.decision.authority.controller.UserController; +import com.fr.decision.authority.data.CustomRole; +import com.fr.decision.authority.data.Department; +import com.fr.decision.authority.data.Post; +import com.fr.decision.authority.data.User; +import com.fr.decision.fun.impl.AbstractEmbedRequestFilterProvider; +import com.fr.decision.privilege.encrpt.PasswordValidator; +import com.fr.decision.webservice.Response; +import com.fr.decision.webservice.impl.user.type.UserProductType; +import com.fr.decision.webservice.utils.UserSourceFactory; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.exception.RemoteDesignPermissionDeniedException; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.security.JwtUtils; +import com.fr.stable.StringUtils; +import com.fr.stable.query.QueryFactory; +import com.fr.stable.query.restriction.RestrictionFactory; +import com.fr.web.service.RemoteDesignAuthorityDataService; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.*; + +public class RemoteFilter extends AbstractEmbedRequestFilterProvider { + + @Override + public void init(FilterConfig filterConfig) { + FineLoggerFactory.getLogger().info("RemoteFilter 拦截器启动"); + FilterMeConfig instance = FilterMeConfig.getInstance(); + } + + @Override + public void filter(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException, ServletException { + String uri = request.getRequestURI(); + if (uri != null && uri.contains("remote/design/token") && request.getMethod().equalsIgnoreCase("GET")) { + FineLoggerFactory.getLogger().info("拦截器捕获一个请求,design/token {}", uri); + String username = NetworkHelper.getHTTPRequestParameter(request, "username"); + String password = NetworkHelper.getHTTPRequestParameter(request, "password"); + try { + FineLoggerFactory.getLogger().info("登录的用户名{} 密码:{}", username, password); + JSONObject apilogin = apilogin(username, password); + if (apilogin != null) { + String remoteToken = tokenLogin(apilogin); + FineLoggerFactory.getLogger().info("响应的token: {} ", remoteToken); + if (StringUtils.isNotBlank(remoteToken)) { + WebUtils.printAsString(httpServletResponse, com.fr.plugin.JSONUtils.serialize(Response.ok(remoteToken))); + return; + } + FineLoggerFactory.getLogger().info("登录异常{}", remoteToken); + } else { + renderError(httpServletResponse, "21300007", "User password error!"); + } + } catch (Exception e) { + printException2Frlog(e); + if (e instanceof RemoteDesignPermissionDeniedException) { + renderError(httpServletResponse, "31300101", "Fine-Engine_Remote_Design_Permission_Denied"); + } + } + } + return; + } + + private List getAdminFlags() { + InputStream inputStream = ResourceIOUtils.read("/config-all/adminflag.properties"); + if (inputStream != null) { + Properties properties = new Properties(); + try { + properties.load(inputStream); + String adminflag = properties.getProperty("adminflag"); + if (StringUtils.isNotBlank(adminflag)) { + String[] split = adminflag.split(","); + return Arrays.asList(split); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return Collections.emptyList(); + } + + private static boolean isAdminFlags(String flag, List flags) { + return flags.contains(flag); + } + + private String tokenLogin(JSONObject jsonObject) { + JSONObject entries = jsonObject.getJSONObject("data"); +// String usrNm = entries.getString("usrNm"); + JSONArray rlNoList = entries.getJSONArray("rlNoList"); + int size = rlNoList.size(); + List adminFlags = getAdminFlags(); + boolean isAdmin = false; + for (int i = 0; i < size; i++) { + String adminPost = rlNoList.getString(i); + if (isAdminFlags(adminPost, adminFlags)) { + isAdmin = true; + break; + } + } + if (isAdmin) { + FineLoggerFactory.getLogger().info("remote 本次为管理员登录 "); + try { + return getRemoteToken("admin"); + } catch (Exception e) { + printException2Frlog(e); + } + } + String usrNo = entries.getString("usrNo"); + String instNo = entries.getString("instNo");//部门id + String realUserName = usrNo + "_" + instNo;//更改后的用户名 + String fullName = entries.getString("fullName"); + String alias = fullName + "(" + realUserName + ")"; + try { + User user = UserService.getInstance().getUserByUserName(realUserName); + UserController userController = AuthorityContext.getInstance().getUserController(); + CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); + if (user == null) { + FineLoggerFactory.getLogger().info("设计器拦截 拦截器新增一个用户 :{}", usrNo); + //如果usrNo的用户不存在则检查realUserName的用户是否存在 + user = UserService.getInstance().getUserByUserName(realUserName); + if (user == null) { + FineLoggerFactory.getLogger().info("拦截器新增一个用户 用户名称 :{}", realUserName); + PasswordValidator passwordValidator = UserSourceFactory.getInstance().getUserSource(ManualOperationType.KEY).getPasswordValidator(); + user = (new User()).userName(realUserName).userAlias(alias).realName(fullName).password(passwordValidator.encode(realUserName, UUID.randomUUID().toString())) + .creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); + userController.add(user); + } + } + + String userId = user.getId(); +// List adminFlags = getAdminFlags(); + FineLoggerFactory.getLogger().info(" 设计器拦截 当前的管理员标识符 :{}", adminFlags); + List remoteRoles = new ArrayList<>(); + String pstNo = entries.getString("pstNo");//角色id + String pstNm = entries.getString("pstNm");//角色名称 + String realRoleName = pstNm + "(" + pstNo + ")"; + remoteRoles.add(pstNo); + + //先判断传过来的的角色是不是本地都有,没有要加上 + List controllerOne = customRoleController.find(QueryFactory.create().addRestriction(RestrictionFactory.eq("name", pstNo))); + if (!controllerOne.isEmpty()) { + try { + FineLoggerFactory.getLogger().info(" 设计器拦截 传送过来的角色在本地已存在,添加到用户{} 到角色:{}", userId, pstNo); + CustomRole customRole = controllerOne.get(0); + customRole.setName(realRoleName); + customRole.setAlias(pstNo); + customRole.setEnable(true); + customRole.setDescription("通过xcontent添加"); + customRoleController.update(customRole); + userController.addUserToCustomRole(userId, customRole.getId()); + } catch (Exception e) { + } + } else { + FineLoggerFactory.getLogger().info(" 设计器拦截 传送过来的角色在本地不存在,添加到本地:{}", pstNo); + CustomRole addRole = new CustomRole(); + addRole.setId(pstNo); + addRole.setName(realRoleName); + addRole.setAlias(pstNo); + addRole.setEnable(true); + addRole.setDescription("通过xcontent添加"); + try { + customRoleController.add(addRole); + userController.addUserToCustomRole(userId, addRole.getId()); + } catch (Exception e) { + } + } + //在判断本地的角色是不是远程没有了,要移除掉 + List roles = customRoleController.findByUser(userId, QueryFactory.create()); + + for (CustomRole customRole : roles) { + if (!remoteRoles.contains(customRole.getId())) { + if ("superusers".equals((customRole.getName()))) { + continue; + } + FineLoggerFactory.getLogger().info("远端没有的角色本地移除:{}", customRole.getName()); + userController.removeUserFromCustomRole(userId, customRole.getId()); + } + } + + DepartmentController departmentController = AuthorityContext.getInstance().getDepartmentController(); + //移除原来的部门 + List departmentList = departmentController.findByUser(userId, QueryFactory.create()); + for (Department department : departmentList) { + userController.removeUserFromDepartment(userId, department.getId()); + } + String instNm = entries.getString("instNm"); + Department department = departmentController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", instNo))); + if (department == null) { + FineLoggerFactory.getLogger().info("添加 部门,dep: {}: name {}", instNo, instNo, instNm + "(" + pstNo + "" + ")"); + department = new Department(); + department.setName(instNm + "(" + pstNo + ")"); + department.setId(instNo); + department.setEnable(true); + department.description("通过x-content创建"); + departmentController.add(department); + } else { + department.setName(instNm + "(" + pstNo + ")"); + department.setParentId(null); + department.setEnable(true); + departmentController.update(department); + FineLoggerFactory.getLogger().info("设计器节点 已存在部门,dep: {}: name {}", department.getId(), department.getName()); + } + + PostController postController = AuthorityContext.getInstance().getPostController(); + List byUser = postController.findByUser(userId, QueryFactory.create()); + //移除原来职位 + for (Post post : byUser) { + userController.removeUserFromPost(userId, post.getId()); + } + Post post = postController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", pstNo))); + if (post == null) { + post = new Post(); + post.setId(pstNo); + post.setName(pstNm + "(" + pstNo + ")"); + post.setEnable(true); + postController.add(post); + } else { + post.setName(pstNm + "(" + pstNo + ")"); + post.setEnable(true); + postController.update(post); + } + try { + FineLoggerFactory.getLogger().info("添加职位到部门,post: {}: dep {}", pstNo, instNo); + postController.addPostToDepartment(pstNo, instNo); + } catch (Exception e) { + } + try { + FineLoggerFactory.getLogger().info("添加用户到部门,post: {}: dep {} ,userId:{}", pstNo, instNo, userId); + userController.addUserToDepartmentAndPost(userId, instNo, pstNo); + } catch (Exception e) { + } + + boolean isDesignUser = false; + List designFlags = getDesignFlags(); + if (!designFlags.isEmpty()) { + for (CustomRole role : roles) { + String name = role.getName(); + //如果是有 + if (isAdminFlags(name, designFlags)) { + isDesignUser = true; + break; + } + } + } + try { + UserProductType userProductType = UserProductType.fromInteger(6);//数据处理用户 + if (isDesignUser) { + userController.addUserProductType(userId, userProductType.transProductKey()); + } else { + userController.removeUserProductType(userId, userProductType.transProductKey()); + } + } catch (Exception e) { + } +// if (isAdmin) { +// FineLoggerFactory.getLogger().info("设计器拦截 本次为管理员登录"); +// return getRemoteToken("admin"); +// } else { + FineLoggerFactory.getLogger().info("设计器拦截 本次为普通用户登录:{}", realUserName); + return getRemoteToken(realUserName); +// } + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + private static List getDesignFlags() { + String adminFlags = FilterMeConfig.getInstance().getDesignFlags(); + FineLoggerFactory.getLogger().info("remote本地设计角色获取:{}", adminFlags); + if (StringUtils.isNotBlank(adminFlags)) { + String[] split = adminFlags.split(";"); + return Arrays.asList(split); + } + return Collections.emptyList(); + } + + private void renderError(HttpServletResponse httpServletResponse, String code, String msg) { + try { + WebUtils.printAsString(httpServletResponse, JSONUtils.serialize(Response.error(code, msg))); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private JSONObject apilogin(String username, String pwd) throws Exception { + String baseUrl = getBaseUrl(); + if (StringUtils.isBlank(baseUrl)) { + throw new Exception("未配置接口地址"); + } + String api = getBaseUrl() + "check/pwd/finebi"; + FineLoggerFactory.getLogger().info("remote访问的接口地址为 {}", api); + JSONObject parm = new JSONObject(); + parm.put("userName", username); + parm.put("verifyCode", pwd); + + String resp = HttpApi.sendJsonPost(api, parm, "UTF-8"); + FineLoggerFactory.getLogger().info("接口响应 {}", resp); + JSONObject jsonObject = new JSONObject(resp); + return jsonObject; + } + + + public String getRemoteToken(String var1) throws Exception { + User var4 = UserService.getInstance().getUserByUserName(var1); + if (var4 != null && RemoteDesignAuthorityDataService.getInstance().hasAuthority(var4.getId())) { + return JwtUtils.createDefaultJWT(var1); + } else { + throw new RemoteDesignPermissionDeniedException(); + } + } + + private String getBaseUrl() { + FilterMeConfig instance = FilterMeConfig.getInstance(); + String baseUrl = instance.getBaseUrl(); + if (StringUtils.isNotBlank(baseUrl)) { + String[] split = baseUrl.split(";"); + List strings = Arrays.asList(split); + Collections.shuffle(strings); + return strings.get(0); + } + return ""; + } + + public static void printException2Frlog(Exception e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", s); + } + +} diff --git a/src/com/fr/plugin/RequestWrapper.java b/src/com/fr/plugin/RequestWrapper.java new file mode 100644 index 0000000..fb596b9 --- /dev/null +++ b/src/com/fr/plugin/RequestWrapper.java @@ -0,0 +1,128 @@ +package com.fr.plugin; + + +import com.fr.log.FineLoggerFactory; +import com.fr.log.FineLoggerProvider; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * @author 01 + * @program wrapper-demo + * @description 包装HttpServletRequest,目的是让其输入流可重复读 + * @create 2018-12-24 20:48 + * @since 1.0 + **/ +public class RequestWrapper extends HttpServletRequestWrapper { + FineLoggerProvider log = FineLoggerFactory.getLogger(); + /** + * 存储body数据的容器 + */ + private byte[] body; + + public RequestWrapper(HttpServletRequest request) { + super(request); + // 将body数据存储起来 + String bodyStr = getBodyString(request); + body = bodyStr.getBytes(Charset.defaultCharset()); + } + + public void setBody(byte[] body) { + this.body = body; + } + + /** + * 获取请求Body + * + * @param request request + * @return String + */ + private String getBodyString(final ServletRequest request) { + try { + return inputStream2String(request.getInputStream()); + } catch (IOException e) { + log.error("", e); + throw new RuntimeException(e); + } + } + + /** + * 获取请求Body + * + * @return String + */ + public String getBodyString() { + return new String(this.body, StandardCharsets.UTF_8); + } + + /** + * 将inputStream里的数据读取出来并转换成字符串 + * + * @param inputStream inputStream + * @return String + */ + private String inputStream2String(InputStream inputStream) { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + + try { + reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + } catch (IOException e) { + log.error("", e); + throw new RuntimeException(e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + log.error("", e); + } + } + } + + return sb.toString(); + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + + final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); + + return new ServletInputStream() { + @Override + public int read() throws IOException { + return inputStream.read(); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + }; + } +} \ No newline at end of file diff --git a/src/com/fr/plugin/XContentRequestWrapper.java b/src/com/fr/plugin/XContentRequestWrapper.java new file mode 100644 index 0000000..76da138 --- /dev/null +++ b/src/com/fr/plugin/XContentRequestWrapper.java @@ -0,0 +1,97 @@ +package com.fr.plugin; + +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.third.jodd.util.collection.ArrayEnumeration; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.*; + +public class XContentRequestWrapper extends HttpServletRequestWrapper { + JSONObject jsonObject = null; + + public XContentRequestWrapper(HttpServletRequest request) { + super(request); + String header = request.getHeader("X-Context"); + String url = WebUtils.getOriginalURL(request); + try { + if (StringUtils.isNotBlank(header)) { + header = URLDecoder.decode(header, "UTF-8"); + jsonObject = new JSONObject(header); + FineLoggerFactory.getLogger().info("当前请求地址:{} \n X-Context :{} ", url, header); + } else { + FineLoggerFactory.getLogger().info("当前请求地址:{} 未携带 X-Context ", url); + } + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + @Override + public String getParameter(String name) { + String parameter = super.getParameter(name); + if (StringUtils.isBlank(parameter) && jsonObject != null) { + return jsonObject.getString(name); + } + return parameter; + } + + @Override + public Map getParameterMap() { + Map parameterMap = super.getParameterMap(); + Map stringHashMap = new HashMap<>(); + if (jsonObject != null) { + Iterator keys = jsonObject.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object value = jsonObject.getValue(key); + if (value != null) { + stringHashMap.put(key, new String[]{ + value.toString() + }); + } + } + } + stringHashMap.putAll(parameterMap); + return stringHashMap; + } + + @Override + public Enumeration getParameterNames() { + Enumeration parameterNames = super.getParameterNames(); + if (jsonObject != null) { + Iterator keys = jsonObject.keys(); + ArrayList arrayList = new ArrayList<>(); + while (keys.hasNext()) { + String next = keys.next(); + arrayList.add(next); + } + while (parameterNames.hasMoreElements()) { + String s = parameterNames.nextElement(); + arrayList.add(s); + } + ArrayEnumeration dayNames = new ArrayEnumeration(arrayList.toArray(new String[0])); + return dayNames; + } + return parameterNames; + } + + @Override + public String[] getParameterValues(String name) { + String[] parameterValues = super.getParameterValues(name); + if (parameterValues == null && jsonObject != null) { + Object stringObjectMap = jsonObject.getValue(name); + if (stringObjectMap != null) { + return new String[]{ + stringObjectMap.toString() + }; + } + } + return parameterValues; + } +} diff --git a/src/com/fr/plugin/error.html b/src/com/fr/plugin/error.html new file mode 100644 index 0000000..f5e45f8 --- /dev/null +++ b/src/com/fr/plugin/error.html @@ -0,0 +1,16 @@ + + + + + + + 无权访问 + + +
+

用户不在白名单,访问被拒绝,请联系管理员处理

+
+
+ + \ No newline at end of file diff --git a/src/com/fr/plugin/util/LogUtils.java b/src/com/fr/plugin/util/LogUtils.java new file mode 100644 index 0000000..e64cada --- /dev/null +++ b/src/com/fr/plugin/util/LogUtils.java @@ -0,0 +1,90 @@ +package com.fr.plugin.util; + +import com.fr.log.FineLoggerFactory; +import com.fr.log.FineLoggerProvider; +import com.fr.plugin.context.PluginContexts; +import com.fr.stable.StringUtils; + +public final class LogUtils { + private static String LOG_PREFIX = "[单点登录] "; + private static final String PLUGIN_VERSION; + + private static final FineLoggerProvider LOGGER = FineLoggerFactory.getLogger(); + + static { + String version = PluginContexts.currentContext().getMarker().getVersion(); + if (StringUtils.isNotBlank(version)) { + PLUGIN_VERSION = "[v" + version + "] "; + } else { + PLUGIN_VERSION = "[unknown version] "; + } + + LOG_PREFIX = LOG_PREFIX + PLUGIN_VERSION; + } + + public static void setPrefix(String prefix) { + if (prefix != null) { + LOG_PREFIX = prefix; + } + } + + public static boolean isDebugEnabled() { + return LOGGER.isDebugEnabled(); + } + + public static void debug(String s) { + LOGGER.debug(LOG_PREFIX + s); + } + + public static void debug(String s, Object... objects) { + LOGGER.debug(LOG_PREFIX + s, objects); + } + + public static void debug(String s, Throwable throwable) { + LOGGER.debug(LOG_PREFIX + s, throwable); + } + + public static boolean isInfoEnabled() { + return LOGGER.isInfoEnabled(); + } + + public static void info(String s) { + LOGGER.info(LOG_PREFIX + s); + } + + public static void info(String s, Object... objects) { + LOGGER.info(LOG_PREFIX + s, objects); + } + + public static void warn(String s) { + LOGGER.warn(LOG_PREFIX + s); + } + + public static void warn(String s, Object... objects) { + LOGGER.warn(LOG_PREFIX + s, objects); + } + + public static void warn(String s, Throwable throwable) { + LOGGER.warn(LOG_PREFIX + s, throwable); + } + + public static void warn(Throwable throwable, String s, Object... objects) { + LOGGER.warn(throwable, LOG_PREFIX + s, objects); + } + + public static void error(String s) { + LOGGER.error(LOG_PREFIX + s); + } + + public static void error(String s, Object... objects) { + LOGGER.error(LOG_PREFIX + s, objects); + } + + public static void error(String s, Throwable throwable) { + LOGGER.error(LOG_PREFIX + s, throwable); + } + + public static void error(Throwable throwable, String s, Object... objects) { + LOGGER.error(throwable, LOG_PREFIX + s, objects); + } +} diff --git a/src/com/fr/plugin/util/RequestUtils.java b/src/com/fr/plugin/util/RequestUtils.java new file mode 100644 index 0000000..fc4540a --- /dev/null +++ b/src/com/fr/plugin/util/RequestUtils.java @@ -0,0 +1,26 @@ +package com.fr.plugin.util; + +import com.fr.stable.StringUtils; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author Peng + */ +public class RequestUtils { + + /** + * todo 有待测试 + * @param request + * @return + */ + public static String getFullUrl(HttpServletRequest request) { + String url = request.getRequestURI(); + String queryString = request.getQueryString(); + if (StringUtils.isNotEmpty(queryString)) { + url = url + "?" + queryString; + } + + return url; + } +} diff --git a/src/com/fr/plugin/util/ResponseUtils.java b/src/com/fr/plugin/util/ResponseUtils.java new file mode 100644 index 0000000..9c8223d --- /dev/null +++ b/src/com/fr/plugin/util/ResponseUtils.java @@ -0,0 +1,35 @@ +package com.fr.plugin.util; + +import com.fr.decision.webservice.utils.WebServiceUtils; +import com.fr.json.JSONObject; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletResponse; + +public final class ResponseUtils { + + /** + * 打印之前会自动处理 Content-Type + */ + public static void printAsJSON( HttpServletResponse response, JSONObject jsonObject) throws Exception { + // todo 这里的 charset 有没有可能是别的呢? + response.setContentType("application/json;charset=utf-8"); + WebUtils.printAsJSON(response, jsonObject); + } + + /** + * todo + * + * 参考方法签名: + * generateUnavailableWebPage(String result, String reason, String solution) + * generateErrorWebPage(String result, String reason, String solution) + * + */ + public static void printErrorPage() { + // 可参考 ↓ +// String errorHtml = WebServiceUtils.generateUnavailableWebPage("单点登录失败", reason, ""); +// String errorHtml = WebServiceUtils.generateErrorWebPage("单点登录失败", reason, ""); +// WebUtils.printAsString(response, errorHtml); + + } +} diff --git a/src/com/fr/sso/cas/FRAccessFilter.java b/src/com/fr/sso/cas/FRAccessFilter.java new file mode 100644 index 0000000..1871ea2 --- /dev/null +++ b/src/com/fr/sso/cas/FRAccessFilter.java @@ -0,0 +1,464 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package com.fr.sso.cas; + +import com.fr.base.ServerConfig; +import com.fr.base.TemplateUtils; +import com.fr.data.NetworkHelper; +import com.fr.decision.authority.data.User; +import com.fr.decision.mobile.terminal.TerminalHandler; +import com.fr.decision.privilege.TransmissionTool; +import com.fr.decision.webservice.Response; +import com.fr.decision.webservice.bean.authentication.LoginRequestInfoBean; +import com.fr.decision.webservice.bean.authentication.LoginResponseInfoBean; +import com.fr.decision.webservice.bean.authentication.OriginUrlResponseBean; +import com.fr.decision.webservice.exception.login.UserLoginException; +import com.fr.decision.webservice.utils.WebServiceUtils; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.decision.webservice.v10.login.TokenResource; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.general.ComparatorUtils; +import com.fr.log.FineLoggerFactory; +import com.fr.security.JwtUtils; +import com.fr.stable.StringUtils; +import com.fr.stable.web.Device; +import com.fr.third.fasterxml.jackson.core.JsonGenerationException; +import com.fr.third.fasterxml.jackson.core.JsonParseException; +import com.fr.third.fasterxml.jackson.databind.JsonMappingException; +import com.fr.third.fasterxml.jackson.databind.ObjectMapper; +import com.fr.third.fasterxml.jackson.databind.type.TypeFactory; +import com.fr.web.utils.WebUtils; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.util.Hashtable; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +public class FRAccessFilter implements Filter { + public String[] loginPageNecessaryResources = new String[]{"fineui.min.css", "login.min.css", "fineui.min.js", "I18nTextGenerator", "ConstantGenerator", "login.min.js", "logo.png", "login.png", "login-img.png", "iconfont.woff"}; + private String servletNamePrefix = "/" + ServerConfig.getInstance().getServletName(); + private String[] noFilterUrls; + private static ObjectMapper objectMapper = new ObjectMapper(); + private static boolean isDev = true; + + public FRAccessFilter() { + this.noFilterUrls = new String[]{this.servletNamePrefix + "/login", this.servletNamePrefix + "/login/config", this.servletNamePrefix + "/system/info", this.servletNamePrefix + "/login/cross/domain", this.servletNamePrefix + "/remote"}; + } + + public void init(FilterConfig filterConfig) throws ServletException { + } + + private boolean isApp(HttpServletRequest request) { + String header = request.getHeader("User-Agent"); + if (StringUtils.isNotBlank(header) && header.contains("FineReact")) { + return true; + } else { + String deviceType = request.getParameter("deviceType"); + if (StringUtils.isNotBlank(deviceType)) { + return true; + } else { + String terminal = request.getHeader("terminal"); + return StringUtils.isNotBlank(terminal) && terminal.equals("APP"); + } + } + } + + private boolean isMobLogin(HttpServletRequest request) { + if (this.isApp(request) && request.getMethod().equals("POST")) { + String requestURI = request.getRequestURI(); + if (requestURI.endsWith("/login")) { + return true; + } + } + + return false; + } + + public static void printException2Frlog(Exception e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", new Object[]{s}); + } + + public boolean getAuthenticator(String useruid, String userPassword, String applicationUsername, String applicationPassword) throws NamingException { + boolean result = false; + boolean flag = false; + DirContext ctx = null; + Hashtable env = new Hashtable(); + env.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory"); + if (isDev) { + env.put("java.naming.provider.url", "ldap://XXXX"); + } else { + env.put("java.naming.provider.url", "ldap://XXXX"); + } + + env.put("java.naming.security.authentication", "simple"); + env.put("java.naming.security.principal", applicationUsername); + env.put("java.naming.security.credentials", applicationPassword); + + try { + ctx = new InitialDirContext(env); + FineLoggerFactory.getLogger().info("Applications Authentication Succeed"); + flag = true; + } catch (NamingException var31) { + if (ctx != null) { + try { + ctx.close(); + FineLoggerFactory.getLogger().info("------------------------------------------"); + } catch (NamingException var26) { + FineLoggerFactory.getLogger().info("ctx close occur NamingException, error message:" + var26); + } + } + + FineLoggerFactory.getLogger().info(" Applications Authentication Failed, error messgae: " + var31); + flag = false; + return false; + } + + SearchControls constraints = new SearchControls(); + if (flag) { + NamingEnumeration en = null; + + try { + constraints.setSearchScope(2); + String[] returnAttrs = new String[]{"*"}; + constraints.setReturningAttributes(returnAttrs); + String basedn = "dc=bizenit,dc=com"; + String searchFilter = "(|(uid=" + useruid + ")(smart-alias=" + useruid + "))"; + FineLoggerFactory.getLogger().info("filter:" + searchFilter); + en = ctx.search(basedn, searchFilter, constraints); + } catch (Exception var29) { + FineLoggerFactory.getLogger().info("search occur Exception, error message:" + var29); + printException2Frlog(var29); + } + + if (en.hasMoreElements()) { + DirContext ctxUser = null; + Hashtable envUser = new Hashtable(); + envUser.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory"); + if (isDev) { + envUser.put("java.naming.provider.url", "ldap://XXXX"); + } else { + envUser.put("java.naming.provider.url", "ldap://XXXX"); + } + + envUser.put("java.naming.security.authentication", "simple"); + envUser.put("java.naming.security.principal", ((SearchResult)en.next()).getNameInNamespace()); + envUser.put("java.naming.security.credentials", userPassword); + + try { + Object o = envUser.get("java.naming.provider.url"); + FineLoggerFactory.getLogger().info("LDAP认证服务器地址:{} 认证用户名:{}", new Object[]{o, envUser.get("java.naming.security.principal")}); + ctxUser = new InitialDirContext(envUser); + FineLoggerFactory.getLogger().info("User Login Succeed"); + result = true; + constraints.setTimeLimit(1000); + constraints.setDerefLinkFlag(false); + constraints.setReturningAttributes(new String[]{"*"}); + } catch (NamingException var28) { + FineLoggerFactory.getLogger().info("user Authentication occured NamingException,error message:" + var28); + printException2Frlog(var28); + } finally { + if (ctxUser != null) { + try { + ctxUser.close(); + } catch (NamingException var25) { + FineLoggerFactory.getLogger().info("ctxUser close occured NamingException,error message:" + var25); + } + } + + } + } else { + result = false; + } + + try { + if (en != null) { + en.close(); + } + + if (ctx != null) { + ctx.close(); + } + } catch (NamingException var27) { + FineLoggerFactory.getLogger().info("close occured NamingException,error message:" + var27); + printException2Frlog(var27); + } + } + + return result; + } + + public static T deserialize(String json, Class clazz) { + Object object = null; + try { + object = objectMapper.readValue(json, TypeFactory.rawClass(clazz)); + } catch (JsonParseException e) { + } catch (JsonMappingException e) { + } catch (IOException e) { + } + return (T) object; + } + + private String inputStream2String(InputStream inputStream) { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + + try { + reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); + + String line; + while((line = reader.readLine()) != null) { + sb.append(line); + } + } catch (IOException var12) { + throw new RuntimeException(var12); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException var11) { + } + } + + } + + return sb.toString(); + } + + public static String serialize(Object object) { + StringWriter write = new StringWriter(); + + try { + objectMapper.writeValue(write, object); + } catch (JsonGenerationException var3) { + } catch (JsonMappingException var4) { + } catch (IOException var5) { + } + + return write.toString(); + } + + private void mobLogin(HttpServletRequest req, HttpServletResponse response, LoginRequestInfoBean loginReqInfo) throws Exception { + String password = TransmissionTool.decrypt(loginReqInfo.isEncrypted(), loginReqInfo.getPassword()); + String username = loginReqInfo.getUsername(); + if (WebServiceUtils.containSQLChars(username)) { + Response error = Response.error("21300015", "Special char prohibit!"); + WebUtils.printAsString(response, serialize(error)); + } else { + User user = UserService.getInstance().getUserByUserName(username); + if (user == null) { + throw new UserLoginException(); + } else { + String adminName = "XXXX"; + String adminPwd = "XXXX"; + if (isDev) { + adminName = "XXXX"; + adminPwd = "XXXX"; + } + + if (this.getAuthenticator(username, password, adminName, adminPwd)) { + String token = this.login(req, response, username); + OriginUrlResponseBean originUrlResponseBean = new OriginUrlResponseBean(TemplateUtils.render("${fineServletURL}")); + LoginResponseInfoBean infoBean = new LoginResponseInfoBean(token, originUrlResponseBean, user.getUserName(), loginReqInfo.getValidity()); + WebUtils.printAsString(response, serialize(infoBean)); + } else { + Response error = Response.error("21300007", "User not exist, or wrong password!"); + WebUtils.printAsString(response, serialize(error)); + } + } + } + } + + public static boolean checkTokenValid(HttpServletRequest req, String token, String currentUserName) { + try { + if (!ComparatorUtils.equals(currentUserName, JwtUtils.parseJWT(token).getSubject())) { + FineLoggerFactory.getLogger().info("username changed:" + currentUserName); + return false; + } else { + Device device = NetworkHelper.getDevice(req); + LoginService.getInstance().loginStatusValid(token, TerminalHandler.getTerminal(req, device)); + return true; + } + } catch (Exception var4) { + return false; + } + } + + private boolean isLogin(HttpServletRequest request) { + try { + return LoginService.getInstance().isLogged(request); + } catch (Exception var3) { + return false; + } + } + + private String login(HttpServletRequest req, HttpServletResponse res, String username) { + try { + String oldToken = TokenResource.COOKIE.getToken(req); + if (oldToken == null || !checkTokenValid(req, oldToken, username)) { + HttpSession session = req.getSession(true); + String token = LoginService.getInstance().login(req, res, username); +// session.setAttribute("fine_auth_token", token); + req.setAttribute("fine_auth_token", token); + return token; + } + } catch (Exception var7) { + FineLoggerFactory.getLogger().error(var7.getMessage(), var7); + } + + return null; + } + + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + servletRequest.setCharacterEncoding("UTF-8"); + HttpServletRequest request = (HttpServletRequest)servletRequest; + HttpServletResponse response = (HttpServletResponse)servletResponse; + String noIdmParameter = request.getParameter("no-idm"); + String pathInfo; + if (this.isMobLogin(request)) { + pathInfo = this.inputStream2String(request.getInputStream()); + + try { + LoginRequestInfoBean infoBean = (LoginRequestInfoBean)deserialize(pathInfo, LoginRequestInfoBean.class); + if (this.isLogin(request)) { + String oldToken = TokenResource.COOKIE.getToken(request); + OriginUrlResponseBean originUrlResponseBean = new OriginUrlResponseBean(TemplateUtils.render("${fineServletURL}")); + LoginResponseInfoBean respBean = new LoginResponseInfoBean(oldToken, originUrlResponseBean, infoBean.getUsername(), infoBean.getValidity()); + WebUtils.printAsString(response, serialize(respBean)); + } else { + this.mobLogin(request, response, infoBean); + } + + return; + } catch (Exception var13) { + FineLoggerFactory.getLogger().info("LDAP认证失败:" + var13.getMessage()); + } + } + + if (this.isApp(request)) { + setCookie(response, "no-idm", "true"); + pathInfo = request.getPathInfo(); + this.forwardRequest(request, response); + } else { + if (StringUtils.isNotEmpty(noIdmParameter)) { + boolean isNoIdm = Boolean.parseBoolean(noIdmParameter); + Cookie noIdmCookie = com.fr.third.springframework.web.util.WebUtils.getCookie(request, "no-idm"); + if (noIdmCookie != null) { + if (Boolean.parseBoolean(noIdmCookie.getValue()) != isNoIdm) { + if (isNoIdm) { + setCookie(response, "no-idm", "true"); + } else { + setCookie(response, "no-idm", "false"); + } + } + } else if (isNoIdm) { + setCookie(response, "no-idm", "true"); + } else { + setCookie(response, "no-idm", "false"); + } + } else { + Cookie noIdmCookie = com.fr.third.springframework.web.util.WebUtils.getCookie(request, "no-idm"); + if (noIdmCookie == null) { + setCookie(response, "no-idm", "false"); + } + } + + if (!this.isNoFilterUrls(request) && !this.isRequestLoginPageNecessaryResources(request) && !this.isFromNoIdmPage(request)) { + User user = null; + + try { + user = UserService.getInstance().getUserByRequestCookie(request); + } catch (Exception var12) { + } + + if (user != null) { + this.forwardRequest(request, response); + } else if (StringUtils.isNotEmpty(noIdmParameter) && Boolean.parseBoolean(noIdmParameter)) { + this.forwardRequest(request, response); + } else { + filterChain.doFilter(request, response); + } + } else { + this.forwardRequest(request, response); + } + } + } + + private static void setCookie(HttpServletResponse response, String name, String value) { + Cookie cookie = new Cookie(name, value); + cookie.setPath("/"); + response.addCookie(cookie); + } + + public void destroy() { + } + + public boolean isNoFilterUrls(HttpServletRequest request) { + String requestURI = request.getRequestURI(); + String[] var3 = this.noFilterUrls; + int var4 = var3.length; + + for(int var5 = 0; var5 < var4; ++var5) { + String url = var3[var5]; + if (requestURI.contains(url)) { + return true; + } + } + + return false; + } + + public boolean isRequestLoginPageNecessaryResources(HttpServletRequest request) { + String queryString = request.getQueryString(); + if (StringUtils.isEmpty(queryString)) { + return false; + } else { + String[] var3 = this.loginPageNecessaryResources; + int var4 = var3.length; + + for(int var5 = 0; var5 < var4; ++var5) { + String parameter = var3[var5]; + if (queryString.contains(parameter)) { + return true; + } + } + + return false; + } + } + + private boolean isFromNoIdmPage(HttpServletRequest request) { + String referer = request.getHeader("Referer"); + return StringUtils.isNotEmpty(referer) && referer.contains("no-idm=true"); + } + + private void forwardRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String contextPath = request.getContextPath(); + String relativePath = request.getRequestURI().replace(contextPath, ""); + request.getRequestDispatcher(relativePath).forward(request, response); + } +}