From c073c9171b5c4b2b45756770c373a792960895bf Mon Sep 17 00:00:00 2001
From: Adrian Damian <Adrian.Damian@nrc.ca>
Date: Wed, 25 Jun 2014 09:22:35 -0700
Subject: [PATCH] Added the core authorization classes

---
 projects/cadcUtil/doc/auth.html               |  36 +++
 projects/cadcUtil/doc/uml/UserAuth.png        | Bin 0 -> 18102 bytes
 .../src/ca/nrc/cadc/auth/model/Group.java     | 263 ++++++++++++++++
 .../ca/nrc/cadc/auth/model/GroupProperty.java | 199 ++++++++++++
 .../ca/nrc/cadc/auth/model/PosixDetails.java  | 167 ++++++++++
 .../src/ca/nrc/cadc/auth/model/User.java      | 146 +++++++++
 .../ca/nrc/cadc/auth/model/UserDetails.java   | 287 ++++++++++++++++++
 .../src/ca/nrc/cadc/auth/model/GroupTest.java | 122 ++++++++
 .../src/ca/nrc/cadc/auth/model/UserTest.java  | 110 +++++++
 9 files changed, 1330 insertions(+)
 create mode 100644 projects/cadcUtil/doc/auth.html
 create mode 100644 projects/cadcUtil/doc/uml/UserAuth.png
 create mode 100644 projects/cadcUtil/src/ca/nrc/cadc/auth/model/Group.java
 create mode 100644 projects/cadcUtil/src/ca/nrc/cadc/auth/model/GroupProperty.java
 create mode 100644 projects/cadcUtil/src/ca/nrc/cadc/auth/model/PosixDetails.java
 create mode 100644 projects/cadcUtil/src/ca/nrc/cadc/auth/model/User.java
 create mode 100644 projects/cadcUtil/src/ca/nrc/cadc/auth/model/UserDetails.java
 create mode 100644 projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/GroupTest.java
 create mode 100644 projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/UserTest.java

