credentials/0000755000176200001440000000000014476167757012577 5ustar liggesuserscredentials/NAMESPACE0000644000176200001440000000126414402461137013773 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(credential_approve) export(credential_fill) export(credential_helper_get) export(credential_helper_list) export(credential_helper_set) export(credential_reject) export(git_credential_ask) export(git_credential_forget) export(git_credential_update) export(set_github_pat) export(ssh_agent_add) export(ssh_home) export(ssh_key_info) export(ssh_keygen) export(ssh_read_key) export(ssh_setup_github) export(ssh_update_passphrase) importFrom(askpass,askpass) importFrom(askpass,ssh_askpass) importFrom(openssl,read_key) importFrom(openssl,read_pubkey) importFrom(openssl,write_pem) importFrom(openssl,write_pkcs1) importFrom(openssl,write_ssh) credentials/LICENSE0000644000176200001440000000005114402461137013552 0ustar liggesusersYEAR: 2020 COPYRIGHT HOLDER: Jeroen Ooms credentials/man/0000755000176200001440000000000014402461137013324 5ustar liggesuserscredentials/man/set_github_pat.Rd0000644000176200001440000000240614402461137016616 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/github-pat.R \name{set_github_pat} \alias{set_github_pat} \title{Set your Github Personal Access Token} \usage{ set_github_pat(force_new = FALSE, validate = interactive(), verbose = validate) } \arguments{ \item{force_new}{forget existing pat, always ask for new one.} \item{validate}{checks with the github API that this token works. Defaults to \code{TRUE} only in an interactive R session (not when running e.g. CMD check).} \item{verbose}{prints a message showing the credential helper and PAT owner.} } \value{ Returns \code{TRUE} if a valid GITHUB_PAT was set, and FALSE if not. } \description{ Populates the \code{GITHUB_PAT} environment variable using the \link[=http_credentials]{git_credential} manager, which \code{git} itself uses for storing passwords. The credential manager returns stored credentials if available, and securely prompt the user for credentials when needed. } \details{ Packages that require a \code{GITHUB_PAT} can call this function to automatically set the \code{GITHUB_PAT} when needed. Users may call this function in their \link[=Startup]{.Rprofile} script to automatically set \code{GITHUB_PAT} for each R session without hardcoding any tokens on disk in plain-text. } credentials/man/credential_helper.Rd0000644000176200001440000000152714402461137017271 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/credential-helpers.R \name{credential_helper} \alias{credential_helper} \alias{credential_helper_list} \alias{credential_helper_get} \alias{credential_helper_set} \title{Credential Helpers} \usage{ credential_helper_list() credential_helper_get(global = FALSE) credential_helper_set(helper, global = FALSE) } \arguments{ \item{global}{if FALSE the setting is done per git repository, if TRUE it is in your global user git configuration.} \item{helper}{string with one of the supported helpers from \link{credential_helper_list}} } \description{ Git supports several back-end stores for HTTPS credentials called helpers. Default helpers include \code{cache} and \code{store}, see the \href{https://git-scm.com/docs/gitcredentials}{git-credentials} manual page for details. } credentials/man/credential_api.Rd0000644000176200001440000000506214402461137016561 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/credential-api.R \name{credential_api} \alias{credential_api} \alias{credential_fill} \alias{credential_approve} \alias{credential_reject} \title{Retrieve and store git HTTPS credentials} \usage{ credential_fill(cred, verbose = TRUE) credential_approve(cred, verbose = TRUE) credential_reject(cred, verbose = TRUE) } \arguments{ \item{cred}{named list with at least fields \code{protocol} and \code{host} and optionally also \code{path}, \code{username} ,\code{password}.} \item{verbose}{emit some useful output about what is happening} } \description{ Low-level wrappers for the \href{https://git-scm.com/docs/git-credential}{git-credential} command line tool. Try the user-friendly \link{git_credential_ask} and \link{git_credential_update} functions first. } \details{ The \link{credential_fill} function looks up credentials for a given host, and if none exists it will attempt to prompt the user for new credentials. Upon success it returns a list with the same \code{protocol} and \code{host} fields as the \code{cred} input, and additional \code{username} and \code{password} fields. After you have tried to authenticate the provided credentials, you can report back if the credentials were valid or not. Call \link{credential_approve} and \link{credential_reject} with the \code{cred} that was returned by \link{credential_fill} in order to validate or invalidate a credential from the store. Because git credential interacts with the system password manager, the appearance of the prompts vary by OS and R frontend. Note that \link{credential_fill} should only be used interactively, because it may require the user to enter credentials or unlock the system keychain. On the other hand \link{credential_approve} and \link{credential_reject} are non-interactive and could be used to save or delete credentials in a scripted program. However note that some credential helpers (e.g. on Windows) have additional security restrictions that limit use of \link{credential_approve} and \link{credential_reject} to credentials that were actually entered by the user via \link{credential_fill}. Here it is not possible at all to update the credential store without user interaction. } \examples{ \donttest{ # Insert example cred example <- list(protocol = "https", host = "example.org", username = "test", password = "secret") credential_approve(example) # Retrieve it from the store cred <- credential_fill(list(protocol = "https", host = "example.org", path = "/foo")) print(cred) # Delete it credential_reject(cred) } } credentials/man/figures/0000755000176200001440000000000014402461137014770 5ustar liggesuserscredentials/man/figures/logo.png0000644000176200001440000013572114402461137016447 0ustar liggesusersPNG  IHDR_ cHRMz&u0`:pQ<bKGDtIME}^tEXtRaw profile type iptc iptc 28 3842494d040400000000000f1c026e00035246471c02000002000400 `=IDATxwx$y NL΁Y,YYl+8Ȳ֖~֯,[e% V6%Q09rCQݍF @]UNsG ?N>Tom᱔B\2UFLj#j쀱?f;z'59ӥJcc[U׶kUx@!_1/c%jXJb,ek^}ژ鼫xrJF5U\{Ft6}ڣ;rߵ待e,+D&v<7`9uj]JUY9+jûV27ܷW9^U<hR'.dve(n0UpwMϟl\)%Bj aǫo׾+4fe\x3 +O=0icMޒ`.RJTE(ߵ:t˾fK}x/9$TX)7Y.:ݫ)ww %l2^"rQ/=?paDxʩq<)7&3!D!MqZv =uR";˸5{i/e(awvߌ:%pyˊЗַ#7;^)Y+>۟gʰL`_ߗqvV+!3tFUceW=Ex! DJOt~ʨ'w(;ߩw@w F+'?|=Ux=9g/g}kC}1[W4tUB BPTAmD.>I>Z[vĖN˸Y-x"p+GuHYsf[*MFY8@SPjPI85 TE(xR`9#1li{,J)pjck3ׅ6vQrO2nsv3u8d*oeNUZF%VAS}ap=H,WblGדrPVnKkuN潽k/.6m#;[]}j1r=)|wsG]¨ 8ʈhB`*{:>TYVc23fP`S{y ˣZANikVT:3vTwy7^F%^x_T_~ta(np(/PpUU)=+A@s뱌3* $g4k o]M߄El@cJgcՑH\okBp<N*5On ʈ1=r?e"xٖ/f Ug&3u3Qp@asGڰbh`+k" .WF%],5[4 ̶`!xƶ ~O[6lܾ2r=e"x<4U-ɼCRv qPj!LIiGB !su=2c:ktJ<%llVs+aۭuNLf%-\:Ou$e7W/e>y)xRfųr雴1u@gs( :Z 'mMlgjGR S}Twª+t2vdsGP}RvA׳щx#(uQ-!CʰЌiw#]ylV Q mAV70mϒ3ܢ zڈʥa量ku- * 2n(/Iy)eд_==Ǭ$mq3Wݫ¤ .'{ oÊSVoJI?l;zZ J; *5Mc`ҪwY=s)渞;7xe.;מO^?piеQ篎-˻Pv"i AZj4L䥝Fc{g]%۞3ʞ5aZj4h&T8[`({UEuE Tghw w6ꟺceRҳw}JMGO 5c)瓗G1E9!#" wZ“pvi4;15h[$]ڃV)6r>|JƭRyWnRBt6/5RQ]yNe| -^Z}k9yg]v:N xA{ FKuX%wo8fD wO\M7|VJĿoUL&ny&_Sł~)`)qG ~r.˱<EH# Ř[r)%a]X{un>.Y̥,L&EpPPQ2bPV/U'M?a!+F/m+K_r tY\2p-TJ̯aY ,4eYH%F'oyd B@YSǖ*B2'^ e޸mX7";L0^=gM)%Ϯ:=d ?&eumZG:w<[FO+aTzcg[*1[ ѠkgW@4A۬XƷwN\3XN\"mx0f7*`(! 5:t"bx]ɱ|1\`\[-3_V虰ׇRjeUH`{ee,c&nK:EV*2vhșthP@aZu(h E;x~vY_-J6ڂk 3<9XAU'J8MϛMVS4&T\P&w)ș%ˤ%o%b|~cY'%{JtMߖeR^LpyԤ؊,[mɁuR?=^J}BEgZuX& '9r_$%\1:j2rc(m nS95߭CA45@LW0=r]()97[Ƌe=?5w-HҰI}TeMoACHv^cزi1kxrE[FgCۑ-0"W[^T=w}0A1G[Ņ=\Uj4_(]߭Gț>ɞ>`mW5[. ˸u,<ɕQMp0o Q ):YD%RdMsݫ4/OM88/HªUM\O5f2Ex&5cͨ1n;UrT -*z4Pnm[9%oiL.pkcY[G|h1kAn*E/?b!SpKOZlj {M hHa"AM!w=͋]yͼ(~s,)ݸð}oz黪BE<T)4Wk誯eXNq6|OZˏWi6?8KY֠AQ"Ys1uCD~!4ܡ/B}-EOL䗮.r\hR_UpIC m pnpY[ z͐"YG#eJۢ}x`k{FЊEѠ ǑUܵ>ʳs\Z|֊;#M#!l <~>A+z n2Ji@SX`]Kq&p\ϓX[yu'P̥;W-eָS1۝֔y)/SrSACkFtڏ ꂼus%.~OIT|XDq\ *=9$ =_d~(p~XD(;7DX,6U+} jB\}|ҁ"OM"C ֵk}8t5P|lWPB`m)K1yE˸5  +py:lֶYl 5akc&ybKclW5z^ϻqn]uܹ>RYr*rWTu6jiCfU!u\xqz ijGzs{otg*_x]BYUI].XkdC[1K!!X& j/-:sAC+u6cVAҝQY2e[|.V{vkV=FUwo(ƅҎBTC߄[$ۃ.{ C 'lQ6=A޵O?#9WA4Up@,p:,{{/ūF̺<{9`UO%ZK6q$T_5uO!aI}$SW.?<~0$8K9GSty%GV Y&S& ΥΛ.C<.ūB3tVDəJ(~MmuXZʈ瞊s'_>t$^0I>tҽѠOL^/J\Rٽ:FKf9g۸Q} -Ii(%'^b%[8PQRB>igNH] `9>g^)|#I{%=\ԝ zpP")^"7!g9V-%bOI=Ļ.ܕk̜:Wg^^(_M-ö+ .9H/1U_϶+t9%bRr|h/dXtUpŽ!6螰8_ g,^|#Oh,eeX:j򭣩Y&Ng2%wBpiȸϡƑV\BK# {z~R\bCǪ&UX!`9trM! ۂOt Γ~J'%"QSQ/r^LZES⏧JcsGxnJkiVB939rߤTwu׃o~TWWBww<}t{CCCadd䉚xGG򖷼h׾qK \(M毯uC/Z"&xIC ao[/4HP!,z&% pHܻ1R櫒0M2l`JZ)b*\k]MhP!⼀й088H0:s̎˗/c=|>(ڰaCݻx7l2$2,㬦-1^6Jsg6[WIYCmX`yh 'lExJa *5Sژ5EYwN(ؕxs?L~3Ho *-C`7M|0NٳmַggΜӶ,.utttGoo_[n nllȈ{፸q 0@Xǻ_H qadZ5`7_JsC!|!4U$MU*"-Np,K)xS05MFѣ3Cu25f-H2w TX89MG@ӦUK&ɫ *l[N#kRH=C/%l2{L:)%-5:BdL<~'>ʕ+L&ģ7Pq<?^x_^[Div/9V5l6; niI_ j{uİOy!oRުʾaֵl6CM VQ-{_Jǽ^c0n\'H$pUW\ӧOڵk&''\);YpP5A n,\ץK.֭[wnݺGGGr߄-+AMPRv!yI$Y/|`po}udDdz55\HW}k#G?iK,-e~72%k22򦡡b3v[٪ʥe6IM^Lԕ%F"|]LXXRXUrUHEFkd g$\ڝrJ^Y :}1 ELÅ 9s̾?p]W\4JY[=,t 40MϓT3-2ǣUFs*6].94Ǐcǎmضm׀k7oFn-r\RTr_o[zrI)5a!EU '3DU)G56{cեa}ir7pxK'8.>}h4J.{Ç?zw_t)Psad8`l4M6S(.hhHٌXA>L8OTyO;::/>c[S\nrΝ7in r UQ(%@"b]OQ&p;'}/y)'LLAk`@5ES=moKW)᫱:7AGNU$^y^X~sT! (cF k,uKR3a"4g_FIj5=ǩ  *###gΜǏʕ+qqx0l8::T*[iP(ts߱b*z ReK)~((@K QUZj4vP E>}gH*p;OuHac[!}1%Ұlnom+#&qxKoτ5gR!{FFF>o  wRm%2H& < ѓe;#UtUʹQTo Q/ǧN;GGG@ޮ[J }s贘dFSt? !])Jw9\\RCp@.`,e_)&QDOUt6XG౹=HKF=L/fa MAj*WFLd,-u:+8)M=z}s#we*:٬I"Q v8<8'Ų,\-#8Ufœ(aKf-BB:BVK)<1x≚K.}bw[ju>ЗG  )S9B9>Ouuc푷$啓º%e]BH~9ᑷ< v|F ϛn]xZ{7EhY<6Y7–K sIVGJxJga{_ 08ܕcCkڈdPk$]Tk#*"qqmz{I+;P=E`2̥,KڥV#QbwR29屭3ĝPDBK Vw0)d!6GZzɘ8i9G Pq*#EEbeZ  1105At}vm^ [ϻ޶qƿڶmɡ+n]{ppe֖:¿[OϰsUwaE r0b\Se~Q*bXSD{UKV{_6}6AAA֘Q׬r'6cC׸IVj&F?advvL>tH& Sv̱1σGH6ԃ:CH~cMNO$|ߠ1?:oaNY2v|>놇ߵk׮7oڵ -z^ ~3uf/N\Vyi ̹ [:KʀRool Ḓx]UiRyM$gM(ɼH .ã.Д~$U屡X{؎GMw=ɢ0(35e禒Y2)]y^ PTDU ΟR2&ҒI@ ԰I"(eO\s-lEU4MІH$o_D0'>ԧ>⿠@7w$ OJZj4~Mܵ!r]Yp9;`pj#ӑxEZ}!ypEhut嫇$mt1mq[l9o` 't";WE)pY}b1re׏&rY l”Zyϼ]#d (-kc !3r=-K2uZ$si")}r$ U*m)HcYlWYszÜkJÝk.A2QP鏋&XQL[(^֪"8{*x6rH%G`B>N"X588###|3RJ("PoN̥,:7di0mWs|`=%Ea 8*lV/WV?^cqD?hfhaTi#!`OU&8 _G)`DP 0sɱ!*Ӳ\yYں0!NŴLe||O{zz޳e˖ܵkס䋝_lԩΗlW,S֎ВsofYV5Jj K"8ÇqF!sعAWGMϓ-0qxZ_d$L,YrF6G1AMBw4!t?h5Ү(p yuU[wE\vxE2;|:O?x\mދ.aGdLo@YKCQ_zWAIlѰdׇD(LJV\-[~rr83gl Z.hڃs.É7f\2xZV6>MdOJYCWs˓vǿxd3~X(d LL;%>!wط߭ \NIM'xi\,GP(qLp#[9f+9H#MCJ)Bަ6D]]O˜lqrr2p~e_yֲ>o۶퓩Tl.3;::n=JlHnl8%KKFxڷO׵۬liiiVq<* ҆GpI]~p&U11j1@"'d )%ě8GlyXmPr/Q0V2cIO?QY7>]N\v .;$sBI];tm7J<'Ъ1wU[3Du{&9jy' 2661mWnQxpF}T˵1)!7vF1PSS͎[ \E6cR!QJ$RT/իWΝ"OLLȖ~.rC(MMpnWu$J5'{ <- ַhո6f^)n?qdO )]YP[j5 . C%~+ H;3|7vX'Ld㇅Dž4B+[(ȱ>g M= QZT wNB ǎ&HSfEW QvjMD;IV|hr ?`,s6Q^'{ꩃV31_0H)ٽ{;Ng'^f([,GGGyG+Vzlٲ'ϟOl߾iF{6ݣ)q_B\9.כ89UpTAMDe4itud`Ҧ:003ƙ1ݚumÇOT<Q\RKPҥP0H3ә23i:LNe-j QkC6щx;wo477{{Y<%d=yTy |%u&s-)Yy!bE[|GT]TV/f!%o λLd?t!Ө xs8]?ĝ8[.\g8'LL,܊]R+ʹn2XI*G;pukWaGfllbj%F,m1sN8[V2~sywCe{wu"ard.s F+lnTmLAsV;GFFX,Z[[puuuСCs\TQ̹rnxHe~iJ^,7h8[Us4ژd)GPxjmtUpq$ܳ1ʅ!=-% t])JLz7مt7'ֱ]ihΟ(Hs/tZ7Q+t!OAJ477c}w(g+syK ,^{,Slq+WImH-8zp"ܬ˲VhJuu0>vUU-[\ڽ{׷n&''9^Bcfjecw쉞/]6~1qjv*+t. r] tT7^$R~VJ"AE@S% rC., .+H0Eg3qie=WXȩ(Y Jr!~Z?|:O̽ >PUxoNU *'뺌:=qw hϒmkuIǷ(oyS)2UM*ԯFD*^;[pttϟ?k|;>-)>ɵ- +?ZVτu.5VBid4˸g\l: eR9=釚"mX:uV5d =bR#s҃Ç};7CJo^):ǫ=uQaf Lz&:' E$ Td5vK)!u/q}~GT‡kH~e1 tXGd<=#(DEVۀPuM\Ŋ.D1-.Luv?ۢZf͚/=sÎ;MNN֭+3g7"4s˞pǪǭɜ]v*)3_jlj rfP(.%`sƆ":j2%upN KlEM!4H )M!__[8KtJxƁ\r!d޼a.g*YG\NG#(8plR0\X*Y\ IJOs!=͈@B=[|j|η3YʲEf-fY.\4::w;wn޼ Rŋm}yuXwq<֕^5yshX(U:CA7:Rx01X-QZj49&Up j{7Eg]6hNNr؃W9wQm{K.d\Znίby~!ɉSƼ\m-Zq |^HT6$J| tEA]{oCg@ciLCUK >r@@_a$iSQə]cV^g Y^RY$y?z @ss#kp]x]޽ KNK)~*rфpae|93aFvF %T|>jmG\qa'qv ǶI=*?ڸaIGx(G3C+ٲ"DgNu/,I.xLIVL{Je~Ic2022Ɖg/L-JUflWT;mAe ܡ2{^O O|Q0s! h}ku|Nk5M̈yT~̜XyIYo(/p*&3/דPH<ޛ)b)/͆H,GRVHH@S6qPl9}etZt-D$S!rd8i<gM C 4ߎ9ը 7.xs/g'qӖ#, oP(d I2/RWWƍH$R8S偓.P\rgFFI#g.xy';Kxˇ*"\;2r[lW U<\csnei-d ak. zNJ бTlψANyUxX>CosЊ=C+_>HdII.ҥk#xJNkN枸ceQsL}=EnY%41MTVϕ:fS!9屢^'햂顩 _=O3afC .F1d s WYE*6W I>E v,z[@*m{5u{<~cYzqI6NLj ]-`ttMd!`xF%|MÙMboޏ8sSGCC^qz%eض}hӧΓ*鯥e)N+u\6I7?:OP*_Z6#rܞ楜$q-!ְ}Ι  s&CSUG*p*(_$1)HJ>?JqMAFFzKzUQ͑'9x8H5^yD*еYBWpCLW$KSki~mJ,1OCkp[iyCK*yr\;#y46 ۿgN8lr$/ Xn5@kze D/C/oj>kN9ÛBt )ܽ!hҰqݴ:qQR[K[AH=I(@`&QhY[0R=9k 0O*s9q RP'~yAkk?o_/LKǔz/ZWsߑ˩)ZE8¤o#,)j{߄NЏN-Tb +jQ^O-[f]*$W]wlHETFt*1*A͖[]]ϼ <}:;~l65\`࿽uwssYO&4qcg~͕z') sDaECޘMwzMgMz}骗;"$)RDšU!\551'rrKiGgg;Hw/~BH' H޹U/F^ATHZ MO5bNԉA.9gs$G%~/0xvuR='mlCj 5Bp΅>)at}EU5rܜU"&/GykaJ5pbrkR\`'ܵ:|&H`~_o>v=ɉ-Q6w97`0\Tybj5aJE(i79*\ҏ?bIB ?%*. b6hZO!-3;󷊰FYxǦR6JMPJR(3:NSa\T\kůRʖoa. 292rAbSGp]+1XXpP@ ;^ܼ3m»uK_Z|~zc<ˏu}EBMXuKVQK*Tp9ڕ9o]Ã۪X$hMnBJt 7Дi R}nRQ*ivs௥IWPkЃЉ6@ ?a,H&SFrz ߶+x*dSD~u_{J^AKk3k׮ByG*]?eױ6']8\s 5J9cڂ#&g*5ˋ~ד83O=uAFGy ES^f_WR%kz^n\J: %l:jj)0oJ9:-pVORVi۴9-ef)|ת*.;ږ9:M6k{}^ed!m !@oCZӚq{ɓgbb/~|kN*!ٿg#xt]c|".K(m)8 Nx/_'(R?M-xM^t!p?)K DFk -|ꝑ8o>XD";G!T#$y 25! d>9 SBS|g7e !ҡ; ==c6J V= rYLӢrBbp`'35SUxܥo/GBS7-=ޯ#wb-FoqJXJFMڅZ/ݷ9tU)C- s:TUo~ẅ́#a=M>__wld }Q V|>ze;Cf̆Zm،O$1 k^u6*A02G5@AU;PXM3BO#Z~}s;f㾊"xpk.p!4B7=ȇ?򳴷py2ܴ+کIK-G2s'dfK 7aٻ?!h \Tjkvb;p%XɷG.WF?SMX{&RoE:}e6D87`pqȠ?f)-Ai.X.:G7~Cgl ߓ뺜>}z._F.B v++B ?ks6 DYJ+^+p 5-`A5ի87L,n)wrxG"'gUDKwW1wuqrg؅ǡ#y.^6D'"^|<Î[3g -*;5;o̽liGJ)ʧt♻j_K h~uY9ʳ=ȁu,l4d}kqd7VkOl~wT?ry%9}կ0͇!S If$L=ZFiB,$t'|[؉xў;/ cJF޴`zWGwoi8;a,waRi{ t\Y` maxG@pg!%;LXߏH\3BW^ +fU~8!5m=g]͙I*ԛ5jÂC,O> W.Ws6'ϧOΟiw|H KP&-2量kCz݊}tħ>(\sZ~iǫJ8]CBإ\v !, oh4x='P21j0nbwRÎs\w= V!)`ѹ:_?z> O\qΕ; ؍(lݶO}stw/.T 0$c/Fr4l|`PW h_ۚ΄wܱm7ÉgL*k͛sH_~/x|Fcߦ[J_JES2cҒ`,OT=nY(C "Y]Uu]aW sq~H g&v߷rPXm }NoSKٴ־»H|U:Rv֮];|'NR<`a1#q)ǹ'VLդnA`2<4ʕ022F&` ?d}QWG9W)f5h+Ïn ᓮ'3/6n%r(A1L_Lo3/XΆk ^UVpĹ{oOb"8T/~B:W- 5ľl흡kxDɬVÁ'{-gc[^ QDKeLk!455onvΉyOO?0.^!q5z{etD7gUTOe~xnP1NMU/7i}3^Ƨ_s7~h4dY!(ϟ߱yzxʢ/RsvoU܌/*gWq=Yʚ,xX㑹8L ~RrB駺*J]]-^Ҵ/.|ΜPUuޗ)ZXP1X|ad],9v.*}es aT;υ_)[̇e% yRtJmm5@:]SSE{{k9 )+PUzqH&Ӫ`Ŭf4*DAc^j u뺤R8RJB 5(B)SՖʶmhiiFQo{u Tl;!4M\N~ocE?zI+<͍oozkWpD2Ņ Wʿ~#OwRy;]wi1::s>׮L#hn-ۃr0OA_`;p5f*֬YUٳ@@ d{.L4.?l޼UUky^myO~B\é0Ms!zIя$Hqέ4aY6O<,ccsp 6nZ~ ϐJ;^8z GXa \q\ǥwa-_,(5y0?ѓu>~'9jՏ}~}֐NgHΪUXq1koGٵk;0::Fl2N}};vle֍lٲ…S P}cs5vuMxYo9'R5|,_ʷyC޽^w?39~TٸI6k455i:6lX,UThٳ:2,={Q֬YWsqΕ=oMoyMNɿ,gؖmAÝwA4>waN8{yNMM5m  7n\CvǏs6^+_6ǎ$_(ka2O߿a2YR2gCLc1e(Kgg T|{?B?<}ǡGܿ||)%-,jji~lظ|>_`hh<\|_|lɡve.\L{{ 6av_UBEQؾ} +;;pS'200m;vw=;8rdl$ uݷ@ @oG²,֮]?>֬Yɹ?$Gu\$+W'7?țO< Ǐ$k?Uy8vUUUdjϽ;7_yOa~{kWzz USxCO{G?B`:H ѪQ[VUQTmVί/ʩ<u]oXb;=(3F`ŎL}Q~pG 011󇎓hhD"T*'#'ٻof7eӦxǙ3փu^I>|cO=uYQl.O?O:u.۶9z$#޽;ioo-kK)iimf(BwW_9%k\x d˖ [A*AQbXKv\ȁ;PUQ3@@gン63>㙧'Ը^Pϱx9^(%Y H)~Zt]#KXU]gbtdT*Ccc AWWOc5]wlR[-[6f*,S )ȑs~ZZسwǏ.ۿ@0W9yLK-֯FQҙ,-: ϓL4!HNaz{N|N7?D}}-XϏ~]]B_~(¦Mul6G4aDJBb&/Hz@Je83;P3|(Vs]{ygdBAEmm5c1:iL[Rb:oIDATѣg۷o~ck׮f˖8cY0kkkhh\v?BUUC&x\.?M{BO>,;~\#M3 eH$LKKRJ:;;?ϸBB  ,+H *-E4uz̀k@bi.hop{YzQT={hnnb׮׸prϵ=?w Ilu8wםLpQ  $Y]ebtڽ#GNQW7>_Ź .cG_˾p/İ L ČkEpO>ăǃ-ٴyۿJP[~y\B]o3LeqA0" %uk6ܽAc,1pg/D6 c]]SECc݂ʛ СofEg;?Ş=;cxd#GN8,zTu9y lݶvo_.֯fM31+ T6'ϣ(u8X|Q3c^XӴ8y,_>·?~Ļ6}0A:-\v\e|𹰬BߢLGUo/U~k*AzVn!L<ϥ֣͛-9?_:Q ۷of۶MܹUS9w׮K;S\͛7~ZAKk3#c>|L{8ss HA$q4AX#hd+%[cKǛ{v߳޵wweY Ok8H '0@ r&2htjt# r3]uV/ RJjjvs04[lj& Lrj/ŧ3Z]TV066TT6>?ew.8L.rOHL*\W5"(WYYv; stn:N={(U<&r%;B9P0Dii1wu'`\졿o8+1P;;쬮⡇v?Ydw30Av!&&䪊n#~ EE~{)*g "6F,'fzS߸)%.7hftO$J}}h6'4 DɓGOP꺎y䡇޺Z'F]k .3>>GI.]@ۄ,YlobxdOSVV"9s`0ǓyFqw %躞Ih1s].MfABYY t嗙(ieTDQese:{PUv/PVVyY* EQtgHgkm˲ |c:YXXp[GmHwё-ƅ1O?fO.B>RUUg[:oCB,,,q~VS6(mP^^^`tt|KQm:(B.]lGWQ]]x?kw5ן g\dzjT*ᠬC3==77Y~/cltXTV'|0u^7>ӧ,el,(kҦS^^}ICC=< $ (//E(\xo~{B:-󋟿HIc?M(L2jz rYX6іSDOD`&ތNsF?3>>夸؏njDyst:h8d`p]ߍ1H>pN\.0/t}ŗR]]IAq^~4O])PPXŋ{pzq߈;!CcNH?J^FGAn/^UU C\/+=EŅײBGU.orSU Nݎ祤bV'_xm"waRnv+z۬#\Ͽ¥VVVZ@4a3qBA|G&+4M3gDžبd~.B4i%-ݔk=SBKyERJfgRVVbN$a0rVʪrj즸OJӘ>( K$Ifg3,X,SSB/oi ObnT&㛝( EETAeen M /07?,YANe%XUrMSQQNJ)l$mԊ`KQ< Egf[$2~ . Ԝy;v,Bߎ*]la6hOHo:_KV3&Bv)kū v7ԇx;BUU~ye~ MmR7c|F=9? |c#u8lx=NTN7ri#x{ߝ};p8vLNNm=lg?֙6MsnY}Vt)66A^$t5h:_O]`a!h^=QF/dO|򣴴4i==}g̅ o릩rqxv72LPᅨ%~+7+ }x<ϖ kmp^DP>Gp:4lB uu5hhFbx\iSX@oi8R64hC;_^q\m(rFRQ\>x2lq/ef E)ߋ L!qҼPX@N7 rG?KLOdj\MUUn !H, >31SL琉58 82<]י˄CMR[amӷED"4ѾPTw* m*ŗi ]9wGgw9.smS}y֜'ɪ  ?on>8s|bff>vIR<%imȐ @IsNn)%ac5#xų_ge%˜IRZ^Ra[5 .D"A"@ס{)dzk/6 #vl Ұ]F|ؿ`Dnxv,N'fwgeob@|[`F 6A$ BdL$&s~RUqq+T*rJ$L9 mmeLEy\u !|xmz9z_u^o3A ] 'Ivɍf Sa4,o(Sk~/8RǕ#t=9H1?( raHȞgx͋Ŵ֔yJpR=sFA!()X]ܣޡ&txVϾqfOln11qX 7T,~wWla+bn^r\fz6|;t+ԖYpݓ)rxn;gD#q]Ŷ~ΛPz<iG̍B2.#>i 3YURB")0 Y)CKz9Fn#5v)Fu~nj4Y0tI)Q m8<'"$fbLJ>;xg/^$:Ɖaa Q$,CldEH!.j\Eq>VwvMf M;U} 3ZFtvv -399.BX"qsv !R"S #F p!(,, KTͤF2G ^"%$S̤f#O048JkHlѴ.m/WWW>H\Ṷ*ΰm="9˙p ^׊n:;T<`W\SךTseB=e  6P!i!QPyu"Xp* )$r:ޯ;yy#ƗQqC"%2:١"cb<=m-M`]])Mӫ "e[rVB0jyL.NMČaUYJ Wbp9v q㻷 ŧj>J|dT0E!\>ӀQ_ڔFx*+Rnr. 4ʊ"؅ͦ%Vh}Dz!#McKܦ$2y ae'g{NF3u| ޢXh}5MO5|–r2YA/N?wxeYK ܾKj!j"'hnܰфm ^M rMQAJGUX7[nHA<8D^( |Aq9 cZfˤ@FL!*'9TE(xn]2x 镺n./e=|t/8G5&+LtmIXjiInyv)M2$Y:"-ki<7yh4kAlQ mLhPZZDQD cHt(*r|k|hwX)YZNWl}찫9 5,1|S*Bƻjhr!asݼD|eJ />Vo'g<g;/>R.144MeɩE=fߣCpFBh)UQ_7[WjnPP褠Љft~yEp8h}YƶX^;NP(h2ނKhI9(+(}ṱks}>7I?|o9lN`5c(B`َ蚉i !`DzC8v  #zpͱVlXˤ.[e9V񘹏ia6~w:z{%΋ג9CROsjUNNKgZ|PQQǏOU 3laO/ɂȅm# FIĵzPKx/ Sxnf Ēfa.(*̆t\AH(cɌ k\ODh6s!m|`S%7WQ5=A!# 1k_L3vc[ebek$R',rBFmq(VQY6BAuDrKkѳJ2nC]v{l9 rwDCCsj[??㮻BveD Zpl8];oHb.h2N0LJײc=zdi%+ldz&řsQ'RnRg dEOPT.gT+ hR_7~n U0e#f2IJըxT[Y\!P}65n#ۮ_߷ 0)jb"$$5ZFXsKJݔyp:9l6GeKK˧Ͻ!<^ ԧxGqz-fM /YZYId ?815SPW\ Dz['e_]pYTv,,$7omfHݻʣ-6&5}>J7c|\N< m֝%Gwnq"%x7sMX-e'tN梨̇uSν!([X`~AQSNe,,, -kQ%5c+Ds1硤7Nb͵% <NBۘISwr2vn߼s =~p$u6>b'򙙙 fRQBfٺk"ErK5olLժR)UXr oSչ8$8f(fbgmcUu:^ܹV`q>X밟2K?dTjY$$m iN{Nhh\.jkkc'N诮Ro_wݼ߆X/]t׮]{ V-5J(8vNfU616dBuǟzբ#W{sFEYUHx-n wY4u$EJۺ(b㊊455088T\ag#;iЁfa/Juulhhhmiiy***m{oC$ϟ?ɓ[]]~X,j0kPY ,EYYI4":vMlt8 Q*N*͜ =?+|N;{ڒ\2(-#мid%N>ZLCYYxjg%jPsPPPW ( 455-gDee9vw<ׯfӓ;M$4#nv2i)0.EC"!62r]45Xfy9P}>ruYcح?Gm=WNLj$6QUȲ:Gn]-W9Q XV'2GjU;wrz2{H)|444ÇJ^y-+_(++\zbxx_ V|p$@,cH6Y`:ҞVt2Ԅi -71Fl)e|AELH'BlZ@ʰER(%}kR}ކJj{+mwvPpѣ=ş79~pJ(pawl՜)9Jq|>qfR[ 6*קfv(dž2?MMMMx߾}455}b1}y s=n߻WϟhiiiS畮K$LIB8i>'_^EPmXjȔ 6. (bМF0s3b(p ZvXhM^XUUjfN gaaX,HZ-^0\СC9ө=owWNS^^K.}!G4]'px֔-ib=vlVkzK^0nYW[5)r+|nvXgHPeFъ䅞jg,6 1R*+Y]i97;OoxB_NڰP:Q?ppSSW޿[!/0\@ҥKx=Wwww(d2g2m:plq,D )-):]ƈBJ[RI@9 ta\ X Jˍ'Oz} br'ݍ%sZo6̵a3z(ؘ˽{~塇koo n7 7S?BC/_~kמrJܜe,&)Rex Nsn<'C,.7ec@"}]Vh~`ORBTiu8 BuYZ "uʪ &' vx  ͰbϞ=+>Chaa!w[]78N˗ߞttt 3Ǭ/[ԉDǏLv ] >S%:E]।ü%7M.Lۻ惄z܌OL17;O,E2v xΝ;9q|EE??|O>d… ;̫пعs'@bzzzDU?޹swvZ[[}pxr~Rٹ ba~~qfxmxvTkV#n^9rrcc㗒,v]wf:mmm^WWpxS[([LXlZhy( ;vT QTT10á7-R*Ç466~Ϝ9sfʠzU q83g4-..~f"ّ$,/ ,Fd-fgW݈j+-a0Z ώe]ǂ%qTMMڻw;6766ٳV["/yl (?loolHǁ(lXQNQ #9MRV^K?,NZnz;rss՝qQ. pFzѫOuuu`⦴>i/E3.DԝT}2-Zz< R#-1::bѱsZs;6fcIv;555zmmw?x%FQȇ6B&_y?ݻwŋ pSйe"DO d*}cd۹ںUMiiѳ祿VڱٶrPUUšCN8T*oNQy*D"a?}/_xOOݓlZH%5Bqailjئejq3<7.[4Iu>V"RYQ`p.GFq:U^}]/kvr Uj/Nb.ω͖M0vmnn9pjjjz6LA^x]@QQ?`0}}}ܰb;$HtKo9.w27;AAck;'Nw1G"/y!rtvvvww^r%H vZ3>ڹATUplS__?w>S__F%%%! poLGWgggիW?ɾ}[v[̔-bm*~'n U!}j^x.ׅvůq_L&]@ sFe"Y-r.]w囹ȑ#v2_7ǯ W\OtvvuddF7vFrIIIqr_lnn~FJxoW+g?Y}QŋK*I&9㵪 Gss3 _QQ'---sh4k׮[}kr8[)vZsOO]|w]{ =AMMѣG/744φBuu[qKH$Zʙ3g?600T? e:TU[ZZv>c;ӧD"Hgg';::N+^N= 1.3), sys (>= 2.1), curl, jsonlite, askpass Suggests: testthat, knitr, rmarkdown RoxygenNote: 7.2.1 VignetteBuilder: knitr Language: en-US URL: https://docs.ropensci.org/credentials/ https://r-lib.r-universe.dev/credentials BugReports: https://github.com/r-lib/credentials/issues NeedsCompilation: no Packaged: 2023-09-06 11:28:31 UTC; jeroen Author: Jeroen Ooms [aut, cre] () Maintainer: Jeroen Ooms Repository: CRAN Date/Publication: 2023-09-06 21:32:31 UTC credentials/build/0000755000176200001440000000000014476061137013657 5ustar liggesuserscredentials/build/vignette.rds0000644000176200001440000000033414476061137016216 0ustar liggesusersmO 0~MDbA]t;ڲ1]OS#Wξ \=^a wF>PFQZ0kSEsre[B#aZsƕTVD(Zv5&-]w}0~'@߸bm4df3wp"$= )C~AâUЀBϒVЀQC| !credentials/vignettes/0000755000176200001440000000000014476061137014570 5ustar liggesuserscredentials/vignettes/intro.Rmd0000644000176200001440000001574614476060527016406 0ustar liggesusers--- title: "Managing SSH and Git Credentials in R" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Managing SSH and Git Credentials in R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- The `credentials` package contains tools for configuring and retrieving SSH and HTTPS credentials for use with `git` or other services. It helps users to setup their git installation, and also provides a back-end for packages to authenticate with existing user credentials. ## Two types of remotes ```r library(credentials) ``` ``` ## Found git version 2.41.0 ## Supported HTTPS credential helpers: cache, store ## Found OpenSSH_9.0p1, LibreSSL 3.3.6 ## Default SSH key: /Users/jeroen/.ssh/id_ed25519 ``` Git supports two types of remotes: SSH and HTTPS. These two use completely distinct authentication systems. | | SSH REMOTES | HTTPS REMOTES | |---------|------------------|------------------------| | __url__ | `git@server.com` | `https://server.com` | | __authentication__ | Personal SSH key | Password or PAT | | __stored in__ | file `id_rsa` or `ssh-agent` | `git credential` store | For HTTPS remotes, git authenticates with a username + password. With GitHub, instead of a password, you can also use a [Personal Access Token](https://github.com/settings/tokens) (PAT). PATs are preferred because they provide more granular control of permissions (via the PAT's scopes), you can have many of them for different purposes, you can give them informative names, and they can be selectively revoked. Note that, if you use 2FA with GitHub, you must authenticate with a PAT if you use the HTTPS protocol. For SSH remotes, git shells out to `ssh` on your system, and lets `ssh` take care of authentication. This means you have to setup an ssh key (usually `~/.ssh/id_rsa`) which you then [add to your git profile](https://github.com/settings/ssh/new). ### Special note for Windows Windows does not include a native `git` installation by default. We recommended to use the latest version of [Git for Windows](https://git-scm.com/download/win). This bundle also includes `ssh` and [git credential manager for windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows) which is all you need. Important: ssh keys are stored in your home directory for example: `C:\Users\Jeroen\.ssh\id_rsa`, and __not in the Documents folder__ (which is what R treats as the home sometimes). The `ssh_home()` function shows the correct `.ssh` directory on all platforms. ## Part 1: Storing HTTPS credentials HTTPS remotes do not always require authentication. You can clone from a public repository without providing any credentials. But for pushing, or private repositories, `git` will prompt for a username/password. ``` git clone https://github.com/jeroen/jsonlite ``` To save you from entering your password over and over, git includes a [credential helper](https://git-scm.com/docs/gitcredentials). It has two modes: - `cache`: Cache credentials in memory for a short period of time. - `store`: Store credentials permanently in your operating system password manager. To see which helper is configured for a given repo, run: ```r credential_helper_get() ``` ``` ## [1] "osxkeychain" ``` Most `git` installations default to `store` if supported because it is more convenient and secure. However the look and policy of the git credential store for entering and retrieving passwords can vary by system, because it uses the OS native password manager. ### Accessing the HTTPS Credential Store from R The `credentials` R package provides a wrapper around the `git credential` command line API for reading and saving credentials. The `git_credential_ask()` function looks up suitable credentials for a given URL from the store. If no credentials are available, it will attempt to prompt the user for credentials and return those instead. ```r library(credentials) git_credential_ask('https://example.com') ``` ``` ## $protocol ## [1] "https" ## ## $host ## [1] "example.com" ## ## $username ## [1] "jeroen" ## ## $password ## [1] "supersecret" ## ## attr(,"class") ## [1] "git_credential" ``` The function `git_credential_update()` looks similar but it behaves slightly different: it first removes existing credentials from the store (if any), then prompts the user for a new username/password, and saves these to the store. ```r # This should always prompt for new credentials git_credential_update('https://example.com') ``` In a terminal window this will result in an interactive password prompt. In Windows the user might see something like this (depending on the version of Windows and git configuration): ### Setting your GITHUB_PAT Automatically populate your `GITHUB_PAT` environment variable from the native git credential store. The credential manager will safely prompt the user for credentials when needed. ```r credentials::set_github_pat() ## Using GITHUB_PAT from Jeroen Ooms (credential helper: osxkeychain) ``` Use this function in your `.Rprofile` if you want to automatically set `GITHUB_PAT` for each R session, without hardcoding your secrets in plain text, such as in your `.Renviron` file. ### Non-interactive use Retrieving credentials is by definition interactive, because the user may be required to enter a password or unlock the system keychain. However, saving or deleting credentials can sometimes be done non-interactively, but this depends on which credential helper is used. The manual page for `credential_approve` and `credential_reject` has more details about how to call the basic git credential api. ## Part 2: Managing SSH Keys For SSH remotes, git does not handle authentication itself. Git simply shells out to `ssh` on your system and uses your standard user ssh configuration. Hence authenticating with SSH git remotes comes down to setting up your ssh keys and copying these to your profile. The `credentials` package provides a few utility functions to make this easier. The `ssh_key_info()` function calls out to look up which key `ssh` uses to connect to a given server. This is usually `~/.ssh/id_rsa` unless you have a fancy custom ssh configuration. ```r ssh_key_info() ``` ``` ## $key ## [1] "/Users/jeroen/.ssh/id_ed25519" ## ## $pubkey ## [1] "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILGN+5tybmwKhcxvnwPSKlrp39Ni1gMD0UhV4gCxHg/x ..." ``` The output shows both the path to your (private) key as well as the ssh pubkey string. The latter is what you have to enter in your [GitHub profile](https://github.com/settings/ssh/new) to associate this key with your user account. You will then be automatically authenticated when using GitHub SSH remotes. ### Generating a key To use SSH you need a personal key, which is usually stored in `~/.ssh/id_rsa`. If you do not have a key yet, the `ssh_key_info()` function will automatically ask if you want to generate one. You can also generate a key manually elsewhere using the `ssh_keygen()` function. credentials/vignettes/wincred.png0000755000176200001440000022640614402461137016737 0ustar liggesusersPNG  IHDR=KsRGBgAMA a pHYs%%IR$IDATx^|֙ݻ{wnr7bv8ihb'v{odY]%7[ދEuQU+{b 0!9x88r8l*RJ)RJ)|_=0RN ׿5Əm۶B!B! cݞҕ_t $Hh۶-oߎNcPJ)RJ)\vܜ t H 󘥔RJ)RJ)>+Lw%ȯ6tVRJ)RJ)8lq :LHRRJ)RJ)4{Nea©SurJ)RJ)@:?wQ B J)RJ)@{ =WPJ)RJ)m'LYRJ)RJ)4ЎO0LRJ)RJit&PJ)RJ)4 (RJ)RK RJ)RZ@~.Զ6rkԺte5&xCw }n1xG~h7tfZp }9(RJ)V{_YpʸeX?<vil_[;toܻ݇'OχOzvy(RJ)RZ}O |W)cbМrlQd=me)mvkfȣ~nM)pyk~Re5)ymð0gB[DcJ0|T FcJ1%c\Qǡs~h]@RJ)RJi?.d'\R꽮MV772nH1~`$lvKg>p}mZ|ɧ;Hm׫[zizKg62 mI;h/Z0nd Yq0mL1eS1tx >4} C#0hh]qe;n">>lRھ)RJ)VkTA4 ;vgѕY+ۛE8a¦o[ov_oXd;Wޥ7q*fΙ'l &N1dXi/e W1n>4 ow7vCǮ}QR:]{S\Fn3u|淍aRJ)mxև0}, ϟZ~}g{,@m_CAn|'ӷc[ri3`l$L2eddq/git/KJxHn~lq V= 7vCCw>Eɪزkvjcnxqy,b^ql0!䳠M;7NYgsmn_׌7ŏ0RJ)o2LV sel_2wqߙ0Aum5ئ&ݡ_%e:H()cn#*w[˟˞o<6?:U{cjl\x9t}GUi}Et (S҅a+Fnz.SWbk]%z}Mg~[_Gk&PJ)?&T+3gRLzۏƸL&&|z<@̴ՃM039s,Q)LoNq'aLxL2ou>O?g.yd.n67Gqar|\d \_\}:<4p<hԾ. RЯ-zC:)%HcXA+g C9 =ѿ Aڼ{".}l*jSq#->7ÅL߭pS:]ǞLwSAz5 O叔˨:tw¥OEw;O "ӷgv;iͷw z^a->RJ)PLwn@\eIOXvڕRg {,I Ν;3*ힾj2DE'Lp%oo ۝1aT :GdDq˨peD\l .m: ?<e_~<26W;'wB|Axxާxvi}h,+]v^%jﺖy|^i/~> w"ͳLZۤmӰ]GV=z'ld|=h\&xzMF8ZIuʸܙgб[cRJ)R]]x6Jy欕L5;2nXFY(cٺukJyIoX ܕ;Ҏ &\}ml_O٪eowz<[l1i݋_~Y:as,Q|e'LX!&վ߮'JOƀAw0vŧ~qQ@B%L(ŷ/?X˚ m;ExONXOmHwY#\kYyw\%[ɮep*Ҿ >'o7t/OΤMxLF;|]{|x|Mf>fh zڷ6Ч}nRX=7ֵm(RJi!k0>X߱(x6mڔ}as, .XdXhZ/ʺ~OF_w5Lp%/ oeɫ㛟gbl >&e[F>Ի}K¿+/{r&ZMτi}]dL_RL݆ˤs-s.oi_i6<69mηe1U4`?~ w<0m_>cgϑ]{Εcƹʰ\kg 97隯AJ)RZ&7;+Ec+f |b(֣( V5Lp%0!hO^{->m|)͵rzwB6as,Q|zݱ0aȌM<}#LY4l6lFރ0xP1߁KJGFc~ػ'k]3<8 z(|?y5ȼwgWuucS×|IiӀ=q7L-3KWc /)?Gj^&T!> dؗxygj~c#zsMFm >*ɾϨׂ.SJ)20y:wUMͤ&PGQ uelkKai|O^{ :71`տԶKas,Q|l'L:ssleGZ<@=;]ՇpAR|2|(ҦZou*e_upѣSkRpuQv Rt5-u{r:IQ&Ezx3v׌3m_9WRJ)M"L-0w,b=Ol<:09\Ä7\ ~z]]1^w5K nZŷs5Xl'L6{Klgm֏CgnkCflЏM&Mu!ӭܤ _w pP\t`t>G3R  oBuRyOksGjs_8o˾uޱK1d:տ3')c2M^F=#Wc5< :y03?ܧ/)Ӿr}~lϋo!ڡ,){Z]Gէc+frުi"cʟV/ǟaWݮ>+?sccL1U'+tm[w72ns,Q|p'L1gkVW;s?8e~d`9.l>44˛Aҥ2}=N߀~*Kk<;m_45ІRJ)Pێue2~y}:)ƷE>X}6}[>L\q4=L`gJ'NGuose;o;J)RJ)m6}cy )8e2x,R4Z=nr~flkejh-{{t+_!;?- cRJ)RZG}s޷s8~4?ei[J)RJ)T ߓg# OS50J<}>i>=Fm(͡#'ҶRJ)Rڰm(}mif&>{GO 3ǽi6oۅ%Wa%3o1-][veֽ/gRJ)҆mC)kOS508tуj =p\cAM!#1bX/)Sg`JLL< %0bX <  S}ׁ~D)RJ)6ls ظl^>܅}CӔx*L_=wtǞ'cQ 9%%e3o!-X =Fb[Rq1N-/?I-~cȰ1رޗRJ)҆gC)kOS50؉ؽWa_ga|[Âi3`y(-0x$Ga9 1vD}SJ)RJ)m6¾64^ ?{O:}L: 3Ǹ?a &Lcq8K+6b#6m?EKשmYkJJO߁>giZIn5?x3 q:Oca;F},pJ)&iC)kOS50ARkd+F-ôs0n$L2el /^Ǡѷx: -GS[=>Kc̄Ř2k5acѥko,XgqMݤ<z~[koy5p|}pjݖ cIR=/A%0F؝kFCp U_99M}[n}_kx~ahs]4k5RJ)uƆRFkaB~#0g"-q'bLIGڌ(>zj.{b6L\T'gOG] k(Ei}tZ'„%LĽ7jJ[_@aB vu-L\CΘuAZ )L<"fu0RJ)1l(}mi&& 2 SDi$"L2 *fߠL\KO%L%M'fp帰y9l t,X|]-h߷ ;v'ze4ͪ  fy3n/@ In]ԞJRt[j<&l5d^[fT%w5a12L$J)aZKt0jj6>ݧ)x 8qr9FSo0k KVU{uQepcqq)T\$!ݳpYR\d\Hn_{۟O AxP'ue9p-XHaB^lXaBkuMܤ aB&&PJ) (i DS[i6}r CƄI1z,&$8a pq󩸨D\| qxdE-fҦcpc>h׮k@#O㍨}SkONr7ҩ16=vumžpq1}zdE"b6M͹ohjӠkw&8\=Q洺]ȹyk|A0]ߓi=d|s~yǢ= v^o2\Σ뜹TPJ)l {T-Na4^ ?ul&Na#`2|6nŲ{dnf k o7/QV/ пpCqc3ph߮+>,ϧs:Ѱζ֍gI76 魃9a|NUb 0WovckZi sQᆳ1|L~AǴ6L?>wy :m5;&}8ǔ,>]Ǩw.Ȳ˼>[{5|8sNC5gis`8?k%X7Mi׃SpiRJil {wYm5>}FOti&4:x3CGx?8QZQ?;.zd2.jZ NGFGLB!SЩ}WX֗Q&mԍƺ zоQ\O1uZAs_ʉmm67u'`u,lϣ?Z1۸Rwߦy^~|^cEt޽c4F+L[AMxypyg9 io6cy_:duQ|TmlRJ)f";[)MMn}Fݧ)]p$V?h1a4 X!CG`ȱو1vpa2\Z$*||1衑i~W@[7/L2$7kq"{6q| #Kbtg1d3zU(Xdv1c* ǘq?B=<\OL}ҷ۠q8t:ct澓 &یU?W(:8yi.‹R\6X?i<e_qZ}#޹c͚k<[/s˞m^΃mk\txDc+V;>sMGns߾>q]9 mtOyvҿX^7ӵ&#^9jQJ)S؛45Mao}OS50)x8ek>}?o~xZ2|C񟏌9S1` (|ѸəhA_E;ܬ/N1ou}ק΃: nжU{kNvQǒL׮{c [G)2Naod6?d6}~Ӕx*Lwl:ꍇPv|Jt=zEރ0xP1߁KJGFc~ػ'k]3xh8Qcln\׏ўKS l7e]`zKČw/V.J-˚-=Ig7V+^}S_U"Mso/Bz^lƓ><^G!:^[X&/V?3y2'1Z]k@k_$1~Dk4ֲǒL]OѮ|ORJq\̶Ӕx*L4n>\'P 9{EՇpAR|2|h|XG:\p\T,ڇjr:n[7[mn[m5>zYu-˯xuqjo2NOd\r.clq.Xw//c1ν5_nrӏOwz<mk9.W_z|,sS^?WI\N?Q8lK|̑ܳ_krszEPJ)0m(}mif&>u[*'W] 챾pP?ʟqcq#uU/-ƗwYOܬᅢϵz?zbR˵>[ uL;"B7h8.UQZ[مYϣ}:%2b͓5ӏZvlchO5$ktΧ,PJ)m(}mif&8|˨`ǒIqa󩸠|gdU Ul)Ɨ*Åė_/Ji}p.\omZGivNjm (RZl(}mi„20L3ظXd7l=b> FNW1d,tH[~,+_>CXmk6(>O_nXGiNÄuRJ)?6¾6ߔ:Lشxl'>h|-mC)-L{/{~-4Gzk~4RJigva2"D顣gyljv6w(ۈgи1^ihKiGO:J)ݥ U]7>u`f&>z[vRJ)RJkE^T^5vIZ}9L8I)RJ)֊Rn;}2J¾!0dJ)RJ)֔·&LimU0A- S3aJ)RJ)C7n a¾Pe¶m(RJ)RZOl֬s6S@!B!§W^0 8ÄjE0J)RJ)0 B (RJ)7L0n# (RJ)e f}äm֧>L&O6IM}RJ)RJi&LR &֭[҇rl5чɆ/RJ)&7L0naBZb0, MMt&˱D&ZRJ)R0malcݦ _J)RJ)MʜÄja&P(} 1eʔGR:}mDYKɤwLRJ)RJiRzS&50\kZ/(}xlӨGN7Sqt#nQޏ[6Q ;6S8L}ڧ+LmΟ[V55\a_J)RJ)MJ`}a”SN6d-*Jƽ\o6.SuhżmܢyFdرG4wNxucsHJ[謗GrvRJ)Ҥ \8 &~gW{0iEUPNmb5^fԇ,&8-JneavlN ˽^A2ִN;d"eӯw]A/RJ)&eP/L7Lwu/7]TɣgIg=m2! qR<-ޗ۰csVVj謓GyM cv^}ŝ?if{q5Gm3K)RJ)I Ly[R1Nn`-$Ɲ?iZ ޯMO6ͽ>LQ/RJ)&ea´UB vgtP(}H@a۹MQ*cw&.L0.م Q;ǝ?iV+DyTսu~)RJ)4)a)p[a`=sSڹPemX)Ԅ&zdޙ 'P&Lˤ"uŝ?ieykw?ٜ7z/Gg^fRJ)RJ20aZfLMWiާ.}$e>d{4KPֹuޙY2(v^}ŝ?ifU;O,RJ)Ҥ e{&mĸEiNDva?P lQg ;f3ZKُlG~eY6NSLRJ)RJiRzS&\s l„(]TɣӇB1jAԇ,KaB&Î-; ن >]Yfjvje$eqdۯ,w[1SRJ)R05 C&$rYPney\g[jرI ~΄led[ T^ڙͶ\/RJ)&?L0g0aZf] a:tsRzp;•vH\Oև)7mWRJ)So`„kZ„g2$ EoHʺ0&k_J)RJ)MJo`t /}lhRJ)RJiR&Yw(La@)K&˱D&ZRJ)R0 s03 >a6IS&Mmԧhj>)RJ)4Ia)p ?jX@)RJ)&0aC2LRJ)RJ05 Ce@)RJ)/a9p 6&HgRJ)RJ)-|a)p a:atB)RJ) u)p m:&B!B!!u)p o:*B!B!9}ȁ]v8tPB0ij{=z{u)p l>*B!B!0e@!B!u:LXH !B!&S&,S d@!B!<5&0a֣2L B!Bj Ly[_P0B!Byj2L0na6"D B!BHSa!p Vl?*B!B!0~L^ FA2L B!Bjaǎ 2eee4i&NǣX N[ LaB*Ä&B!B!5OX۠K.=z4ϟ X"UVsI 0 0&B!B!5Ow&dAL0n&B!B!@Li'A`„0B!By'P7L0n&B!B!B!B!Ģ '\: 1L B!Bj ـ_~#!B!Rd`„ۏ0B!Byj2L0n}a¬ζe@!B!<5&r|aBH,B!B!&(zÄLB!B!`@!B!X0L B!BH,&B!B!$ !B! B!BB!B!Ăa!B!Bb0B!B!`@!B!X0L B!BH,&B!B!$ !B! B!BB!B!Ăa!B!Bb0MF&+ĺih,cOEuwMJ6IPRlF:ׁsi`J7*FƩsY'.r-hc}sX52뤟0wVa<zka}zo {<z]|sX<̹9Ϝ ּeqE-,ow1OB!iL0߈e,)x+&I ƺIM!7B7Jx#an>X| XTV}V%ӠuSǹ =LЯ kӺ5$Bj nnIaz5EMB~P_Q78Jjr }$sw !ڃaoNLwtn@=͂۰ n^ _(˽w=eB/&ob*N}qDXTܔO݄^W[׿eB߻9ތ5$0žd]~Mk߰d?ƚ(?׏c 9>xaޢ~_B!u&oJ,Gݺ1Ie>n>1sm3wnjMm\䆜B='֍nk? :D("<`\zkSMkE'rFMB??5 xOX۸ai?/srw2]xr:&B!nA~n [McH ) MO&>gH~y8~4&_t=G! I Y'Jۇ?7L<k[5jϻmйg`[s~m5avl'Lc? >jz!a&$.B!#IܼeG.Mi{ `k"srAH\2~Aþ'x)V^ ױ_Cl>γ"={Nk Ca+(P-ibliaY;!=1 sD5VFmc^9jbQd<ن>_FDKԿx Bj 2yhOca +"޼7W"W1Zqgޏ̥iAL}aVk˸/}ٿD$z\A} d]vs,mCHǫϱz6-ˁ]\"'%&x8_L!{3=_D^!a ΍KߍQK|(?n&zoN9nf"pS;&}ci[Ms3̓c7=`6Bcs6ywVNsfD\lNd ņuM4OA!ku-w9o&#d~:Lכxc!Ǡm7?yfٞ/}NrB! ߙ@qBh !uWPEX@! !m蟪פЩa] B ! 6XR_a[B! B)hLoQۿ9'ug\BHR0L B!BH,&B!B!$ !B! z 7@)RJi$0L 4č7ވEcغRJ)m۸ #F Ä{pYN]`&knXG>6xp? w}.\) QJ)Ru["ia8R?$VRv#0FU"] ߚXwAɯ|]'vS+(zxŽϕHe:=7Q~Hoc׉zY SJ)RjTo$ ]"[|&XEkPY]y{wB;(A 23\Aq{:r!Z2tM0UA)RJi K2S+,8Y_E.5 Hh`n A7ʶ&g$/@)RJi{c0 .LȺz[`SS)zҶ^VAj^ν+8wr1!c^s3|VEs)(rk]Za/,LʗlRJ)4T o+$eVQ+D *>eLAlc^bqdx}t\2UY{<uq%MyzH:O`n ><}!Hr> (RJ) aB40! "[}ŏ]Dz_A㵊di~b8S^A4Ga}X`eq=߭uA}0o!D zs„K7QJ)R*ÄaLE -Px럶z =[Ȇ3h۠N 1L *"Ta)8&H'Ll3RJ)2LHC27 !\zoKBha/L=+3< S&ߤaa@)RJ0!yw&R$9}+ACPoߙ\4 ΉAx 2_¯xm\&PJ)|0!y\C CA+~t\JX:}G*$&?sQ{}żuAFW9G Jz-g J)R&$O ź_4E65]eIg_MH'?aS|{ l\A`X_zHEt8.2cy=8gl‚@62_ NӲ8m&PJ)fo٬hs,h`)+H 0ACV2r KƢUdRxuPAemg/L_\ʱ OS\4T0^b^1&|d:acҡIm:dtzӎaRJia8&Xv2n鸂d< 7L9VQBZ& y(uaD f.B)RJc*?_f;&_[pʸe aB0LH 7LH>$0LRJ)7ҵ;0y@'-PŻҿ2l~vejޯa[t\A2LH ICPߕ`$>8 5|WBaRJi0a.LYvܭnZ>O-_`+S_Mm\ Tv9UIOw__ާKSes?gg],{֧dXJ>s?h.sC{yj=mG9 jK)f.ۚӏ;w̨{Շ(]6}&+H 0.hmD[潿O4mnlOݭwgXm >ezeUoӽ sdQx ڧox'ۘ"hy)gܿ#zRg":54~'Lr{.» CrR]q?wmcǿ]rvƠ l8qhA B1̛i2{w='d=OOM ݕbb:= ä|poڦio xnnWݧw]X[e3z[0v2~_GJ{{1D1m~*A{RoZ}nnH!lhp`ijY>SQwٝy̴A'\1~nmo,ͽRυ\α+9RJ $„u֥ C%(eHSφO)L0W aBLtV럲Ӱ>_aBHc C_}˱_G1sʹ9ި;a¢{rr|gi*8x]_g:keu{5P<ݢVhVÄ=ݵLqܙ~U{-9cV߯sLi=;N4qQ},m"Ͻ}Nѵ9RJ \Ä>$ZY/H) ץϽ>ӯ9+H 0!&M~k#LT)an t_(i&|s\}6Xb[( o؛.)xtѮّ\y::t{Wi/ް6sMw.Vh~VҰq|8~ϥ6ثڄTXskӗPJ)K'Lrvm:#؁K6cԔ7reahLLZN1mF4/v򂵘B L$ÄaLhuCP!eֆOW3XXMw:'ٜ 3OM3αϏ3 ms>z569?ۄ5orp ;a’(M2& (dU޲sWirZ-WS:m$` XzW 7G]ֻ $P\u8˸e aB400T}ހ@MimQJ)M)n;kװ,݅hZgmZ^P-Sϕ{]>-e2~q0!y&B 'LX 99 a=RZ}bq ,w[V.R_ei[iYo2n鸂d< !&Tl=D)RJcIx6>Vpʸe aB0L $NbaJ)Rӹ+vncO Mt\A2LH L3l@8amG(RJ) aB0L $0aRJ)&$BHA RJ)4T 0Rw&B!ϫ7#I a!BL^&$BHA0B!? a! a@!BW 0R0L B!+Äa@)H&B!taB0L $ !B`0!y&B  B!D0ye< ! B!"2LH a!BL^&$BHA0B!? a! a@!BW 0R0L B!+Äa@)H&B!taB0L $ !B`0!y&B  B!D0ye< ! B!"2LH a!BL^&$BHA0B!? a! a@!BW 0R0L B!+Äa@)H&B!taB0L $ !B`0!y&B  B!D0ye< J4iJ1Y{5 `P9;h޲M/:yM@W ӰcWmկcaºihܨ#InĘϒ&Ѹh$A! z qnM7Nqbm](| pHM{=y"40L DtaB0Lp#\h<~R`, j&mA7F>q46U  \; CX?Fݶ(2LHTq^;FH'su*k4i&Ba0!y&o[u(oGBFQc~z m[a~[y>.+0AR3mӯZ?VeuS37ǯ?2ou2LH?ߜ[;\7`N4ic5иDeS.$Ի_{ tV8r~i*^M /Zx2Χnl:93I 7d>#G>9Iog.ZXf3IV)J[뒴q6dE!Cܾ\ҧ{h?Ob(M_0YXdc]|f, {Ĺۏ\_q^;&ws?{|Pay9qswN{?%_[#? L[k7Lc{Q (2ω|ޢݫO|f*kizTu0L 0ye< !Z| aŸL61-hpAyN:?76Yߙ˺&ݤڸ 70Ai,T}c0#L61J+6|OvaB0ߏI[A=h_s^2]H>3"U&9t-sô"g;2bI^֣Zw" L^&$f234Ĩa.Yq;2: vݷ.Q$y$„|llSPLpn΍szL$&7܄ǜM_AK5&|S5Nt`Lc:φ91>=V^2\qO-2Ga>1ص} OutM }j&w95.cy!W 0C?&[3B./aBX>s},Ϡr-%|1߇} ּet[\ 0Ae:c. 1ɦ/!x>'0_Qw0י!$p!S;4%z@QC0icRxσ;oscA>k"sŜޗ=a}T{{sBHc0!yV`on}Xwζ1k1C{n jx1 ~s8vl&̷y=|S=c+`aB>nxROÄy0/-d04s59bNS:ˬ6c6p;f!$2LHBHS„ԍʹC>1eq *ăqnöq }!hmoxq|=A :\qqspUo'sd}>2[N? a! sa" e>1*Z gL$Xq]OqM_7moX_ՅOLXehk 3vF\ S ٷsi4ND=cSA6R쾍ÙqBjW 0R0w+877Qn.Դa7g,d\jl#zn]uݐgWō>ߦ8d]Lu?'Uac 3vfOM^/ naΪϛg]c +y 3gsM(8qT~i}2MZI ׎:'W 0R0AoTS7AnF7ܶP"]@D)d Jӝ* 6)wvd17B|7TjJc[>0z܆a"c7m~jzqڇ]ad>x\}Ua/A>﷚{M ~dx- 7BH1ye< !I kA7n=Xv7Ac2/r!pspnC M/n4_ol"N>ǙcVc@6L{Ǧ&{w)WcJQ}o_Թ O!pMYs:Vx3|*!&0ye< !I!)`YBHCtaB0L $ itBHCtaB0L $ iȸ~![ !{L^&$BHA0W?@!D0ye< Hc]QGuJ{ O0L  }%!+ÄiXaºih*2W(<0:X`ࣤ/@&0~r2p@!>a0!y;:P(9#&a@!B ? aC@h2a B!`0!yg&x̌EgIXAƽN.6mkDT(*wc#}0'h}&VmXtg\6ӸhZyre>|̿oC?+>&B!taB4`DO~A J1gAKk*S+gܺHtogwqddƟ'^c)XTn7h~rOk?7_ƹ{ol6{lecJ;Ws]z_Bs,Sf0O92`S{/3͓iޗa`\qm}.s:>vWX4wE8F6^`<taB0L $u3L/`zύx77}Z}n1{C+Rx* ˣ1 b̕iQ#g{~C&Uyk+x< )hu"Uرy8ί¼=}JWmסS!\)hzxƝ'o6uq/<8_Auy8ǘ"uk/1:!0ye< !IC RGmPf<777bW!GchAbaW ;6Ϻ>Ux窝{6>a"~:QVL"oyXT< uqIhg\q>:ȵ3wi}D9SDaB}G Z\wyvlOg>S_[Dr:4nBH]taB0L RUpVAڿNU鷪{ksD 5HgœH*};ٿ; `r9_zs*ԯPT~>kܻ&\PtRH:ų/t]!P_#0L-0JiT_]`4v-.-Q7Ƥu礉:΋S<'~Âz7 y%C=1%1LH oIR Ro[W`40?BÄڂaB0!س֠"Km&j߹ȴzA@c>ۛ r>_%r@G s& 9~ w%B W 0!JvހXb1ɲUe?EaBK6 $f| p'xAs_A$15% /SoAC֟0B!W 0!O}!].f ~`Qx`I|&H@Ak8\ϗ\CQ4%hB2L B!5+Äia*F)mOuqVit$BܸLS"eۧXE~rW/q/k۠wC8~w--h^&B!taB4w&EKa+L L@`@!BW 0!VU $B ѐa@!BW 0!ߕ 0L  H0L B!+Äabݚޢ !0B!? a! a@!BW 0R0L B!+Äa@)H&B!taB0L $u)LشiRJ) 0ߘ2LH aRJiaoL^&$BHARB!R{2LH a!BL^&$BHA0B!? a! a@!BW 0R0L B!+Äa@)H&B!taB0L $ !B`0!y&B  B!D0ye< ! B!"2LH a!BL^&$BHA0B!? a! a@!BW 0R0L B!+Äa@)H&B!taB0L $ !B`0!y&B  B!D0ye< ! B!"2LH a!BL^&$BHA0B!? a! )0A^n݊kRJ);>GwjW 0RB /{ ;wRJ))9r#=+Äa@)H !LظaΜ>3g*RJ)s? aquT'uMVk!'uBΝi-RJi}Uw侧0ye< m<^X7 ŭc+}5.g?+\tb?'J)Һ!Ä ÄqMJe)VI(Zg? `Pa@)RZ7d0a4:L(Fw'h&xa@ ԋL,YjF]bޟ_~7nܤ(qرC+va}طoVScwN .xRJ)0a0!i\aSU\]+ϟOk }vݻo>@>7#S +tQ= JK1jhVN:&OIP6azΜ9Xl܋/E+WJbJߕQ\%WepF݇Wg |--XP}(///Y6qvR 0#FQP2nMض};ߏCi`:RJ)^  Ici?V&oSaǗ ,@K0p- [uYGbگۨa<0˴&=v lٳQ i)aÇ;ž~*͛O|*WBٳzXUU*5FIݯc-_amR;'(ˣ#zeeZ1L@~tƜR=?%ppS<<|~Cj;bƲ6v,ʧO >_}P3LRJaBÄaBW E|QX(VՕ0!;!w2?G/88+cGŠ˙wKԣ0@!|˗W`ѢEywa Ӌwy^}Y}cT+7;Ys1t<Ϙ 6C+f4J)f/Ä Ä * {?OBT}xESjmo.f- َ4$ɴl'Lpt~ ke2&X?9^!: 3"{F2?-LbwU9sd;) "o?QIHau?RJsWw&4<&RKA]O&K <ʟ~ a̘1:`p yfo߾ѣzՠݻ~?])g&,XjPtJ|L7ڴzjlٲEC1" J)-uDie0a@H-0!7S J[RR#FR K 7u F๷ї@sز5Z|?^y)|x <|Ӷ8zTq}BS=x=xguZ5& {|OfJc{7S>?u)w)=P2oƩq(cƻB;B9|R-+V9D9XY1P=]FMBǁѹTlO^iAa…<ʟ$wJH&!#Czw(КVw&4<&R> 6l(0s&OB;~G MP?E=^8XSo:8k;9fl3^i >򔞫S+uLӷ/̝+Wb֭#arDdROcz)!&:$Gdz4_ÄBHAR„Csj;lpL: Ta, :\y^k [" Ä ?}|*ꋁʃt#pLl:z%8Yv ]:qF?M[g$pL`K16n(ƾu8p":NSU&ǹM{*NcŎ-(W0yq} @s+L8_e'ϣӴ9xЭ{w̘9+*Y>7aϞ cB Z2Lh0L $)L8^dF3y1|4g%"zm Jyg0a8{|%PPWϏcl?"HX~tdUiA¼w`pg>ذu&&c0zN=E'ΡÌx郏бs'L-/DzgNaT0Ra@kC  „Z'NT/ԋA,_^?qܹjԟS+Lxf-ǬЁK`ʃ,AGN aT ǩ; $Hwxل=g@Qj9Z( GSEPm7)W+uqrTl<?6]uiaX/Wc0D ԼH{z6vǖPv@Zu?=Bq5A RѧCGL8 K.ƍ?tq!?~w.rQ~Oa!dz4_2Lh0L $&Q;LOۇŋc|Y.Zd}ѹs8 „v0WJKA_yJ9sz*jpl,kG=ߩܣ zx`:>yUUCp>8 T:vOky~P8>9m{Ĺ}CQAYnTTnQ틳&ȱ~vQX۹ K0A9=՘vǹ-;ظf*m;W „`ΑSh?{1^nE:9]/AS)1l( &Ci>  > YC` bI-&vN KoY1@f9J+P…}p|ݿM ˏ;ܙV`{?';;UA'oWB_-SV'NyV=]JcZv#vpV=Q\[-U]jCSR쎣a5`ہÝqns']Q-hQ.ob~`x f|rrZ v|rOW3NMwګmϮg*qJ}] [C3N%4-@an)NXHڟrGXfFFn90j^ ڣ/v성c?nzع?tܛI62L %&ڐaBÄa! )0ጺ;5(Qܹ fFyy9֭[Äؕ0A^}O +10(8aC+B3N5N䙍֎@h mgVY}SK#N}B\^Q#ۆ8OVT%jjjr2kZAWSN,&‘mqbG8C5﫱,}uu0|I7 lg,W={FcގM@A>Kժ`l: ĩ[=T)o8U}\}ENQ)A®~W_*3*+ײnrZ@{&V]`;ҷym][7qfڛ(ݸx mEK n*o Ç ԹXw`߾z&B+ hm0a0RNpF&wOn۾ӦM̙3eUǑpZüO0oz *^:`ɘZ` oSƶWźvWhӡcyXf )dM$孝 !uy=%Rw&4<&B &lٺSNٳup1<|ն{G=KAł/xN;\&Ns%9eT\mG5p`@r85R},xYhڽLx kA=5pr3?kyׁ(߹F#Vg?'`@kC  Rn޲E%98 A7)eʍUnggjqn8}&LQGym5Q-jʕo}x (oSr㧨Zǿi/ _f=U.{,Ώ|STwW༚|>_/k:`=gV^C Z2Lh0L $&HjP^ vӦMO_{ѿ /(ů^ S^߳>w`{[U|[Xs/V:SZNBY}rƛ*U?E=\\ܤoTeھjq/Ty'@7pv8zNc=> W+g~~vkT-kXT//jZ 8Q,~)5'U'qxXt0qTi]ǮC뿃sgNc(ߺNj#J'ѯ_?̚5 +W͛ʜܛI69f@+0!J!Ä BHAR„ 6?a`۷O-;޽(akAݟ{ګ\[޶p~;Xw-W+W*_/Tloj[8W˔ _WEzR]%˕yC„[̤V87NhCF38һ+2Ly)GgTx秵T{IE5>uӟE՘gQ9 f>@eǀGU)Q5os`#Al?y Z\m9u[wBX/G߾}1c Z2Lh0L $)LX~=F ?]xAܵEsbkU@U@REʖ8mTnsj)+(g*'cD\|i=fRWh?rvOhZ}oFġ/wEg'`[<*g>3ܧպOjz 8Q( jLyDھڪQMP5IGUۇC8=8> ~J8u6mߎM[u\T<_N%ӧcŊ ws^(a¼Oղ[\zÆ#{gz{Y6Sz7}>7»=VW)>5d86B 6d0a@)HC /n:5J }N:|D{tpϠb*׾SWU 5O)׿ V3K ljʱ>q46jۗQ5]}@)m%>濂pl8Ul}Uq A{p[3l{N5;vCo+o*<};zނ]߂o Q ' Zb;z k7nF:Lxl&Z|1:u:LXak&σg'߳[ּKW+LG<6B 6d0a@)HCpD@;VZAcz]nFQ^E )sVld;U9Z99*@U_TX*eRL)/ؘgPsU7Ǯnq{ 8*ie6ñMQ9aT-yXO|@yϽjj];Pp'}߾u/@?Ô%)YN'{O8Z6`z;x董X~WAHl<ر#N˗׍0!][?ɷu be>gL_KI"L#Ba@kC  „cnjʋ|8tRg!n߁kcعs&*BzrȘG2,BC9E9N9Q9V9@=>&JV/_Oz'FľrG&8ة)vxx{'?sBպww;%nV7_pƭ8nʮTP6ongoԺp͵8z ~hqՅ`ڲ:Lh2a.z۷ǔ)Sl2;vu{9dN #L`h@HvaBÄa! )0G֤J~» ^Yy6,_vů~ܭ_^9a`#j\9vc48RYNwzB.Y'ԟeP%<7N|{{?c}vc&8飘X=8nU[FմprU7jX P{ls3toʢ!ʉX_ʢp%`u8u8ɯqk0x_6Xbʗ,GO0id,UsFm۷cM5r ErHkj&LǒTi!Y!GaPc`@)HSlrW OПd aN+Lh}T PEUa*UŽrxZR}=E9C)8CEP|rrR-@e{q8GG~CU_Ù᭰ǽ8,??|ZށUoކCEL+!RR{T Qw|=qk>TcϫuR]_6c6]~A}^e=K+Vbڢhlwm0q$,Y4&ۿv.̽]Ϟܠ:\j;Wٿg1o[pl3 hm0a0RLpZU/ތMo? x {ўx=:TT{ν| >}=νk_+?TU%=K{U}TYK{W@eϟ?ǠGwwU`ڼEht=n~&9^fo;ۯtNQq0Acn=.ݚ J>LP8ᇭjMai!^toP9  P„SEV^Mʋ}xb!&Z-;U]tkM.gUZs VzRޙ0.T aVQ-C7ǷGw+wI~wUeF*)݊}v'7a{`[Ɓͭ~ߎUc;ת\NW] t*_N>}%6%ρW u|ש6?ř?FlS}S?ɢxg{K1c|_7/o|9^z5nݪQ7lHc G0!J0Lhx0L $%L\h:w *VeNlٺ ]tk˻B{Uqg`Mۡʾ>^U] Y nBUGg^=vQ,ބwo7@U @O\-`Q>Ae;^Սﰹ8Y6T ?JWjl@G?WO7\cT=rj߭19j? tU~T8Gx_=0b̜5헮ß|w}zWZ0Ra@kC  `„SÄ SNXl9?K+0mVOcH}z|/zY#<揪X'.{h[z:zF,|Zl~j>`ε88UsOQicTQ}~3M~m ZÄBHAR8a)?g!$Lر#***pq̚iV+Lt 0򷪨襊7_YSC^I/`@=@qQ%뺨ǶcLkЩsg Q:*{bR[T30CU[#z|G}-og_-ν{TuMmnuj#_, Zo W{7+g~]-76?q%Η}!>}T}=+Pwv mT66N??qoUڨu| 1*L[8Էqo}o tx?px> [c¤r*qwѦMfʕزe 0Roa@kC  ye%4jFMVI6+. 53n4qY{Yân]υ&Q/&v} ̜dZ۰ %LX*;~W?Wʢ+Q^g?P;Gʶ+U 78oPZop^nVi=' *[_9Z=^kT+8ϰ}O_;[v%3o}ۛoPoo=ñ./E ?Pe8v81٧}CE@}Q-@ lڊ9b .OZڴؒ,Ä[`=zM$&BN`z4_2Lh40.6IBd4:(1qgd44a#Ϣ0!.0A]y?>:t+*pQL oDǎ/^۪滨j=U_} ֍7~TEUQOkרujyq^ׯFKWP!7|_K>RKT'P8~'Џ`GӟbDm}rx25KqI峗>..|x1Ms D/}Ʌ꘾7;*_"_hO[-=y z4š[0f$ 6J _ƭ[oc9fؽ{{9F D*L0^Q/&4LV`c.B)@$ Äd.L[0Ť1l,Yk0翪 /BU_y El~ ^Rs*|xT[N?W g9^8/Q'' Um~3O6X *_1>s}1}6Xop{զå~8K_GC~.*o r!v\K8|U>PcE5ɫ>< xwAՍp?kW0at$ +„;&B hm0a0s4 0LH 5&V/&=r*tۻ„gchIX56Xa•-UxbU_TE{QT=>|L7P7畏] <ڽm#Uvε_Px8G8Gt|ӟfj+?BճCG?ޛ.Ö7]\3/GUa jϨq>屮/qw_#Q-kgVJ?W8vpϩO P@DFoP=0[ jf*Qxb?L9X0AtNQO>#D^(J!Ä 7 uBm#H([xhbޭ:>ʻB&Ě5.ǎ(Zg--j_g1k:WQ?gkÍ\ *L0@g!;w.ڷoo |EÄ ˛"**/‚W <{ U_ -"2z2T>M__nW_|e8y%8Ls1*TxBk8{Wp?\vQ8u_|hQuߡ[opʿŮ5νEϡ |~'S-O)oUP~|DyC#@'/n_„%0t<1i&~a޼yelܸ ܛI6?~ ?|RZ$K+J%Ä 7yxg.22_Nxoa~ 8ǥ{Z*8EtCaBc)~=FĆz)=\g>oo@̧̃qư Է05&Lctzt0WvW"ao/\KpneQO(+T.W gno"T{NkzB_[싨M>pg8/b>-?/buP#T^Uwl?l_yBՕU T<|IyR+Tި\9Oa֯9XaD 2O;&?vr>`<-ƔRZ돼둼.^(͗ & toD)tijpÍ}FSO=:1eSp n{7Ʊ$|);^PP6 t>TSÄfcИ2,XoaBK|$0A~ťj%8R Q|\>/W/?_/۾k8sWqꏪs;NAU/][gy_qmW-ph‰?~G. nj3?5lL kWרjK=L)A½J !TJFN˯?*Ng#H1e[DF%0!f4_qÁ !׊g>?^+LZ`9tt a+tPZ>GKmDN/`|xL‚P_+goU  ܭW)xg,o-_;/?]ݗU*"^ojngn<]p8g Ul_U9T^7U^?{j\j U_g~K p jW6B`AB?kA5£_„ac'`&|mܢ (҆,Ä ߙ HXa ˦4`.Ū2HO{gH7a~nd~Lv4&T`W>G|΄MVp  $@P6Sʇ.;LZCݗ쭗J~xz (6_3U~ n*N^^8yV?Z8su?_Z#=WsSUY"8N>uPI8?A{Y6J T۪+ja7ŗ0xD d 5J)!0a0!S%Q/31 tPb.*3(w&ĝ};",o<%k^ XaBŚ0/`Y L_kw"8 ,0a$Z0L{'Ey9{I)h4S;6PX؍$FJcQP,(DєT) X#;޽߯w;ۛgggqJ0!L ib~KU*:6l&T6w4r~ekS#}fWtvG>pu6ǿDj!]4ut4j[j}n?Awį={~iK(=Hȟ< aC>s-Lxn^Ӌ0qH0iW&@SJa'O>Y^|Sa€0ϿгQ/jJe001svM3ݥ~FG֝.wu)uS~C]k;~Uz a[錝mTq*?d36Zw8os>ʏi&T:}T-s/~]]|ġ]NҵL*i?ݤ*%N`7FJ,|J&҃֞i!I #}I?:nvτg „9ZhV1)T/.DD~QbmJ0!L„#G{>}ٳfa0WO}_.Ur'ﻧo}-}E?.{_' jMﲛVEkOJl*?~{ӶRIֺSyTnHhJlʹ&q]8*Pur=7ov z*?V֞KUi͟v}c'|тyoFtt01/kcu ӵi5tM7U^܀>jL I~Wzt饗{\{!{=ץ+ڒ0aB%I}  Yfig_)34W5u͟M}55;IW.]Ct`a]pZ{^*?uO=u*NYkIkOkݑ۪Im-w9rKV!n8i,uh$S&_ :Ӻ߸j'yn`q87gn.TXp׮F.Z0`q8L_Lr*.`A -SnW$druD#&lO;=oWyo4POiezd̫:ծG/6S!jL /lLu^rA #Lhx&@IR:a7kb ?L2df̜|Wcc'jJe0Pf]+?I  琢n{Z}ZyW﬊vRyU% k=9j+=t =d3Uo#[ k~o"dW$4Nm$S_R_xʹR }鋣zqD\|פ[w q*h魥'CC„ a$)Lx뭷4l0M6]Kko/S„y „լSwuNҙvS4xÞ{H:{o'҅{TU~̶ZsUqZwf*?El?5~T:7s[ꆣje:2{z#=ӕ ==x{t^rEeGxحL_bmvs x=>`RsU\i݉N ,,/-oafzvUnC<[~an: {^'GvƎ0 1$Lh&@IR„~[Ç)S2{FMV5LiW霝 wwE辧th榻y.9uTjۨȭokaR8MľpacVO}4"O=Nt i{~= ;ӳ׺y\?CyZga߿bLjun%=0nC'O׻# n=vO*jis=]&kGOԉNAQ{o ͛%K&@07 „\>&$d0OݭK^1S}y#E%%շݘi _(?v/+܋ٳ54)zorMP&.EKu]umm5];J =Tq]:{7촫* ]vr9IWSguКvJ֚CZi-TѮVwoOlsm+O+=]CXOb< SeHOw=q t?L^igLVϞ[kEK7WU*,4iE+tZ`biVxO}=aU'óSFuǝwj /„dRϭƤ!L:Rbmi; &<*==/ls%?.v f7~vBo#(0駟F\0{l=05zc"=;y[wܭkfSW  ܾt7팝;*?a{;8qU+ҷuEU к#R&Z}rcvl#;}[O)Ө <]wktqzto2 8LT>'zz4OC]s'OO^钣==wO{aO4}cCMU~- v_68lW'Mop7>v Ouo4b_O Mѕc^N_{B[ /21)D{70 ~`Avbmi; &T-|qU a„AP&puB) ?(Wv s#I}xw{-Mz췭fwE:oGo>pv*HXyW֞vAn/u*:llpZKgt{~{=UU~ BoD`W'+yZ{<Ha鿿46&<4]T;51uc n0nxM͝7O,>jL 0AzX[&4LdPz&.C&XSOWk%2:[S-I ֿ~jʻ3wSyI*t kN_}}}6Z}谹*NTڷֺZRM[Jl6ieѾLS.t<`O7著+>'xx\?W̻3= >Ln*[僞<\  TS*aWf`T_'歕5'fz# ʏP+Ì:2~|.D~je2۽~ׯUu}Fw> cٖIiO1m3Ʊ;W~b)J*LX*R~<^|U^,@K΢e~o5&;Lv:9,AP,*Ä+ڒ0aohNtѵ'[WNm# b)]e)D-) ,’p_7Ϻ>,\ByC-{P͹'s|CcmD1˵$SJ#|_gtE} K5][\/9_S_&m5;k!?ТCwCw҃wQ!鿇}_Fk;n56WEMZKǵ#Zhuf*?=}Uoxzխ{];Okc xZma/=} =}3kW:i8Ӝx꿣[Ä3tދ3ɉjͺ/ס02U?,T}Z a@ L!aBÄ0!KWl_XtQx2sZ5]k^q !79 ʨMշQ:EM zVÄ?E+yXc&;sz5c h رXMk,H& x4蝴q;ǝ;h ISEmm[kš郃h񁭴bm}wpcF 2oF\iҿ< O9Z٣&.i9 taF]a<==*]= 顳==wOi•^LzzwHXy*_V5;=\H:ȹ<}kOi=}j7Yt*ini.iS6&|ag딗g}Unְa~0a,toa6V2HUt; r}Y& &4L 7(wj4 Q<W&lpՕ &XoI]P0?Ly'Q„ ꥗^ғOiohᇟhҜ%ط9lVV>:b'}xNZz}-4s-֯7״_kfqS-Y#}e_鞋#,8pyFK9sێ4;xz{ 'i'sv Ӹ+<~7yx2}7\fsmXR+kO?ݣ}^tFZM'ezezwZ_ݯf;纟ocu޺|J^_щo5~:&>QB;]W)TOO= S4n됺 wwtOg7vO7OϞiVoO3oezk2w_#xLijfzrl v?&…e]#}sZWw7^[kՍFn~tx㖪q }{z6:oZzx4aڎxQ'xsFQ-L5&h'P w5wW0g۟BjrTmC&կ`H 9XH^IPzdKQW&4< mg6x(p.bEogDQ3c* \uM/>Om I#z7#&I@!vX;̗Q„ŋk„ zz zoǚ<0λG5nZrKͥm*]Z:tv fMӚ82,9 o4zFzƺھ lO]D.)nC7O:4=sy»<TS4{H#-|hyi 4VמˍanezrO/~_ YSEW _8u==YF|9Oka7uz|F'֡0L_"QgeL˷l颾pBW+l~ :rCC„I EY52eQW!E0GZ![mA M?]VF7[?VlB2뷟e|s~A9m-|b#qsT?`Ƀ&\/l }h#zpzmp,0&fK>҉-T~8fsMZk]*?\7պ4Q_kseZwh*r^_Opbezz&zeqeoW\-Ӱ34Fz2=qQ;W.h5+[jzd3yOSMr} n4q-lhМk뿿pmhB_m/ҽV_HJw&uwS{Vgc6ߚ6ZwZ3}ٱݿLiGiؔ:ngXu|x oz-<-[ë3W&DzW<723:eRMې؏ua07 eBR(W&#(Êd7&|]Pݕ+ j4s/ÄwÄ죶Nh6SyWWhwT [[IS+;xW8ۻNV .Ө2>;L/^DO\X/l/kK i?kzzs#-ۜw;]{|=>l.=DloD#.l\æ&?4FN7WO=&MzۍB-[fVFG5Ly>Iglg}zf[s\Pw„+ڒ0aBTQjj ~" >ԮL9إSNՐ걧Gkk{iuǝtkN37h#*VEB6Sy8ֶk,HK*msvl"]!޿LWif5f^XO\D/]D{5sWkiZz4kH#3>J h!-5mPKyW qg3TKok5onoD+.һ\젯.wٽG.qyeѴ 9r=?y:l{߭gI կL(&?-,0ʃ\|"<Ot2=oGP և ѯY!aBÄ0!yJ0"pUQ+pg̘{Ï?YVj;5}G9q]{ґMUqTKJGl[f8{lc[{+֮n!]뼴tv#ʄxGO/ݾ˧ɍ6×6רRz妦zztS3ާ^^Hh[k}jҿhCmjít&Z4r|B~J+Ƶ7o^'~k<{0ZJfMie{:MWO^>.B5fM]GQW88Rz0 |"Xo6 1 1$Lh&gF]sPsS`/Vw}zc g-\뎁tOh6s&Hl) FyצҕR7\im;O߸_y_{*?-sOKTOL/ܴwo76[i֝5g+M%u}kj&p[&kɷ7Ի6Ѭ-m4mH+1=B*N'ҢQez Oi=}5qm: N-N ,dH?~y':{O~={Kې墿i;v^{5͚5 ^xjqGFq‚U |Ӥ. iև ֝5L0B/fCC„ a$&؋`?]3>C͞=[=~aMZ\/B}9Wh/_Nꃮ뛋7le3}}U+}צcc}ծpZOc}_cmc}z`c77y6K7ռA+[^m4fzM4Mͻ nko 7ը[~hmm45oV߫޹lS-s+}^}G Ecn=k® qm9ywڛ9e2}aS 붥z,^-}B]/H Ҹq4e+87j L&4<$/a} #<;>.w>3ӛ }GmN8e+=as mU ikVdVV)q?VV~VzJk`s~=ra۶}Vfy֖}ftɭ6[k8궩?k3`K]{sֺ[-5[vBܴzw̵ޣn߫pSwkVzgg'gS8:;;;#hp4k'ڬ Z;hȑazl"=5o@/HҺuku͗RmS~NVq+umKucK~k?RljV:Q}O~[mQ{1ZqkuhV:O[[?o#:۸euo=uiNu=q{uNt;ۉI;8;n;m8trkg_ǹӵM['\S&u<^z|A?^oΝŋa8QcQ P L!aBÄ0JR + bG\]=p }{DOO{WgY Whh'_ô|{Բ2ҏOv{~/\M7{n<^v{ {s3-Aۻ ׭{Ū/=p*MD/L}G@]2f>Dq}t&N3fj޼ZxgW&B+;둽.E^!֖ (IJ+L5Z*-ۍ|l^R}o.ְK[K55z|=Vʑ33kd'OXXOHφu?1n]ڞTtEaOt}swuE:Y:rS:mor\`.]Z1(TaRaWW%aBäA W=u!+jL~~ݡ._5XF)L%KM˺t o7יoP7?PyRASAaFmߺ/GiCGOQg^=ddT?v:4+cW诮x<{/e3>Х37cB/xڻϧN/NmcP?):}~|s?X5tv 8I6 8LrǿW /_1sL͟?[gG&4LvPN/fPWI9P&l80^`r|'zJK]+u/\SV'_G]륮TknTnV~uCuݫ|@Gv֡0_]&kIΉwksojq o'w6mn$}r8|=sߞv-=kgmw?:x:_sgxm\=_];憋yzx@=&Lӧ``W~,[ơ&&@]"^kK„ a_<&$&'q|c#pSڄ0a'L0B ^w}ॗ^ҨQ裏ꡇum[nuޢ}nV޽uS>G[|u.GyWgWg:ϸθN:\GG][G;S__eޤZs.e<⚛t_mmutoOv{kSo]xco]zc/nr?;pУۿԳW/}>~? 8Ps>=ƍӤI pƾYx55/6&GQWs ~e9._OzLϋ,3?{̗ ryCFofه?*?"jkgf^\ }&xcxH|aBqs5FW=֪2!s-L@Ϙ1ܿH`[`O<c=aÆСC"ۊu];uw_~۷7ۻwop |J{`>[ӳgO_o>}m_~߆-.k!C60>+j(K.ĿqCk?:dGQW%aBä ){AxPaaEbbm9ɏj%ȏWI,!eCO $MO|mX_\ӕ Vn۷Xal} }ݡϦ +hviZ1n7*48 HbPvLnkS>{Z3-@ OJ+ b, !"@;q*#"nh^pcH0!LGb*q R 7jl+? yA/ ?1v$iK@eB/~ϵXJ)LX‚._na}P<|a KhbW$/rp>q|߹sU:g\Wh[1Gfv,߷zҙ33C W[ +--c˛Lm'6X}m[n}p{~X?-Y71S_3f&4LP.*n0!Wa$)PcU b$LH~SyhD1ƣ+bkڻW*NiW-;v]hڕ %޽w3Q wG ϗ66dښ43"#̫#aBÄ0!u.r~&[o5*s5[KlA0emZX%h!&d30 v. ,ԃ3ד퇍33(2 7 „gB`~C`- |#LD)[[UV'?I„?aU鿑s6R pYlxٞ7 F֖y3j?̨FDDč/aBÄ0!~QZA`jP/ª|,cwayЌZwMe[ovx (Lp$mXsTo7l&;bh/QxK];ϱ;XD%L(> q(+*Tz.~Ve+ϗ :"~R@ZBZF&H}W0l6dd% P&u0&@IBF_X„C% aQa a$ `D%L(> P&u0&@IBF_X„C% aQa a$ `D%L(> P&u0&@IBF_X„C% aQa a$ `D%L(> P&u0&@IBF_X„C% aQa a$ `D%L(> P&u0&@IBF_X„C% aQa a$ `D%L(> P&u0&@IBF_X„C%I0aɒ%XO: KP|$I&D@!"""b0/,aB!L0ͨ󿰄 Ň0JDDDD4&„\>&dL/;@,){3H%glm& """u040!UPV/Ϊڶ´@4o;![uu}^Ը?pږlxS& """u040jqfEdc0/Nb rk!W'Ը?s Ia""""Qa aB  L4Äs!L37 !L@DDDD3/,aB!L V/(SfG vէY=U\·Fyk@M9%ߖ̲̾{m1rczge5̾^?Y(4]*_#DDDD4&F+6Ow]=#6.V]MWq )I}kkL ؠQۮVDJ96'+gĽ2鶯>X$u ,?5o!L@DDDD3/,aB!Lp`ނ=[3j"y t0Q.)ez&m_ f_mŷ3{cl Xnrm@*kcc\IT[?s݄ XLy:{A\4qml1/E]u\'qe_X„C'Lp|!Q<~ը>=ir)2<ڧ 8aB jk_tXϜ0gM}qmllܢ3Ӟznʕ+n:8xٸEg6&„ź_f˞-4q5Ahm?\tUl„6gp/L7> =L(uIc9i 6<ֱot ]u]'/K/5k mmllܢ3Qa aBb&7(dw^l!CMOP,oX ?i?L0!f̻&?:L0o3i3'L@Dbi;7/'ߖc-j<3KpъuW/fƁ,IDAT_X„C͑+0H@4[gqCR}]x?iP/HvvMO- ʜm&l?a""ֆaǮXźm0aڵ~aaBi@umE^68(@.E6Sm6dO&8GwT;q&8 \{"lL~7x0%aBiIP%L(fmO8dz٬D;~s~9o$AG1 ̶mO/o+T_.]fM˦$md{<`7ըO!Cq'8x HG? X, JB„iX7%L( I\CFuA_2na""K„Ғ0~KP:&$"U|0N/Q;& "b „>H]U8% E] J„* L*$!L@DbIPZ&o J„zʴw71+yg:L2.OfWW~ 7hZĴږ0t L0e Ϫ4lĴWDϥ}~t„tpiu[a$ X,0+R?;P bzUg z'aBto~TLih4LXjw ц5uCGRaU/ml ~-Za}*i^vIyOJ{_rIm{' ^/Z^U{WVv66^ A*< L)+LH]sBNyJ;BŲ„g\G"my'?SmR„*E~B}*L[W9_ ~(~݄ ]&>  baboBݨ(DD@+Jv._TWN~v„3tkܰ3R'XG]+Ip WꗮO7bZV_ԕJ~U:1v+|mm J~TArv#RE]/׌W@*@\WmۦYըbY =F|vC! #F_ةJ-)9qqo,YB2I#a""˒ V>viUWhD\&SAê.S D.yӪ}0!_.IIhaBi<N^:[+r >vGTF6gJ#UP \_pMo嶲|ܠ֟ ɊAE!=v[w)l$L@DĢ WIb'goĴ>9OSaB\j=ׂeJCa»ᄏc 1R~{>^>4+r?ە 5rW';1 Z( $cX gB0{wWc+I0o<, J„$ VDl xaB9)SaB6Êlߐm"L]mEr ' q{6v„BDD, %aB0t LEIU/Rl<)GmDXe+0s<6( G+OZ=Q}T{*Î֏Ç fyC;,Dޗ\b}%L@Db oUk²ﰎkd7L5g,mJ„R##L"FP-Ƅ0%aBiIP%L(J „ |+=~jDD,V]a+I0k,, J„R0a:!2~J& "b$L(- 귄 aB s DD,0\_Ku\'aW_}~KD/„Ҁ0JDD,:V;NZYs\u['/K/ղe駟j̙XǵqqlF%L(> P& "blq7mf9qmllܢ3^z_~wnkde5ٌ: KP|$!L@DbzϸY:Xq(o&nt6N6^Q˨󿰄 Ň0JDDDD4&(Iь: KP| pȯ<,*j @jҿ hF%L(> ,LHOy_aZ/NHVm];~͆5ÄZ6_`Df_X„ÄEK.*NP[P0!UBN70!>?NCDDDD4&„&$ARGȷCZl?aBn6|)- hF%L(> 0 M୼էYQ=UFykO9G~_m|czmYOǥzW5kq~3>.W*ӳPp$ kGь: KP| XW<يjPjQPG1\Х bFB?jTAjb>&BBւ6/ ;I:N O_řH?IɺnL@DDDF%L(> Y *Eo;~p*җg+^lm=|zc Bb9OxA!_/Ä hF%L(> 1?/\XTWb4kZ&% q BWRU":„|K„> UJ& """bÖ0& 6oPFaBʄ M bSykrj&ٖ)091 Ň0!I1\+0H۞4[g b} ޘgw7ek3L(*֝m W„C۶QQ68(D.PE$ORmm wWNowF*iGݩ?QF+DDDD+aBiaBf6l"l茹/dxB*mc³'(Eʱ-$? ͿX&۱ac.I"-0'o0JP| I @z.„XA_$~-À0JP|*s^-@/tQȄ0!DDDD+aB!LH@ޫ „49J(„L1 Ň0!>6cg3䪂  a""""ƕ0&@I q$L(. P?DDDD$B LD&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0A LD&zIOȇ/݃uvO?ѐIUܮkT#qa:OP { @Q!LmP*V`,OadXu}Qxo0I7)^ж hxa.,]&8EmbaQJaN(*  wl&?}@P MP7hsRڨ]GA;s͟&Z.]DZ.?^5xIyUڛkZXZ9_1* 7*womB1)02j$ǧ a[GmrmƠ?P7 dbPG+ ÅQs~u/LoP4ƽ/r͗`ΨyhQtYmzzxmӰ ֧s>ݦg+7sy4q]8'WXo(„)PE+AmEߖ3 E+.`~_G)~;rk|'?[nmM~)jskC F'M[ۣ*o „0 BIZ|?_ri!'La!WWnu,ELO4ق&i| . J٨05Yu I+ʬMK~v I 1oqTdZY[gDOS„45?\@"$W^GWtg}g3rAՄ,\&6N?juq]$:#'Z&|)8L0r;߱ia.]\$-Ht)sN6kGjl-t+SB+^G_f֦\V$}>D'>$h~uib~Ll|cPQYk_?ir]Ce+$ VPR%(rY$o.*OUHEchLjz~, Y&/d&l`T!Yh-mcg1蓌uE9OF'}vD-+H$WP q9?|p@Q LH:r6 !1x(A J(: a ; , Ha$0A L/,Zy|1큯[:E>*"mAO-jQ-߂KHU5jzDQzLܿJ„z;S)Vq=ˉN2iץ-x%jرª*L+aFmHos}>װc~ (?iR@qTG)񖔺:^S֏9x&=/In0GCP ʄ{D]]*((E<ޒRWǫb8sn3_b$2$,$@A¿_2cPisOݶuld@3DN' FN*͜Uen?0[nCl'7Ֆu[^O#i_JeqeIeSJrq_)0I+ |$/al٤ǁSexYKukq\&˾f6JOYw%sp?9>meϗvdq`B}hZl^#2wfF!of 3F{&TdN2ODNɆd"vD?!ͱNp{ak]n2^Iw~{>nf1cjOzzJt~[Wwbo@b'Lw~/('ߗmIڟ\OgߎJҮlǧev> k3Z׷ǿm]A_>wvF ֝?k?c„'v$?I-9-{& el4(v2ukgIlmm@Wbì43ۘI+شp[߰}\J:֙\/IO6)R{uG^s.o=u?kTsq߰20u|2ܾp2^[w|TOgM ad!hي)*lzɉ[6gMF+a$qO4^Pɨ8'֙x+x߀\G}3/rqdkg)xδaˉU7c$t다m Zwx@oO~;?k?5Z0!?wu"l'DQ'Z v6Lw&-$lX}Zog?? ƫg%؇I4^1![s|o!/IǯA {q/d̳}8ЖW&9>>oM+_Gҿl+%Ϛs&'IN*d?apg;''FMO'׉Tb!X1HҟZ(dC4x[gBl}_ ȵNп|k{Ro^\q~Jq/o㔭Oq כW&$ϚP Ea v"P0Eh'oLlKy'Nr-'c1Hҟqw?NLg5,tokF)n\S!ߋ?mX_meۊw$@;!ǿac/QEi3<V rl#<^aSh׭;8?LOgMۚk_6> 3LNO?L Up"~>8ye>4ymIQ m']rm;KulX&iwB+)~[fZ?g3eH:^I7#'bo@u~% :jB)Tuh݆'ɶScve#Dq>s'n[>m2W׷H?_ߜYl(ͶvgMOu&''&Ǵ$'Nv2F13'`:mrL*-d9Zl$e<߈3㕔!{`O:^I7#c(\mG0~_E=)%?_\ lSo̶pe-s_W w|jj@>ufg\ @{&ll2OޠxAѩDžC]{1_0IK^~(8@݇0ac[ixAm`pzTk/~{btR׷]eP,(i66 [Kixm|*J„IiB݂/ !L WD5%~iqC_hP&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0A LD&@"  Ha$0'LcԼ Ha$0A LD&@"  \a|Wy G )„A9LPO&KW]uH{衁' )qLxjȐ!4hn{zG5qD͞=r^L$g3<ѣGkԨQ̙%KTg3+L0@ 3PxΝ A$&S @(7ο"aҥυzJ0!*P?~ !3D/CP&dA |x I„̀ 3H ArJ}HP „UVWXP,+A`W%@ @PXB@ %LGaB0믿ey AKzD&X_Z`;PZ"<""""""bC4ue ~5-[VtXoaT_Z|jXFmUqʕG >3-2e V*~i53?Xqj\ZE} &L k;[lX׍q/ „B""""""b]7ƍ#aBAGأ5_ب """"""u3k$51aBN ߣ DDDDDDĺnfmUg ҝi9jQADDDDDDfֶAܣGʟ >L W\qZ{x(Xͬm:kѭZ{TРÄ :瞫|.slFmYu7ި;7x.(40!*H裏W_|>?QADDDDDDfֶAܳgO 2Dw_@ QAu]0a„i""""""b]7 ^z7j`6o ۷E렑#Gjر ̬m:8~iF?~of̕ {LF7na""""""63k۠0ᡇ+ٳgޫL y=﬉'V>̟˨ """"""u3k۠ӧf9sdɒ筆ZmaB@aҤI6mZsg3j;u6.YA Ad vU¼y*ev뺙mP?Zpa laB a""""""63k۠ %\VignetteIndexEntry{Managing SSH and Git Credentials in R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- The `credentials` package contains tools for configuring and retrieving SSH and HTTPS credentials for use with `git` or other services. It helps users to setup their git installation, and also provides a back-end for packages to authenticate with existing user credentials. ## Two types of remotes ```{r, echo=FALSE} delete_git_config_on_exit <- !file.exists('~/.gitconfig') credentials:::set_default_cred_helper() library <- function(package){ withCallingHandlers(base::library(credentials), packageStartupMessage = function(e) { cat(e$message) invokeRestart("muffleMessage") }) } ``` ```{r} library(credentials) ``` Git supports two types of remotes: SSH and HTTPS. These two use completely distinct authentication systems. | | SSH REMOTES | HTTPS REMOTES | |---------|------------------|------------------------| | __url__ | `git@server.com` | `https://server.com` | | __authentication__ | Personal SSH key | Password or PAT | | __stored in__ | file `id_rsa` or `ssh-agent` | `git credential` store | For HTTPS remotes, git authenticates with a username + password. With GitHub, instead of a password, you can also use a [Personal Access Token](https://github.com/settings/tokens) (PAT). PATs are preferred because they provide more granular control of permissions (via the PAT's scopes), you can have many of them for different purposes, you can give them informative names, and they can be selectively revoked. Note that, if you use 2FA with GitHub, you must authenticate with a PAT if you use the HTTPS protocol. For SSH remotes, git shells out to `ssh` on your system, and lets `ssh` take care of authentication. This means you have to setup an ssh key (usually `~/.ssh/id_rsa`) which you then [add to your git profile](https://github.com/settings/ssh/new). ### Special note for Windows Windows does not include a native `git` installation by default. We recommended to use the latest version of [Git for Windows](https://git-scm.com/download/win). This bundle also includes `ssh` and [git credential manager for windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows) which is all you need. Important: ssh keys are stored in your home directory for example: `C:\Users\Jeroen\.ssh\id_rsa`, and __not in the Documents folder__ (which is what R treats as the home sometimes). The `ssh_home()` function shows the correct `.ssh` directory on all platforms. ## Part 1: Storing HTTPS credentials HTTPS remotes do not always require authentication. You can clone from a public repository without providing any credentials. But for pushing, or private repositories, `git` will prompt for a username/password. ``` git clone https://github.com/jeroen/jsonlite ``` To save you from entering your password over and over, git includes a [credential helper](https://git-scm.com/docs/gitcredentials). It has two modes: - `cache`: Cache credentials in memory for a short period of time. - `store`: Store credentials permanently in your operating system password manager. To see which helper is configured for a given repo, run: ```{r} credential_helper_get() ``` Most `git` installations default to `store` if supported because it is more convenient and secure. However the look and policy of the git credential store for entering and retrieving passwords can vary by system, because it uses the OS native password manager. ### Accessing the HTTPS Credential Store from R The `credentials` R package provides a wrapper around the `git credential` command line API for reading and saving credentials. The `git_credential_ask()` function looks up suitable credentials for a given URL from the store. If no credentials are available, it will attempt to prompt the user for credentials and return those instead. ```{r, echo=FALSE} # This hack may not work on MacOS server where cred helper is osxkeychain # which always requires user interaction. Hence error=TRUE in the next block. example <- list(protocol = "https", host = "example.com", username = "jeroen", password = "supersecret") credential_approve(example) ``` ```{r error=TRUE} library(credentials) git_credential_ask('https://example.com') ``` The function `git_credential_update()` looks similar but it behaves slightly different: it first removes existing credentials from the store (if any), then prompts the user for a new username/password, and saves these to the store. ```r # This should always prompt for new credentials git_credential_update('https://example.com') ``` In a terminal window this will result in an interactive password prompt. In Windows the user might see something like this (depending on the version of Windows and git configuration): ```{r, echo=FALSE} credential_reject(list(protocol = "https", host = "example.com")) ``` ### Setting your GITHUB_PAT Automatically populate your `GITHUB_PAT` environment variable from the native git credential store. The credential manager will safely prompt the user for credentials when needed. ```r credentials::set_github_pat() ## Using GITHUB_PAT from Jeroen Ooms (credential helper: osxkeychain) ``` Use this function in your `.Rprofile` if you want to automatically set `GITHUB_PAT` for each R session, without hardcoding your secrets in plain text, such as in your `.Renviron` file. ### Non-interactive use Retrieving credentials is by definition interactive, because the user may be required to enter a password or unlock the system keychain. However, saving or deleting credentials can sometimes be done non-interactively, but this depends on which credential helper is used. The manual page for `credential_approve` and `credential_reject` has more details about how to call the basic git credential api. ## Part 2: Managing SSH Keys ```{r, echo = FALSE} ssh_key_info <- function(){ try({ out <- credentials:::ssh_key_info(auto_keygen = FALSE) out$pubkey = paste(substring(out$pubkey, 1, 80), "...") out }) } ``` For SSH remotes, git does not handle authentication itself. Git simply shells out to `ssh` on your system and uses your standard user ssh configuration. Hence authenticating with SSH git remotes comes down to setting up your ssh keys and copying these to your profile. The `credentials` package provides a few utility functions to make this easier. The `ssh_key_info()` function calls out to look up which key `ssh` uses to connect to a given server. This is usually `~/.ssh/id_rsa` unless you have a fancy custom ssh configuration. ```{r} ssh_key_info() ``` The output shows both the path to your (private) key as well as the ssh pubkey string. The latter is what you have to enter in your [GitHub profile](https://github.com/settings/ssh/new) to associate this key with your user account. You will then be automatically authenticated when using GitHub SSH remotes. ### Generating a key To use SSH you need a personal key, which is usually stored in `~/.ssh/id_rsa`. If you do not have a key yet, the `ssh_key_info()` function will automatically ask if you want to generate one. You can also generate a key manually elsewhere using the `ssh_keygen()` function. ```{r echo=FALSE} if(isTRUE(delete_git_config_on_exit)) unlink("~/.gitconfig") ``` credentials/vignettes/keygen.png0000755000176200001440000007545114402461137016570 0ustar liggesusersPNG  IHDRx" jksRGBgAMA a pHYs%%IR$zIDATx^kyX9dR%[FZ 7&b䙘ر=;1c MH%{IEAw A\w4 I$wɔ,On}YUdeeVe֩}D8}22*_Ӌ~GG!B!Ʌ k%o?ަwbJuϩϿB!BhugԦ͛3ޢ~Vuj߽B!B-=|)r >mN<B!BhILx:mHOv B!ZxZz~ ݁!B!ZzշO88B!B;lD!B!T2x{!B!TIB!B P<B!0x!B!aB!B#!B!Ԃf<޲]}V/8I?}-} !Bt5ծ=$qKed%D%w䁧K*Wu_TQ-Y\"B!P$O\N>s삓-ӗђݗbrZ.#KI+kC!Bh!oX;{Ϲ'[׾I|=Uo I=ztm쾄h`g;ɶNuMʍԚ' f.7{f]>B!xc Nwu/>\tgL[cIYQ>-K2x;x1KyVoQ7,_nجvܣ?8v=g +תoXn}}י^="nB!?V=,zbIܗsEq7W߄\eeee%D?C&O_Vm6}Sm16oVP[Tۧgj.5-[w[ߡiԕ<B!xEu(1 M]Щ';?VҁS~~tsÉzg,ˮt}KKȎ=lJbe$n/!Fljvu)uНqۣۛ>[WSժuk6&ڬ֮ߚU3$Fp[,ԍB!B657E,#=HblxW!rմ/OjrO~}LnlTNL?̝٣ڰubbnrC K7}?O]p؇Bё6 2Xr9Sn}iMLޮcg#M ^ޗ d6oߩ9wQ{n^uM+~yx橽5ԵvQ˒V+7TQPܰBMmخ]wYgwĸ$ ^[5 ~ݣ.TYoMыT_/?4h8.K}򣦸֟,Q!BÐ6<-15Xrgui H}WԖY~faWSǎPwnjNuۆ2+T ~Lk}KGrTΗvFu57ޮ6nѿgՓ^ۋ|Q24bױO0M jwOb޺ԟHsfҜMgZ.Kj3z]M˕ B͝i/ٓ]~κԶx\MT/2j>%DUk7=jړWnf<_WOQdvߺpE/P_r@}lW6dGz-[ղkWW\uc Dq*lmڐkx7GcrcZPK䉭vC!iyw{7ޱUݲvS`D{Ŕ\&')/\[uW$sqv{ehmwLd ]f##_:>t>KߺxFWU/Q_W}hb~jun)U7Av=ѫP4䫉3.:- B- }u'^VGx)h*9Uo$nH;|5yMꪥ)ȷ/m}3Q! niZ3ˎ\hsgƥkw8!/QeUf4󲮜sզj<3V! S2 ]keC$;=*[J޾44xz}O=w={:_|+߸Lݰjv6wf>LnZZ wwwpy.#O}]I^2vH;CWl:/VuuOݵ Y& ƶoOZ\MMYgMߖ-jK\QROfԣXNv_l~mZ _UK?G"]qv)+WΫOX1X1۬xWBܑmJs͏t[xB!40EJs]Hٗ^x'y_gbW̬ۗ&*ޟAxk%?Q3GOÉ7} Q;lnYMڳ_ڬ6nSn^:~]3}D}01vpVnuw/ަ~-nS%iu.ޣ]5Yz~B[=/T?P7௮' mz CHQ,~sNs)=O9alۡ'A24˖׶kR#W"R{%V=^:C٥]gUұj?^Y\2J B!6LK/Xrv]Hٗ{'y_gbWۗܬY&*]:g}6XR^~wo=h7tM'&cZ~ڸyv5;7'v.ڣ0m_جqu~uOQ?bYfbSKfު>6K,j~RHv=\J)8.5c?P{ݿ\eۤΞd+*w_@}q%nf^z͔ZqZ~fu57M3H%.1u91>qFuiO^A]qfu?U?|}OӚL//?hoO/mϨEƶn󅷪i}b ۋǹSW^2IU*21 [g;ke gǾ͊\Ulے >@M:rB6$ A ޠ_qˠ'h,9~PWKCwtj6]1d2vߥ 65xy_BT2x}!JG'nYVZ6nVݾZݱzmJ$FnV} 1xN񋷩5fz_bmQo[oX6/׋r{ۅc܀ʕ՛kOJ'/_l~W?&>r1.ՏQٲ*Wmjke͊)=B!4~R;=m^ Uo3B/WTb\̕8L] s[/o>ȓRtqHi3f] -?ޓ՟m΅\HB!Ԏd!>/I&Ob%5};wA ^/k?YӫlMBhyߊ$ _1Jv]>Bޟ.N6Yeu;q.5-2!mW_}ʇ+G̪%~{eRq&gb٥|c;FGS3h ˕k{?kJ Ҧs$mX!B/YjK32^ 72On('n5sRn߽z&׿yZ@;ђ^y{>_7G?C}肭꽟ۮ>q]#_M {'foשO}NmN?9w6Y4_tYEpUg1Jٱ+s,KmXqP^gKA#i?IO嘜gݥ6Z~8{;r}c_ܞ橮4'Sq\Һ?s6B S#|IoEMPZ;}81B;%z$c`%7xzRmwZym.q3GS1ay٣IO<6Pv]Rv=gS)mV)7xy_BT2x23E"Iܗ㏼X2{onTag쾄dVd6'?N. =HٟǾ;+\v; bQ6|~eB!4 B3' $n}KɯuoRFڗo;)~oMtϹ}ׯ3ݗ Oު{ϽГ<˜z,QF$B!U]vՔ:ϒ+ N+JKvcߣωN$?zrM\ܲ۳;!BHΓ'^|{IhKJB!]>q~$qKed%D%w7B!B-@ ޳B!B N%гo!B!Z?? 6x*B!* ˛O'.c2rGB!*;ͩajty3[ ޯ?/Az.mwAw,rcB!B+1#Ro;\I6]V#Gw]?h?wI"}R7^8.B!Mٚ6]V#~z_^_~--{ԩRw?}Rzzlj9֬+߇B!Mٚ6]V#3_:z_W{uSզٽjj߁#jރjzfڴyZvZjڱszz̺B!B)1#/j6L5mFO>6z+益^z;:v{NiS7=Km6lQ۶TۧgjG}fdrZnö:]m!B!FGbM7__SϾ~=~!y˴:~:pNsvں}FV3TbLmVmRnQkoUΪ]ͷܞpnB!hj\|fz{u){Auqcvڹkڵg:rڴuujCӏwϾNw.9怺}Zu˭~б m}].6uchnKw7<؇Pȹeqi\Bh<4.fk>ty3[ 8ξ]V[ϪG;S7޺N?yEqqfQu!bujyz#jqmI·M7RSoX9g^],\ryDX_I̎rUPzU#ʤ;ޗҦt^*J#Í1{>gD#?ϋt1mQ&ěayߨDY]BVbM7պ[r:v>K;wmӳjj^M3'~BS_:>ZѯUlRKWR7%puB[N- FUW] 9 o] '[dˍP65Nj~ݶ%{o]KօՇ.ڥu%/ڧ>pvoWHwoU-Qtͅ[@8p C HnLCQT%0BIʵǖC i:+Wq~\_ |Fދ B`Ieʚͦ2ty3[xnu~b>ɧꃗQpȗTkղkWKoUA?!e ǔzf^L0cGlY.Z;2bhEoWbU&R9Tж\/s?9헫>r> <>' >9_)?cXϱa;wse75g:ƘƚGB5[Ml\*lŴ }6]V#RbN{ͩ巯|mjYu)u}O~YzE]f{;<%jj"X? ֫}wՉJw9eښ-yޟJ/~:+ b{q?.W ]v]!0NcOF{i;׫2f|6qy򍣕hyPOI9s}*lQiQn{vdl}侩}y:N5cro/1>R 1ۓ#^=}/ԏBuŚjbLce*gդ\v!6]uVOaf5qޱ[9<`b>x: f]}mS%iu.ޣۧbt-fnn얖lNnit1{X=;ڜ-tfe޷tQtCbmbЌCr=RǕwH1f eʓV__y-8*\Uݸ1Řt:΁vJs:|޴9Or_>7<}/U-b~yZ~!󡜏3Bk|r,Q.oI6Cdf~|uneubUԺ-oߖw^ߜةλh6z´z緩~aKת[N?3jgzc_KagXƗ-V g8Muz$G?7[`feJMFMeX˟U;[mG^csU^+ַ4M]g1;^kc,7gR9]."Ge1Oۉ:džߺ=Ac;!jGMO51[.cl4w\lmF^y 87T3;k6n_֯߬.UwRm۝< cߜQd:ob6{݋7e+vN^~en=2mw6;]ﳅCajʝźUߝm}v_s$,S/Qu>nP㥮i|Dν1{_[k3~myh e2qQwxؕg9:oys?槖y.U{\D*&2))uϱns2p>9.[_UB咫KM̖a)g٦˛jf^{?G^֯7-_X^m4Չ[R]vͪ$_ة>~6eS}{綨귿rD}ǷN~ІWǾLb=⧳cZSm|GOe˰?/*kG߫W$uRѴf~rm*Gt(`ڏ^^tqi'fN7[vwlՏI#޹{MU5lSiw\9f~={r`jBvc\rur'ruI"W;mmF=ϼz7/yF]{Juͷ[QkLWlO>pzf?xZvJz-ϖL_بgKBRU=C<&BL^k}q>ץިvef%)OGq Cyt|-"ƞ gsLh3ySa)*;YEV";_l19ʷ[r96ݬ_W̅/?!ոhl53x?{W{Jʫ<{ҟ{]ʟC8.تoez5\1;?[?R:·Z'2u罖G}]Tzs? %k핫c5ujǬvy{]R\m/$kI~cT΍U>vV cԫwww=cr.fLYa[7w8KyعLu)۲2!q1[Ѧ˛jd^/գO]+`_T__w._]?Uwzf߬sPB :& {\jkݤv:!lG.of{_ǟy;X=V/;6T6J^uTxC{7Ԋ2<އޔ,ĿVrC ެcBk\|f'}'ZbEvԽ뾯vm.-oGh@|ϱ!5.fk>ty3[ oJ=Kt7JeIYF$B @oS).j ؇Bh3BbJ{Caqhl52x?K S!B! ʍϹ~r-W65m /B!B 1#ϼ1TI6]V#7B!*I\JpmM7 ~B!Bhf٘E.lۮ^UT.B!F\k"<:~}lH/_:EU}뛷9|&tOenQl|Qoӯf;B5k!Bu_#Z7Ņ|OR&Q{'2MTUky5xps*?)lL%?^g&fR^~յB!'BSgMܸ҆U,g(ZT I32rΥf/T.s{l|/!5Nqyls]!BOEmym1 ik옚<3g&ώ˥ؙO'6lɸMI]myyy5k!Bu_#O~v [еhKum63:R`}fy]e<|i%qI9{rIdc#Ķ[U6|ek!Bu_#O^ssT%b\mϡ2sF.f]&5 >OJ71x'xyq鶾b ^oO66Nl>VqyvB!P5rO*U-dͅFsy.|mmf.emXSn|43xe'&O$Lo{&cQz8ve[12u"Bk$ ^zl ]yN~2HKYo1OL M (-_ls4r\3HRGve{cEu"Bk ^[2f!.}m2ojUUĠ&`A=~ر7XEvԑ*?9mg\v͑\m䩙Mi|2/30Ƅ>Q}'׶ pnŪ y]bA#=OO`29(QK7;xU޹euYڶ)m/X1x јAҏda<Ser"WxwZhI>m"v"2xOxo2ҧyKɭ6,3WUy-l 3!Y,o>YuY.0?BoL:~i{\K6R\O!jXȱcPj#ɏcf2ab&dZ"vA\W>_xIy9j_LS[θ8*4?U㓛o)3Rώ5~AsU]smR*#1eLOӼ%vA\WyI.nC ^hxISELJaYkT>U'H2^n6KLJa/]Qgqͩ3x H`bR\y13Nuu:G՟1jhդz & ֺuF ^q9@yO`7H;1őY"0~9>v<}>Q2xOFwW;:J (,r>YHv|XCM2&GH\H~}ߛg#gަ\gL>SU5t, :ݯ&'^]'y:֛Wni*kИ@۲m}&V/r\a܄Q"I&yʙ6 |Tr-;W>].KR8o]H>91Jr8;r;[sr*sdh'>26ф@LS6 Qz⣑1cs@&msO`- <<<<m'\{Q"wn2ڋW3>O7c;xh"{KV'FoyL<O:-qo;x0&t%*0F痬t @Gt @Gt @Gt @Gt qnRERMglc\qID/<8L\!1>$GN@n IwŌZ0'*|j_!O|&7|n $?el'mVU/2R> 0nj yh,⥾|ܜ~ Hߓ6\Ƞc[^_,s_nx|Ov4O;yx^Z65痬 ILb:䩖eLtۉlIj1bbkn}L:%T8nYN`YO#FbSe! H>/KUUǔ]a|f2Kok8_~ O!7"9O(w^RQ~ _g$O>4?F~o<|h`1&S~ ΍iOBkfLY' ODm>u%u:Iۉ-/̩Kʏi/9܇?^v dQ^\M b)2Fy_]*0Oq:b3I,ufI嘐޸G ,gre#Jde\ ߒ/!\.SS.3/I9s 6j3=n.MJ$Iȓ>/Y,ghc h ޼G"~e0xO旁_ayzo.';b lZsEJ_RͿd,(<<<Ax(W )ݗܟ&<7,}_ 0tMO IiLspIc./Wig<.3jb8d~h9҉8B_"~gx7YO[l#'He[޷ls݅k|r.a?910xSjRnS<,e,V{Wly ^bJa, tīh*ĮDVx azOk*2bG9S22>83n=7:6^"]%h> &󪟻|3g!_=}!!yNs9޺%t|-@ cbeaVXپ1 ED 1ԴŖPEn\>Bʗ禆Eh>bꗯ=ݧds1[cףc\9~C\~O~k\q M'v>MÉ'8='x_{kA,7rG2T]oxtA㑽jZVQ5^P[NyYeo˷zO 9̇֟?U׆Z<@812ִes#P0jBnМEmƞ! olv->$M惯Oǫ<_oW{ټ|MOI:QNh2&_JGm0^/./P ED{.2c@M6{Uzɹ`*=횗|4c|텏Wy>i2bs;=urQg4rFx spbys>/Ϯ惱3x}P^-Ć}[ظ,oz96vCd>k/|LHIi4dN_;mT]d>MÉI>ȱ/2 3x r_c3~"3|B)6>6b:!-`>戯m._tDdl*SC5Ϸ0E6TW`%ynNt<+lԑ\j`4K޴݋])ZUX(8ۍ-jH _t΅,ssMB˳o>dĎ]][4tW| 3xξ깟gqۮ6=_B+anNx"g>^iz{JM99OVq.x݋/L4H?E!ע Lͷ F]f>*?oA#[4Ֆ K΅i|0͔Va }XPj骿n!ZjE|Q| ?W.k+1 CBoJ|G1@[15]%W3tUiWftze$;X*}|3YS;3VO #`:#`:#`:#`:xsEKԢ\f;C 6u 1zS4t663Np:nΨ0.G6R+$.S粷t do? ޔ\h1_rNLV<_?mkteode35z* x3oOm]m OAl-.gɪ*7'xaj>F*&'y3^/ ɡiӻtu3b| |;6x .5qeW&aF <Gq)c`ay.Z'\h͢C7ӦE=VuLn~}0̷n3J}nv,eL~*5|8o§ Xl{o8,ی\%6=纹lc>`,gqb`i A7e}N6mפ Lb?|r~rEP߷@j1?rC;*wNۍ\aZC17 \ITU̹+2so_<9K}%&8Uu1DK3m7 *M;a/&d'}Qw]z11\5Xן_l{ _zk\@Kmq}[! sj8^Uxې+nUF ibnʝvVi'ܑucN"yj~}eד'X>Tͻqa6#j~68_RO\<}S{,稿1>WG}ͧ-{oH ~JvO˲9mO>6xI 9ю&d7`d"g9^u/>9ұTG7A0s;6_jCJ?W$|̩O}>$҇yC$:s7g5xcgE>ToCoM옚ģʺ#v|cK\ۇߺ:eյkQ|Kh|~Y_hpup]\ԟ= [նu^#}_C ae]eIM 7c%+ tIsnRJB_mTtArQϏi+aW~#x3ʅRblq1Cz7Q8ι1>m#sޕE1o~1̷̏ ngx|ml9.0[]g]b[Eu9ݧ(] ȘD]ԟv!UMB!7Yx|𔚔+b({,)]Gevn>ԙʦc}E}s4͟?n1uv}iU}s~%#[T)x:Wcg%p*?sxœQD]]T h>7oB]wJ0G5O ^>Q=i9 4rO tl)ag_EC.>Y}!_@_MSƩox|LmC_p.8]G_xܹ7)ӌR*nl77sܶYonr>dE[>MOρ뉾gVi4:rnVaxЮ'~gB%p4EU~Ҽqj|!6)'ܯoi^SyƎo qι++ [4>"ݧO'B6M.囲??)RO;_\X_ {xu\"3 8?tt.oN`4Y;srS}>ggf.u}+pra+?o[NǓGs-/X:_ϫq)6?DoP>q=1ܪt'ĝ3Pm-z0x AA B ?2ndԃGd!CY``qsUM~1#p F??a4t @Gt @G+'߄w˷zA@;g~QAVqKBn0Oc1u'ZgDB`6|U/?e< M&OdUx&4x O"f+{:XgOr~~;pmcB/5f~ ^S<10x3K[55fS<0x%k Oo߰f~'-w@L 3~ӦY&Uml-9 t BÊo`2x @F8ڿ'"_0x0x0xa So\&e;\LO&d<; (KG"74u5y3-?nec7#!i~ҿٿw?,, `JM7 ? =_d DޅȥX0鉤:Br:zaӦ_Cc*\߂ ^姹K;h\`U0AnS*&Խ18n=:h|:~,Ĕ,ܐYABSq_0 `* %;Q5>H=1 ._Gp Cg>ɸ/tqU0/AV:v?${! ^?\IҦ磇*+-mo'峂ƟIUe:=}oy'|>c<3b~_6 rDBn?`&_5\DD)c&70xŎ^`A?wy~8qJ>+:G|u  ^ 勒m$"#YXTRs\ڳXS۶z,S`@v_15G1Oj9ο[^$QO] g\n:`e0Y .r-Q/tMXd:H^ϟ&97VߐSޗQOu u׷6ss  ]m|74xe 1[頋Gw<%gʉAS7?I<󧇿o F!?1ͱ9ƠuUƹ2W˷oq;LdfuyYyEyVg0x |60xOϟ& dZAwy_NG!?1?6?UQmx @0x |,L*G]Ǯ'9ra{:.Oh4> 6@OW#aOFi3̧AWi0||GAb߀9p@`*vYc@HO$ؘc*8=}˺۝&Cdb|qh3鸚2$Cl"i^<h +|#7Y$ cSœAǮ6X$=1rI=>|q({+ kOin=důU*xJs&Gfh{ -_S.ӻc~\q[r͡5ZGkp|np~^j!`Zd2@-]Ė7B!? EGslq|/0>30G3V`;pN-}`Dt @Gt @Gt @Gt @Gt @Gt @Gt @Gt @Gt @Gt @Gt @Gt @Gt @G+7=D-|%{7Hۋtsb} 3xJ5ZB1caM.KMęl5yٻyC/?reLOO{O ]PUC1ej\#TO1{e WQF,YfAL6'b\ctV]ڔy4LY63fvu) p9`CenOwc<=,3| .al|OLw*xc;xa46J)/AL^f&yw*àƬxl٬.)^y3x26Rg2 \`+W<,|0x߉KT$]co<<ce}[25y.bz*-7q&Ai7P֖%#uF{smF<(`uV>vs' ٢7xu܇|o]Y'” (oR4[~\bUǘ H7#)ϼNFeS3$¥d͵irĠ/+ACk&V7Zq\9xS/uGpD|A=>؞xR)9:>hscv!o;onu-EwB7giksJMޯADo5h97U{ Ĥz>|HL ]> \d(dLBZǨs8PC ivD9uί}n3`*v07&YDڢF9 tPۅ\7WW+dT|%8;nlwx-e=y W,h uPo/t>4Y=]V ȷ>ޓϟy7Ǹ_3v='?FݯcO$Q'7V\7bYKC0`0x |($tG2J&N)O1a7z+ש_NL岉8\6zu}|Q*U.v,EQws 蹞usAy@t[=c/B}?+/bN96ÜoPWݘnkǣq??r>Ƚن&- 6ޅcΧ:UF+wLy=f2tΓ]rte}Cܪ `[(Wϑ#ߟBzyƫw>痻~!vr^5!*&Y\(Cԅ7@0x 0'&sC)/t⤟>=&n5ɗ *L8Isd:Ɗy1JeY6GꨜH/h q9/+|񸶻1Ȝ 9߫t:v\zs fdRlӝs#Tyɧ:X!g͑:| 5 G·u*Ŏ1IM|!e!)4I> .˿g {sŮb=w54a/]geìCSipΉq}p9\98}+Ɨ?WK8W}bbLn⑞u'Bl>_']XTN+徠\u8ױ1aRuQ|y_]R䵶zǷ&CoC//*ni^֯yasT<1p_t>eDפ*5Tݿݛ%wIDžXri#W9wubg{ھ_H}8t[9Ǟ5{αt|8 ̇:\y?#fu;,7*罞fka.jŔH\}۬zm۱/&E 2IϨw^ ͑ #p3/zc k ǀX\ g?|P*9۰ԞsىWol927|cJlM\׉m^3YoZLUrM ]Ww*71#t\}{9dk0:d>TgQ7I+~݆>ls08YnW (˻N ݾ\hc$F__?e_͹Xu他+߸cʈ-arqx]H?b秮c\,༶~W~e+0|+կǢr˦s.|}W>4w ]~n ظn$=6V:ꎿBẁZʛ|G] ߦ7ꜘ9oބ]qQ8[TYS76BUNu}1=?̶u>? Ì?::zȵ)~TŘ_EjEU/uK]lTFֺXcZKn郙Ci|\ҷ/~|:=D_󹒏Qo1US4* K ]u*'ㄌ-P+K)1j兪Y>m{+/dǼ}Ƿ;l5D!熎✏ov_ה,,[!o0:&{P2ncQ9͝yc[5_u[|;{Yuc+7_=K˧_&bvi qB?3&sW˷N>!'>%|h5'~Nd:FỈ)4jSdB婰ϵ-Aն._(csE0xԧ {N~N@CbezԘZϦU7!pGfuo$Z*?|Δ_s:c-[h|]R<_{S;1s%9o01w&7!(_?qkG?#x>8ɯEs[fzmv> Һkgm;~i_Su?bԌu@-ܱ)g\cX͸g'4\6\yc#Mr8g>1b7u뼾ϴus"î.`߸7:BͿ^ f/Wð ذJ?hX7maO`jՐ/u2,؎_a8JӁv!A_@+z`E'pt @Gt @Gt @Gt @Gt @Gt @Gt @Gt @Gt @Gt @Gt @Gt ^S- `5#)<10xMk F ^S0x0btM.N Xs6'x&S8ym M4/1cA1f;m&#Uvtde:1xb&xc< ^΄_‚'xM1f2>o}1o/Q#x~J``5#F&߮U^N^a*jDfY0DŽ.L}TWԒ%KԵ^6n3y<9& ʕ+mݦnfu׫뮻Nxj͚5СC'x#FmݺUm۶MmٲEk߾}'蕓<rf;vv>6w3`3xQMޮ]'|6w<9&LC=T0w4l͝l˚1xs@3Mid;` ދ/^ID<9$7x~ '-O0xs@n]nR{z<9 7x*V<9 7x>;,` ˘% <1aÔ˛jd!B!-ial52x?cSOC+WKj]})kS27d\A!BNO?tral52xN C=^?s/+"$DB!7sk͖|Jq\V# / Mf%Y\s,S[E[Zv=c-gP]w]s!B?<ӫ%1v[fg K/Tt̵}I-[$N ,tvؽlnAB!>$oס25 yC/s‹!BP[0xd$D!fP 5xoYz+wd檳kzsϿC!BM[nUޛMa%]em߿zӅ/u^/'}>>??Pg㓟n7ߨ_h窳kz>1w>'<B!*Ӵ&-WeLsluI@ K/|)u̙;Ww.R?ruM7^}U}ӟnɏVZ ή3#gbB!PLӖ~&o m}s~k֨[7\Tm[v93I&ή B!4mտ4ycm\S~ /X2sjཝUgׄC!BMdkL<'>/iK/P2sJɗ0x!B\>𙼱5x'?%ORc%6s?|r[n}qg]!B5/C)yb6x_/U}_W_~9/PtG3xaB!}ԾE<ȵ^?|կ~U}[RO~ ^zdΞ{r[n)w!B5]xjҥjý}cO^Md~W'Kz=r\=r80x!B\C;v`یMލ7ިVZ #I?fm}ή B!ejm7x{"|vv 畓ꎕ+վ{աC㉙ܶoSs'{o3Mޮ]_|I=ꡇQ`uզ }UO$r[^db\uvMN=焞<B!*Ӵo4w" 4y"y*kK@ /j=u}ɓ'b욎sR;v\9!B4mI3w" !kUYS2bCgy{<B!*۴F܉0x-H@ <9r^#tj߾CNcB!P\-D$ GWV{PGծ]^u}cB!P\-D6x+$I\nK5b ry1b =WB!`Sh]tɓjÆ mu;w&?B!Bbڔm:c\E!B+ךqral52x!B!FO<B!0x!B!aB!B#!B!PGC!BB!B!B!:" B!BuD%B!BhgB!B-tQ9*JKIENDB`credentials/NEWS0000644000176200001440000000235014476061104013251 0ustar liggesusers2.0.1 - Precompute vignette due to credential issues on some CRAN machines. 2.0.0 - ssh_key_info() now returns ECDSA instead of RSA key by default if both exist. If you get authentication problems with 'gert' you probably need to upload your ecdsa key to your host (e.g. using ssh_setup_github()) or otherwise delete the ECDSA key from ~/.ssh/ecdsa to go back and use your old RSA key. - ssh_keygen() now defaults to generating ECDSA (P-521) keys 1.3.2 - Disable example in vignette that would prompt for user input on Windows 1.3.1 - Set permission to user-read only for generated private keys 1.3.0 - PATs stored with set_github_pat() are now stored under username 'PersonalAccessToken' to match the new Git Credential Manager Core behavior. You may have to re-enter your PAT. - Small UX improvements. 1.2.1 - Quote path to $GIT_ASKTOKEN, for example when credentials is installed in "Program Files" - Fix for set_github_pat() token prompt in R.app on MacOS 1.2.0 - Bug fixes and tweaks for set_github_pat(): gains parameters to disable validation 1.1 - Fix for vignette when no 'git' is installed - Fix for vignette when credential helper requires user interaction (CRAN MAC) 1.0 - Initial CRAN release credentials/R/0000755000176200001440000000000014402461137012752 5ustar liggesuserscredentials/R/ssh-keys.R0000644000176200001440000002100114402461137014635 0ustar liggesusers#' Managing Your SSH Key #' #' Utility functions to find or generate your SSH key for use with git remotes #' or other ssh servers. #' #' Use [ssh_key_info()] to find the appropriate key file on your system to connect with a #' given target host. In most cases this will simply be `ssh_home('id_rsa')` unless #' you have configured ssh to use specific keys for specific hosts. #' #' To use your key to authenticate with GitHub, copy the pubkey from `ssh_key_info()` to #' your profile: \url{https://github.com/settings/ssh/new}. #' #' If this is the first time you use ssh, [ssh_keygen] can help generate a key and #' save it in the default location. This will also automatically opens the above Github #' page in your browser where you can add the key to your profile. #' #' `ssh_read_key` reads a private key and caches the result (in memory) for the #' duration of the R session. This prevents having to enter the key passphrase many #' times. Only use this if `ssh-agent` is not available (i.e. Windows) #' #' @export #' @family credentials #' @rdname ssh_credentials #' @name ssh_credentials #' @param host target host (only matters if you have configured specific keys per host) #' @param auto_keygen if `TRUE` automatically generates a key if none exists yet. #' Default `NA` is to prompt the user what to. ssh_key_info <- function(host = NULL, auto_keygen = NA){ keyfile <- find_ssh_key(host = host) if(is.null(keyfile)){ if(isTRUE(auto_keygen) || (is.na(auto_keygen) && ask_user("No SSH key found. Generate one now?"))){ keyfile <- ssh_home('id_ecdsa') ssh_keygen(keyfile) } else { stop(sprintf("Failed to find ssh key file for %s", host)) } } pubfile <- paste0(keyfile, ".pub") if(!file.exists(pubfile)){ key <- ssh_read_key(keyfile) try(openssl::write_ssh(key$pubkey, pubfile), silent = TRUE) } list( key = keyfile, pubkey = openssl::write_ssh(openssl::read_pubkey(pubfile)) ) } #' @export #' @rdname ssh_credentials #' @param file destination path of the private key. For the public key, `.pub` #' is appended to the filename. #' @importFrom openssl write_ssh write_pem read_key write_pkcs1 read_pubkey ssh_keygen <- function(file = ssh_home('id_ecdsa')){ private_key <- normalizePath(file, mustWork = FALSE) pubkey_path <- paste0(private_key, ".pub") if(file.exists(private_key)){ cat(sprintf("Found existing RSA keyspair at: %s\n", private_key), file = stderr()) pubkey <- if(file.exists(pubkey_path)){ read_pubkey(pubkey_path) } else { ssh_read_key(private_key)$pubkey } } else { cat(sprintf("Generating new RSA keyspair at: %s\n", private_key), file = stderr()) key <- openssl::ec_keygen("P-521") pubkey <- key$pubkey dir.create(dirname(private_key), showWarnings = FALSE) write_pkcs1(key, private_key) write_ssh(pubkey, pubkey_path) Sys.chmod(private_key, "0600") } # See https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/ conf_file <- file.path(dirname(private_key), 'config') if(is_macos() && !file.exists(conf_file)){ writeLines(c('Host *', ' AddKeysToAgent yes', ' UseKeychain yes', paste(' IdentityFile ', private_key)), con = conf_file) } list( key = private_key, pubkey = write_ssh(pubkey) ) } #' @rdname ssh_credentials #' @export ssh_setup_github <- function(){ info <- ssh_key_info() cat("Your public key:\n\n", info$pubkey, "\n\n", file = stderr()) cat("Please copy the line above to GitHub: https://github.com/settings/ssh/new\n", file = stderr()) if(interactive() && ask_user('Would you like to open a browser now?')){ utils::browseURL('https://github.com/settings/ssh/new') } } #' @export #' @rdname ssh_credentials ssh_home <- function(file = NULL){ if(length(file)){ normalizePath(file.path(normalize_home("~/.ssh"), file), mustWork = FALSE) } else { normalize_home("~/.ssh") } } #' @export #' @rdname ssh_credentials ssh_agent_add <- function(file = NULL){ if(is.na(Sys.getenv('SSH_AUTH_SOCK', NA))){ stop("ssh-agent is not running. If this is a server ssh in with: ssh -o 'ForwardAgent=yes'") } sys::exec_wait('ssh-add', as.character(file)) == 0 } # Only used on Windows for now ssh_agent_start <- function(){ if(is_windows()){ out <- sys::exec_internal('cmd', c('/C', 'start-ssh-agent 1>&2 && set'), error = FALSE) if(out$status != 0){ warning("Failed to start ssh-agent", call. = FALSE) } else{ con <- rawConnection(out$stdout) on.exit(close(con)) vars <- readLines(con) vars <- vars[grepl('^SSH_(AGENT|AUTH)', vars)] if(length(vars)){ tmp <- tempfile() writeLines(vars, tmp) readRenviron(tmp) } } return(rawToChar(out$stderr)) } } find_ssh_key <- function(host = NULL){ if(!length(host)) host <- "*" key_paths <- tryCatch(ssh_identityfiles(host = host), error = function(e){ message(e$message) file.path("~/.ssh", c("id_ecdsa", "id_ed25519", "id_rsa", "id_dsa", "id_xmss")) }) key_paths <- normalize_home(key_paths) for(i in key_paths){ if(file.exists(i)) return(i) } return(NULL) } ssh_identityfiles <- function(host){ # Note there can be multiple 'identityfile' entries conf <- ssh_config(host = host) candidates <- unique(unlist(conf[names(conf) == 'identityfile'])) candidates <- Filter(function(x){!grepl('id_dsa', x)}, candidates) sort(candidates) #prefer ecdsa, ed25519, rsa } # Old SSH versions (Trusty, CentOS) do not support ssh -G ssh_config <- function(host){ ssh <- find_ssh_cmd() out <- sys::exec_internal(ssh, c("-G", host), error = FALSE) if(!identical(out$status, 0L)) stop("Could not read ssh config. Using default settings.", call. = FALSE) txt <- strsplit(rawToChar(out$stdout), "\r?\n")[[1]] lines <- strsplit(txt, " ", fixed = TRUE) names <- vapply(lines, `[`, character(1), 1) values <- lapply(lines, `[`, -1) structure(values, names = names) } ssh_version <- function(){ ssh <- find_ssh_cmd() out <- sys::exec_internal(ssh, "-V") # ssh may print to stderr instead of stdout trimws(rawToChar(c(out$stdout, out$stderr))) } find_ssh_cmd <- function(ssh = getOption("ssh", "ssh")){ if(cmd_exists(ssh)) return(ssh) if(is_windows()){ # Try asking 'git.exe' where it looks for 'ssh.exe try({ gitssh <- git_with_sys(c("-c", "alias.sh=!sh", "sh", "-c", "cygpath -m $(which ssh)")) if(nchar(gitssh) && cmd_exists(gitssh)) return(Sys.which(gitssh)) }, silent = TRUE) # Fallback: try to find ssh.exe ourselves in the usual places try({ bin <- dirname(find_git_cmd()) usrbin <- file.path(dirname(bin), "usr", "bin") path <- Sys.getenv('PATH') on.exit(Sys.setenv(PATH = path)) Sys.setenv(PATH = paste(path, bin, usrbin, sep = .Platform$path.sep)) if(cmd_exists(ssh)) return(Sys.which(ssh)) }, silent = TRUE) } stop(sprintf("No '%s' command found. Using default ssh settings.", ssh), call. = FALSE) } normalize_home <- function(path = NULL){ path <- as.character(path) if(is_windows()){ homedir <- Sys.getenv('USERPROFILE') is_home <- grepl("^~", path) path[is_home] <- paste0(homedir, substring(path[is_home], 2)) } normalizePath(path, mustWork = FALSE) } ask_user <- function(str){ if(!interactive()) return(FALSE) return(utils::menu(c("Yes", "No"), title = str) == 1) } #' @export #' @rdname ssh_credentials ssh_update_passphrase <- function(file = ssh_home("id_rsa")){ key <- openssl::read_key(file, password = function(x){ askpass::askpass("Please enter your _current_ passphrase") }) new1 <- askpass::askpass("Enter the _new_ passphrase") new2 <- askpass::askpass("To confirm, the your _new_ passphrase again") if(identical(new1, new2)){ openssl::write_pkcs1(key, path = file, password = new1) } else { stop("Entered passhprases are not identical") } message("Passphrase has been updated!") # Wipe the key cache just in case environment(ssh_read_key)$store = new.env(parent = emptyenv()) } #' @export #' @rdname ssh_credentials #' @param password a passphrase or callback function #' @importFrom askpass askpass ssh_read_key <- local({ store = new.env(parent = emptyenv()) function (file = ssh_home("id_rsa"), password = askpass){ file <- normalizePath(file, mustWork = TRUE) hash <- openssl::md5(file) if(!length(store[[hash]])){ store[[hash]] <- tryCatch(openssl::read_key(file, password = password), error = function(e){ stop(sprintf("Unable to load key: %s", file), call. = FALSE) }) } return(store[[hash]]) } }) credentials/R/credential-api.R0000644000176200001440000001377414402461137015772 0ustar liggesusers#' Retrieve and store git HTTPS credentials #' #' Low-level wrappers for the [git-credential](https://git-scm.com/docs/git-credential) #' command line tool. Try the user-friendly [git_credential_ask] #' and [git_credential_update] functions first. #' #' The [credential_fill] function looks up credentials for a given host, and #' if none exists it will attempt to prompt the user for new credentials. Upon #' success it returns a list with the same `protocol` and `host` fields as the #' `cred` input, and additional `username` and `password` fields. #' #' After you have tried to authenticate the provided credentials, you can report #' back if the credentials were valid or not. Call [credential_approve] and #' [credential_reject] with the `cred` that was returned by [credential_fill] #' in order to validate or invalidate a credential from the store. #' #' Because git credential interacts with the system password manager, the appearance #' of the prompts vary by OS and R frontend. Note that [credential_fill] should #' only be used interactively, because it may require the user to enter credentials #' or unlock the system keychain. On the other hand [credential_approve] and #' [credential_reject] are non-interactive and could be used to save or delete #' credentials in a scripted program. However note that some credential helpers #' (e.g. on Windows) have additional security restrictions that limit use of #' [credential_approve] and [credential_reject] to credentials that were actually #' entered by the user via [credential_fill]. Here it is not possible at all to #' update the credential store without user interaction. #' #' @export #' @rdname credential_api #' @name credential_api #' @param cred named list with at least fields `protocol` and `host` and #' optionally also `path`, `username` ,`password`. #' @param verbose emit some useful output about what is happening #' @examples \donttest{ #' # Insert example cred #' example <- list(protocol = "https", host = "example.org", #' username = "test", password = "secret") #' credential_approve(example) #' #' # Retrieve it from the store #' cred <- credential_fill(list(protocol = "https", host = "example.org", path = "/foo")) #' print(cred) #' #' # Delete it #' credential_reject(cred) #' } credential_fill <- function(cred, verbose = TRUE){ out <- credential_exec("fill", cred = cred, verbose = verbose) data <- strsplit(out, "=", fixed = TRUE) key <- vapply(data, `[`, character(1), 1) val <- vapply(data, `[`, character(1), 2) structure(as.list(structure(val, names = key)), class = 'git_credential') } #' @export #' @rdname credential_api #' @name credential_api credential_approve <- function(cred, verbose = TRUE){ credential_exec("approve", cred = cred, verbose = verbose) invisible() } #' @export #' @rdname credential_api #' @name credential_api credential_reject <- function(cred, verbose = TRUE){ credential_exec("reject", cred = cred, verbose = verbose) invisible() } credential_exec <- function(command, cred, verbose){ input <- cred_to_input(cred) on.exit(unlink(input)) if(is_windows() || is_macos() || !isatty(stdin())){ text <- git_with_sys(c("credential", command), input = input, verbose = verbose) strsplit(text, "\n", fixed = TRUE)[[1]] } else { # base::system can freeze RStudio Desktop or Windows git_with_base(c("credential", command), input = input, verbose = verbose) } } git_with_base <- function(command, input = "", verbose = TRUE){ git <- find_git_cmd() res <- system2(git, command, stdin = input, stdout = TRUE, stderr = ifelse(isTRUE(verbose), "", TRUE)) status <- attr(res, "status") if(length(status) && !identical(status, 0L)){ stop(paste(res, collapse = "\n")) } res } git_with_sys <- function(command, input = NULL, verbose = TRUE){ git <- find_git_cmd() outcon <- rawConnection(raw(0), "r+") on.exit(close(outcon), add = TRUE) timeout <- ifelse(interactive(), 120, 10) status <- sys::exec_wait(git, command, std_out = outcon, std_err = verbose, std_in = input, timeout = timeout) if(!identical(status, 0L)){ stop(sprintf("Failed to call 'git %s'", paste(command, collapse = " ")), call. = FALSE) } trimws(rawToChar(rawConnectionValue(outcon))) } find_git_cmd <- function(git = getOption("git", "git"), error = TRUE){ if(cmd_exists(git)){ return(git) } if(is_windows()){ locations <- c("C:\\PROGRA~1\\Git\\cmd\\git.exe", "C:\\Program Files\\Git\\cmd\\git.exe") for(i in locations){ if(cmd_exists(i)){ return(i) } } } if(error){ stop(sprintf("Could not find the '%s' command line util", git), call. = FALSE) } } has_git_cmd <- function(){ !is.null(find_git_cmd(error = FALSE)) } parse_url <- function(url, allow_ssh = TRUE){ out <- strsplit(url, "://", fixed = TRUE)[[1]] if(length(out) < 2){ if(!isTRUE(allow_ssh)){ stop(sprintf("URL must start with e.g. https:// (found %s)", url)) } else { protocol = 'ssh' rest = url } } else { protocol <- out[1] rest <- out[2] } password <- NULL username <- if(grepl("^[^/]+@", rest)){ auth <- strsplit(rest, "@", fixed = TRUE)[[1]] rest <- paste(auth[-1], collapse = "@") password <- if(grepl(":", auth[1], fixed = TRUE)){ auth <- strsplit(auth[1], ":", fixed = TRUE)[[1]] paste(auth[-1], collapse = ":") } auth[1] } rest <- strsplit(rest, "/", fixed = TRUE)[[1]] host <- rest[1] path <- if(length(rest) > 1){ paste(rest[-1], collapse = "/") } c( username = username, password = password, protocol = protocol, host = host, path = path ) } cred_to_input <- function(data, input = tempfile()){ str <- paste(names(data), as.character(data), collapse = "\n", sep = "=") writeBin(charToRaw(sprintf("%s\n", str)), con = input) return(input) } cmd_exists <- function(cmd){ nchar(Sys.which(cmd)) > 0 } is_windows <- function(){ identical(.Platform$OS.type, "windows") } is_macos <- function(){ identical(tolower(Sys.info()[['sysname']]), "darwin") } credentials/R/onattach.R0000644000176200001440000000470014402461137014677 0ustar liggesusers# We load 'askpass' for its side effects, see below! #' @importFrom askpass ssh_askpass .onLoad <- function(libname, pkgname){ # Loading askpass automatically sets SSH_ASKPASS and GIT_ASKPASS variables askpass::ssh_askpass() # Note: isatty(stdin()) = TRUE in Windows RGui if(is_windows() || !interactive() || !isatty(stdin())){ if(is.na(Sys.getenv('GIT_TERMINAL_PROMPT', NA))){ Sys.setenv(GIT_TERMINAL_PROMPT=0) } } # Start ssh-agent if available but not running if(is_windows() && is.na(Sys.getenv('SSH_AGENT_PID', NA)) && cmd_exists('start-ssh-agent')){ # Agent not work in Windows: https://github.com/libgit2/libgit2/issues/4958 #ssh_agent_start() } # If no credential helper has been set, use the 'cache' helper if(!is_check()) set_default_cred_helper() } .onAttach <- function(libname, pkgname){ tryCatch({ gitver <- git_with_sys("--version", NULL, FALSE) packageStartupMessage(sprintf("Found %s", gitver)) helpers <- sub("^credential-", "", credential_helper_list()) packageStartupMessage(sprintf("Supported HTTPS credential helpers: %s", paste(helpers, collapse = ", "))) }, error = function(e){ if(is_windows()){ packageStartupMessage("Git for Windows is not installed.\nDownload from: https://git-scm.com/download/win") } else { packageStartupMessage("Unable to find git :-(") } }) tryCatch({ sshver <- ssh_version() packageStartupMessage(sprintf("Found %s", sshver)) tryCatch({ key <- find_ssh_key() if(length(key)){ packageStartupMessage(sprintf("Default SSH key: %s", key)) } else { packageStartupMessage("No key found. Use ssh_keygen() to generate one!") } }, error = function(e){ packageStartupMessage("Failed to lookup key file") }) }, error = function(e){ packageStartupMessage(e$message) }) #agent_output <- ssh_agent_start() #if(length(agent_output) && nchar(agent_output)){ # packageStartupMessage(trimws(agent_output)) #} } set_default_cred_helper <- function(){ if(has_git_cmd()){ invisible(tryCatch({ credential_helper_get() }, error = function(...){ tryCatch({ helper <- credential_helper_list()[1] credential_helper_set(helper, global = TRUE) }, error = function(e){ packageStartupMessage(e$message) }) })) } } is_check <- function(){ grepl('credentials.Rcheck', getwd(), fixed = TRUE) } credentials/R/github-pat.R0000644000176200001440000000635414402461137015151 0ustar liggesusers#' Set your Github Personal Access Token #' #' Populates the `GITHUB_PAT` environment variable using the [git_credential][http_credentials] #' manager, which `git` itself uses for storing passwords. The credential manager #' returns stored credentials if available, and securely prompt the user for #' credentials when needed. #' #' Packages that require a `GITHUB_PAT` can call this function to automatically #' set the `GITHUB_PAT` when needed. Users may call this function in their #' [.Rprofile][Startup] script to automatically set `GITHUB_PAT` for each R #' session without hardcoding any tokens on disk in plain-text. #' #' @export #' @param force_new forget existing pat, always ask for new one. #' @param validate checks with the github API that this token works. Defaults to #' `TRUE` only in an interactive R session (not when running e.g. CMD check). #' @param verbose prints a message showing the credential helper and PAT owner. #' @return Returns `TRUE` if a valid GITHUB_PAT was set, and FALSE if not. set_github_pat <- function(force_new = FALSE, validate = interactive(), verbose = validate){ pat_user <- Sys.getenv("GITHUB_PAT_USER", 'PersonalAccessToken') pat_url <- sprintf('https://%s@github.com', pat_user) if(isTRUE(force_new)) git_credential_forget(pat_url) if(isTRUE(verbose)) message("If prompted for GitHub credentials, enter your PAT in the password field") askpass <- Sys.getenv('GIT_ASKPASS') if(nchar(askpass)){ # Hack to override prompt sentence to say "Token" instead of "Password" Sys.setenv(GIT_ASKTOKEN = askpass) Sys.setenv(GIT_ASKPASS = system.file('ask_token.sh', package = 'credentials', mustWork = TRUE)) Sys.setenv(GIT_ASKTOKEN_NAME = 'Personal Access Token (PAT)') on.exit(Sys.setenv(GIT_ASKPASS = askpass), add = TRUE) on.exit(Sys.unsetenv('GIT_ASKTOKEN_NAME'), add = TRUE) on.exit(Sys.unsetenv('GIT_ASKTOKEN'), add = TRUE) } for(i in 1:3){ # The username doesn't have to be real, Github seems to ignore username for PATs cred <- git_credential_ask(pat_url, verbose = verbose) if(length(cred$password)){ if(nchar(cred$password) < 40){ message("Please enter a token in the password field, not your master password! Let's try again :-)") message("To generate a new token, visit: https://github.com/settings/tokens") credential_reject(cred) next } if(isTRUE(validate)) { hx <- curl::handle_setheaders(curl::new_handle(), Authorization = paste("token", cred$password)) req <- curl::curl_fetch_memory("https://api.github.com/user", handle = hx) if(req$status_code >= 400){ message("Authentication failed. Token invalid.") credential_reject(cred) next } if(verbose == TRUE){ data <- jsonlite::fromJSON(rawToChar(req$content)) helper <- tryCatch(credential_helper_get()[1], error = function(e){"??"}) message(sprintf("Using GITHUB_PAT from %s (credential helper: %s)", data$name, helper)) } } return(Sys.setenv(GITHUB_PAT = cred$password)) } } if(verbose == TRUE){ message("Failed to find a valid GITHUB_PAT after 3 attempts") } return(FALSE) } message <- function(...){ base::message(...) utils::flush.console() } credentials/R/http-credential.R0000644000176200001440000000427014402461137016167 0ustar liggesusers#' Load and store git HTTPS credentials #' #' This requires you have the `git` command line program installed.The #' [git_credential_ask] function looks up a suitable username/password #' from the [`git-credential` store](https://git-scm.com/docs/gitcredentials). #' If none are available it will prompt the user for credentials which #' may be saved the store. On subsequent calls for the same URL, the #' function will then return the stored credentials without prompting #' the user. #' #' The appearance and security policy of the credential store depends #' on your version of git, your operating system, your R frontend and #' which [credential_helper] is used. On Windows and MacOS the credentials #' are stored in the system password manager by default. #' #' It should be assumed that reading credentials always involves user #' interaction. The user may be asked to unlock the system keychain or #' enter new credentials. In reality, user interaction is usually only #' required on the first authentication attempt, but the security policy #' of most credential helpers prevent you from programmatically testing #' if the credentials are already unlocked. #' #' @export #' @family credentials #' @rdname http_credentials #' @name http_credentials #' @aliases credentials #' @param url target url, possibly including username or path #' @param save in case the user is prompted for credentials, attempt to #' remember them. #' @param verbose print errors from `git credential` to stdout git_credential_ask <- function(url = "https://github.com", save = TRUE, verbose = TRUE){ cred <- parse_url(url) out <- credential_fill(cred = cred, verbose = verbose) if(isTRUE(save) && length(out) && length(out$password) && !is.na(out$password)) credential_approve(out, verbose = verbose) out } #' @export #' @rdname http_credentials git_credential_update <- function(url = "https://github.com", verbose = TRUE){ cred <- parse_url(url) credential_reject(cred) out <- credential_fill(cred = cred, verbose = verbose) credential_approve(out) } #' @export #' @rdname http_credentials git_credential_forget <- function(url = "https://github.com", verbose = TRUE){ cred <- parse_url(url) credential_reject(cred) } credentials/R/credential-helpers.R0000644000176200001440000000230514402461137016647 0ustar liggesusers#' Credential Helpers #' #' Git supports several back-end stores for HTTPS credentials called #' helpers. Default helpers include `cache` and `store`, see the #' [git-credentials](https://git-scm.com/docs/gitcredentials) manual #' page for details. #' #' @export #' @rdname credential_helper #' @name credential_helper credential_helper_list <- function(){ text <- git_with_sys(c("help", "-a")) m <- gregexpr("credential-[^ \t]+", text) regmatches(text, m)[[1]] } #' @export #' @rdname credential_helper #' @name credential_helper #' @param global if FALSE the setting is done per git repository, if #' TRUE it is in your global user git configuration. credential_helper_get <- function(global = FALSE){ git <- find_git_cmd() args <- c("config", if(global) "--global", "credential.helper") git_with_sys(args) } #' @export #' @rdname credential_helper #' @name credential_helper #' @param helper string with one of the supported helpers from [credential_helper_list] credential_helper_set <- function(helper, global = FALSE){ helper <- sub("^credential-", "", helper) args <- c("config", if(global) "--global", "credential.helper", helper) git_with_sys(args) credential_helper_get(global = global) } credentials/MD50000644000176200001440000000242514476167757013112 0ustar liggesusers67033c9ba675b7f74729d2c85508b67a *DESCRIPTION 27b6128fe44dac890bf5c9dfce3a744c *LICENSE 3f9a1504712d83a9346263ef9a199b3f *NAMESPACE dd5de86cd7839fdc6603fe4897f84943 *NEWS c98ed9fc412b8a0b960765cd74a7fb41 *R/credential-api.R 16298fddb82887829393af2c2469d4b7 *R/credential-helpers.R 8886a4fe29155dfcfa6b35a16079a232 *R/github-pat.R c928b0f6fb79574cec5af42c164e9e98 *R/http-credential.R 65bcabd0700b0a8893f20acba9b32981 *R/onattach.R addb008761e123649a869d74bd657cff *R/ssh-keys.R 538aff6b37e1421ab2e6d1b68e1622ae *build/vignette.rds 80410b923c609a1212e5246e9ebd02a0 *inst/WORDLIST 226d0cc912b9c84fc5942b67eda3860c *inst/ask_token.sh 62bbbc91a860d73b39719b46285e2dea *inst/doc/intro.Rmd 3a8dbe0fa42e9d5396d46286938c875e *inst/doc/intro.html a962d9619a3b1302bc2fdde3597b8dea *man/credential_api.Rd 0fdfc6bd70ccd814015d18b80e7b0f69 *man/credential_helper.Rd d80c5e9d7ec4f849f296c7d3f15967bc *man/figures/logo.png 796553a137098ec948b9cad44e337fb1 *man/http_credentials.Rd 6f3753307764fbe501ae52773173fd2b *man/set_github_pat.Rd a53160aea5b019b65b35c815dbb311b8 *man/ssh_credentials.Rd 62bbbc91a860d73b39719b46285e2dea *vignettes/intro.Rmd 7617eb7e3c72c58e099e68f2a8727d5b *vignettes/intro.Rmd.in c6d62931c246f7f97fc1209544b1feeb *vignettes/keygen.png 41748938c3df6c35beedddc655728837 *vignettes/wincred.png credentials/inst/0000755000176200001440000000000014476061137013535 5ustar liggesuserscredentials/inst/ask_token.sh0000755000176200001440000000020214402461137016035 0ustar liggesusers#!/bin/sh # Prompt MUST end with : to support askpass_mac!! exec "$GIT_ASKTOKEN" "Please enter your ${GIT_ASKTOKEN_NAME:-TOKEN}:" credentials/inst/doc/0000755000176200001440000000000014476061137014302 5ustar liggesuserscredentials/inst/doc/intro.html0000644000176200001440000047651014476061137016340 0ustar liggesusers Managing SSH and Git Credentials in R

Managing SSH and Git Credentials in R

The credentials package contains tools for configuring and retrieving SSH and HTTPS credentials for use with git or other services. It helps users to setup their git installation, and also provides a back-end for packages to authenticate with existing user credentials.

Two types of remotes

library(credentials)
## Found git version 2.41.0
## Supported HTTPS credential helpers: cache, store
## Found OpenSSH_9.0p1, LibreSSL 3.3.6
## Default SSH key: /Users/jeroen/.ssh/id_ed25519

Git supports two types of remotes: SSH and HTTPS. These two use completely distinct authentication systems.

SSH REMOTES HTTPS REMOTES
url git@server.com https://server.com
authentication Personal SSH key Password or PAT
stored in file id_rsa or ssh-agent git credential store

For HTTPS remotes, git authenticates with a username + password. With GitHub, instead of a password, you can also use a Personal Access Token (PAT). PATs are preferred because they provide more granular control of permissions (via the PAT’s scopes), you can have many of them for different purposes, you can give them informative names, and they can be selectively revoked. Note that, if you use 2FA with GitHub, you must authenticate with a PAT if you use the HTTPS protocol.

For SSH remotes, git shells out to ssh on your system, and lets ssh take care of authentication. This means you have to setup an ssh key (usually ~/.ssh/id_rsa) which you then add to your git profile.

Special note for Windows

Windows does not include a native git installation by default. We recommended to use the latest version of Git for Windows. This bundle also includes ssh and git credential manager for windows which is all you need.

Important: ssh keys are stored in your home directory for example: C:\Users\Jeroen\.ssh\id_rsa, and not in the Documents folder (which is what R treats as the home sometimes). The ssh_home() function shows the correct .ssh directory on all platforms.

Part 1: Storing HTTPS credentials

HTTPS remotes do not always require authentication. You can clone from a public repository without providing any credentials. But for pushing, or private repositories, git will prompt for a username/password.

git clone https://github.com/jeroen/jsonlite

To save you from entering your password over and over, git includes a credential helper. It has two modes:

  • cache: Cache credentials in memory for a short period of time.
  • store: Store credentials permanently in your operating system password manager.

To see which helper is configured for a given repo, run:

credential_helper_get()
## [1] "osxkeychain"

Most git installations default to store if supported because it is more convenient and secure. However the look and policy of the git credential store for entering and retrieving passwords can vary by system, because it uses the OS native password manager.

Accessing the HTTPS Credential Store from R

The credentials R package provides a wrapper around the git credential command line API for reading and saving credentials. The git_credential_ask() function looks up suitable credentials for a given URL from the store. If no credentials are available, it will attempt to prompt the user for credentials and return those instead.

library(credentials)
git_credential_ask('https://example.com')
## $protocol
## [1] "https"
## 
## $host
## [1] "example.com"
## 
## $username
## [1] "jeroen"
## 
## $password
## [1] "supersecret"
## 
## attr(,"class")
## [1] "git_credential"

The function git_credential_update() looks similar but it behaves slightly different: it first removes existing credentials from the store (if any), then prompts the user for a new username/password, and saves these to the store.

# This should always prompt for new credentials
git_credential_update('https://example.com')

In a terminal window this will result in an interactive password prompt. In Windows the user might see something like this (depending on the version of Windows and git configuration):

Setting your GITHUB_PAT

Automatically populate your GITHUB_PAT environment variable from the native git credential store. The credential manager will safely prompt the user for credentials when needed.

credentials::set_github_pat()
## Using GITHUB_PAT from Jeroen Ooms (credential helper: osxkeychain)

Use this function in your .Rprofile if you want to automatically set GITHUB_PAT for each R session, without hardcoding your secrets in plain text, such as in your .Renviron file.

Non-interactive use

Retrieving credentials is by definition interactive, because the user may be required to enter a password or unlock the system keychain. However, saving or deleting credentials can sometimes be done non-interactively, but this depends on which credential helper is used.

The manual page for credential_approve and credential_reject has more details about how to call the basic git credential api.

Part 2: Managing SSH Keys

For SSH remotes, git does not handle authentication itself. Git simply shells out to ssh on your system and uses your standard user ssh configuration. Hence authenticating with SSH git remotes comes down to setting up your ssh keys and copying these to your profile.

The credentials package provides a few utility functions to make this easier. The ssh_key_info() function calls out to look up which key ssh uses to connect to a given server. This is usually ~/.ssh/id_rsa unless you have a fancy custom ssh configuration.

ssh_key_info()
## $key
## [1] "/Users/jeroen/.ssh/id_ed25519"
## 
## $pubkey
## [1] "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILGN+5tybmwKhcxvnwPSKlrp39Ni1gMD0UhV4gCxHg/x ..."

The output shows both the path to your (private) key as well as the ssh pubkey string. The latter is what you have to enter in your GitHub profile to associate this key with your user account. You will then be automatically authenticated when using GitHub SSH remotes.

Generating a key

To use SSH you need a personal key, which is usually stored in ~/.ssh/id_rsa. If you do not have a key yet, the ssh_key_info() function will automatically ask if you want to generate one.

You can also generate a key manually elsewhere using the ssh_keygen() function.

credentials/inst/doc/intro.Rmd0000644000176200001440000001574614476060527016120 0ustar liggesusers--- title: "Managing SSH and Git Credentials in R" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Managing SSH and Git Credentials in R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- The `credentials` package contains tools for configuring and retrieving SSH and HTTPS credentials for use with `git` or other services. It helps users to setup their git installation, and also provides a back-end for packages to authenticate with existing user credentials. ## Two types of remotes ```r library(credentials) ``` ``` ## Found git version 2.41.0 ## Supported HTTPS credential helpers: cache, store ## Found OpenSSH_9.0p1, LibreSSL 3.3.6 ## Default SSH key: /Users/jeroen/.ssh/id_ed25519 ``` Git supports two types of remotes: SSH and HTTPS. These two use completely distinct authentication systems. | | SSH REMOTES | HTTPS REMOTES | |---------|------------------|------------------------| | __url__ | `git@server.com` | `https://server.com` | | __authentication__ | Personal SSH key | Password or PAT | | __stored in__ | file `id_rsa` or `ssh-agent` | `git credential` store | For HTTPS remotes, git authenticates with a username + password. With GitHub, instead of a password, you can also use a [Personal Access Token](https://github.com/settings/tokens) (PAT). PATs are preferred because they provide more granular control of permissions (via the PAT's scopes), you can have many of them for different purposes, you can give them informative names, and they can be selectively revoked. Note that, if you use 2FA with GitHub, you must authenticate with a PAT if you use the HTTPS protocol. For SSH remotes, git shells out to `ssh` on your system, and lets `ssh` take care of authentication. This means you have to setup an ssh key (usually `~/.ssh/id_rsa`) which you then [add to your git profile](https://github.com/settings/ssh/new). ### Special note for Windows Windows does not include a native `git` installation by default. We recommended to use the latest version of [Git for Windows](https://git-scm.com/download/win). This bundle also includes `ssh` and [git credential manager for windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows) which is all you need. Important: ssh keys are stored in your home directory for example: `C:\Users\Jeroen\.ssh\id_rsa`, and __not in the Documents folder__ (which is what R treats as the home sometimes). The `ssh_home()` function shows the correct `.ssh` directory on all platforms. ## Part 1: Storing HTTPS credentials HTTPS remotes do not always require authentication. You can clone from a public repository without providing any credentials. But for pushing, or private repositories, `git` will prompt for a username/password. ``` git clone https://github.com/jeroen/jsonlite ``` To save you from entering your password over and over, git includes a [credential helper](https://git-scm.com/docs/gitcredentials). It has two modes: - `cache`: Cache credentials in memory for a short period of time. - `store`: Store credentials permanently in your operating system password manager. To see which helper is configured for a given repo, run: ```r credential_helper_get() ``` ``` ## [1] "osxkeychain" ``` Most `git` installations default to `store` if supported because it is more convenient and secure. However the look and policy of the git credential store for entering and retrieving passwords can vary by system, because it uses the OS native password manager. ### Accessing the HTTPS Credential Store from R The `credentials` R package provides a wrapper around the `git credential` command line API for reading and saving credentials. The `git_credential_ask()` function looks up suitable credentials for a given URL from the store. If no credentials are available, it will attempt to prompt the user for credentials and return those instead. ```r library(credentials) git_credential_ask('https://example.com') ``` ``` ## $protocol ## [1] "https" ## ## $host ## [1] "example.com" ## ## $username ## [1] "jeroen" ## ## $password ## [1] "supersecret" ## ## attr(,"class") ## [1] "git_credential" ``` The function `git_credential_update()` looks similar but it behaves slightly different: it first removes existing credentials from the store (if any), then prompts the user for a new username/password, and saves these to the store. ```r # This should always prompt for new credentials git_credential_update('https://example.com') ``` In a terminal window this will result in an interactive password prompt. In Windows the user might see something like this (depending on the version of Windows and git configuration): ### Setting your GITHUB_PAT Automatically populate your `GITHUB_PAT` environment variable from the native git credential store. The credential manager will safely prompt the user for credentials when needed. ```r credentials::set_github_pat() ## Using GITHUB_PAT from Jeroen Ooms (credential helper: osxkeychain) ``` Use this function in your `.Rprofile` if you want to automatically set `GITHUB_PAT` for each R session, without hardcoding your secrets in plain text, such as in your `.Renviron` file. ### Non-interactive use Retrieving credentials is by definition interactive, because the user may be required to enter a password or unlock the system keychain. However, saving or deleting credentials can sometimes be done non-interactively, but this depends on which credential helper is used. The manual page for `credential_approve` and `credential_reject` has more details about how to call the basic git credential api. ## Part 2: Managing SSH Keys For SSH remotes, git does not handle authentication itself. Git simply shells out to `ssh` on your system and uses your standard user ssh configuration. Hence authenticating with SSH git remotes comes down to setting up your ssh keys and copying these to your profile. The `credentials` package provides a few utility functions to make this easier. The `ssh_key_info()` function calls out to look up which key `ssh` uses to connect to a given server. This is usually `~/.ssh/id_rsa` unless you have a fancy custom ssh configuration. ```r ssh_key_info() ``` ``` ## $key ## [1] "/Users/jeroen/.ssh/id_ed25519" ## ## $pubkey ## [1] "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILGN+5tybmwKhcxvnwPSKlrp39Ni1gMD0UhV4gCxHg/x ..." ``` The output shows both the path to your (private) key as well as the ssh pubkey string. The latter is what you have to enter in your [GitHub profile](https://github.com/settings/ssh/new) to associate this key with your user account. You will then be automatically authenticated when using GitHub SSH remotes. ### Generating a key To use SSH you need a personal key, which is usually stored in `~/.ssh/id_rsa`. If you do not have a key yet, the `ssh_key_info()` function will automatically ask if you want to generate one. You can also generate a key manually elsewhere using the `ssh_keygen()` function. credentials/inst/WORDLIST0000644000176200001440000000016614402461137014723 0ustar liggesusersapi AppVeyor frontend Github hardcoding HTTPS keychain MacOS openssl PATs programmatically pubkey repo RStudio stdout