From 887f701a762f35a9af5b15912943f6a5e91f7866 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 15 Oct 2018 13:42:17 +0200 Subject: [PATCH] Add permissions (#118) * Add permissions * Mention owner and explain merging --- docs/auth_index.md | 21 +++-- docs/auth_permissions.md | 91 ++++++++++++++++++++ website/i18n/en.json | 3 + website/sidebars.json | 1 + website/static/img/en/auth/architecture.png | Bin 10340 -> 11104 bytes 5 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 docs/auth_permissions.md diff --git a/docs/auth_index.md b/docs/auth_index.md index 37fd942b..1e1fbea7 100644 --- a/docs/auth_index.md +++ b/docs/auth_index.md @@ -25,21 +25,26 @@ Each person is a user in the system. To log in as a specific user, authenticate ### Owner -The first user to log in to Home Assistant will be marked as the owner. This user is able to manage users. +The user that is created during onboarding will be marked as "owner". The owner is able to manage other users and will always have access to all permissions. -## Clients +## Groups -Clients are applications that users use to access the Home Assistant API. Each client has a client identifier and a redirect uri. The redirect uri is used to redirect the user after it has successfully authorized. +Users are a member of one or more groups. Group membership is how a user is granted permissions. + +## Permission Policy + +This is the permission policy that describes to which resources a group has access. For more information about permissions and policies, see [Permissions](auth_permissions.md). ## Access and refresh tokens -The client will be provided with an authorization code when a user successfully authorizes with Home Assistant. This code can be used to retrieve an access and a refresh token. The access token will have a limited lifetime while refresh tokens will remain valid until a user deletes it. +Applications that want to access Home Assistant will ask the user to start an authorization flow. The flow results in an authorization code when a user successfully authorizes the application with Home Assistant. This code can be used to retrieve an access and a refresh token. The access token will have a limited lifetime while refresh tokens will remain valid until a user deletes it. The access token is used to access the Home Assistant APIs. The refresh token is used to retrieve a new valid access token. ### Refresh token types -Refresh token has 3 different types: -- *Normal*: is generated by a success log in request, and will be sent to user and possessed by user. -- *System*: can only be generated by system user. -- *Long-lived Access Token*: such refresh token is generated by user, but will not delivery to user, however the access token generated by this refresh token will send to user. +There are three different types of refresh tokens: + +- *Normal*: These are the tokens that are generated when a user authorizes an application. The application will hold on to these tokens on behalf of the user. +- *Long-lived Access Token*: These are refresh tokens that back a long lived access token. They are created internally and never exposed to the user. +- *System*: These tokens are limited to be generated and used by system users like Hass.io. They are never exposed to the user. diff --git a/docs/auth_permissions.md b/docs/auth_permissions.md new file mode 100644 index 00000000..c8363901 --- /dev/null +++ b/docs/auth_permissions.md @@ -0,0 +1,91 @@ +--- +title: "Permissions" +--- + +> This is an experimental feature that is not enabled or enforced yet + +Permissions limit the things a user has access to or can control. Permissions are attached to groups, of which a user can be a member. The combined permissions of all groups a user is a member of decides what a user can and cannot see or control. + +Permissions do not apply to the user that is flagged as "owner". This user will always have access to everything. + +## General permission structure + +Policies are dictionaries that at the root level consist of different categories of permissions. In the current implementation this is limited to just entities. + +```python +{ + "entities": … +} +``` + +Each category can further split into subcategories that describe parts of that category. + +```python +{ + "entities": { + "domains": …, + "entity_ids": … + } +} +``` + +If a category is ommitted, the user will not have permission to that category. + +When defining a policy, any dictionary value at any place can be replaced with `True` or `None`. `True` means that permission is granted and `None` means use default, which is deny access. + +## Entities + +Entity permissions can be set on a per entity and per domain basis using the subcategories `entity_ids` and `domains`. Granting access to an entity means a user will be able to read the state and control it. + +If an entity is specified in both the `entity_ids` and `domains` subcategory, the `entity_ids` result will be used, unless it is `None`. In the following example, the user will have access to all light entities except for `light.kitchen`. + +```python +{ + "entities": { + "domains": { + "light": True + }, + "entity_ids": { + "light.kitchen": False + } + } +} +``` + +## Merging policies + +If a user is a member of multiple groups, the groups permission policies will be combined into a single policy at runtime. When merging policies, we will look at each level of the dictionary and compare the values for each source using the following methodology: + +1. If any of the values is `True`, the merged value becomes `True`. +2. If any value is a dictionary, the merged value becomes a dictionary created by recursively checking each value using this methodology. +3. If all values are `None`, the merged value becomes `None`. + +Let's look at an example: + +```python +{ + "entities": { + "entity_ids": { + "light.kitchen": True + } + } +} +``` + +```python +{ + "entities": { + "entity_ids": True + } +} +``` + +Once merged becomes + +```python +{ + "entities": { + "entity_ids": True + } +} +``` diff --git a/website/i18n/en.json b/website/i18n/en.json index 63aca45c..118ef449 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -48,6 +48,9 @@ "title": "Authentication", "sidebar_label": "Introduction" }, + "auth_permissions": { + "title": "Permissions" + }, "config_entries_config_flow_handler": { "title": "Config Flow Handlers" }, diff --git a/website/sidebars.json b/website/sidebars.json index 1a77b797..6447f591 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -25,6 +25,7 @@ ], "Authentication": [ "auth_index", + "auth_permissions", "auth_api", "auth_auth_provider", "auth_auth_module" diff --git a/website/static/img/en/auth/architecture.png b/website/static/img/en/auth/architecture.png index 7e2febcfd73d6f755b9285e1db5804978e506608..e68619f130b3c0e5ca557282490ebeca0ef880e3 100644 GIT binary patch literal 11104 zcmcJVc~n!`wy&`$0i_fX5J4d*A_^)>-z5=6>4YGl2m(%~Iufb)?rJ_p{6SI+{@16Z0^GsZf+d+8O`hqaIZ4X&&!;9FDj z!ma%=tx3kNIEtDtV%`0Cr3s|6!NII0)fkipqQEGi8n*Nys zWw=Wo_rCVT9vus6UrCbTi|3;6_TE8&=ha{{2^?As4Jusq;EVJX{zzB7$&qh{@z&CH zMK?K07#rR?vX&?wz6sN8AP1$Z#uNT~|CX2$YOVcd&4!ntB|5SRvqSEGWa)3>fXg)- z{2uN9qkXb%vzRI7Ovcgpy<`ZF(+Vj|RQ&5`v7^o64iJwKR|^bVZ7dMg!}8z9s1K5_ zwad~LR-m!xAxvZwGftMa)EMe*cD#GxW#D40kVR&HrecJ!?%?*3a?3-bXHeJ_(S0q^lAy((nTG*<+2CIPRvSBoE{z1^ zr;x=D*j;~JQt@;G^d|7x2;^N-Yb(Ht-;=@NbeMO?0gIKvVm87(r?UshjK{i<&Dt*G z>!pWgH80j<%=~jXq4yiagUkbyrviMv0vV72AynV3L0f;igmE7ZjBnef#RzT^|15iDo4+ds%}{7uy5#7#lR#Gh-cdGVRI>g^OmXF$KXirX zy)_L92; zq)~pX%*xU~3lxp1BPVuAG0s}d4BgDCZCH60t_s|_K3<8SkzV#T!Y=KCO1bA?;RSJXVef~z>x7)}m5 zcI8X@W0R^I96|~Q{{WYA@`G;nf^ouF1`)Ls;%<^}w@)S0w=lM>X2jp;6(cb#O5=tlIwF&Qm>mHR(6cj*Hrym_2Cg@ChfRiqxzvL6 zlAp9@HA*I*cUs_@32$E3|MaQrGvjH(!AMN;wGRl|4iRFPsQ+>Pu`3Bh)mGgY;mkw( zj_7;uuO*NRYWF8EV3aV;*qnf%dZx>Q*E_)@#ytnJvd(FJk^hdJw6kbBGa(#JBj&`TTAw(H^oBJ;MJF>|STu`59z2Us2c ze5|@%-K^2<4ji{Y+^Oe56o%j$gXeObj}}ioXh1ly%UBiUOU>ApW}18$g!9}`Tg1Pb zY{U$#$-|@e7(C_>0ur2g>!6ZSE8HW}5oX&wxu^wSPD$Tf(4i%AqWYk;V{rO(ngX4| z%wi9RAjgb!u`t&A{HHl)Nc zu=X!6hUu5_tP5uqLfRLvq0H8a0eNiuhhocv)z7`Nz8EZpv{TO*ga~I#zb<{+O^0b@ z&208>nsaNktT#>WJ?U}(;Qq$UBJKgIPZMKay*A&74&_@*jsU>^FRm)*LXQvf#r7oxo;EW4+&It)XV z01sY-RmD!?`R#?)>#=%#lpjU)DV55_4^8-rSVm42FTH6QH~T%mYKuIbc~V98w-K5QuFceIh`iH_u93+@89Qm`{Tn}dF6W^U{n3<5Chsk{?96g*E7ed4d}TW? zRV7<6z8#RTtuegdO(!&+#!ovLG9z737s}0w4XpCX6?jC%OP?@3!Ek0-yM?g5veYA| zCNX?Gdx=ppKix{%8W(F(_=ARQ?8W-nH)HJWxy@xI}X?Vz)rf~}C z>1$?sFt{Ft=^_?v#E)*ho9HcQighh=8_{ zPjf3zMYG1n-3z2fHXsZ5OHTAP!AP;yNtJkUB4W}Z^cp$itjeb>B*8IK`J;0))a66= zJ@d_OabVQNu}J(!YqOyV`Ez>_=pi|_cN4#`8IH8NWbvEHo&aTF@yaO$3NP=~xOG#N zAgtUO}Y z_OWiE3vutm+we_*zMiNoeLm)K&$X7biT)eMwl-d{y2mjXU*VRH&fIbC`B{%^E_76g z5#KcL+M92>4vWHPjtb|NJdYO5dm=1y{awCpjTjw@7wg6UIk=$c_if(`t)qetNJZRJ}D#iiujQ7=MRg5{}GvJd^KqLbkU0m zlaaetfvZwwFPK$u`_56BnWdqJIwB`|x1}{tS``70hfYFG{dbF;LVV$sl-w(s~dYkL>-zP}}qs$TA~%4m&Jg zA`oaSQIYYR>#rB5aa<+K9fwhr6o#SH}xtwxH zy3Q#NJuX~+>5knjQA6UpGBX2=#`%h~ne`_=AeHH`!k+sLp`Q@yJ4t1F74yWQdZvzO zNgd(d(0?z?ccPb-BjcebEIhp{pu9x7AJ2L*sk}}+t_nDMWp$B+;bGSCoL9-!7Qy zf0)V}FwzoT=v}P#Jm0y6PO!hASCVu0VNXEN=ToS%7z%d(m*2uS>ILoX^O``ZdFyPU74$-}o?Y{BkX`Ks|mypTF4IJuh4s$5Tz0cPRoLXZMch zw4YlVo>PnQ)cz^(#Ijnpn+$?sR({@9C3@W_^Unu+KFEqTW18VK=~Ek9pPfZtstL(g z49prxv?Q^UgC3l2{OCEdy+rtwStQ~*=m@64{lAzeb8zlI?x$C}1U_?XCf{x**q_%}Tla2ftzR!I zt^H6Lw5$!q?mC$ObxUhGh7xG+-5aGiF>MAGpRzgqRx2qv53WbcIa%2BpxcSpx^Va4 z_)6L2APTl}nO>Rt3{#Ln%Qk3fXd13*Q$0JH8&caPS)hB~sK(Pa(;c}bi4k=$uTrm? zp4^=3GwvUkZC<53kyN07=T%0MJdbHYwc8oo-fP9T`rxjFpDG^hppmVDycv2OD^(tr zN2|%APE-F84Y~{8CHg>Jxz&&T=gjYMn4j7JHzWePgf^8_pw__yRPQOFt@<&OT1=>1 zVr!|_ofbC5dVrd`LnPD@4Yj0?#h{0NxQ$TUn^p&rs8cfErsQUR*K|s|S>whO3S^Hd z#Ub?gdUw6X%SI6LmPYj$`20t6jR4}+g=4AhMhONg=k-Q0(|PWvQcq1Lw^G;^-DiFt zVA$>L8^o9@*+K~(!x0b*Zo{oZ`*n%Y4FS2-jC)$_&=CzaIDA9XH2mRGc9&ajCd8?k zOi-+|uXC`lkG5wRG%k`<%H5BR-AV9{fT!D*`3Iu~+xSm%PvAuW-rqz;&o z@s;Us(;uCjmdwu}(oS7ok{9XJ^tqsq#cy(sw?3UsPDg+SO@*CJ!t??QT#;q$a@5p( zL(4RA;wzC|{cK@RPl5l0<-J8j>ix^ql3n>4+Q>um!woNNF(CF!MPNl4coth+Rasw- zepI{Aa0Tgwyu*Pju4>!+6`JvIpBgQZM0Bf6zII~#VtFd{gKN?k<0wS_^_qi2Cq6EG ze0nm!B@FP)w_Y*#NBGZ{>&y#_7bs0VpIsOzpBo00O2#<%GBP#d#&=wR=0pd%=LN6% zkQ4BWmCr6~ku132vv=`3fwI_tjMZaD4u>A-U)n;?Oi4h}BTI~dQVp1nHy;5qHo zSwbo7#RKJM08$m0hW;FZQFwxK~!7Lx(e7MtM%9<-K{09T^J!BAyyRT zQC?!+m3N&u(@YM$p!B`TBo1P8N-94Gd=Q5)EiXtla;hl{JR75c4mIzST1M^LaHn7( zExW8o?yJ(R#Mbw|HzD~8ZgD!&G|$`e(1`;_aob7D(RtYll9T&?fhyWrI__yntyRkC z;+?BuqpfeMjOe)1(5d~P62~e(u_Nnl8@FDL#MrmL!F)ZoXkp*6eA(|9OBhde46V#I zUVl0*;g~SI=O_))cK5YzWrUppl=x8__y(ax)xDV7F}+vzgZxOwf}q7{&4a6I&WCDfdeNjwIQr^G8s)m5Kn>qb~$Qv0ffFF(HU_H zkOhB;!8q{z%kdNx!4h7&u*T;f1;RnfKidOFAQZ1x=sE$4YB7S0FKjMR5k~6T<{MOb z=PMi@H~3>SD10OL-@u>b^3nWiQdcY_Sb}b}!%Ic0-GQmtN8Pdy=T-Hvk{$v1o2ODy zLi=j{A$F@y#Y*X6|9ovNUG~5B*gr4(`0*fDzI?`N*@ovE^zo%UfFt9pO$bk>d;~-) zgY3#*Ee!qqBi|zP09H4BM3XC1TJl;mdtvh`mi#`@Z{3Ff5E(XlO7uJNFJO!5LtmVs z^DpAyf-dOGs|02!nn&E&Bw#hoePj6D7sa6~b;#`vQ)m5RIAb)lW4^)-&hPPJ#ViqA zv_$KQxz6E`Ko)J!^yklfSw+RNAmF2}K_`>Hik1ZpJz5s`SFn?wm@bq!P_ArY&7YGA zSEs`$kyz1PG9Mqu#xN9~it(CD_vfPlJ~S3JQcoZto{5dGjB;^Iv+MNb$)3`YgNR3rP1Ba^Xn9pu=RC=7&2DnNc? zpl^JsRU^Z8eq14RF(ockFu;DvKeKaaV$PYl?E}z^Z<@|zg6Fd-7EvP4QS4buS19}4 zD$lGn)=VW6TJ2IO!D?Lb0vpBNKQvx59wGV+6HN=ilf*~r){-=f|05q&9eMgCjVK;= zLWBGY5Uw&-?{`W%@1cBNeW)T}TR=eLW9{x$9D2}~XS~bM-W)d~el0C)Ah?Nt8uM#! zuI4qDCtAXf*PH6(QV4= z)}MGd0SX>NYXWA#Z)d4xhZ=RsFGiG+M|k0$4+{wP#F%bu!~NgFdA*0*rjJG^{2zdV?Mose@0ZHd~pVA#6a zvnh6J#a-(Q?48#ku~c;=n4}q07B%G0w&V#yFky0(5Cyw5OkX{Gi zI=6q2eNq$#*XT|f27zL*5+wjlsy|#e;O3GF*|ejSY-DdJ&wCKoOolZRV)>mmph*}E zYv~k}EO^=(qw9t;?C74l+TN818-p&zPVpQk-$yf_`5^h&7Zt~TH7#0P^jYb&Os6fp zS`efe_HCPu2fa=x6$MW{SATw8cgqiTawjv`Jwf**=PttObh+^Z?pW)`Ab#5ty5uxn z(ikM8e$g&x>^wCztBfcvkPGcLi=HJND-F{Pj=eH378(}@}g(>J99(o zcZnwZyOy7{1z-oM(<`0AG-jWwy`Sf31)~Adj6L*ti(oQ5mfO}|$j7!HRV_;=q~A`T zJJ~!Rr7oY_j%oie{pmde-Qa*BlEW)Y8bV&(k|=a#%=yPMQuwFy6y=L``Pl}G0`x$= zB!a0eljZyK3kDUATM!rHPn|syo5wK6G9wmedfawP=FF!i8XpTjO(GE14 zFfQ!ZRRM-y%s4&KO_lyciQX*=eA+n=)<&YSgT-9Iz`2);kR+#fh;ttU?YGjQG<)Lc zo<;w+2LnoIfgKU46tdd}!4iSBhe~}>O-^@wroP3so$X*$K0LwjgMB+GD}27`0Zh)Z z8U_7B(2lb>sq_cMtrmtkKi~58>v3(XJJzI4=UahsDG7x?Efu^iJYajFhXgwB@^*)i z?UuUG4lIx__cBja`E^?tpqY_DyY{wrN|c$!Y@np1s8zhp-cwF(3^QLCm6VunBcguw zz&06^v$hwTP^Wr7Pq7i}upypK1Jp>9-XH1Y;WGdJgD!(GT#R%08Gh?tW?i@2a>ZQk zSFpfN5$HcZ8eUbBhmg!WCqSKXR8pB_uLkIe+hUrAE4W~A%Oo8J6K1uz_gEBvuzjH| z%eT5vFw@e|rS7WozA~Ugdy~VdEE7Gw1f82HqaAZTsN{f|m`?dhQJXIUJhOTipoe)l)$CyNT6XK2<4+Q)9+ybQ@rys4 z7T6St602SLOF9;c_v|?@e~Kxx@{lgPXes?DvuEATMU&qSfrSjcdnG$AOq%#Q%XmfB zl^Z|V40OhNG!hwkqRFKFJGRl{hL`NwmsN#2Cv(9rJT8pqt|{nrz3Vp+q(y&8^s#el zoZWl0t_i0tA_w*pczu6*i#LD5&R zI3u*lnU{92M^%4tmndbvDBz==O*d__kz%1+v%58`>v-z(oV?)i9k{ZCtt!Bnv%xxS zvV{_0?4rkp`fFSgcKF_KcKVRUPlcnMg%;o?^hC;HOK82)K?ovm;Fo!7 zTF`@;7LNi)*-sI^KTG;1*B^@vCS4d}!>gv^`|pr+;+mT1?yQX-^Xc4RybepU`4s`d zbntM8Lp-_VWg@b==dk8B>57E1!$=jtk<|f{=v9L+*K5mSS{EH`ct7j!7xlmOiTg+G z-An3nlh2Y%kn?*+Uh%(wn+8)Fc^o2+G2jR_#usPQRdOubL6A4HSZ!i zUkML(pK)&{vpjAN$+%A>+s^J=Tp8aUEb|xzZ&@Vd#k||cb|>qnh^DS#O+LPq0e_n! z^wW;ZYaZ)SBva(;$-tDq8I`3VK!MvA=S)s1BsLSOKb3rN9_-eL^TE|`yeU~M?3S_; z_s}JK-+aS{!2Ovahl=xyN4p{-y=LtdF*i6M{Zv$%!ICq4RB<@=4ZiL8z(QW3pb%jR zo;KN2!`2Pd%cxGXDKo)g*uII^`|`dhCSJyi5n?okpGs)Ew!AIneg56;wN2X{Uo8xu z@XTHhA%W^_ly2(lE zEs#FwXm#Hn&+8MY)U}{2d^Q6&P)orNS&K+NFPZjpOmzis-EfL zEeQPwt8R0YfV9N(4kLrIWmNOZ5F}W#OnoGiC8PJcqSqNJz|_gX=gvLX-7L6%llGA^ z>yH1TO;=1jL~BBA)D~D!?)7btu_gwd6%}It^z( zcmj2zDorlJ)_Oyq@p6k-)lM5HDl1CkmbPMwkprg9tLjUlal}0>ZX3k`^3B+}fk$^1sUzisAMwUN z8h%6%>@`vXM38N7Gmanb8L1{4UvAx)JNL}8cA9ONsHi29NHYl(fk{4KuoNfnx5j%A zAV+=Je4>5(V3wb$63~ATQZzfZJENLy5D3+ouR)1k?zxz>Zd*zG>2H!sqQq8reLnV` zCIE>$b$K}pnzSHnXsHar&)P=3p&jFSTDb&>y1B8O%;yC;WfCd(`)sc& zN?@JrOt(yXcQK}0nZ$=X`!`f@5PIXj-9~Y@wfQyef}Wij-|1Q0UQrF_!oWc_G5+Uz zhc)FbuMad6Y7Pi~I;`Rqyqm(_Yn9QGc5F-Sez&r}N@VfYW`b8tDw6Q)7r_tSDFSb= zl|6Bh!8Majyz`Z(^UExShYYdesjEIV0P(x`(i5O5!+<@~?Q-fDrS}w?S9XKJ&6@ih zgzJ@_>+_Zy1dwji(c`v zFdJG_L(g-~GcFZE!6DQX#{3)>byD=L$>|#Rw*M6tc;mvEmrgo=U z$^Pq;crhITt{H+Fvw@()%2A7H>A6ixCo{pW{; zzhC};=Mq1z(u%)D59a=}uUMa;v^y}lYdxzFPy9+D00@M|e9}exeObjW0;6w?;(zzs gQHk{%6~Bt5qmRBTw2sHAuX=Mh=3-lQyY&Aj*C_1*8i>&sgCBm3EBKYQ^uncg8&3tC$&imknF5I6$k(J4b!I$;_;nknU`P zcgc{yu?`nOUNa#pNj&G?%5oxrI}&_TgdUC4MbE2~1<+^d#Tv=` zmpJU+$%V}DoxyuYKF$) zI9tXb|H)6eX7u*i$T-g9?I^kbA8Y?M7O-`C)qE3i(_b(6554@GUFTFfnvR?k zERsb%-FRcpTxtoIL`IDjh{e-LA?=2c9XqSWm>sOfGfdHZXfK8P3?`^hqfQyjuK&}W z?V`g9B6i(;G#5pWoT|tc9mTAj}X!2x-J;+6a634mSDGv=dBS@v-iYrLI<6< zZ-R{}Hp5r%0h1##X6%9!uVo6v5cE`-{kd2&A9k}PguYzvJUg;j=!~0RO`u1v*zpE} z8-=oY5E(~sN^9tkFXO~Z#5Jbi8++3;V;^;n$*Q0k zWzTP*aTk@PWR7Z~M?#1sVcIa}&Mro83F=MHc8S_3iIHNJ|5UvPJM+r5_ zgNpjTB>xV!KY=ccg&kovA**F)swP&dN)yE6>3LqTB_-|}ar{18-vcwm=0$Tydg+T@ z@IXDjwzKB<;Jz;s@l_N%K^&uz;OH-ElgGUfjSSQ~e|4Y`z8aEm8uer_Cd9Q?&xe-; zSg+pqS?kKx1&I@9qdu&K5d~9O9gTCfy{%xBfRt|;VTqbBAc-s7qC5oAwQ7j{oyhzr~pJWuv7x~`}v{^b9+5N=ux^saYL$gMFl z?n>+vZWafr8_TUM`ccMZjaJ(h=O5lcp}yLSkI}-3ZX%V-aB`$Ef-1bHCmh&G7!%U_ z(ia!;Wi3a|mZ@RpqZMxZ3}HU}$lZ^njM@3c2O&^a9rdCZ`ymHHR1h7)2>Z1#MRNMm zdjxUFKew3AzT7>DA7?V=q6ecB5%)MN%-M8Xk`)S!0UsmK18dI(*1(PyfQ2)u^v4_` z?=TxHGIBzs6oCVWZ0xLt={|h@bAxyeaSAeLG#=(O6EPGnEzU(o50&z>|EvwCS8-PD zrwaw@UoR94MGb?e9Hr5)t2M}KPvcyZAp~YqFiKG$xxap+oG7Xr;r)qZc`D8bV&xc( z93q>vaTQ5XyD>^Nzp5MmRkF-CiL>o<4HHQw>gBAHL$7}l30#ja#9FnJ?{S<7C@KB? z8wYfhg>$9}CXQz+HOBBohyEH1R6o)BTq%Z@5@%<8NmC9hz$6xrc11RB9>e!!mL+BP z4f$LPru`C-+Ke=w@lZF;s6F7ZwVZV*d1vJC{EwDk!zW4E2J7|;Tr~tc&61>EWR@Mf z^TdiM&SLL&FFlh}^SVFbYjtyQgoX=rY zRC`bTP98sN6$`ddepWc{k+`*3Tkd`5ujP+e%XHf~@zr~<1*{S;hg|@cXQY%&Ft)xe z$rLX>WXxB_YlwXf5euQHbn@3tvxs{z(oZiV53%zXSAF&b%RCD7iiS0-+_qc5Vy|go z(sT$*)5ok_Y(>?x`G;^sO=jDLJA&OjOgS9Lb-$X5Vtf4QY)s%3uOn}=Q=6sB9dYye zp8Dg@_ZzRVc7`iCxr<1OpI*uKzkXZ7&7-+LVL!*PX`Gr2Q zvr-r2gv%|i2O%>)tG>aj{YdFjjC&Q+Iw3O7m=!$qw5Gh|GKzc)d$qE=Z*-!_4plx^ zIVoZnCEpIvVW*nwjkcT6I-b__G#Q5HdMmJFn~7&g8Av3Yk}q|uuS;o}|1uL==;gx~ zmIDhkIFm!kou>7`>#ON2>&9TC&B*mLYu58Z?AT+ebyF*j4`M!5Q?zO@9Clb%$ME8* zs2#3j!$lm%#I##lT+nz{IIuC4ay=WTJa=1nZ_@dcYcj%@?Kk%s-1+2u&28|MUdx3W zt_&<`pj@9%>tN+b!^U9g&A1iQ_Rhk-S5bj$WryUl`g{jMrhP5z+3+3Z!UoyG2YQ54 zt%04J$B@~JGr7AD%TF)*P3e?vsEgXoy=kJt70g_=<&zlxOIWAF*U{zAtfurjeSv>``V?^?nVn>Wq03l=K#M%IHv@R za565c%BtHb{~epuoLS{w#$0?;Q5#_31aq>tF@LCiYoE%%bVmE~#p&Q+CKie9q9~^-z-S5*pH@=c5M9xG2o;e~^IL_nc{Rr)4n3K$Xg+V4F@YU<@eUU6dSA*P?fycqB=lK0v#wEn&*x%zv$pXk zO@c!go-flHp$jh9*7B0d@nW5FTwtYhJkxM5xy!#TJoJY|*3`Vg!R~nL4~>nmkRI)D zub18iBzihy;`l(g8uxJ{W+td8GLQ$h@{2JW#eXkMbfq!;sajx1*LmGQ`Y1`aZ$!=X zt@+lhS-&}+fKLK96HA+MI5jI)NMd5;v`vK*0sp?u;hO$5iyG?QGz`!9AcdZbxvnMj z&mzsQqAi4x4aE&18Jrbdjc;gV1G~%4wcE%4FtXxd<#=V3Z*Xh$WUWx)&dl?I440W& zVT85xO_>2Vho0UoXuCTLp-81n;>)b29C1+Lv;6ALf)>l1yVC~}Soaj&1*x`Ixe$2v zn(QkLz;(9es{g(iWMgtp{z0N9JIC{)+Yv*mC_Mhf(Ov zN7(T0o~ozP-V?8i7HDxqoK#t9$}@KvTYPj?hZi?4|LFi<^_B7v8@_dT$bM$S+eZ1d zrsp2G@n|_w-Nk_fmEVUZ{RUD*^#yij<1whSBF2CiK8|cxj(p0FQR0KH&sqC*uLUweFZq&bg)eCBz6o;P)JcXU@{{dR(dV>J>`wba)2(pgg*}r=m)9PgE z;*+KQQ@lX@Az&XmZp+Zdn0Gi7*rk3sv_o_;s8|QWYS;iH&|HE7M)@h&enu#b1DQPO z(R+I;`nT#Tt1&#ovtG#m-EUD*8a;{(J6?PE$HF1hvO$)X1lRAv#XO|YC~LRx9p`8g zt;zOF2CT48L9mXqTKIviaX)uS#!OWW`=f^_MwF)x!>)LXeJxU~Gkb=6uv_hvi@ds{ zbBHtpz_)^XWQIo+k6M3u`K|}G9B1B)yc@2Av887-w>4IE39!k%D8H_59Nk0k5*p$tkq9HuyQ`rf)J$;dIf{%F>>mdw0@aW z8jF-eH6yvRl#xZApws8NNitIl%WDXjL?v=4l~~8dq-w>z_+-lR<^UQ;6W7XXu-oN+ z?68k9qW6Hshl<9SMrVs|R2;EAOqH<&_9rTqcPs6|QBuPw^m8jdE;sDku*drSJLP|>>$FCzco4=1nv*? zDR$NgGzdAzwA}Ud!b)Ie=_ZGK8xuY^(r}rFrKEuul-^{^Pc#fv*MxkIIF0b2cXKZw z>_=6OTRReI4bJ5@qv<>?QuHxq+c;~r9HFH82esri$iV}y8JxL9j+;D_xSd599ns{AZ51hSAVL3fx zzr)?6|8+?fW4EAF!~11MLnd{dBYXHr=ct(oxp_C+!dCxFQHC#1mufw=O~+_%E-?7L z%*_Q}-3y=iy`Uf9r#sl*3+r2VFF$@g&8y^Iw&I!O6#$DT#`qknKBLQ_tfXvcvrb zql95A45RF$hLMOCgFHd8;+n?mj&MJ02KetRKte9Z;;mY1>ZUN;GAv?ABcfnOX4{SW z?99^*aRi)sNlVIv#!1_TPwG`x-jAFOg{++L&pB;lAjz;G*%CvFjcRj~LgaNmNeP8;M5i5QL zMeC-Yvf-*NLBHtf_J=$@`a7k9D&6_X=9~%kZoQd2O6Ag@%mT7VNhI8ahP72IiZWdw z#{B6q=umgxn=o#jS2I0b?(h!L;@27oH1^^c-mq5w?`e`2?-+yo+L%QL_;s|!xLPIW z#6XTOP4}yu32GUnAJOW_`{^oP-Pj{ywCm*9a^p^5C;g4TAX<#!%dwrEy(Za)$7Iza zJW#9iko*Kn*Usou?2Rn%Pr)++ZbSH57wS_`*Rp+Yw3D{=uhqQ>%kLMqe_iV3MaG-a zQDgQ!@&^;l@^9>qsCee-lZGoZTsqVEfETmofiGHm9{{z#n)lNIz4aXmw@13_=FZa^ zvx}h#laEdY{gO7x9oedk0+@ia|41$v2+ejP%BHP#MN&&~VW6x?Q%>l1jc^)`~g*N;(!jh73N9LAS)qi)U`KdZyr6 zYqvgrM`Gp{k9=HOWdVxl3U{E^$u&mVEa56NpjM&m?%fZvPhikF9PJFz@+!m@jI zu1fP-XRT+_MhI_lA7yYi2y_Ya_DWJKBr+hWgSAcBv@~m?BDo-`j@j*HKH~-A^uZOxRXou-F^E5d?bY9IqT3ko4ez z3r**ZuV;Xj_Y0C^QS$dk8D>f6+1NUUKWkgSul2FLM>K8gKhFWS2D4ltq{WyTs=|fP zQtZV0yG;y0%AYcSAE5C*LdGiQP4%I;Ix^_e>V~ZF9scoNom-+mlu6yXmI~7i3E_aJNj3y z2;Yb=56{9;QH#A5V%7TfAkY^MX%A_!a@5oL%X7=8D8D7TL8sq}@pc+oWxT0pyP91``SOR)Iuh^9Q? z&^x8%?BULS+zuR&wb|y9_p|MvXy4!LotWVm5;1DX+Wh2s?GjzLGiYk+!**agO>6yL z&<;&E)qYv`-i-q@Ce>GN0iy0=+^HQj=aiNiR%g4-lTvZiS%6-@HU5$HzSxQHRDVU2 zv}bPXRQH#WqVKsJw~Sh~ugDq>3@r|pt%aFNU8l3(0v;)&&sPXP9brxz2SzCStFF{S zO-pA-JR^WO0u(duAFUeEqbjr}jU^2WQ6@&6?J-(4h69jxz3=^euI@J*?Oyq^UI^&W@gzfg=7 zw8|u&T-zSB(>qr+Vo1T?JkKGF;t9x#<9go*9Jv}akPG3BB<(1&kkYf2B>qwDr5CBs za^fbG>c_Fy-hauK29!^DYVWeKvJ9%xeDkx&{T7_9W{8dLje%_ zH;0Al6VOLqECxh=K2q`za}b99l~| z+D^7zxYu;c2ha~VFik`E9$~VGqjNVOVL@I9;gg6Hfs#qR)JvjWwx^(L+>h?2a(KM> z=OGY4th`G*l-#;M(({GPd77^3%8E?Iw6)Atpd0e8EOiuEv8M_tL7(ZdQAt2?&<2n1 z1jbyOtqpr2<3zj9n?m0d)oAzt5xjG{-oFo*J>mX-{?I(>CrpN4D7DlZPzJ%TliXHe z#;|oKwA^&W7_f46XoDnV&aogBZ!c&K_%3m>`?9XuXVx}JLOG7-my;IKYqnwZ-6wOC zjf{Y#kB3!BMqJo=5KXGg%4p;+N)hJLWhE!|jg(QV@TiUO`lV)_`;IXJ&iqgT)!TVZhW)V6lKM z1)k7XWG*h#-$>ynVO_|MrEM~R_>Do3vNvI71^b$X1AE_%*ZF<*`~!*p1>=HLF=t-_ zxagEyl&X|UtM0v3$W zQJyiyjlV3Mn*fRl=#rw}7uMk|C^Yw4Q1Ap$2ti*Ck&J{HcyZ%cQ5cW6BI1ohYaRpg zTV4Bg*hadYbDuD`YY#3(-aIjExFiW_BjXX!)Ne?kQi=uf~P3j+Pw-&p@$4L}JJiKHQ=^4}TvhxtD`JTTC$ zKj>;%(4Ie=Vj$3;z(55A`m>v~5zzVrfvo$kX1?hk=pDi z&3zG6s=?SsP{2KoMGFK@ubpLP8!9a5rlVM1y>#~*^B?Vxo@iU63(_L&DA(68jgcBM zMD85Spd88x(4&s9xhO6ReXXVO#Px-VW5RKwxRE$QspDdqi21BIw9Gx&X^d|2xPV4q zS?{`6;1NhzZqls`YnBBKYK);7D7?dp+QRm+J*NG|!R@YLRiDwZ> zj>Qi+_m{1mi?}McnFxl0$(czPIbJ#QE)_$VQ-Ll%d7F4H1i;&||*^ zi@h$2-kf~~FywQu!eD~cv34 zh=x@>_%nNUqzE%9gaFuL2Wmn`Y`40SA!$y9XY*yqbY68j;mUAwR&|A|zSVN*^2#d@ zccv}ht?vwVxsiH(w1nU+nlMFIFmyw8o6*XD`6pa+&fY8FHQNrEg7I#`!_df!tWbWv zlj=gSSwl8%v`=H_8tnl%3t{^^VpT!iseS9tuFn#M&h zO@HS14arb9R_6x7J@%<1N91xA4@`BKyvlAZjDawVi`Mhd{UD1CsxY2j61a(Ez~!Gc zt4>|=BDkOjh8~%MI}F%SLcC41VQ+5)SC%k!aPK$x34|ai-OlR(M1IH#OvUdzBuPLd zYv%-(l0w0;rph*gV>})1TwLepbrcN;cZiCG&zo8!4>1psG^N|edG+O4f-H473+PlW zm>p3|P|rwQntfk(6cTnP%`2q10$0RJcgqKw4!-I7(%na&#!#d$rjf=UKMT#NeijNY z(#3#9Ii=YCJ*y3jh0OA&4GA-Z8Y*j(xhZy}D z(#cVQ7U4c?>u&XtK&X4Fy4eO*{SK=rze|@s+G7bfsuuc}H~8>bs}GWXGyfsQY`9#7e?ESey_}P`?*PP+6(UD6 z8b!_z`fD|O`-(Tq)7yh|yEmIpSXv)e!M6U5>ZO(zRz9@S2lQ$Bm8r@UWjZ3Q7i{$| zbYwU=4)t4f)-Qe0zX!5;=;3I)HmW>Vv=55c8NceJE?62xI=Yfge(`7WsMOduuC7RP zC`?i^QaR2pCmY|Wsl*blEji@rX&FBb?9oiuzE6vTg?yrz%5rzap|Ylb>|~#B>0Qpw z>pOrCK1XUk0+sou0%uR7HmYmMi#EAcA9vcHLNPjaH?88?>}!oCOQ@F^yXn_wrbHFL zpEC?zYDmv?nE4^WH{LB&FLCnoql)#2rFK>8yY0`|2cnA+L}M*SaGy2$Fh>4S<(TLM z!|VhZy_7(kuPl=B)sf?uEhEU5{C0XT&IWGaMW^BP#|HL^Qt;q z{^@1&yIf$}z6@?byY?hQzh=5annx%{YmP5JF2`_I2smC6Jus_ZN9m<4+uPMtx$NOD zRjTD@H>mys(28D6=1Q)W-N(d;K#wDwQP(SuuE1SBL;~ zt3*LmU8+xHTK}MWcKRhb^k*-pq$I8aX#L% z9BhbsX|=0xg?RX>75m!}sQX|bY`dsJQKa#aBt1UZm8bg2ml>w&!ipR^r0VqG9(*_y~U7Xi$KT$VlH9qk?bF#JlRJqC~@eIIX{`xb`rN*qvk5q&$Mw&P`^@w_y zh_K1nEOZIJ)ZuAAucozjIJ!kuDG@ROAK0U_9w@l8M@z=;stLars8cS5TE551r z@$#wE{c-?Ixc?rLn=kb!?@7L=>Ox}c!X#bxH|wEOROVafF}R;p7OLgxR+HVCO@)a$ zXRjkW+!9o(IO$Iosa8-_zb|x+8oJPf%s5amqh9jNvHz&Ns6t+J)@!(<9MhiKKsj1; zkyqJYrk(9}z0lV6Nw^rHx>iU&HH^*X6)@z$Bs{&vg7cp6>!<=1BP8;9wx z3-B0B=2T@AhPk2gSr{#&##q<)b>h6=Ys=x&RIn;13~T?IID2A+MUeW5;t$71p>Tl% z(fJ4p$|pxDJmUenxWuO(GdKWjkV~3zFv1eAJRqN@T%^~n&3hH>pU$1!^|@OydS+OU1 zVBZeE1@YO4-AvJ7U;nL#F~e7LbblBn!a}g2Kb&i=ZNIY?mYbucie2)o4X9@;kfqK1 zR+t2x0q^Cc#>1uMB|aHt)uVHFUk6AIt2YYG(6247|4ktIZvgZ=q4fWN9wkK6-+}A* zzdZp!=D)(b{~M6}PLoN1s|0`&g8_czANcs+^#6%Z|4RU>EJr?Z{VNiVJ;94itmq=y z=bKdT?HZj=N}cq#!`|%CliaEQhd=LXr^M?cdd_%2>z3ps@3J(vGb=rP>CXQGi1!f&