PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/saltstack/salt/lib/python3.10/site-packages/salt/modules
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/mac_user.py
""" Manage users on Mac OS 10.7+ .. important:: If you feel that Salt should be using this module to manage users on a minion, and it is using a different module (or gives an error similar to *'user.info' is not available*), see :ref:`here <module-provider-override>`. """ import logging import time import salt.utils.args import salt.utils.data import salt.utils.decorators.path import salt.utils.files import salt.utils.stringutils import salt.utils.user from salt.exceptions import CommandExecutionError, SaltInvocationError try: import pwd except ImportError: pass log = logging.getLogger(__name__) # Define the module's virtual name __virtualname__ = "user" def __virtual__(): if __grains__.get("kernel") != "Darwin" or __grains__["osrelease_info"] < (10, 7): return (False, "Only available on Mac OS 10.7+ systems") else: return __virtualname__ def _flush_dscl_cache(): """ Flush dscl cache """ __salt__["cmd.run"](["dscacheutil", "-flushcache"], python_shell=False) def _dscl(cmd, ctype="create"): """ Run a dscl -create command """ if __grains__["osrelease_info"] < (10, 8): source, noderoot = ".", "" else: source, noderoot = "localhost", "/Local/Default" if noderoot: cmd[0] = noderoot + cmd[0] return __salt__["cmd.run_all"]( ["dscl", source, "-" + ctype] + cmd, output_loglevel="quiet" if ctype == "passwd" else "debug", python_shell=False, ) def _first_avail_uid(): uids = {x.pw_uid for x in pwd.getpwall()} for idx in range(501, 2**24): if idx not in uids: return idx def add( name, uid=None, gid=None, groups=None, home=None, shell=None, fullname=None, createhome=True, **kwargs, ): """ Add a user to the minion CLI Example: .. code-block:: bash salt '*' user.add name <uid> <gid> <groups> <home> <shell> """ if info(name): raise CommandExecutionError(f"User '{name}' already exists") if salt.utils.stringutils.contains_whitespace(name): raise SaltInvocationError("Username cannot contain whitespace") if uid is None: uid = _first_avail_uid() if gid is None: gid = 20 # gid 20 == 'staff', the default group if home is None: home = f"/Users/{name}" if shell is None: shell = "/bin/bash" if fullname is None: fullname = "" if not isinstance(uid, int): raise SaltInvocationError("uid must be an integer") if not isinstance(gid, int): raise SaltInvocationError("gid must be an integer") name_path = f"/Users/{name}" _dscl([name_path, "UniqueID", uid]) _dscl([name_path, "PrimaryGroupID", gid]) _dscl([name_path, "UserShell", shell]) _dscl([name_path, "NFSHomeDirectory", home]) _dscl([name_path, "RealName", fullname]) # Make sure home directory exists if createhome: __salt__["file.mkdir"](home, user=uid, group=gid) # dscl buffers changes, sleep before setting group membership time.sleep(1) if groups: chgroups(name, groups) return True def delete(name, remove=False, force=False): """ Remove a user from the minion CLI Example: .. code-block:: bash salt '*' user.delete name remove=True force=True """ if salt.utils.stringutils.contains_whitespace(name): raise SaltInvocationError("Username cannot contain whitespace") user_info = info(name) if not user_info: return True # force is added for compatibility with user.absent state function if force: log.warning("force option is unsupported on MacOS, ignoring") # Remove from any groups other than primary group. Needs to be done since # group membership is managed separately from users and an entry for the # user will persist even after the user is removed. chgroups(name, ()) ret = _dscl([f"/Users/{name}"], ctype="delete")["retcode"] == 0 if ret and remove: # remove home directory from filesystem __salt__["file.remove"](user_info["home"]) return ret def getent(refresh=False): """ Return the list of all info for all users CLI Example: .. code-block:: bash salt '*' user.getent """ if "user.getent" in __context__ and not refresh: return __context__["user.getent"] ret = [] for data in pwd.getpwall(): ret.append(_format_info(data)) __context__["user.getent"] = ret return ret def chuid(name, uid): """ Change the uid for a named user CLI Example: .. code-block:: bash salt '*' user.chuid foo 4376 """ if not isinstance(uid, int): raise SaltInvocationError("uid must be an integer") pre_info = info(name) if not pre_info: raise CommandExecutionError(f"User '{name}' does not exist") if uid == pre_info["uid"]: return True _dscl([f"/Users/{name}", "UniqueID", pre_info["uid"], uid], ctype="change") # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) return info(name).get("uid") == uid def chgid(name, gid): """ Change the default group of the user CLI Example: .. code-block:: bash salt '*' user.chgid foo 4376 """ if not isinstance(gid, int): raise SaltInvocationError("gid must be an integer") pre_info = info(name) if not pre_info: raise CommandExecutionError(f"User '{name}' does not exist") if gid == pre_info["gid"]: return True _dscl( [f"/Users/{name}", "PrimaryGroupID", pre_info["gid"], gid], ctype="change", ) # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) return info(name).get("gid") == gid def chshell(name, shell): """ Change the default shell of the user CLI Example: .. code-block:: bash salt '*' user.chshell foo /bin/zsh """ pre_info = info(name) if not pre_info: raise CommandExecutionError(f"User '{name}' does not exist") if shell == pre_info["shell"]: return True _dscl( [f"/Users/{name}", "UserShell", pre_info["shell"], shell], ctype="change", ) # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) return info(name).get("shell") == shell def chhome(name, home, **kwargs): """ Change the home directory of the user CLI Example: .. code-block:: bash salt '*' user.chhome foo /Users/foo """ kwargs = salt.utils.args.clean_kwargs(**kwargs) persist = kwargs.pop("persist", False) if kwargs: salt.utils.args.invalid_kwargs(kwargs) if persist: log.info("Ignoring unsupported 'persist' argument to user.chhome") pre_info = info(name) if not pre_info: raise CommandExecutionError(f"User '{name}' does not exist") if home == pre_info["home"]: return True _dscl( [f"/Users/{name}", "NFSHomeDirectory", pre_info["home"], home], ctype="change", ) # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) return info(name).get("home") == home def chfullname(name, fullname): """ Change the user's Full Name CLI Example: .. code-block:: bash salt '*' user.chfullname foo 'Foo Bar' """ fullname = salt.utils.data.decode(fullname) pre_info = info(name) if not pre_info: raise CommandExecutionError(f"User '{name}' does not exist") pre_info["fullname"] = salt.utils.data.decode(pre_info["fullname"]) if fullname == pre_info["fullname"]: return True _dscl( [f"/Users/{name}", "RealName", fullname], # use a 'create' command, because a 'change' command would fail if # current fullname is an empty string. The 'create' will just overwrite # this field. ctype="create", ) # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) current = salt.utils.data.decode(info(name).get("fullname")) return current == fullname def chgroups(name, groups, append=False): """ Change the groups to which the user belongs. Note that the user's primary group does not have to be one of the groups passed, membership in the user's primary group is automatically assumed. groups Groups to which the user should belong, can be passed either as a python list or a comma-separated string append Instead of removing user from groups not included in the ``groups`` parameter, just add user to any groups for which they are not members CLI Example: .. code-block:: bash salt '*' user.chgroups foo wheel,root """ ### NOTE: **args isn't used here but needs to be included in this ### function for compatibility with the user.present state uinfo = info(name) if not uinfo: raise CommandExecutionError(f"User '{name}' does not exist") if isinstance(groups, str): groups = groups.split(",") bad_groups = [x for x in groups if salt.utils.stringutils.contains_whitespace(x)] if bad_groups: raise SaltInvocationError( "Invalid group name(s): {}".format(", ".join(bad_groups)) ) ugrps = set(list_groups(name)) desired = {str(x) for x in groups if bool(str(x))} primary_group = __salt__["file.gid_to_group"](uinfo["gid"]) if primary_group: desired.add(primary_group) if ugrps == desired: return True # Add groups from which user is missing for group in desired - ugrps: _dscl([f"/Groups/{group}", "GroupMembership", name], ctype="append") if not append: # Remove from extra groups for group in ugrps - desired: _dscl([f"/Groups/{group}", "GroupMembership", name], ctype="delete") time.sleep(1) return set(list_groups(name)) == desired def info(name): """ Return user information CLI Example: .. code-block:: bash salt '*' user.info root """ try: # pwd.getpwnam seems to cache weirdly, after an account is # deleted, it still returns data. Let's not use it data = next(iter(x for x in pwd.getpwall() if x.pw_name == name)) except StopIteration: return {} else: return _format_info(data) def _format_info(data): """ Return user information in a pretty way """ return { "gid": data.pw_gid, "groups": list_groups(data.pw_name), "home": data.pw_dir, "name": data.pw_name, "shell": data.pw_shell, "uid": data.pw_uid, "fullname": data.pw_gecos, } @salt.utils.decorators.path.which("id") def primary_group(name): """ Return the primary group of the named user .. versionadded:: 2016.3.0 CLI Example: .. code-block:: bash salt '*' user.primary_group saltadmin """ return __salt__["cmd.run"](["id", "-g", "-n", name]) def list_groups(name): """ Return a list of groups the named user belongs to. name The name of the user for which to list groups. Starting in Salt 2016.11.0, all groups for the user, including groups beginning with an underscore will be listed. .. versionchanged:: 2016.11.0 CLI Example: .. code-block:: bash salt '*' user.list_groups foo """ groups = [group for group in salt.utils.user.get_group_list(name)] return groups def list_users(): """ Return a list of all users CLI Example: .. code-block:: bash salt '*' user.list_users """ users = _dscl(["/users"], "list")["stdout"] return users.split() def rename(name, new_name): """ Change the username for a named user CLI Example: .. code-block:: bash salt '*' user.rename name new_name """ current_info = info(name) if not current_info: raise CommandExecutionError(f"User '{name}' does not exist") new_info = info(new_name) if new_info: raise CommandExecutionError(f"User '{new_name}' already exists") _dscl([f"/Users/{name}", "RecordName", name, new_name], ctype="change") # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) return info(new_name).get("RecordName") == new_name def get_auto_login(): """ .. versionadded:: 2016.3.0 Gets the current setting for Auto Login :return: If enabled, returns the user name, otherwise returns False :rtype: str, bool CLI Example: .. code-block:: bash salt '*' user.get_auto_login """ cmd = [ "defaults", "read", "/Library/Preferences/com.apple.loginwindow.plist", "autoLoginUser", ] ret = __salt__["cmd.run_all"](cmd, ignore_retcode=True) return False if ret["retcode"] else ret["stdout"] def _kcpassword(password): """ Internal function for obfuscating the password used for AutoLogin This is later written as the contents of the ``/etc/kcpassword`` file .. versionadded:: 2017.7.3 Adapted from: https://github.com/timsutton/osx-vm-templates/blob/master/scripts/support/set_kcpassword.py Args: password(str): The password to obfuscate Returns: str: The obfuscated password """ # The magic 11 bytes - these are just repeated # 0x7D 0x89 0x52 0x23 0xD2 0xBC 0xDD 0xEA 0xA3 0xB9 0x1F key = [125, 137, 82, 35, 210, 188, 221, 234, 163, 185, 31] key_len = len(key) + 1 # macOS adds an extra byte for the trailing null # Convert each character to a byte and add a trailing null password = list(map(ord, password)) + [0] # pad password length out to an even multiple of key length remainder = len(password) % key_len if remainder > 0: password = password + [0] * (key_len - remainder) # Break the password into chunks the size of len(key) (11) for chunk_index in range(0, len(password), len(key)): # Reset the key_index to 0 for each iteration key_index = 0 # Do an XOR on each character of that chunk of the password with the # corresponding item in the key # The length of the password, or the length of the key, whichever is # smaller for password_index in range( chunk_index, min(chunk_index + len(key), len(password)) ): password[password_index] = password[password_index] ^ key[key_index] key_index += 1 # Return the raw bytes return bytes(password) def enable_auto_login(name, password): """ .. versionadded:: 2016.3.0 Configures the machine to auto login with the specified user Args: name (str): The user account use for auto login password (str): The password to user for auto login .. versionadded:: 2017.7.3 Returns: bool: ``True`` if successful, otherwise ``False`` CLI Example: .. code-block:: bash salt '*' user.enable_auto_login stevej """ # Make the entry into the defaults file cmd = [ "defaults", "write", "/Library/Preferences/com.apple.loginwindow.plist", "autoLoginUser", name, ] __salt__["cmd.run"](cmd) current = get_auto_login() # Create/Update the kcpassword file with an obfuscated password o_password = _kcpassword(password=password) with salt.utils.files.set_umask(0o077): with salt.utils.files.fopen("/etc/kcpassword", "wb") as fd: fd.write(o_password) return current if isinstance(current, bool) else current.lower() == name.lower() def disable_auto_login(): """ .. versionadded:: 2016.3.0 Disables auto login on the machine Returns: bool: ``True`` if successful, otherwise ``False`` CLI Example: .. code-block:: bash salt '*' user.disable_auto_login """ # Remove the kcpassword file cmd = "rm -f /etc/kcpassword" __salt__["cmd.run"](cmd) # Remove the entry from the defaults file cmd = [ "defaults", "delete", "/Library/Preferences/com.apple.loginwindow.plist", "autoLoginUser", ] __salt__["cmd.run"](cmd) return True if not get_auto_login() else False