From dc17dfc3fb65c87f2912300f0d11f79781240e78 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Wed, 17 Jul 2024 16:43:32 -0400 Subject: [PATCH] feat: implement async `StaticCredentials` using access tokens (#1559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: implement async oauth2 credentials * minor cleanup * inherit base credentials class in async credentials * fix whitespace * implement builder class for oauth 2.0 credentials * feat: implement async static credentials * revert implementing oauth2 credentials * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * update values used in tests * update the exception raised for static refresh * add async anonymous credentials * update docstrings * address PR comments * chore: Refresh system test creds. * fix lint issues * add test coverage * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot Co-authored-by: Carl Lundin --- google/auth/_credentials_base.py | 6 +- google/auth/aio/__init__.py | 25 ++++++ google/auth/aio/credentials.py | 143 +++++++++++++++++++++++++++++++ system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes tests/test_credentials_async.py | 136 +++++++++++++++++++++++++++++ 5 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 google/auth/aio/__init__.py create mode 100644 google/auth/aio/credentials.py create mode 100644 tests/test_credentials_async.py diff --git a/google/auth/_credentials_base.py b/google/auth/_credentials_base.py index 29462dc0c..64d5ce34b 100644 --- a/google/auth/_credentials_base.py +++ b/google/auth/_credentials_base.py @@ -37,12 +37,14 @@ class _BaseCredentials(metaclass=abc.ABCMeta): keys, scopes, and other options. These options are not changeable after construction. Some classes will provide mechanisms to copy the credentials with modifications such as :meth:`ScopedCredentials.with_scopes`. + + Attributes: + token (Optional[str]): The bearer token that can be used in HTTP headers to make + authenticated requests. """ def __init__(self): self.token = None - """str: The bearer token that can be used in HTTP headers to make - authenticated requests.""" @abc.abstractmethod def refresh(self, request): diff --git a/google/auth/aio/__init__.py b/google/auth/aio/__init__.py new file mode 100644 index 000000000..331708cba --- /dev/null +++ b/google/auth/aio/__init__.py @@ -0,0 +1,25 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Auth AIO Library for Python.""" + +import logging + +from google.auth import version as google_auth_version + + +__version__ = google_auth_version.__version__ + +# Set default logging handler to avoid "No handler found" warnings. +logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/google/auth/aio/credentials.py b/google/auth/aio/credentials.py new file mode 100644 index 000000000..3bc6a5a67 --- /dev/null +++ b/google/auth/aio/credentials.py @@ -0,0 +1,143 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Interfaces for asynchronous credentials.""" + + +from google.auth import _helpers +from google.auth import exceptions +from google.auth._credentials_base import _BaseCredentials + + +class Credentials(_BaseCredentials): + """Base class for all asynchronous credentials. + + All credentials have a :attr:`token` that is used for authentication and + may also optionally set an :attr:`expiry` to indicate when the token will + no longer be valid. + + Most credentials will be :attr:`invalid` until :meth:`refresh` is called. + Credentials can do this automatically before the first HTTP request in + :meth:`before_request`. + + Although the token and expiration will change as the credentials are + :meth:`refreshed ` and used, credentials should be considered + immutable. Various credentials will accept configuration such as private + keys, scopes, and other options. These options are not changeable after + construction. Some classes will provide mechanisms to copy the credentials + with modifications such as :meth:`ScopedCredentials.with_scopes`. + """ + + def __init__(self): + super(Credentials, self).__init__() + + async def apply(self, headers, token=None): + """Apply the token to the authentication header. + + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + self._apply(headers, token=token) + + async def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.aio.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + raise NotImplementedError("Refresh must be implemented") + + async def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + Refreshes the credentials if necessary, then calls :meth:`apply` to + apply the token to the authentication header. + + Args: + request (google.auth.aio.transport.Request): The object used to make + HTTP requests. + method (str): The request's HTTP method or the RPC method being + invoked. + url (str): The request's URI or the RPC service's URI. + headers (Mapping): The request's headers. + """ + await self.apply(headers) + + +class StaticCredentials(Credentials): + """Asynchronous Credentials representing an immutable access token. + + The credentials are considered immutable except the tokens which can be + configured in the constructor :: + + credentials = StaticCredentials(token="token123") + + StaticCredentials does not support :meth `refresh` and assumes that the configured + token is valid and not expired. StaticCredentials will never attempt to + refresh the token. + """ + + def __init__(self, token): + """ + Args: + token (str): The access token. + """ + super(StaticCredentials, self).__init__() + self.token = token + + @_helpers.copy_docstring(Credentials) + async def refresh(self, request): + raise exceptions.InvalidOperation("Static credentials cannot be refreshed.") + + # Note: before_request should never try to refresh access tokens. + # StaticCredentials intentionally does not support it. + @_helpers.copy_docstring(Credentials) + async def before_request(self, request, method, url, headers): + await self.apply(headers) + + +class AnonymousCredentials(Credentials): + """Asynchronous Credentials that do not provide any authentication information. + + These are useful in the case of services that support anonymous access or + local service emulators that do not use credentials. + """ + + async def refresh(self, request): + """Raises :class:``InvalidOperation``, anonymous credentials cannot be + refreshed.""" + raise exceptions.InvalidOperation("Anonymous credentials cannot be refreshed.") + + async def apply(self, headers, token=None): + """Anonymous credentials do nothing to the request. + + The optional ``token`` argument is not supported. + + Raises: + google.auth.exceptions.InvalidValue: If a token was specified. + """ + if token is not None: + raise exceptions.InvalidValue("Anonymous credentials don't support tokens.") + + async def before_request(self, request, method, url, headers): + """Anonymous credentials do nothing to the request.""" + pass diff --git a/system_tests/secrets.tar.enc b/system_tests/secrets.tar.enc index 77ab87e0f1bf6d7d084d21c1c2fa8691e6c59709..ccb040256c0cf0e7e68c58b58536bc49bf81293d 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTDVLy#gfH-(y_Mjy4?vF&&O%0G>H#U)y_iplwfS-qaGRPylv+ ziVmQZ8#O2*BUAwbe_beW@vGyo@6qr0@X;P3vM1}BQqs!=-95vaROO@Y>_PSvkS0el zH^->IncNcii@|3w)Jl;X;%JX!Y(E-Ld%qG%2Y1O0;3vFPZsCO=W0j%HE=n z5yHp+V-IyV8=6(hZU@ANp*L*|pfiG1J9PbXyI7wi+z79yi|7{S9T%4Q2k-#cpV-Rp z)(c&hUt#SS|EqN>-ZU-{8=27D9J6h3Ze~?Q%~lxY!v}s$yAqUBS2VWnXhs^&9o4XU z1IILcRjYBB$C{0k?iXr%mWC@>duCN!(1JSeVD|kxe){rUI{(#Dzz_}8X=xJ)DPp|5 zj7dSbcF2N&Xk&KLdM8j0%=@O8Pt~Gwgs}g`g1dD}FYl44d;{urqXoYXvgxm5NveZ& z^E&OnvEN7WzNfn50IS&5uW!Pe8B$+nX(neDre8hnv-2=Fcp;u9835-JO_cJb1_61j zfct$;$UDdbI@HQ&Q@H5nKio_>^d5^q-kyeFGuy@`j6UC5amjgSR_fM2a~qbzsdvPO zJ=RM2l_C~zLzGX>2qRy9Y#yh>y8p(GQL}@rzTZa&!(c56se*YK%<=K+dZGg@U zigK9EHQ`b_GXn0HbvqmaAtqC)qs-VW^0S?g?NgmHpqx$7Rj*HE zNPPrJtB96l8wa>#*D73tZ&iU1;=D=;(H~=p7>E!uV-`n&A&u@+i(w(?P>L^Hjnv)l zNF(3D`Rket2cI{u*sFnEpG`EhrGU!zC_U9wsnq(~Q>6=uNPzF=WwwF6;qGg=btd=y z2Tz)I3Gf4tRH?5K{U6%_fK3vxHZnkij+2`Vw(;+xe|6-?iMyN-kHK+hp)(fA7PKKl zeBeSGWgl?vq1vB9&PcCS!KUx#HirZUd)8s{$LCcR4A2*rj{ z@MQ_V#M&8IBRY+)D4(C+XbNyDhg&glD|bjZZ__jzR{U(m#|}n_l}zKHG|kDnMyw#S zbm>oBqsd>NGNPv~@p$Y|JKdW2@fbT6e``l)^21-2*H=#Tb!UngFfCrsn<|{AqGm?p z=LIL>>M&fk72zlk5Vu;PjhE`M!FH3}_)*6>6;S`+L$>avXqJ9G+2(-5z^Nl9>CVgo zwe}7xRkEAm;S#GhmduE885?i>J{P{aBdd9w0a669|B+YB8EoZ-@mH4qDz8|^^R#a< zGqvEkQ~wB@B^ByafhZQuE|HBNBb?PUD$Z7GuAA%cO3*iw8lPvdodPybFCkw0+PLHN zYKwMy?9!=x@OhmOKXM9Il(fn@3Hg%_`t}y0dR4PLy7qw9PY1E@?j|Q{B$;4Kli1F7 ztDh9jq@-A>@~-1mRwGess&nZ!(GCTH)MvD|IXUQGA8rWgC^Im`nooX$wPsQ4B5e}n zFcw%b&QTDM7|3_bZ45cYUlDhv@daA9OEjOKAa>Z-?n^&^WH!-(&A}2V3A6$Uet!8m zCYiz)bv+e&hAulboFhU)9MDa^lX=p+p_0&oTK&k3nr<0(3z2BV`y8dmR_pm2GJ=w4i|ir=lzoK{;N^^zIt2(<97p zjt@vYuXlWkn~fZ^F|CD0GfH9u3w1OdQlS=e6WAIlJR;Q!VoN{Zc}E$_{`_cUH&HEUR^M9z{)qo_3J+{Dwl> zA*2gQ)K%)s5pJ~$sy^XPWVBGY64z-(nBEn6o=FRkGKnY)wDSbC16I@xzQ&&nfja{2 z1`B@zy*cCX%XRkbI2|b{HT&}&iHM<@D7p~d_bS@Zy8(nnIci(ixz47L=z3(Wy|{46 zoOK^79G<1kk{VjrGo%C&hMp^9%$%$al{Dwk($uS>EmsHyLr1z=!OM|q4j8PDt)Jf7 zKP3?X$2JdetJu{gOma481%d*_Tr`7QlgB_9o*c%i+6S$tNR)a9dz4g`cR7L-*eC1U zPXSSKReG0%=lp+&2?iKx$xaL=4ll85lg}SiucsYjIN;L)Zm4-x{2*oPhuwpES1uQX zI~@J_!qKl&{)E1`y&EaT1rF>{1-(JAYTYSZ(FVOWN};(_wdx<6McN{j_QA>y2)*<0 z4@!*qqJNBxqe1DMNOTLF&wKU#e(tFS{^L6p+;@H`3~}m4M3ys`?*@&PXZ6yt+{;1M7$gB%p9xx7WVGBB&m!9H^NkG^?Ug#&2pvkKt<++!$Zk@#pom z_V5Gab*9Tf&EN=05#jOLJVu(W>oN`>KM6pV>jP-sTvmk<%KUfQt3xyF79a-_$NZph zEN)B~5Nq8D3w3I4MDwxNDQJC;ck2Ori|g2L#6H;!Y1p}PKOF9NBgrrjck0Lx+4Ok^ z)DrF`LAI$4)ZMPNv<-qGOar&MN3BCS1LIVZ7Ber~d_@&pcP}IHP{5h9eo7841ui0< zQBuY&c@79RCV4l1E)15ngPjO*U2;86Mb|E5{ekA8{lqEw{~GJT1>^}}G>H0%_W?6G z+M7epaUMz4JfH}0H0%S~gI%i9=B@#i2_(hvp-C~fs8PhkY9T#2?gNy6u-?EykDb5| z^P&@E(ZnI~A)2y?)4P&diI}YnWHXHR*NUp$uMB<66dd4B*UM-37}nHq^rJdel0+F4 z`*nd)JE}kHpDr%EW5yQKISBK{Tn;L}jT0j@EVS}6l$5I$P#PCFqy&GseNajR?`9YR z=~DfG)TBub!Ai!HIf>ucmMb~yFr@gYTI@3GvXR4fTt%aecPgg~rF4qu!9X}%0qV+m z)3eQoz;8(fvJe%|xWVA!gBs0lgA9$~7(3wQFR+dO)mJ~tj@gIL-h8~X-`71P__$^p zDzubb2VjCQE?N8w)T>aw*KM~;Kf9>jX;R*S6GA`kAs>k)6!aA|O(Yt6pm<^`gGl$| zJRRSm=9}xYV)c$69R4=rEraUb)0M^Km4 zcsoE@(K4G)s(KD%?D?7bR0_}IUpRcta?@qVtzlRI$swew4`NZA^_ok?EY(D;%)t!V z9?rg4{br*9L2!9713;LH360?ed7HxJbKk#cKEBH7CFV?eMVpOtEYu0BH~R3fybV8? zNjKN63IMW`xBr51CrW8ap`BAL39wbLhTNF1j|dIfIJg6~m9C>=x6w&_P%GF9s>5|U z)S;J{{pGB{8s_yW35s>V5t072pD7Y9GY)#mT-TDVIAWx!e8o$ zjBlE=5)uHTDV>*>@xyKqiPqCQ%Vt^etPg)$l4a=wbjHcs>!iQn5>D{0l%<2tUd#Xo zCCmPA3(=&}w`9A=;B4L|YN2%Qj@E}v(F>F8< z>VbWgQ$xjUxdu0$)Adniu>De`KyjOHDRR`UIuahGf`8}~X?!a`0gBBm7rp)}=r+b` zc2@hRp*|o%BbAiN8%?><3MjcG27%qB#+xrkyE5?okz48TpCgR=AXSYr&?y`z6F)o5 zas^63>#frlI-+biw4;&xo0Qg%wlE9#Iin~t^8MmmPy4Tl^h?4$2U{_Mo?3uQOA52# z{dL~B&?S_kz(OvZFT(;42$)d!`sFDmx8D+u;Zgxomrknd?Ej34dypb<`Ou{Hy@i$C znvBv@-KaXwx(_#)usMV*cm7R-w+7tPkJ?akynF*({h$S;02%wjNbRMnRV@y14z@60 zQ9Uk>5z=v~zgI@?*08b;B=Nf&9S=Jom00q1=1S#Gp|mAg2?2rwoE>w@VZ+yWBFJX( z@T}tFSv$P^felUYOLdcy*W5;z)*dS*#&a59K_b{|M;P?lItP>32xu1lr z_HJ8&&8la|7zcC>&gE`#0V(>OiLH%_SM`;`$F1>F<(W$Y84t?&>cgypL-tOGlK36a zpho+@`gx2O^gn~;$N&o=qFhF$98Fk*$N%h6Zf%z8$gZA_A738D%`t?|NE`a;;NdJq z99)NXVN7nm`ta`TBEun=Uk9~|EKt1+<&dyy^8kR}nor>#iMsk^250XHQtwb@gyU6s zgGyZci7);Nxe3pJ9kZ}7C*Cz%S*_I*izwDL$<^ozBN4^kN)nJMZc=KuVlt7%KVh63 z>2Xm&Q-D_tXrHH;l_H7q`3tp40qniEh3x~LO8G$?D>WH|;*l$x=RS~sQBrom2tf^G zU@sH`RtN8`{*<;};eBP;7YWxvO=L2}8H0l**InXS<@s4zaE<~JM1)aI+nB^O_vt^I z(&ja8b`L@A^mG1?u8wNO+=LEqZ#wAnjA}Yd@L|U6i5ws5TkOM?I(F~ic#63~ge9w~CJe$enMopV_D;tp+P#MTQ zpYCdeSC5Q@VBupJzM&mrf;xgm^kLZEr;)$q%dGz@lfQyK0tibvifvJoS@LDxn{*x* zSmL!@@m~M)LQL|veBMRC66>%*7^MO%j?`oW{kavx^a)|4Z0JZX=(l@%QK_)2fw_HQ zqj4ZHtzh+xs)H*ImsL}sV}tya+_=f)rcM7ghZGWw1C@JJ0}vd_t$n+ma?s+nKFl+s z#y%iM^Vo0PJA39fUaZl@PpK6Iv$zT?QEaG7k9D-2Z2-Gas8-(FrNL=P%57FCY6qWn z+*4VYA#nJn4RM2)BDl+UbQfC~wN5cOdcYP1O?-0YPkC(ALVezI48u*AG#lrj)-Qj| z1EuL5d=E$Y@h;*FcAG+RSUVv&0qOBA5D%gfNikOvN_6vL6~x(KP-xw{TkoF5c}D^9Y<8()99Zq{ahum-f&w# z;d9Iu1?OllhzkTe+j_qJTDvB2XFmJi9(Tyou>(` zc9(7k5zn%D^u?!<4G~@&GLdx*rTz}EhwQJ)`~f`K|Fx6-%fhi-;Aly+4eH@1fPCDE z3FQYn(o!;@7nC_+?QLTfAdLWAuGRx6*jp}fc{$z1BT7IL6!UcDB?t}MF*2{;>ij*o z3e@A8tq=r+PBKoZC6UR``xjObAgJ`&UWxu56@Itw9Q6-lyX2F77{Qv7yrp|Z$~InV zDoBOxaJn2gQR>;@-1aUJ=`0r>r&CpFLMB!zM|j((3mJLhzYzz68G>Owu=&@`-$I~e z5q}aSi*Oe_XK~Ua<0f*c+uRGZ$XFP^nTYIy4#OT+;z9yEY&wuiU0ve(s1fjUW8UAC z)XBW*Lw93@)XstiWiH#Dy&v5Z4$$T~{~sc_kAIgO#Jn1o$vHX%SE&1N!_96DyCVG= zR6{7!?T9GAVN>R=Ic9gV|3J;C)ReMDbN@XcnzEqXVbv8QT^8e^pqrn$hzCP!Ae2kr|LF65^keQKB z2mOYpjv|)Ce@u`KX~ks%Z34Cqpc82l*-<0hM-;z>M z)rD7stUS7ayG3ancGPdJ!&69~Xt{SQvSd_v=`ND*EsJl@9C78VlNf^h+|n=Lr&)AmI5V2ulpM~HNnO?>FVlAxMwHD{}=35@vtIZ2*kdAAw!nhefqZil33pth;kLMRgNoGn+4;}_+T6X0h;^A z0S+tR&lhe?aRSx}5IXj=o)yZ?5p=zlK@2D;HQ`|db3`7Hs%xjCQ z8SawLOL-$VZI4jKJjWDFtW4ae$E9M?Mlp+I2Y~<-t}gR?9#N&p5HB<$nEvt*Q%z>y z$AWC`8T|0$>;9@fY928(z3*~8SBA<@9E+oVx!C=l*$)&2AFbFEw{vNw2r+v1Z`SDcU%h@=LRQ~B1o~8nw&JZp0_Ono0vWIjmS<|8~gxK{TR;F365PYd$ z0)L&BjM3}FW%}eWPbI9h?c>6RJFbZV<9$v}N*-03v>(VSX3;d2m zi1Hv{VZ};1W}~SMLIs7Z!0V?hXVPdsjCgjUOxz9h#M%Z?op3ImrY_RG1-*NRI zM{b!*9|x`{n#A?N(4J=n^t>H254Q0q`qjTG73}hv>x+lWE<~5=4D>NkoBaF4awum` z3LmC9;0nCBVw;uYlx-Rx*={3)LiuwqmC6Zw1}B5s$PVpPrMDwYLc z{-bztbw~S=V2S#Uv~4#1dWT>+3cN+&&uIH3L`-dk~>~f5dtJh5{c4?IYMkf ze{SAd2dWegpOCvXlq2`=Q>O}SEbq%Pu_D^rP;i4!zv&3J&f?>SP#G9WL3=G6lgH9> zMo?YUZLZBX?;I4wh6J+44cL)0UJ7;D^8wdvjm{yJa?uo8ew6c33HHxUQ%UAb4q`T& zklHj3)GnAX` zY3E`tZdCMzEgUK+mr}X7Z$0DNX01$J(BNlE5DcWAOn8P<^8c_m+Wv}ePJPDyQN*{{ z226FGT=`DUT=#)v3<3u6o!QqoWf{@Rp4_Z>hC8dBE%3;nUeFVU8m1|b)u6(818tJc zsQ!C^M6Hq%{5r)q7QyQ~QncTMglpr>dgvFFK&8;$NDEJdii-pL_%m;wY$cc-$_G-o z7eLkiny9?YL=oELv(uYD%G(|dgVD1DL6ug)hI|vo3_X|Y{10D8i(H=vt|`JduF@=? z#}PT%QmqXPHd9zjM-j1yS>Iz|#!m3_(IA9H0lt0OW~%SeqEFv!x#pI{$Xeiozef7L zIJGqqM+k|`Xs57#fKW!+78HpK%RDNlh_e0m*;2e^ymdj~J^K2XaPzmd{3spj`$tU74raE;6&SBT>jmD&@WU?()g zHb4v_E?|f}R|mYA8<^B;m~an`8Rt(Y_B4VOoE-`*!|+lk8?(q8xFCr+nC-47%F?}{ zN9})?zmVjWTv$m`YOS;4D?fBSpjo(bHMR_<VY-p;Yw`*MoPzJBBgQmO33+I+K zb7?$1nD;%)SP^axadH)7CF=7Y6uq8-8f-43JsTLtRkpplSSS%BbCpFe{1f-)qudHbu&j4W|Qe*P+Mq)t5m>J=tz?pOdIm8c0!^$M%W-Ve==1 z@ee|Rfp_T;L4$3+?N5*81;OIVErBXRE&=$!Twt&_S^}ayi%>%jZ}u zfq^Yru3VL8W8^1;g43WOrl#FWWtuPSW`gSF`flC>c{E~Km!`6q5jad`s`BAsN8H2j zh&DkEyue zNaEnKb!z|-kth}3!2bAHKP*I8c-SD)ZXB#(=#bUB=yXRfCF5s)LQt8Oo8J77lvdCa zIP?Q>cgfD+{*g8A03ssk`fX}J8Lz4m_*ES|u8^V*F8ttTxi>shF6GPbxK)Ha zHC)3+W^{%>nie^kFWD5t4y*#@GWilAKk%0c{+ZSQPM5o{8 zOqwoe8zGHjNfV}J4sV0?+Yw)t#S97%i`)5{OjqsJ>9LJk!iKl!>(ka7F%?d`5cjInO*G!|WXmq}r`(+mlazDa`R zLFX(Ui9Z*156)%zK$-|-Y+rTcFac`Mq;uaq_gd_eE9STRW9wtV)}Qq-!Ed@Y$yJty zrh39r45jl2+H`RKs3~{PA2N$$YI!3*6V_No6MEH!XB3kw|M#ZDV{H(ghpVP^as#K! zqHn=Y3T-P9&8EzxjD>HFem{$<$jwv=-5k14|`FIL;}SN9kPFQtroKhzT~ zILjq*v6Q8)jk0^%vY@CEE z3_%rLYp)bCV9nL`4IfwU`&5UX=$NSK5_M??5}+-eZnjCS*-l~by|6@z1(J$G5lx3= zXR99FP8%MXQv7>l8&v;mVvB9A2)2Biuu7p_U6=h(6;B}@f3TeU~>}K;7><_`R{J>N*=k#=*dGb%hYhw>8WupgE)(DQV=N=t()7pA2&uusye&GNY zdFq?=Id8rPLrgMr_1}J+^f+b+aKm3Ar}9M5dm&b-#7VIm_4KEfZjD1QkDsEbZ~HtO z^6Aad(SpdwX@uiKykWuKKQ__7wObjY3w?JBBq@i^?1E9+`An&F0D{38N$}py9|Da{onTst4{xBitWAI_VB?N z=_Bo2KE=9!vX*}MpXvl;jWYM7$~)tEn~&p;XJ8^a?lx;@q#lFZDB*!)&Au>*V(z{I zR~?@)SZ7QL&Z-@(5qR{YYv7uogW`T*<*CPqMuf(Ld=Ke*uBcB5r6gF8P&i@Sn(Ti& z;IH?>Kx@8t^VGeeb|{Kls4`hc^x|k4A6@zjqn@tBEJExu(|?!pbOiW<@mI^bXBBl*J3^CT1=t1bRFGJ_M3Ldzy8;3>A0ZB z*iY~33P*w&9q&u<*M9>moKXSfg+c@kLbse+Wc66P`7iVp{bM5w%=^`W`ZlkNXd zEMg(+v5|_av+sqTE@)KXZllS>@STBw?DSYV88#KYyY>g`GIkHVZ=A00APP!Ot^wE4 zp^(cAb9`%w{TV_dh2WCXp$Ued$uBJs&mq5`ZF-qA-Oo{I#1zTce2*Zn{-yz%`6%-O zu@ccfTjORs1g|tse`tr3wMNh5DxMprO>$`GUC%_bS~xb%5$u6}=x$3g0yD`DX1?HR zZgf87xD`_&SS=vxplv(Pl*&gx(m7**Weizp%G<=nRhxwtVx?h0Aw63foOyx`6Fw8J z{ftmv$Kg;cpE&W52f?VSo{L^ zh*nO|Yl-Me-2{`w$S$O)at@xhiA@+( mk{}gQ<$PWIvGl*&JlJ&TT4TOTw|N(T42Uimww>3_rV-bFw;@CT diff --git a/tests/test_credentials_async.py b/tests/test_credentials_async.py new file mode 100644 index 000000000..51e4f0611 --- /dev/null +++ b/tests/test_credentials_async.py @@ -0,0 +1,136 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest # type: ignore + +from google.auth import exceptions +from google.auth.aio import credentials + + +class CredentialsImpl(credentials.Credentials): + pass + + +def test_credentials_constructor(): + credentials = CredentialsImpl() + assert not credentials.token + + +@pytest.mark.asyncio +async def test_before_request(): + credentials = CredentialsImpl() + request = "water" + headers = {} + credentials.token = "orchid" + + # before_request should not affect the value of the token. + await credentials.before_request(request, "http://example.com", "GET", headers) + assert credentials.token == "orchid" + assert headers["authorization"] == "Bearer orchid" + assert "x-allowed-locations" not in headers + + request = "earth" + headers = {} + + # Second call shouldn't affect token or headers. + await credentials.before_request(request, "http://example.com", "GET", headers) + assert credentials.token == "orchid" + assert headers["authorization"] == "Bearer orchid" + assert "x-allowed-locations" not in headers + + +@pytest.mark.asyncio +async def test_static_credentials_ctor(): + static_creds = credentials.StaticCredentials(token="orchid") + assert static_creds.token == "orchid" + + +@pytest.mark.asyncio +async def test_static_credentials_apply_default(): + static_creds = credentials.StaticCredentials(token="earth") + headers = {} + + await static_creds.apply(headers) + assert headers["authorization"] == "Bearer earth" + + await static_creds.apply(headers, token="orchid") + assert headers["authorization"] == "Bearer orchid" + + +@pytest.mark.asyncio +async def test_static_credentials_before_request(): + static_creds = credentials.StaticCredentials(token="orchid") + request = "water" + headers = {} + + # before_request should not affect the value of the token. + await static_creds.before_request(request, "http://example.com", "GET", headers) + assert static_creds.token == "orchid" + assert headers["authorization"] == "Bearer orchid" + assert "x-allowed-locations" not in headers + + request = "earth" + headers = {} + + # Second call shouldn't affect token or headers. + await static_creds.before_request(request, "http://example.com", "GET", headers) + assert static_creds.token == "orchid" + assert headers["authorization"] == "Bearer orchid" + assert "x-allowed-locations" not in headers + + +@pytest.mark.asyncio +async def test_static_credentials_refresh(): + static_creds = credentials.StaticCredentials(token="orchid") + request = "earth" + + with pytest.raises(exceptions.InvalidOperation) as exc: + await static_creds.refresh(request) + assert exc.match("Static credentials cannot be refreshed.") + + +@pytest.mark.asyncio +async def test_anonymous_credentials_ctor(): + anon = credentials.AnonymousCredentials() + assert anon.token is None + + +@pytest.mark.asyncio +async def test_anonymous_credentials_refresh(): + anon = credentials.AnonymousCredentials() + request = object() + with pytest.raises(exceptions.InvalidOperation) as exc: + await anon.refresh(request) + assert exc.match("Anonymous credentials cannot be refreshed.") + + +@pytest.mark.asyncio +async def test_anonymous_credentials_apply_default(): + anon = credentials.AnonymousCredentials() + headers = {} + await anon.apply(headers) + assert headers == {} + with pytest.raises(ValueError): + await anon.apply(headers, token="orchid") + + +@pytest.mark.asyncio +async def test_anonymous_credentials_before_request(): + anon = credentials.AnonymousCredentials() + request = object() + method = "GET" + url = "https://example.com/api/endpoint" + headers = {} + await anon.before_request(request, method, url, headers) + assert headers == {}