diff --git a/projects/cadcUtil/doc/auth.html b/projects/cadcUtil/doc/auth.html
new file mode 100644
index 00000000..461ef1b2
--- /dev/null
+++ b/projects/cadcUtil/doc/auth.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<title>
+	CADC User Authorization Model
+</title>
+</head>
+
+<body>
+
+<div class="main">
+
+<h1>CADC User Authorization Model</h1>
+
+<p>The CADC User Authorization Model is a model for representing CADC users and groups. The model is used primarily in the GMS and Users Web services.
+</p>
+
+<a href="uml/UserAuth.png">  <img src="uml/UserAuth.png" alt="CADC User Authorization Model"></a>
+
+<h2>User Class Features </h2>
+In the system, a user is uniquely identified by a Principal (in CADC's case that is the CadcPrincipal) but can have a number of other  identities for different contexts:
+<ul>
+    <li>HttpPrincipal: Web user identity associated with Simple HHTP User Password access.</li>
+    <li>X500Principal: X509 certificate identity. </li>
+    <li>SShPubKeyPrincipal: An ssh key identity. </li>
+    <li>CadcPrincipal: An identity used internally at the CADC. </li>
+    <li>CookiePrincipal: Cookie based identity. </li>
+    <li>OpenIdPrincipal: An OpenID identity. </li>
+</ul>
+
+<h2>Group Class Features</h2>
+Groups represet associations of users. Members of groups can be groups of users or simple users. groupWrite and groupRead represent the groups that have read and read-and-write permissions to the current group. 
+
+</body>
+</html>
diff --git a/projects/cadcUtil/doc/uml/UserAuth.png b/projects/cadcUtil/doc/uml/UserAuth.png
new file mode 100644
index 0000000000000000000000000000000000000000..ae0738a6cc4438945b20bac98a823a9b2f326086
GIT binary patch
literal 18102
zcmeHvc|6qX+y78fjv^!?io_JvQ1+!oL=)8{N}-w1Vi(zmoXVC-#3)NAj3isil5B;^
zo}EU<$uf3jER8Ydci%(b)A#%Qp5K4Z@2}^)&QU)1=f3Z2eP7po-Ph-Yo;ESu$hVCT
zfk13DI-!3Sfmn+{Ah<`@uLDo;Y1fMo2m^m3{UhhRzD$0->GZWIHn64QaNv$|?LU*)
zCvK?TUO!rt(>A0j7uxF=n%c=F`cjhInfIJ#AG$c7%wD2NfMpQg4Qt^aC?4?RFq#Ye
z6a<?f5LF`ZC+9Ij1wQ_723H<epyAv9W#;d54hX|nep~kU!Ij7VW2E#KZ0s*|E`Nd3
z;D&s7S)ftU4SF1cs}OVn4q2#*r8SV@!~6%>9Bu{I{Ogs~zo07o{_EI(Ot*O)?T_NH
zhT^f9sVeC*DkjR15Qsn`x1#?y|64Qu^E7N2ksE=)^0}OR|8s0^*Uyn=!t!!YD7~Xg
z%HQ7!UCYq5){A4_zNv?wrI~KpgasCW<;$Hk+mS(VFW)UsN-fi+HOov7j17$V6)pZ8
zDYfFc+ujw5L?CvM5Yfql1|Lq;b`1vTE;PHPM~5NPrZwC=PBnEL$wqSp&Sc&U!v9>R
zJe|Bt<knUorDm&YP_EC0A?@4Vz3V`#Jqkf0P8Z-AbL0E_D`<H`&4g!I-<>1`wl1ev
zc(%`+l<IW1ZX=%)oeRinu_|U^fy!oGfp4fq#Ghpzk{cilCd8!67&N0W#!hta8R1}{
z+$xca_qpjibqIY_wjp{mCNOG2f_CYYtH;f{maGFBZQ)fh?M#JGq?{@Jy6e+s;haEj
z1S-w?+=zyAWMsHO)x#X?0jd?8NKX8j?S<W5{BqYe4@C@M_?0N;M!Gq3d5IE^#PS{J
z2%QSaRPcO&Of&xkSlL0EjYOK8M_syXJ3|}DN!}YjJZ>gu-gdGyZlqbB6auFVo|l&I
z#0D~W1BtIq(MHEZKar!ZlXN4kemtVHvoCJO00ubLflBj6BR7h@ZBrDdr|u%{0Mik0
z9s+AzNb!qY7wxzO^PH-7s3?o#S0%D)N{7+KwumpE%(g4#pplF+Ir9{YeZLJF>5_hq
zHOt^gF@3|)J_2%7wS_tn9EY%qRQaW}<d53ihtc9&&bcLbV`dH}kIc%LP8Kh^7cwC2
z6(8hTEqY`>@Cp}-2gM_Z(dPy}i{hygsS*i%FU;=MJBH?39d!A&A5t8G+yO{ORGBPe
zq-9A!LV?6PMDFep{|yLxHi5_uEEUU#<qL#nyE=G!E0+~J{Qo-idHC?80R5j$4g)12
zECQrAN_uYvf}5D=|53)C-%UaU?#1k&)rp{vqbr*>P4JidGZ~xD0m-`fZ2<moCvkf&
zqv<r7tKx^8n96Z9mzGwpiU<abY7C0mkf!OWxcy{82(r6Jho<_FZJz|WM?~&l(8ws?
zW<EJL$i7zA-jtbwYj`ry+}_n8)fSmxZQka(6atq1iXG}*5`}7%wAQ7o=a{357xP=E
z@pB$2*>%@9V<h!tNH+F`f6ImBl(&3hI38`<)Yfsp^B&kCf4lXtWXhoAn#<eOI{eC8
zbDCNC`O{{;W=5mTCx=LH60D2!NFA=Xn;z>xE{I1{h;9YPiWb%FQ~JivqQREe#4#8B
z6Zkf~rREHV5x(RgH6ov)NP{Lh3rlt0$x3lSy+m1{mJsCH+YT)UTHVQ!Q1AydNfFvL
zqpl4H2V8sGaK^J?lbuv+Alux0+~>zguop)=Z!~^nl^&O!sn0!AE)w4Nx^&!8S0_#!
zngS3sv|JQVpd&E6?F6TGGXdGifh0}4x)56fbS3&Q`ogW4g;r+h%_%)Q2Q)*4T@4&H
zrVK}XBW5b=)6n6||4{144z~~GUx-L0?8!!DXz<-VEkCbnX@2m!n(Avxk5?fr=}fbM
zC|&}~iikuiB`38puWM)EvY%MS`{*>vFS;8#Ilhx6wd*#f&!5lww7N(h1M}y|R*aQw
z-T2Ld7l0QRnxpP3?+F@prl&A89GGbLU#)KTUgBr$9_#^nz}88bADPzF;c&)-vK?PT
zeBJf+3@{XE+~5xsPwWPy;hCXL1;GvP@pnwAc;9?0^|G~af|gddL*U<LIVN08dC~5s
z<)!s|I{z5o4~5>{Q)#F5(Tsd5=)8I4o3f6E<<N3mT}kvNavby$8+%@@X?}Cx=K^oU
z+~wb)*$ie5`z`*1(R~!A!gC{BT<X1w--A6ed?oi{o;-7VYc=r(ugSbH5uA7{1nJnB
zo-g0<>P=*+)uj21dpD^&dnZ`6LRIV7<`o4c+)^(S!59bZrtnb?L8r}CS&JIVD`X7T
z#N<<rY0I696E_|0<58qbN^SPzAA7p|F7q0vd(RfPdc5vXm07UYU1XdLSV!Y2xQXTS
zn%%{J)keQ6=FwhE(m{ly3A$KwgSOURXx8~YBi{U)m=T(LdBN<s+cEU0Ch?-)N!{dk
z+Z7hQQPrNO&|K!Ez;twl3~C6;On*fDCW?^XZd%fqPF_5%()i>6#;xSct%jJ+%DPE?
zxEF+xibhkq#$wS`^p-+Kf_2z_QZB_&H)?Jt&DSvff}5>zv*lIe5BG`Kl6w$hJn!Xp
zJ3h}K3O(3kDjirbTR2{jKjHG!bh69p!Oqt4x|k%&MnK7mRNt!1?vhr(*L66dh9=Yo
z<pxxW8t;F_8sG9)5XYq2J!t{#6BSBvVJM74SCxng7b<Nm2}8(Cooo6&Z984Q{|rx(
z4#IH?qjDp8cpx>{%$bfXoy@u#*xBCZEF(?p`__hSi0Mt%@Zlk(GrF@w0Xy0O4|S)g
z;$;RgU2!kC8Ri+?Rs*RYf5^G_J_?z9LgcQbO13ZEn|QGl=a&<!71SPx%b=dq4_K#_
zLZ0N`FB|qkxp4!N!b`_Jc!AXli{6B~n>X<K=k5H<!kA}A&>6+%Z-<lG+V8!eSFUSK
zE*xsE2#A}EE7O1c7v-HA%C<Df(b3+ebtmb-l&C)aBhW}t&B7Z4+2fPfMh>CU2Fu)1
z{&F`wp%JzxaPJa)FK}`jmQR8llNsCx^z9>c*!PVjF5v0Fn23ulpNXa*u+UTJBWUqk
zPn$=WN2*19Ciovb<*q?j8u4~o=K`VxF-P-KJBY})g^V|R(k(=6gNWA0e6mcqLjQGL
z^-&kUQLznBxK%M9D_<G|5W23bRD(9EPJV<Ku)>2^h4nYz{W>q6QkFcaV4^v%TlVS#
z*<D&8_e;lz7FV`*UpG`{+!ZI*3yq{w6_GPVvJSHshcEh56Xrg6ipXzgvR|8^GfK{G
z<Jf*o*0<2Tm<j&BrzkkP2gs-eJFT$E30-oWq4_1ryLa*}({rS(TxNBQb3$}|lg<Ak
zb(qy=P<k<X?oED;3~>ivM?k9}#wp-+IOB?a$ll}2EPLxhVRO?wjm39eVw*#e8PlxA
z;@)xzhDLTDvx9oyncC-QuU6hDPb-M+wah(Pp6%IN-j1@F%$g`RAJ8ApbY)BGg(2-g
zK<pM*_D5^{vCG_b=av&qq|L0p48*xbU0FSHOS6quFmWb<C{%h@RxM0l=6L3_wwt|r
zrm44J^nrG-txIYTaNI?TF-ZC{y@mCjB@6)kk$Z>pAk$E4+vycgG0=`Wi*{X70Pz@1
zQS^P<+;hZx*VzOXYh)q&0kS1|U`$n-BJN<%d{B5!x$Nvz*V{1UThogZ{y@JdMwJK@
zqG$wC^_7kN34b)Q_(s}sbg18Sr#VTVH`n8-KtcXMfmvFxF$kkdPI<M(GykD`${`ew
zDv<kV-<d9V!#mE0LL4&_FU=-h3TkY6__Ox-Am77w3z@fn?Vc(1eK09h>|=<o^z@7z
z@wG=AeI1VG+o?gmeh6$vU|fudf5cQVlzqJ7S>M0!#Y}k-<h1fc3FN0gbSUCqlD<!$
z?6=fwOy5P)FTZVqzRqwjYUHK#*jI^kUZ6GbLR$7<jbENk=ku(kl`Y@vpD5%pUvqD0
z)2>)8)(ERaDJN2VJI0NM8&lILW>1wY4S#GBj2OK;`HF~TeU(cpQihlov@Rvfa4J2b
z*V$+8Hl{E^KizU>uEo;IE&{2eE6pnOuG1;@ya#YzYcI(Hzu<Kv_93=pH|a3lu<Nau
z`1@IRrRhH=QO_A+sI;pMA}Q4r@jJvjU<(=Gd;kz0qvYHq9uk$i9Cf8i?d{iN`3myy
z7+-Bj?fpS`Rc-@T+Ce%f`RvW`%Pk;A<*BS?$|v0FZkiWYDDgy$WgbaW;=E+j5Yyi+
za|lOjkMwS(k6W&N*1OlkfDo@NLlRQ~Io-9=#8S<_(w!qN*s|U#RsN90@)5kBm=4ZA
zZ(mRXf61)8=uV3A!DsbhW_4o!e3_8xsols6=H(?mxFlCu^uyfl0{&+9i|p<8CZ8*R
zq!8}}n>Cjv&+1IRJ|>Dmpze-%&MQ6-+2ejW?REWQWR|l(FPVVl+jKtBk9w=5uFm`x
z(2XGG1qhvcu3Ua6|8mfZk+67a>Ty#PrP(s_%59HB)8oyq^NfV##`)6#aw;od?p2=9
zF3?l};fyI?GpTmNBE3ey$x?Z@^;A|%)Lw%rvHSa!1Nt#F@gG;{sb~_MNVZT&bs?My
zo?nPSX4O#WYxSY`sax<&@c+fIc|30#0Rs0&jcj0UQf=7J%Si)IBD}bt6Tki33?uOb
zf6#Ms_ZPh%?-IA1>6B8D^QmtLftfg5<$xLb`i~JS+#4}HyZecMt?^bM>?v?haI`-j
zb0~ROc)Nn?;%&nRD<VG*SU7@4?pZ93V>B%*lqOAgnRrYk{!L{Mv;8%4@BhrwD{xOW
z(bSKb>!g1ADH}!co03$K^C&rM6!_zyG^7QUc42+dz$0=eq4g#sVDcQTcO|T>@eg6y
zNw}G4CcSao&TvmURsYyF`-A~V)podw+l_u2ippt5E~RbJn|XS-vz%vy72xZ)znt~r
zi@246$Qj!w!t;d!!{m3kef_B)eIAuhEA%wK&knB9ey5kDu9|=O82U;j02b*RwWUes
zZ0TbC{98|yrc51O367KG(g4qLu25=|*`koyJ*V-$s+hUtbssQ=`O_wv>#AdVb7e>o
z{*N9rV2YA^8a=AP=RK;Eb;!K_&9(<RGo?3krZU9S%4p3dPanF8m?3a$cYwp-?UF^m
zv1WJOxOd8O@~<rLM*u5=nB8H>mY{bs!QO8-@ukrIM(Z)%@izxKfeC9&wWL0mrd`T%
zn?IBR_JjwG9VA6D%yUzo8*|r6F5v3G`<UDJYEE(CNd2iP3rads7DD3GUE461;<;h&
zA^L0`be4q{90=h-PPZjh(gy9fV!llB>$LEG(P<=mu0suJ`d#|EF2-k@>DW!XB9O)i
z%T$7JbeEDe?0!(8bGs?s=#<(_Qi>@(W4p>wQ#zwa*|wqSJ4j>|1SNK~d9FvLP1Qxm
z7BcjC3%6jFUutFx6CAn(G2J7o($5_16`mjN@wsCCCs9a3R?bJ_^o@ka;x*_VIZ}IH
zb|ehUFl5{bnaRAx{&VtGI-@G7A(~E;b*6_}J+@SSw%n7|T5LW&@1^ZJ82Z>;uepPE
zGDnpR(=cerS6VE6sze;@b^4!Gd%4Ns29G-(pZ4H6)g*RL4mW(fW=$0b+_bbRnjW$O
zQWAZOm&ctma{?q17qh$?vz+ElyPKx)em&zx_m!M-LLGwLmYqQIc80zN0xQfi|BT4n
zB7>4UBz5eTHZN1&!9LmFFW&vB$+(wbvBwJlXm58Kq4R)29RX*tW5f~U4tute|DzLw
zMzV16ca5~jy_g#{s^CNdGKq@HJ>B9mq;nt{sYF!+!na#nC4MTDl1;nS8&y(+7T=7C
zh+j<u+or(b&$m#e>M?TTx&)X^lk=VE#e9l~fGWK2oE~+AS;lpLN;Hff&=^5L+(<@;
zZqk3MWfw;Ab7>8lyS+SD>z5121Bb~f5d|**g2YG6gVGRS)yoE#y}s3P;^D*u2Uv0w
zs~1ns?+^I%KOg>4a1$N^f)aPyKt0WEg(}X?U!!%bVtE7Q!uWm&;@}hjbjcDf$hW{(
z0>M|dNd;m-HalEPj4nMioD7l_Abs22bEPLqDsJAo5`wA=I^CY3;C%73k*iyr8>=U-
zeW&k-Z2+2hh%_5=SUo_0MXvoG>{(V}vb21w^!SC7?5_)~MYadgn?|)i!C>TRTkM}N
zRvv0*Tk8}ojJVsIdrJD3pL=n_|J0bfQt+v%W-~2m9hn{&51_SuSZ1adHb0w53AV;t
zCChKsBl;}+EAl#bhL>XNIvn>!H6PcLW;|*4b@Hx8H~A{W6QYv2Akc>(ZS2=ynpg;6
z$*v>rAC_LY6o1yOzWx))Xx^pH5s^B>DL6bleREzsM@Af}Si+SN-8{zYMnK}(C)1|I
z9g2*UhMo_^JtD*ns%iucTpfQQBSJFfJjiN7h#!uco)E-5n{N6({%&1^#G18SbV9K+
z{rYx6H{KTyw9ATap=0_`)kJWv$MS8It*d|Ls(RzIm%Ud(cF(5Dgq^T1XECmtoGJE*
z>CuvN{l|&nUU}ZR^@d5qvcN_@?|3e`9nq6}l+|?L_T~J`cNo~f9X88<tIniLmJkKt
zqK`KpKNkc}d(S-@J^6+=aDmdzed23hZP!=(;R5O)0-i2YamdT=aSF=Z)1X%+;v=2&
z^ETuvmR};}tyj;;(ej6GZ?eBudTu)c=@G=hatG1P{@a*{*9T{x&JxY)S^;dJpv^K!
z&pEgzrpDt$5zkh43KQP3mJ5`TK&b@e0!Pq4^regZ?TcE4{Sy#7*RAIomWGM|bQ&Ky
zxB1Mo=BDosm-GGa6yFJ)t5_OXCeAEY#h)o0DKpu7`%7dZJlae7y~dt%7Ry&j@P1}4
ze?(hnods`$-f)R>8ZB?MWkE{Z4N})Va<$rIO;U-<Et#se;6M+cx``jQV_^?dG7Zjh
zrJB9aD8UZRl_v5KGp)}_>3O%g#tay8C<K0w;t{o+S7HxK;|TkkWsZ-HJv@On+TIoG
zr|Y40dX^5Tg+&jLt^)hhdy0R!9b-&J*OqRPrxmGc+8Ng}L;a+F8O}B4+E_eYz$qM@
zH<m+?>H#7cms}vG#J{3<<}N+OJ8Z^Sk#}##xBz4MW&^~eb&Mhp2gFq5WJ$Bzgwt-L
zQFg040!w5^Fbw0;8H3te(&zK)GEkW8=cd<T{DnmiSPH9H7N-*|Blq9daPnB1{CS2G
znJQqBiD^$4cqeV0?biTOYR>D`kuA=u4Ip$+B%|13o_@tA{anHY0Cykmp?F^W*hdN+
zqAsm5*9`_r{hmvl;W+J|(tGDlSeu?OH1*{jrn2ZXmDHsr3xY#(#f<ZE5GbR8>F(G%
ziv4K@syio|E%fxhb-et7qP6sR^V3OTi{(bW6$^67MO`g>zZ^G1$#2+NAGb0D&oY7-
zNe6A4qy(0pE$%B(Eq4EkL_&}Y0^Mw%i1RZ6`bfZRb>%%q|JB={pH&VMhU(`xC<2XP
z>q^gjyXKQaFcjSaSOjIaN`sCia4=9LY5Ol<C|jWt<%}-&(RP_Nj!aQ^ot?9%E{xs0
zkwWh18Y)Zv)a*51dR&6DeoYNsW}*4hA4t=r+rOx2kiay3tL&kjHFvwjVkPW7^1LlX
zKlV{GZHYgs85U!)VZj^aOV;A*Ftp^M%qdlc+{TKfl-ua*-j?Y4W<%%d+E9^0=3i!)
zKzM+hIw(s&$L~uGL24;k-(I4GB5fFIz{hF`H(Li-9N*w9IQ%s8tds=Cpj-{b!{O9;
zQB(?030bUZr$3u@zY|70`<nl9V~!phl+f+zYFmz-W$S97<mRR&SnrA4zzzu=WHeG7
zBPWFMc@WyF1rlxOl&-l~mK{{q8IB8^?@XK86Y+^$Y>m-s>?xC)z#m|bY{uLG*~9Ex
zxxfAb#Rm|jMmPVd-DtQv5zSn_A@!CrX1@jV<!h@D!;diC^R4c2=d=K4@)pc_&7QAA
zsh8ic>h<b!Lx4n02ib6XdagM;JK)nsnWJ@9D7kmKF|Sls94Z9K<XsO-y&=d=s=fD6
zLylrOLA&Nk-ArF$sIqD=ERs42$d>LFCNr3&GAwNxiY%$(E&RFie!_%_>uX<;Gm+1Y
za~6^%Fp4J~v6l~eKCUW#(jDh+8xBfX%RL-Cf)-SBYPxt<UuV$+Ys^s6%B+bgiOqRr
ziFNCn9;e+P>l^tKKFyxQ+vVbGRv`@7(?I6&i_5)W!c5+n8S*yJ+N<Qujk)~u%|tw`
z4*@UlzBU_hGT>e-QJLZ>mQm3<Ru|LCaAbs8$15uv{L=8S82|Zxm~*@4=CNw0(1lmt
zfKp=8FplxEMA>$5@(h*_I!jf|3^C6at+7pEey%8Ys6Rhz9jVlZP)udlx}F5y?ecnD
zmA}4g26#|3BX~%fE`YKnkq?yX_7%-FU(}NJDYb{lAYbD2G1o5?87mFZB?K9?SKKYf
zEyOQ5f7-)i&Y(G`d4sU5%sB$_(IUf|_Vq$wXanVO$^+o}{a3pj#bWoPs#jc0#5&&X
zR;$0~<6}+K2Y#2BtQ6~VWAx|`#>o4dDCcTW&Eu6QET^?w>@3DbsEg(MT_{u3Td`?l
zXK`e4=~Ytmv=GL)23?Hn3*+e6$+y`&I1OAr@V^+dX_&EPyk3IHGtdJsUIapyzm{N)
zXV1rMai$yjn*xgmOaqf8;)e;0HL>^xy>-^{XEk$2Bg{z?>UOY53{`Ld4A99-C_LbI
z>#(%&gZM}98q+-+q7(L>yy)*wK*TUvhQE%Rz!cVj)$Esv98$fmqT~O*u6u}DK0KZh
z{C2pBFe2=15E0+HD5_{mhZ+lF;Fja{h6SL8q{J}>3D2^<&Vn-ZG))i$DM<&S-f%K_
zg#)0dv~M~DKk8oVmcJqJ@=E+RcnmP35OE}vZ3(es6F_5nGoTq_X(d2(SRCb*7#8@~
zVla?G!~LDw6*&EmU4vf{Uw`!4x?+yM$pW{G+C`d;P%w-d9=Azrxt?B@Z6@*#+LLvH
z5C@QmdcPzKOew84+RsWljk37m$Xh+roP`Jw`M%kFT&i4B!8dSB$w3g)lwDD@NPm*s
zvS%1n!;9%d<H^?k%MQBjm)^qA(GT87u>oh~rOFv+D<Z!4maXw%T}1l^b_6PoOv4ne
zIj#q9X|Q4O2Utyi-i#II&F|D3m+lUDf-h>M8NoV4%p=R-8`(eMNejW7kAOpHQOw!G
zgh4Bfyl<cYOWXxk2tfu0UqDbyp%XCSDHz5%mji#;Fhd!JT|~D->m5gb%0yjnJ^(HR
zVCfIK1{gp6Ugr(#4f{$q#m_3)03)F)GZ2-Uq<hZoxFJA{=cXEU4=h$vuE3382b;eg
zP3s+rL@fJbFDh-Jc67UZRY^kkACe&WajO$~a&;VL7A&<HHy%6Ab}hs++xIi2?ZT0=
z>BTc!wmZ=w@B=0er%Zn8sHG<p$^)k62AOlrOQ%wXcj?mnNhI-5<lDc7uLWcYV%&xw
zvjkMCMe48*@$$g<QNp13{~`ieES-G(VQ;fF3qRF0<d^eA?6Zti`=yRYDy%OfF=053
zw!&q2hyb??SUwxAvo2{zYiT#LL%@Aw%Oq_ACATf-`HzzTeK;2h;NI*MNSCt?D>>6M
z9&l=-PtujxrBHy-puv_FilkY7{?eND*EL)mofI|sOkjGsDsr3epr9I+LM9@MsS8cS
z*1ax|4dhUF)2m{%((wm#$8YvJXjnL823^hR!%Z!0ZGmbL1oxWY0G>YB-j@^|wl6B3
zd+uGM{5JcG&f&6uD{M)-Xw7w%9lRIw?a1GAu2orU&P*;%%_YGc5K!Y_sOl>XV%v<^
za8lIXZQ(ZtEcV>!VV@SlM8;;NL@#tTC;B&<A=DRr&CR_A1NO=98NAeSMo8X(9Mj8S
z&o$p1W=2bOC<2oSjNVb<d6h!E!!S$+rzzL5X?<*+S$|R3L5*qGyTsN3dJE$VmV;D4
z^A(=@=)B(VMc|b1c-PD!4YqXY-LzeQz7m6gmcd$ZIAjfFR#B-KIGGMIZRHE-(7v$O
zz(6ymk<i5X`fWs43^dI$6^a(9luNRKyE_28jB;p*8R;5~Y>d*(Xx^@pX6F~x+wC^D
zey|1i9Ng&E0Ndv*U4f;1YHg0k4@-NX<dpXrHnrWlfis>?XXuk<i+!BUr}Gww$f$JW
z<A&SNzJalccANMN<}k>tLdJy8^QAwpFKVo)08xdYm_a}937z?r>@l=gvlpB!fK-Uw
zx!ncI3(YX!ACV4DdgBL)2Un;eTmXy=&!f3k7Z9adk#v8BNAM6qY1mI`$Y+yRBPM(V
z`wdXG-^KRb5rQ59Zp|o;YS!#<2KgQEZ6EvK!Gdl4Z|8j_O9?sC`xjXl>#iA^?$h&A
z3sfEb(BJkYi=xtItI9#`RmZq!j`?IxCmi}bk^4Yx0bPLpaS4Y;dzWH&(xwM5U5<Ju
zBtzR1nh?D-;+s?Ya+mznn|Cty`iC`{4E7ScORs#<Z}hbsI0XR@2^6zg&T~L#Fw}*P
z$(S3Znv_|-U&thwhk}x6vhX{Z<gL(WQMUQQo67vzlZ|dcb89G%Y2b26X-9Eo%2mb4
zYv}8&zS|K<IUK2XB@TkS#&0&Q<MhBW8ab3O#65mLZW82ZK4&Gjch8Q^6IxJmK6~rJ
zK(U28mJ_tVopf~dtLUWLyc;d_-aD9A#n{PLr#RC`J<MPr<`AlusMr*6cI+7!%C>3F
zZEnH21l;I6%DiZtG~9-Zi0R~#cYE73f;!_v1{dmZMqRL}DBFM3xg`4edHGXmRPvEE
zTGcTFC+F~|N;ice8$>V{^%~`8Xd3dPN`02#S_l}C(W&xk^?iVo)aU0XC#|8QgNve(
zt3O7jj~{DjD6jSWT;YVWy?+4Q+kAA?ZND47oy4e0{y2XUnuTUf%%@t*F|{E>X=~Pb
z)`<IC^CtCc{YVrz)Z;IkXX_@HY%=I#w)^Y_1U5*oHE7D;l0EWidmKzgp;^g^V6t^1
zm%e7^c(eu+g)F6S>RIRSqv^vU<K4t>ZG83dlX1|D__u)?SgL}MzME+-aJ!hFk8Mgj
zD<4rFREbPQ0JGqL-&a%7o2F%F%NupI#6^G>7p;v{_mE<;$5-$RvbKY?*5nyn0NiI&
z{9rtVgv5{^#JhR4Vzj1=!hdW_m<4SW;Yghtw5#ne;3LkwKw+%(lHJEm23y(fZ_eVU
zQh8Bnm(fO9!NueLO`e=e67=vYkpxB6G99v!0JiSk8QKQDa$pLamIG}C&o}%#GN$Ji
zEl*Y&pj}&o=U`H>LPU_EwZ<c1j4`!7xtzUd-Klv&u~-Q%0<)acB>`*9`y9)wM2PnC
z{-WGg!5=!hQnLp`(DaFE63O>a;|cV2S2OganNlo9KHS77H^8eWyGWVmCyjHN&Rqz=
zkZ`h$G(V8=EQ+p^Q<hBpcD2Pwpi^K!Wy^T<3VjE~!*{9e(l4`%y&f5cbfUYy9!Vw7
zP~02jR_qzvt>u*pV)}Q2YXPQ7__X(eqpnOC@mng{v!IK3lCu^}WGH`tYhxc<W25)i
zT<<6=1DUIj<~^8nefn(fW4$N5%&wk4iVoU9MrUldm($#B-1ry-6BQxMMg0CM5qa2Y
z@XfXaoVy`{2!Ptx>~(FBEk|p{i_L)!5#|<vjq*AXlJ2#Of7_P83M*{ksaVUqs1ne;
zg|@~&h3YQ;cKNzFX|OkJeZk(9*ah>WpN-5~=ZA^2+A5?Kw*vuYGpESm0(ouo+vjBd
z28!-q@e~`Dx3QNbD&~$9GJ7R$wX#^jR%lB7SM$E7rVqYc*SQ`Acwf;yiifz57!$Aw
zWqU6=Nl(6){oaA<#!Q`zi;Dw#<g}vj>+K9(*1*>B%j@K99`aILTmOB^JOj`JVRl7&
z@C4N=uTw5V*FV>z_441wisW8Ic=;i*#^KRPPrP@&oY2b6`e$(%r5_xtYgxcbTUkN*
zLDN5={UTM@03DBfQKZRZPV#~{8-gqV-tOXiT7!wNin(Cw6Y*5$`9B-&hrvV;!)M~7
z-2&tSSm!Jj2HUE7h0eitnb%*8ns0(yPSTHE%D_N4>|vR55Mxb5_nP|X$Jk$bfMtCb
z!X&Aw3b_n^g_vQdej23*)%Qy13`d@XZc#t>B#pdbfLt#8y|DS=3>~Kf%?xz;KueDJ
zd$t~``2%f{H7|ZH0C8Wplb{Ao?2?_a^T{cf1VznJ@k7g>wo}vReJ?;T1&w+Yh-<eb
zQ*U;h+?fc5H+ylm;<N_2V9*!bAMS%o%a}JNZdCR^_<44m)-0vtH&^Q3M{c1OhasaV
z0$EVmoEiN;Tunq1XGH|spDg}VyfYZEJjQk}-$(O3o_#S78VhJ6s<1;1&8g)@_l_xd
z_-gtlW5ZCs+T6Jl^TgJW&D;4^XZ&TXoe>v-Q~_I3l9<S{;VCcFfUgiHLD9g(rz*w+
z2Xitf`VS!tApbDBHjE3u+%&#L#@swI6?<Z=E;=D+zqe^d=4H3Oww}l9vt%9ufrCOx
zb<FuTKn3WIgD292d!d;GcdT(HXyi#!($n<;ltdqw*BJu$2?mi;IMQGMwU@~4B)#@9
zI%p%VuEk8eIB3VVV?I{Jt>Zdxi6pS7wgzcXS1x^iKH=Q0Q0*<B9CfX-z1F}!6uI%2
zq2j>sP-ZvQz&4%`Bnz~_ePIH$S>4|ObOUV+^F4HL<!puxi;J5C-7S2Il9(GZ`d1sG
zAN?>g?sIQOoq3K*8%A{i>ZE|<{Fr%t8Fab?h2o5BqrJ~syB}OOo+K)~ozCcJ^E-=c
zXxCM}0Pyl-6UBZ%04&gN*S(t*;Ynsn>YPGV8xf+V{!wrUat%oiAPy)&uB^(18bYDc
z>|Tk0HvyckQUo2v#K$3ygOsa{VE;fO*a4b9w5>{LeXCt#99eRZ1NK=0!XZTRqN?jX
zd4A0RvBS%oV-wKXAvSJ+=m~|;fX{8()K?9RKLlyDVqN`pB5pYVf?&w`w`sip;#3fG
z!0xww1u-@yM48+WWX%L>Y9nBW)3ye#m#j=xPxcK6fB5_jHN<q*(>wZs-2DAq^3EX|
zc`5huQS>j%LT*}XjvpS2cU-C5Rk9oJt5M{tMD}xRQ#Ax>C@p;%)vDZZ$^Y_=MQirb
z7~Ot)VJu7xA_BlDz^F^E)I07$1sln`;paq*j)y&JPIuxB3$Q|Sc}5~{lee|8JLh~U
zYS1rEq2$ye6RP9dJLIxJMXLDFi>}9quzaUW9H%D7jTW|}*>OQxT!5)UP;Wn8+mUrZ
zhkOXR0ThdKXSN{mCLt67pQTCZ!aEVtw|ZuMPqC@>gp~<(doeQB@GN<FIZG#5{$m!e
zmK17ZTe<ThdvUo_36MzyNp^2}+l|%2dj%SoI;+09jCqwZ%QV5ayR8M;<fc9eoyPPb
zM7{=&q@TPyhMDj4MBu=&6Qiy&eyWLQ<S8WT?ZuQ>S+HF0om*i%d-X#u19az^--t}X
z*1d>!Y@60%Mb!%d^{R(#J^NZZO_79<Jy!Q3Q`8{g+S35`DB)5P1f(FpkI{?Uvrk+Y
z6$Q!7{3fKV>1*GlYfqPnqg0kVHw-u*&i*h&2k9KE+b7>cqT&~$Y{#1S^S=t`cs9(*
z74yHcm}7<3n8d3DpLY?Xg~<U;j`}jtwxBuZnpuErCT!TKVLJWYr3+NBt<5(SMmydR
z&Gltn?33t;6pQFgb46v=d>cDILDIkUA;+$bJNol77P`h*gd}j0Fj06}S8M0AMq&xx
zq%DV_s<NEGApk9fu}V)~^orYS1Kdw7HDm*+e6e{0kRiA^$=Iv8?9X5@sP&=Sv!sss
z&Ci)n>&Ta-X5aE=U8+wu;yB9z|059oL<vZZzvQ;rHt9NfgqvJ_(gGS*da6Y#HWZA8
zLln#X0nYdbP77mXDpSqSqm1R1v1ZMc=}r+y^TN8AOYyB^1w<*7+ybaaB2d+{u)7bo
z$CSRt#S+hIetHGdM$&%R<XQ0%Th}AD+ul4wKl@s!AN$%f1@H1=0EQ76Aa69uK7}71
z{bLW<+T|Ep!{^SaDWxNCP3g58MT$+SqafvI_l4nTb&KwUB<+@$^rNdRt+u1W=nwuo
z;J@;FOi%Eioc+t0vpf*5Aew+&Ll|xSuYT1(MNpN$sQztz_$J)U{At-Uj(Q-Jf&_Or
zvFu+Rs=yfTtO$$K>sr&ay052th&}H#v<5KF;pi8@RG|yLvkKMZ=_F@nu%j3>H_+-a
zM|d^&lhRCx-V8`_#OH2Y1DsnKH2>u;qppZ-5k^zr-M5~AwoMemNLE;|uf4OHERzb~
zKytoZZtq{X`abak_c*oZR@pM6g^KjX8!ogEG)J5@^r}VLwqoA(u5&wCbmA1<!}6NM
zu*F)A9KZ$}2}rG_PIENMAC*R3yhUEz5{fLi5_z1{-HYNGw^k-evP9=6)bcZ^Uq~kU
zR^@nrMAZF<CKIUp4Dp+wK4Jn;h1H8sN_4GHHLEt{tzSvoY*;q~Z0jyu3QY(ZOgPMm
z*Egx@MC>BAuAA68Hqb{|M>#;2fk!5AgK^Bnt|_J}<_YycTmoV+X-B)&OMoik*t!H%
z;SkO7qhvV8z0?EqmcN2drbezMw7g79(Jm6uhnqD>yKhE15^<YhJmBFy(5VMbzs~PC
zoDUZ(r`;;S{$je4D`L($6ot8%95O5BTmcgEdqg*Co8QIA&{mwH&_5*IAwGH^0lM3b
zlXjf(m1!L}63a$!R;h|{#i<QNjGto;K1uj~+RZltZVPP%TnsFBDK>b4SPzWC^n!Jx
z@GtJ}%$dSc0zayH@y0JYz?JrLDAGi~YAw)Cl?Yh|xUk8v3j)Xb=%Wa%Y*L`$%D*1v
z^UDH8e|r=>C&2LphmSx}2@QWE){RSZ_U*S=3riL;Ys6lGqBG!)o1@F;yqJmpuUw#-
zD7<21kYb*7+tJ``O(WO-B%3^(^Sc+5EX|IMy$(`78DO~-P|pHFp9MF(O#VH37GKEa
ze?PVT^7u;82Q+Xoiu5J9KR{)nNcpbe(s^h>6lo4Qh=hh6P%U?$fg=K=#lfZgB9;#{
z8G*3nq6{jOb|J{{Y^{QwochKNQm?0s+reixCH3Wma{H-+o_be=FlcryYUsw(XrJV_
zdiN*6(TLMi-T}&&p~knKaKwf;fqn8y1v?8&ISb_)fsY#U+nrr8<+h*$FG2Wzr~KZG
zg(pm1S^AavXc-`|<T6(6#%)Snjud%Lna1k`Xwp`iYB|F0AS+>wC*^*c%Wo^zv%jUQ
zz8RndzHHL8>0ruu!M_MNenr+z!jwQftGxRNAsL(-!Anz<rcUTQ|JTqB5mhmyxE|@t
zntfWNh;8YhbPisao&+<n%w15Lpo_mH2p>+>)Vi=417K*hl(kx63$&A0$)HYkMuwjC
zeMpt<f1`c@1&Zr7i*#qaThg$KPlL!R)fNXxl9vsri`Tbh1TTFO-hDB`=<_$v)Fjg-
zK_E9I%64{Me!(|3o3bjH@6U)wlh^pKDz#fT6c%s-iARTvc!BHLEEedkzPN)VSmZ|y
z4f*$Hxjmtef!h}7b>HIx45-H+foQ=)IRKmuA+N%Lcd+2<5O5Iyv%t8(zrULi;GLtj
zLKdgy0KhmQ1WaAULB!#jTK=WU%fDICk{z*^Iu`cNE5MSfAe3?N5gHN-3J`SffXxvm
zcA&P$4^M7@s>A5YRVFyKDgd&-Tm1@QVZgp^B-R15>c6_~A7A=qynvn6q2mn@D>+p)
zNHPFjX7H*xa*TvJ&&#nTL5z!x%s*brp$O1dsmc$;QBlj!aZd=1|5L=eQ2tYdLh>q9
zJd@#ySo|TOlQd4>HAo{X@B9Z`5Hxue=VbYVdj0Mfyo5oVnde>6Dsfg+1vSvaUI)T9
z0&z7m3RTbiAvMgr4Zp`hxa>GM8v5>q`}XryFsLFB=340Os3k2JClTuv+RCk`k6XZ&
zd4!8Z^K&j-n)it34)|gquT`3j<ktE%&=CSNPphuqn%Qi92AT@OrcL;2@W4eokS-w*
z34+cdik|PdPpm8>wfVHC9^>-R22F_uTH${FOMk5FgbiDW2RDwlLC$~(q<B?{Ok+O7
zBN8IvO&GE-(-wSD!-J@3GZPgMF)h1u;u6Sk5s0OG)(L{=6x%qHGEe`xVcK$Kmjnwi
zsbol+!y<PmaVBQ&!+UNxrME`*#QHtYW#O*<f1ZvIJon1**Yx#z_j5UntJex+mJ~~J
z+=t~?sMFqY*uv=*0rJ|fDpkdc0z%<1mk=rtu)A;#7nE)G(almzm%fEDHo@Oa3aUi(
zBsx7fBy8am*sv*9=L!#pqr*E@e~QOZ_x)7kEW&mC+IJP~Q_dPr1Di2R3G4TAcB;Bh
zHdt4mImr=G5g$)bWiykQ`;EE@Ze1>CaOt}W&KES*LHP@cF7>WVQ`b0h?z=mV!o#8B
zsF%;V+=l7$Q*>wk710WcEG@0CfqaE1glrh*D!{|d)I$%SI3SR#1F{K3h0&U$cNl?|
zfl$#dcLGCfbctKVibTQC;ln6BLBR$A2Mh?#z$OH~1u=qhA)*Ja@vT}zUrVuu3`<tX
zN)q>L-zag2hu#L>dmiMC>)YY827H2~S>AJJEBxGIsgmgNVev|Q-b60_bttHsn}HLi
zrjNhWHP(`P_gKjii|3?Ro{5E?qDri^r!{CD0|e#pu6{%Iw~h4jw%7kT`nM}?cD9eo
zru_hyOTM>0<Tt&S$TMkpAE51c!SM$@(#q~U0a<W=V3?d(zZ-tEBvf*FaJD&eYCwl6
zC70aHST6l=yq>X_bg(#;QLp@Y*k;~~v6wl!;OrlGc_zt{W%+hVZ7T-L?o1gj9J<LS
zwz?J9CDK|Z3v6K{D_8fox|o!T&7o^E%JFlIcIs{0x@A<_?h~_-+gqlo<Eh=wc?;pl
zthmG{9>W>+U6CLPs$SRsocjQF*CS#}8)@T@6{1cEK9EmK#+P0ko}g#!#buK)zIPuG
zTc-nOC)9{|T@(N1iDoxl{G3F4!nDWiTl2O?|7PoQS_cg?ojGf7dil&Q(i_WR_hDAc
zQqBeWq+GvAQH+nGdGTMvHyHW+@a7mn03G5XpEXH?`oC_%9$Qt)lHQarrW|z92gMU|
z#zVJRS4VV4qP#u**`Ph4!4j3$xW!C^t*XJUF|{sB62%zDKJ6WPF;_m>R_F@rVinjm
zpNIQe+hk)SA`Zz>(+9_^V^qO8aJp@O>Fd;vQ2W{iDOQsEuzhXXPpok{WzEH93Cpr1
z3CzVMO62=*(-{<Xc<W*VuC6W;qJ$m@s#M2heh1Aw<<sRIM;1Oshi69L`}BBCe$utU
zWk0MjYf?;S`9X3SxSaR@=6aQ4FCh;L_aVrqWRXHfgG#P-eSNN_)SI|oQ~V*RiV68`
zPISin&H26S&qsoKY|nU|Nc}U|ko5g0Xbg%tBNVzx7HdpEfxc1Ysq4B~Wc^x(($!I&
zxhUqMJKJQMy037XDM&OYlisyri{I1h(#>LX(sdeX*(i)+Fh>Hhs3C`K<^xX>9;CDm
zAl2WZhCCM2aD|fo(kR;o&>H6qK7i@Vnw~6QEH=)*mZ8%!GMD;?IjLP~A<CPwcwN`@
za+zlYGHU1s33M~oz?MM7NrA)6!LA#0p9P9v($!AJ`!D6}FjTc0`;~3Y431Nn2aW-<
z*1x%U>DVw(1J7HsSO?n)z_I5fxN+BO44Rpso1^mrLO>J43df*4;<y#Ra*oP%0H-$a
z0DjW~dfR(osUNHe3K$@?=UxopoS;^JhrS4}=~kB}L<@}H2V?|8DRP$BH19fdg$3d^
zbZO3$mBX|J^j~%L?mO<&UvYdE@~@<0#p?x{FTlr_;=jFRJx0o6!>EF<Tb<nu$s+Wx
z$~eZALR(?*StKmANKOn4eylx!<%>PV*}T4<h_q=CiHN^rE6jN?jzU#$SPR&%-Z1`?
zh`sO{G8KX>>@}G&wa3#&nk`LA27RZurGjrg4U@|UcJ)*1t(I8o-kUK7i3mK)mrR*(
zmH)O^X#rbD#H&y+&=ULeKo5|;4%T>#sl$4i2rgwyjv9|$HGA(egK`Hwr!sp;;KPCW
zbI|oIbV>T(YC6nznVb9iv3f7OQRkCLGbiZhC}=Rn&rY-F=o)nU&TH=GXd_mq7Og^K
zVceZW9ayV8C()ZZif7R?o8uZRJ429B_<=`rnO93QbgnmEj&-N{PBsV3gD(u8isOvy
zhve1H8M3n+Ord@Qm2h=tpVh+z%%wpKe8!10GmLg70o08&^bVIUs&l^l)to#5%0wLs
z@?vQw=iy~sCphnq9}D-8rTNAwE=1Xu?Y<Ma=+yzbSJbBR-nTo2udeNgGoI`+0I9Su
zXn0PWr;Veg>H%I2iw2KuupNDKlg#^~c#%Z*yOKye>sxJDJ1LlgGY+>cp7X@rr}#Z*
z8_3aL%oTiE5sp`tqpo3GX#xDW_T?MN^5f_pHPT?;H{nvgXB}x{o0b#(VrpV~hi>0O
zk8byu8F!kR48j^GpH6(zRglhctWH$gt<UFymB-7GVm2B0Zox?|g(I~tUC6xwTF*~p
z5L*|(7h%DzUy?ZZR#+VaN@OjlW5zju){2*>R<C~i^(8X?th+USR+rK1JK&qp{dPGk
z4~3bRR!Qp;6U)LH5CHT%*qTpw)Xjeo-#R0TS~Y}uL)r4a=WW14Gc1FRd4nBw?2Kns
z(?*oeVvT!*g{<_<7Y`;Uoc!#|A0p<x$EaR-ohjYBv(3*qHmDa|492B&qK2~5857^H
z4R(M=CEq;|s1&zg-q#rXfwIjynRYu=5L@>Ib=M=7Eso<`VH$j3?5FK&Tjzta9TEnJ
z1}dzc<b7;x*ih{}$dG|!+>qR3w+V@@v%Y{+e&xfx12n^#E~`lv1JT6FQ$PX_{B%61
zk`@V$q`E6E2c;QE{02(8-Q?Q)b03vufy?WoK`sJ{k-SWGhKn*dT&_Caj1b6}!3P?z
zH>^&AWe(0A2xkfK+rK$~|KLX$(3dz>6tpy^zt?wx$lVDlw&1mo?i}9*_a%;aThK%D
zP3_NDvrl_lP}P2g4Hg02j{iLs%69P3i35a!4>UP=z`19HF1q?#C4dXq5w^mH3xJ?p
zu;#yztGZHmg?|*!O5Fd&fAp0q5yhtfWnm$Eg|paG|G?i0K>h;5e*-@*dFf7p`iLQL
zX80YW0RNyMumV2djwjeZ!B1Trd_TJihu*(H_Wu_n`veO9FIot^jICRs1_Vr;)(bes
zh4+)X%3888P{bdwOUw<huv65VD1$6a2x0nk1&RVHf(W()jWfKsU#gsfQUw2h2AD_w
z`Uw+G1WcyEKfevGJpO+UuFUywX8!j0zb^aR;Qx{W<o@4?c||t=&EWqk=zq=jH-G&f
evXuitY@S)MEoz4z@g+lqFgk9cpMBKk&i?_lpc%yg

literal 0
HcmV?d00001

diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/Group.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/Group.java
new file mode 100644
index 00000000..41e2b98d
--- /dev/null
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/Group.java
@@ -0,0 +1,263 @@
+/*
+ ************************************************************************
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ *
+ * (c) 2014.                            (c) 2014.
+ * National Research Council            Conseil national de recherches
+ * Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ * All rights reserved                  Tous droits reserves
+ *
+ * NRC disclaims any warranties         Le CNRC denie toute garantie
+ * expressed, implied, or statu-        enoncee, implicite ou legale,
+ * tory, of any kind with respect       de quelque nature que se soit,
+ * to the software, including           concernant le logiciel, y com-
+ * without limitation any war-          pris sans restriction toute
+ * ranty of merchantability or          garantie de valeur marchande
+ * fitness for a particular pur-        ou de pertinence pour un usage
+ * pose.  NRC shall not be liable       particulier.  Le CNRC ne
+ * in any event for any damages,        pourra en aucun cas etre tenu
+ * whether direct or indirect,          responsable de tout dommage,
+ * special or general, consequen-       direct ou indirect, particul-
+ * tial or incidental, arising          ier ou general, accessoire ou
+ * from the use of the software.        fortuit, resultant de l'utili-
+ *                                      sation du logiciel.
+ *
+ *
+ * @author adriand
+ * 
+ * @version $Revision: $
+ * 
+ * 
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ ************************************************************************
+ */
+
+package ca.nrc.cadc.auth.model;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+
+public class Group
+{
+    private String groupID;
+
+    private User<? extends Principal> owner;
+
+    // group's properties
+    protected Set<GroupProperty> properties = new HashSet<GroupProperty>();
+
+    // group's user members
+    private Set<User<? extends Principal>> userMembers = 
+            new HashSet<User<? extends Principal>>();
+    // group's group members
+    private Set<Group> groupMembers = new HashSet<Group>();
+
+    public String description;
+    
+    // Access Control properties
+    /**
+     * group that can read details of this group
+     * Note: this class does not enforce any access control rules
+     */
+    public Group groupRead;
+    /**
+     * group that can read and write details of this group
+     * Note: this class does not enforce any access control rules
+     */
+    public Group groupWrite;
+    /**
+     * flag that show whether the details of this group are publicly readable
+     * Note: this class does not enforce any access control rules
+     */
+    public boolean publicRead = false;
+
+    /**
+     * Ctor.
+     * 
+     * @param groupID
+     *            Unique ID for the group
+     * @param owner
+     *            Owner/Creator of the group.
+     */
+    public Group(final String groupID,
+            final User<? extends Principal> owner)
+    {
+        if(groupID == null)
+        {
+            throw new IllegalArgumentException("Null groupID");
+        }
+        this.groupID = groupID;
+        if(owner == null)
+        {
+            throw new IllegalArgumentException("Null owner");
+        }
+        this.owner = owner;
+    }
+
+    /**
+     * Obtain this Group's unique id.
+     * 
+     * @return String group ID.
+     */
+    public String getID()
+    {
+        return groupID;
+    }
+
+    /**
+     * Obtain this group's owner
+     * @return owner of the group
+     */
+    public User<? extends Principal> getOwner()
+    {
+        return owner;
+    }
+
+    /**
+     * 
+     * @return a set of properties associated with a group
+     */
+    public Set<GroupProperty> getProperties()
+    {
+        return properties;
+    }
+
+    /**
+     * 
+     * @return individual user members of this group
+     */
+    public Set<User<? extends Principal>> getUserMembers()
+    {
+        return userMembers;
+    }
+
+    /**
+     * 
+     * @return group members of this group
+     */
+    public Set<Group> getGroupMembers()
+    {
+        return groupMembers;
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        return 31  + groupID.hashCode();
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof Group))
+        {
+            return false;
+        }
+        Group other = (Group) obj;
+        if (description == null)
+        {
+            if (other.description != null)
+            {
+                return false;
+            }
+        }
+        else if (!description.equals(other.description))
+        {
+            return false;
+        }
+        if (groupRead == null)
+        {
+            if (other.groupRead != null)
+            {
+                return false;
+            }
+        }
+        else if (!groupRead.equals(other.groupRead))
+        {
+            return false;
+        }
+        if (groupWrite == null)
+        {
+            if (other.groupWrite != null)
+            {
+                return false;
+            }
+        }
+        else if (!groupWrite.equals(other.groupWrite))
+        {
+            return false;
+        }
+        if (groupID == null)
+        {
+            if (other.groupID != null)
+            {
+                return false;
+            }
+        }
+        else if (!groupID.equals(other.groupID))
+        {
+            return false;
+        }
+        if (groupMembers == null)
+        {
+            if (other.groupMembers != null)
+            {
+                return false;
+            }
+        }
+        else if (!groupMembers.equals(other.groupMembers))
+        {
+            return false;
+        }
+        if (!owner.equals(other.owner))
+        {
+            return false;
+        }
+        if (properties == null)
+        {
+            if (other.properties != null)
+            {
+                return false;
+            }
+        }
+        else if (!properties.equals(other.properties))
+        {
+            return false;
+        }
+        if (userMembers == null)
+        {
+            if (other.userMembers != null)
+            {
+                return false;
+            }
+        }
+        else if (!userMembers.equals(other.userMembers))
+        {
+            return false;
+        }
+        return (publicRead == other.publicRead);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + groupID + "]";
+    }
+
+}
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/GroupProperty.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/GroupProperty.java
new file mode 100644
index 00000000..af96c28a
--- /dev/null
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/GroupProperty.java
@@ -0,0 +1,199 @@
+/*
+************************************************************************
+*******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+**************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+*
+*  (c) 2009.                            (c) 2009.
+*  Government of Canada                 Gouvernement du Canada
+*  National Research Council            Conseil national de recherches
+*  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+*  All rights reserved                  Tous droits réservés
+*                                       
+*  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+*  expressed, implied, or               énoncée, implicite ou légale,
+*  statutory, of any kind with          de quelque nature que ce
+*  respect to the software,             soit, concernant le logiciel,
+*  including without limitation         y compris sans restriction
+*  any warranty of merchantability      toute garantie de valeur
+*  or fitness for a particular          marchande ou de pertinence
+*  purpose. NRC shall not be            pour un usage particulier.
+*  liable in any event for any          Le CNRC ne pourra en aucun cas
+*  damages, whether direct or           être tenu responsable de tout
+*  indirect, special or general,        dommage, direct ou indirect,
+*  consequential or incidental,         particulier ou général,
+*  arising from the use of the          accessoire ou fortuit, résultant
+*  software.  Neither the name          de l'utilisation du logiciel. Ni
+*  of the National Research             le nom du Conseil National de
+*  Council of Canada nor the            Recherches du Canada ni les noms
+*  names of its contributors may        de ses  participants ne peuvent
+*  be used to endorse or promote        être utilisés pour approuver ou
+*  products derived from this           promouvoir les produits dérivés
+*  software without specific prior      de ce logiciel sans autorisation
+*  written permission.                  préalable et particulière
+*                                       par écrit.
+*                                       
+*  This file is part of the             Ce fichier fait partie du projet
+*  OpenCADC project.                    OpenCADC.
+*                                       
+*  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+*  you can redistribute it and/or       vous pouvez le redistribuer ou le
+*  modify it under the terms of         modifier suivant les termes de
+*  the GNU Affero General Public        la “GNU Affero General Public
+*  License as published by the          License” telle que publiée
+*  Free Software Foundation,            par la Free Software Foundation
+*  either version 3 of the              : soit la version 3 de cette
+*  License, or (at your option)         licence, soit (à votre gré)
+*  any later version.                   toute version ultérieure.
+*                                       
+*  OpenCADC is distributed in the       OpenCADC est distribué
+*  hope that it will be useful,         dans l’espoir qu’il vous
+*  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+*  without even the implied             GARANTIE : sans même la garantie
+*  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+*  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+*  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+*  General Public License for           Générale Publique GNU Affero
+*  more details.                        pour plus de détails.
+*                                       
+*  You should have received             Vous devriez avoir reçu une
+*  a copy of the GNU Affero             copie de la Licence Générale
+*  General Public License along         Publique GNU Affero avec
+*  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+*  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+*                                       <http://www.gnu.org/licenses/>.
+*
+*
+************************************************************************
+*/
+
+package ca.nrc.cadc.auth.model;
+
+/**
+ * A property representing metadata for a group.
+ *
+ */
+public class GroupProperty
+{   
+    // The property identifier
+    private String key;
+    
+    // The value of the property
+    private Object value;
+    
+    // true if the property cannot be modified.
+    private boolean readOnly;
+    
+
+    /**
+     * GroupProperty constructor.
+     * 
+     * @param key The property key. Cannot be null.
+     * @param value The property value.
+     */
+    public GroupProperty(String key, Object value, boolean readOnly)
+    {
+        if(key == null)
+        {
+            throw new IllegalArgumentException("Null key");
+        }
+        this.key = key;
+        this.value = value;
+        this.readOnly = readOnly;
+    }
+
+    /**
+     * @return property key
+     */
+    public String getKey()
+    {
+        return key;
+    }
+
+    /**
+     * @return value
+     */
+    public Object getValue()
+    {
+        return value;
+    }
+
+    /**
+     * @return read only
+     */
+    public boolean isReadOnly()
+    {
+        return readOnly;
+    }
+    
+
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((key == null) ? 0 : key.hashCode());
+        result = prime * result + (readOnly ? 1231 : 1237);
+        result = prime * result
+                + ((value == null) ? 0 : value.hashCode());
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof GroupProperty))
+        {
+            return false;
+        }
+        GroupProperty other = (GroupProperty) obj;
+        if (key == null)
+        {
+            if (other.key != null)
+            {
+                return false;
+            }
+        }
+        else if (!key.equals(other.key))
+        {
+            return false;
+        }
+        if (readOnly != other.readOnly)
+        {
+            return false;
+        }
+        if (value == null)
+        {
+            if (other.value != null)
+            {
+                return false;
+            }
+        }
+        else if (!value.equals(other.value))
+        {
+            return false;
+        }
+        return true;
+    }
+    
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + key + ": " + value + "]";
+    }
+
+}
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/PosixDetails.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/PosixDetails.java
new file mode 100644
index 00000000..dd8ea444
--- /dev/null
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/PosixDetails.java
@@ -0,0 +1,167 @@
+/*
+ ************************************************************************
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ *
+ * (c) 2014.                            (c) 2014.
+ * National Research Council            Conseil national de recherches
+ * Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ * All rights reserved                  Tous droits reserves
+ *
+ * NRC disclaims any warranties         Le CNRC denie toute garantie
+ * expressed, implied, or statu-        enoncee, implicite ou legale,
+ * tory, of any kind with respect       de quelque nature que se soit,
+ * to the software, including           concernant le logiciel, y com-
+ * without limitation any war-          pris sans restriction toute
+ * ranty of merchantability or          garantie de valeur marchande
+ * fitness for a particular pur-        ou de pertinence pour un usage
+ * pose.  NRC shall not be liable       particulier.  Le CNRC ne
+ * in any event for any damages,        pourra en aucun cas etre tenu
+ * whether direct or indirect,          responsable de tout dommage,
+ * special or general, consequen-       direct ou indirect, particul-
+ * tial or incidental, arising          ier ou general, accessoire ou
+ * from the use of the software.        fortuit, resultant de l'utili-
+ *                                      sation du logiciel.
+ *
+ *
+ * @author adriand
+ * 
+ * @version $Revision: $
+ * 
+ * 
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ ************************************************************************
+ */
+
+package ca.nrc.cadc.auth.model;
+
+/**
+ * Represents the posix account details associated with a user account.
+ */
+public class PosixDetails
+{
+    private long uid;
+    private long gid;
+    private String homeDirectory;
+
+    /**
+     * user login shell
+     */
+    public String loginShell;
+
+    /**
+     * 
+     * @param uid
+     *            posix uid
+     * @param gid
+     *            posix gid
+     * @param homeDirectory
+     *            home directory
+     */
+    public PosixDetails(long uid, long gid, String homeDirectory)
+    {
+        this.uid = uid;
+        this.gid = gid;
+        if (homeDirectory == null)
+        {
+            throw new IllegalArgumentException(
+                    "null home directory in POSIX details");
+        }
+        this.homeDirectory = homeDirectory;
+    }
+
+    /**
+     * @return the uid
+     */
+    public long getUid()
+    {
+        return uid;
+    }
+
+    /**
+     * @return the gid
+     */
+    public long getGid()
+    {
+        return gid;
+    }
+
+    /**
+     * @return the homeDirectory
+     */
+    public String getHomeDirectory()
+    {
+        return homeDirectory;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (int) (gid ^ (gid >>> 32));
+        result = prime * result + homeDirectory.hashCode();
+        result = prime * result + (int) (uid ^ (uid >>> 32));
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof PosixDetails))
+        {
+            return false;
+        }
+        PosixDetails other = (PosixDetails) obj;
+        if (gid != other.gid)
+        {
+            return false;
+        }
+
+        if (!homeDirectory.equals(other.homeDirectory))
+        {
+            return false;
+        }
+        if (loginShell == null)
+        {
+            if (other.loginShell != null)
+            {
+                return false;
+            }
+        }
+        else if (!loginShell.equals(other.loginShell))
+        {
+            return false;
+        }
+        if (uid != other.uid)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + uid + ", " + gid + ", "
+                + homeDirectory + "]";
+    }
+
+}
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/User.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/User.java
new file mode 100644
index 00000000..9a120094
--- /dev/null
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/User.java
@@ -0,0 +1,146 @@
+/*
+ ************************************************************************
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ *
+ * (c) 2014.                            (c) 2014.
+ * National Research Council            Conseil national de recherches
+ * Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ * All rights reserved                  Tous droits reserves
+ *
+ * NRC disclaims any warranties         Le CNRC denie toute garantie
+ * expressed, implied, or statu-        enoncee, implicite ou legale,
+ * tory, of any kind with respect       de quelque nature que se soit,
+ * to the software, including           concernant le logiciel, y com-
+ * without limitation any war-          pris sans restriction toute
+ * ranty of merchantability or          garantie de valeur marchande
+ * fitness for a particular pur-        ou de pertinence pour un usage
+ * pose.  NRC shall not be liable       particulier.  Le CNRC ne
+ * in any event for any damages,        pourra en aucun cas etre tenu
+ * whether direct or indirect,          responsable de tout dommage,
+ * special or general, consequen-       direct ou indirect, particul-
+ * tial or incidental, arising          ier ou general, accessoire ou
+ * from the use of the software.        fortuit, resultant de l'utili-
+ *                                      sation du logiciel.
+ *
+ *
+ * @author adriand
+ * 
+ * @version $Revision: $
+ * 
+ * 
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ ************************************************************************
+ */
+
+
+
+package ca.nrc.cadc.auth.model;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+
+public class User<T extends Principal>
+{
+    private T userID;
+
+    private Set<Principal> principals = new HashSet<Principal>();
+    
+    public UserDetails userDetails;
+    public PosixDetails posixDetails;
+    
+    
+    public User(final T userID)
+    {
+        this.userID = userID;
+    }
+    
+    
+    public Set<Principal> getPrincipals()
+    {
+        return principals;
+    }
+    
+    public T getUserID()
+    {
+        return userID;
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((userID == null) ? 0 : userID.hashCode());
+        return result;
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        User<?> other = (User<?>) obj;
+        if (userID == null)
+        {
+            if (other.userID != null)
+            {
+                return false;
+            }
+        }
+        else if (!userID.equals(other.userID))
+        {
+            return false;
+        }
+        if (userDetails == null)
+        {
+            if (other.userDetails != null)
+            {
+                return false;
+            }
+        }
+        else if (!userDetails.equals(other.userDetails))
+        {
+            return false;
+        }
+        if (posixDetails == null)
+        {
+            if (other.posixDetails != null)
+            {
+                return false;
+            }
+        }
+        else if (!posixDetails.equals(other.posixDetails))
+        {
+            return false;
+        }
+        return this.getPrincipals().equals(other.getPrincipals());
+
+    }
+    
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + userID.getName() + "]";
+    }
+    
+}
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/UserDetails.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/UserDetails.java
new file mode 100644
index 00000000..2b1101e8
--- /dev/null
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/UserDetails.java
@@ -0,0 +1,287 @@
+/*
+ ************************************************************************
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ *
+ * (c) 2014.                            (c) 2014.
+ * National Research Council            Conseil national de recherches
+ * Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ * All rights reserved                  Tous droits reserves
+ *
+ * NRC disclaims any warranties         Le CNRC denie toute garantie
+ * expressed, implied, or statu-        enoncee, implicite ou legale,
+ * tory, of any kind with respect       de quelque nature que se soit,
+ * to the software, including           concernant le logiciel, y com-
+ * without limitation any war-          pris sans restriction toute
+ * ranty of merchantability or          garantie de valeur marchande
+ * fitness for a particular pur-        ou de pertinence pour un usage
+ * pose.  NRC shall not be liable       particulier.  Le CNRC ne
+ * in any event for any damages,        pourra en aucun cas etre tenu
+ * whether direct or indirect,          responsable de tout dommage,
+ * special or general, consequen-       direct ou indirect, particul-
+ * tial or incidental, arising          ier ou general, accessoire ou
+ * from the use of the software.        fortuit, resultant de l'utili-
+ *                                      sation du logiciel.
+ *
+ *
+ * @author adriand
+ * 
+ * @version $Revision: $
+ * 
+ * 
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ ************************************************************************
+ */
+
+package ca.nrc.cadc.auth.model;
+
+public class UserDetails
+{
+    private String firstName;
+    private String lastName;
+    private String email;
+    private String address;
+    private String institute;
+    private String city;
+    private String country;
+
+    public PersonalTitle title;
+    public String telephone;
+    public String fax;
+    public String province;
+    public String postalCode;
+
+    public UserDetails(String firstName, String lastName, String email,
+            String address, String institute, String city, String country)
+    {
+        this.firstName = firstName;
+        this.lastName = lastName;
+        this.email = email;
+        this.address = address;
+        this.institute = institute;
+        this.city = city;
+        this.country = country;
+    }
+
+    public String getFirstName()
+    {
+        return firstName;
+    }
+
+    public String getLastName()
+    {
+        return lastName;
+    }
+
+    public String getEmail()
+    {
+        return email;
+    }
+
+    public String getAddress()
+    {
+        return address;
+    }
+
+    public String getInstitute()
+    {
+        return institute;
+    }
+
+    public String getCity()
+    {
+        return city;
+    }
+
+    public String getCountry()
+    {
+        return country;
+    }
+
+    public String getFax()
+    {
+        return fax;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((address == null) ? 0 : address.hashCode());
+        result = prime * result + ((city == null) ? 0 : city.hashCode());
+        result = prime * result
+                + ((country == null) ? 0 : country.hashCode());
+        result = prime * result
+                + ((email == null) ? 0 : email.hashCode());
+        result = prime * result
+                + ((firstName == null) ? 0 : firstName.hashCode());
+        result = prime * result
+                + ((institute == null) ? 0 : institute.hashCode());
+        result = prime * result
+                + ((lastName == null) ? 0 : lastName.hashCode());
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof UserDetails))
+        {
+            return false;
+        }
+        UserDetails other = (UserDetails) obj;
+        if (address == null)
+        {
+            if (other.address != null)
+            {
+                return false;
+            }
+        }
+        else if (!address.equals(other.address))
+        {
+            return false;
+        }
+        if (city == null)
+        {
+            if (other.city != null)
+            {
+                return false;
+            }
+        }
+        else if (!city.equals(other.city))
+        {
+            return false;
+        }
+        if (country == null)
+        {
+            if (other.country != null)
+            {
+                return false;
+            }
+        }
+        else if (!country.equals(other.country))
+        {
+            return false;
+        }
+        if (email == null)
+        {
+            if (other.email != null)
+            {
+                return false;
+            }
+        }
+        else if (!email.equals(other.email))
+        {
+            return false;
+        }
+        if (fax == null)
+        {
+            if (other.fax != null)
+            {
+                return false;
+            }
+        }
+        else if (!fax.equals(other.fax))
+        {
+            return false;
+        }
+        if (firstName == null)
+        {
+            if (other.firstName != null)
+            {
+                return false;
+            }
+        }
+        else if (!firstName.equals(other.firstName))
+        {
+            return false;
+        }
+        if (institute == null)
+        {
+            if (other.institute != null)
+            {
+                return false;
+            }
+        }
+        else if (!institute.equals(other.institute))
+        {
+            return false;
+        }
+        if (lastName == null)
+        {
+            if (other.lastName != null)
+            {
+                return false;
+            }
+        }
+        else if (!lastName.equals(other.lastName))
+        {
+            return false;
+        }
+        if (postalCode == null)
+        {
+            if (other.postalCode != null)
+            {
+                return false;
+            }
+        }
+        else if (!postalCode.equals(other.postalCode))
+        {
+            return false;
+        }
+        if (province == null)
+        {
+            if (other.province != null)
+            {
+                return false;
+            }
+        }
+        else if (!province.equals(other.province))
+        {
+            return false;
+        }
+        if (telephone == null)
+        {
+            if (other.telephone != null)
+            {
+                return false;
+            }
+        }
+        else if (!telephone.equals(other.telephone))
+        {
+            return false;
+        }
+        if (title != other.title)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + firstName + ", "
+                + lastName + ", " + email + ", " + address + ", "
+                + institute + ", " + city + ", " + country + "]";
+    }
+}
diff --git a/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/GroupTest.java b/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/GroupTest.java
new file mode 100644
index 00000000..2fb6f9ce
--- /dev/null
+++ b/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/GroupTest.java
@@ -0,0 +1,122 @@
+/*
+ ************************************************************************
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ *
+ * (c) 2014.                            (c) 2014.
+ * National Research Council            Conseil national de recherches
+ * Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ * All rights reserved                  Tous droits reserves
+ *
+ * NRC disclaims any warranties         Le CNRC denie toute garantie
+ * expressed, implied, or statu-        enoncee, implicite ou legale,
+ * tory, of any kind with respect       de quelque nature que se soit,
+ * to the software, including           concernant le logiciel, y com-
+ * without limitation any war-          pris sans restriction toute
+ * ranty of merchantability or          garantie de valeur marchande
+ * fitness for a particular pur-        ou de pertinence pour un usage
+ * pose.  NRC shall not be liable       particulier.  Le CNRC ne
+ * in any event for any damages,        pourra en aucun cas etre tenu
+ * whether direct or indirect,          responsable de tout dommage,
+ * special or general, consequen-       direct ou indirect, particul-
+ * tial or incidental, arising          ier ou general, accessoire ou
+ * from the use of the software.        fortuit, resultant de l'utili-
+ *                                      sation du logiciel.
+ *
+ *
+ * @author adriand
+ * 
+ * @version $Revision: $
+ * 
+ * 
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ ************************************************************************
+ */
+
+
+
+package ca.nrc.cadc.auth.model;
+
+import org.apache.log4j.Logger;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import ca.nrc.cadc.auth.HttpPrincipal;
+
+public class GroupTest
+{
+    private static Logger log = Logger.getLogger(GroupTest.class);
+    
+    @Test
+    public void simpleGroupTest() throws Exception
+    {
+        
+        User<HttpPrincipal> owner = new User<HttpPrincipal>(new HttpPrincipal("owner"));
+        Group group1 = new Group("TestGroup", owner);
+        User<HttpPrincipal> user = new User<HttpPrincipal>(new HttpPrincipal("user"));
+        
+        group1.getUserMembers().add(user);
+        assertEquals(1, group1.getUserMembers().size());
+
+        Group group2 = group1;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        assertTrue(group1 == group2);
+        
+        group2 = new Group("TestGroup", owner);
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        group2.getUserMembers().add(user);
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        
+        group1.getGroupMembers().add(group2);
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        group2.getGroupMembers().add(group2);
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        
+        group1.description = "Test group";
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        group2.description = "Test group";
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        
+        // group read and write equality tests     
+        group1.groupRead = group2;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        group2.groupRead = group2;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        
+        // group write equality tests
+        group1.groupWrite = group2;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        group2.groupWrite = group2;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        
+        group1.publicRead = true;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        group2.publicRead = true;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        
+        group2 = new Group("NewTestGroup", owner);
+        assertFalse(group1.hashCode() == group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        // test toString
+        System.out.println(group1);
+    }
+}
diff --git a/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/UserTest.java b/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/UserTest.java
new file mode 100644
index 00000000..c87163ed
--- /dev/null
+++ b/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/UserTest.java
@@ -0,0 +1,110 @@
+/*
+ ************************************************************************
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ *
+ * (c) 2014.                            (c) 2014.
+ * National Research Council            Conseil national de recherches
+ * Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ * All rights reserved                  Tous droits reserves
+ *
+ * NRC disclaims any warranties         Le CNRC denie toute garantie
+ * expressed, implied, or statu-        enoncee, implicite ou legale,
+ * tory, of any kind with respect       de quelque nature que se soit,
+ * to the software, including           concernant le logiciel, y com-
+ * without limitation any war-          pris sans restriction toute
+ * ranty of merchantability or          garantie de valeur marchande
+ * fitness for a particular pur-        ou de pertinence pour un usage
+ * pose.  NRC shall not be liable       particulier.  Le CNRC ne
+ * in any event for any damages,        pourra en aucun cas etre tenu
+ * whether direct or indirect,          responsable de tout dommage,
+ * special or general, consequen-       direct ou indirect, particul-
+ * tial or incidental, arising          ier ou general, accessoire ou
+ * from the use of the software.        fortuit, resultant de l'utili-
+ *                                      sation du logiciel.
+ *
+ *
+ * @author adriand
+ * 
+ * @version $Revision: $
+ * 
+ * 
+ ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ ************************************************************************
+ */
+
+package ca.nrc.cadc.auth.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.log4j.Logger;
+import org.junit.Test;
+
+import ca.nrc.cadc.auth.HttpPrincipal;
+import ca.nrc.cadc.auth.NumericPrincipal;
+
+public class UserTest
+{
+    private static Logger log = Logger.getLogger(UserTest.class);
+
+    @Test
+    public void simpleEqualityTests() throws Exception
+    {
+
+        User<HttpPrincipal> user1 = new User<HttpPrincipal>(
+                new HttpPrincipal("user1"));
+        User<HttpPrincipal> user2 = user1;
+        assertEquals(user1, user2);
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user2 = new User<HttpPrincipal>(new HttpPrincipal("user1"));
+        assertEquals(user1, user2);
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user1.userDetails = new UserDetails("Joe", "Raymond",
+                "jr@email.com", "123 Street", "CADC", "Victoria", "CA");
+        assertFalse(user1.equals(user2));
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user2.userDetails = user1.userDetails;
+        assertEquals(user1, user2);
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        User<X500Principal> user3 = new User<X500Principal>(
+                new X500Principal("cn=aaa,ou=ca"));
+        User<HttpPrincipal> user4 = new User<HttpPrincipal>(
+                new HttpPrincipal("cn=aaa,ou=ca"));
+        assertFalse(user3.equals(user4));
+        assertFalse(user3.hashCode() == user4.hashCode());
+
+        user1.getPrincipals().add(new X500Principal("cn=aaa,ou=ca"));
+        assertFalse(user1.equals(user2));
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user2.getPrincipals().add(new X500Principal("cn=aaa,ou=ca"));
+        assertEquals(user1, user1);
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user1.posixDetails = new PosixDetails(12, 23,
+                "/home/myhome");
+        assertFalse(user1.equals(user2));
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user2.getPrincipals().add(new X500Principal("cn=aaa,ou=ca"));
+        assertEquals(user1, user1);
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        User<NumericPrincipal> user5 = new User<NumericPrincipal>(
+                new NumericPrincipal(32));
+        assertFalse(user1.equals(user5));
+        
+        // visual test of toString
+        System.out.println(user1);
+        System.out.println(user1.userDetails);
+        System.out.println(user1.posixDetails);
+        
+    }
+}
-- 
GitLab