From 006c073e87d83a8da492882b7a16d7299e1cc4c7 Mon Sep 17 00:00:00 2001 From: Javier Salinas Date: Thu, 17 Sep 2020 18:25:50 +0200 Subject: [PATCH 001/198] Add gh-action to validate gradle wrapper --- .github/workflows/gradle-wrapper-validation.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/gradle-wrapper-validation.yml diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml new file mode 100644 index 00000000..a015578a --- /dev/null +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -0,0 +1,10 @@ +name: "Validate Gradle Wrapper" +on: [push, pull_request] + +jobs: + validation: + name: "validation/gradlew" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 From ac92da2659cfeea766aa404aa7d8d38ff26a400f Mon Sep 17 00:00:00 2001 From: Javier Salinas Date: Thu, 17 Sep 2020 18:25:56 +0200 Subject: [PATCH 002/198] Upgrade Gradle to 6.6.1 --- gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58695 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 35 ++++++++++------------- gradlew.bat | 2 +- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..f3d88b1c2faf2fc91d853cd5d4242b5547257070 100644 GIT binary patch delta 22808 zcmY(qV{j#0xGWqeJGL>I*tTukw(acLwr!g`nb^)G6PpufV&3<==T?1n{;q$k)>FN@ z`{^ENfgGQLY@!24N~c(^=mrM^!-E6^V@gdT!%kHM#{`nIFq+w$xVgovPCG6OV+t&H zd9YN3JxKVZ2^-1S*bQ<(cY0N@PV zS=>n6=2p6&=jM%efneS-ePI8(TBCZwulM^C6-ZG0*`cuuY)ZG?f^};H825-ytI@mg z>`HgyB7p)H^X5!u6=bA=sOS~4Hh3?eCl+pM+!S$N{q(W2Dr;Bp=)pO)iW#>``R_*6r8#mN! zE1JgVM%3xJJay_{Qs{6!uWOVw;_&wRlt)$O&AMA!5i4U?KC`I1D#;!s-(jq!Tlh>!Cy7!Tn43i8nWi_PKrO&e9yN^r(S?&0C#BHV4NhabM!p7EM2g@sqf=|4(|K{ zJW;H%wl8OTRsd5EANYD@WK2N=GwZFpkIx2N--4f?EJ39&GLm2ztcJtT035NbG-e7j z{F|v;k#uG<6HQ6POmqD)Kh~2ZAl5i24i(#6e^A2(L?WuF+z{?;Fa(RP%KEd5)Qpge z!hbE=(4Slc!9-|c17>gI}VEj2pUxz`gU{j*gb0 zs|TKG*D2(_8S9>G5BAOdCvLGK`UXVE=!?G$R|y%M?5$aIyd93%;{q{`yOwhl&i-Yb-)f8_>9w~(P?`Q-X6{Zk%u*xJ7VQ?V zGC&_rfZ1h~>B9cNyQbHJ21aOy2F+$77I-ZYCz5+{Q8_x47l5Q|*VTqAW%^@X(|-w+ z)_0krySs8W=bX|dIA&V_I;(i)dUTZAW3r7-ItYw_B@`a@lge=nF7P(*k50v>q!I_VefV5F()(xMPP`0E%muv+w0O#cuitgYdIDaLlMsf!AABD*O_8uXGq zHh;x>THO%UO;rtOpwXTjw9&rZ-!k7Nwg`=rl79I9L1QBQdVKa+bwuzZJ?P#2;Epv`@UHq!B>O}?Zqxmo+%wBh?vNQ8e}Fb&VOFYRZV{!#>_~xP zihx@Y2+BP=RLCm|>(h`+ALo++v6P56T>;)GMhXrh!ji(G#61(AgMH5oDe)PCd!C2AoyPm?pr;t>Dpr~em0fv^BraSKZm4}1623tVDg zxyH5{fd=OHwmm1pG>ob=by`PI2M3gFjb>X}y+g3IHFdf&YCUh}5vP6c<$)#SC&AmE zn$cT{lA@9Sc^uqI_S2;1dl4IN>0v0z;dmS{{IEOoc9CY!U7qrEN8q&J-+O*ypU=bm z=`$E8=vjW`PF6tISu{q3hLtip)q@*oalmfqAPiwuc2XDZhHE>(EQx521hV`Y^M}mJ zvPh896*v6=6wCsNgBZsqPS~iAEhr|n^KbgRWnL~pn(5WwMC9ch#A=EAS9S=^f*3BM zbd!kiDMNq!uspV3>q(+KrIRk$IsIY>+K7I`u)W1a@G?vX_yX61i8}l(Bn*~M8yWZJf|M@wn!H*=g?6n4Ochiwbnnm(g3ZDN#TgKw3L-9 zG*gxpOziN`GQxV%jmA0&$B_q^RXUZxnsqT~F+xp>*%*Hpt~&;pTrqHhW{M5TJWeYr z&>yyC&akk30v`;EcPArJ8qE~3A{5&{|WUd$~Yaw z&l_S%)F|Gl#c_pZq!HQf-Z>O!+Sd0Xj@yQoEm(s-LcD*`Zk0?t*t6tmR300f6PIei z`EN2T?=iSm(vCGMpQZx4>l5r?CusT&m=CfDH_$2_=-MZFcJ)+YvULwDOyZ`C7fxP1 zc*MH%@<<(aDmpwaRoUDZ9dKdq!XqqsSlmB3ri+U?$WJ3ynb)5W!6ri=*yViVSG{ea z=~sH=lxv*ubY=A|48`} z>GH3N+WP*oO(OxWP+OT<*0lOTKe8-m6D(l7F%Xhk?uUJ$AxqBz0P3&FXChN}FPxoVDV4f4hS52U&#EkD2olVg8Tw|H783z&Jfh9z1JkO z^sCm}Pb7{nFksrF|!F6o*9B2fkLadDCu0aZ-9v=Vl0Vxh*+?q1e6e4^YEV zeMZtwZWRFxT4SRjCNWA!`NvVDpuBkT*%uC|$%U9fNWtH+t;BiG-o;>;XkYPBfP{oL$gp#+TXs&jN zmYEn3fa{x7gd<%2jlmqcUi=`Z8(+-CxubOr#r#mt z?$ybHvwS`B;s%u#9L!3Q!Jm^U#P80r7}q2Pf3qvYy! z0N(0u>K#^Yn2 zV$1Or3R|OX&8_S%Ih@GccW`>qGCfI@21^tMOY8>Q?oK^ra!cHQfj9Iy$zw6gjX~GF zH(PG=fnR=wFGF620Z0QtO`oYmj~5Q1x4T!`x&EREBduX4+!qBQ{}+bGdx_Dvv-M3!9MU-u&ka*q127 zcRBvo)W}632nARl(TMj#@c5x8w3GPj-{H+2itWodt?fG%#`h&~{LdW|%-71uuh(Y4 z_jUN28jAWM!3&B|!8kDIg-Pg(U{@WzznprP^;T#q!Krp1iNjwC$&C=o7L`LCSJftJ z9J7&x=%CcXG|Tjjg<7NHMWLE=cr(>&g2bxNUPtFEkp?Fd^;v|`J4%2$mu%QcsVARVMIRj{dG!*0<^ zqfo(~yJRX`OXFZ`)=TPzE4o?l%zsV(JaVZ%B?E9=zld8u&+6r3AxfmUT{nB6r9xBc_S_!?*L9E8ZFL=Xpgcsn>6S!41uXu@3OI0|&Z=1BJyfQq-tatB9oY+( zHS~Hip%*+HN3vC8xAY7D;3f1bvO11i>+|C2{bh{~3)S$%>$|ELF!%}l8 zNqrhwPW?=1>Dy4~@~qp8PVB{yfX`dxmqBJ7d>z_Mm8P4Wp%9aF)5<)va2v@-v!G+B zk}m@qh=>gq?zHixd*XSF7aX}tq-|CyQ^3vivlgx&GJ|2p;>P(BSiz zL$dcAdg!K|{pGE)zoLuRe-%8}C$-gG>gf+?6Ot^qR7TF0Ee0oRN{H}1dmkZ|Jrj-b zHUr6tS`Dm`C81#qu7(NIE&1Ha@mt^oKe$8rR;cU8F&GwI`#ksb-%pry?{zxRzT|?qeCb}+`es0Wh(UQ@Mgs$ zI#hTqhwcK$aktJ0vt;Q@X_3G;RIc!+3;Qrhow&&}XWhHD@HbOr7I_CbiS_+rcM+4a zc!T2K6e){RZbjX;CQ6%e`J^=Aev-w90&Yer{YllGI_TgP+46&v_*tesbJ^0CmRP5-=`1T`H8fBcH)Z ze}|f&jOYMbb|Md|j0p#JtQe{-Wm*A_^rDe+{^~R>%nKx zGl^9MA_;G4T*J$1<^GEFEld089=svt%HLoUv6-qkMsW0jMWJ2aIXC}pQH&cW!d=Jq zQ4v4bboVdIPtr6xttX46AvqZ!wI4h98Z16FJNFjl)13{hA98T@B)_6I*NAUsvPo3> zTYK(Ssn=1>dz&8j zgStCAZ#B7tp|{^|cKsvz3p}Icw84EgR{DEhSN!g16$m1I@&k4X(&8ykyRA}tszj+^ z#i?V=TjCICg<+-5{G0|ji)LkEd`>QdSoPV__@!t|eOmk$(qHW|uez^g!Tk+=L?A~_ z<=_>pVUfpVrjdZqX-f1)iW?dH!!+xNAGrtuP&bH4Oar2NEui)FLQ^K8&4c~j853AA zE%4esSXl^4+`l3uIo$*U-QMb~{HAB9XG^NpoxuR+G>4cW$hRFpSSjd8@<&%b-2A9* z`?<(gUL!i6)*@SfaGn=KQVRd5H?5$+R%LA)h?lNV&osP@`2a$6TBi3Gn`K~QHjI#o zsPXyF>EK^btoKsB#{O+er@5yHV@1Gu!052tQFj!gkPFZ0uyn0?w%*nuGn?i?topi` z-r4}v(nF|&duc3wgR!+TL7GIgZ79}USFLuaUU|Q*r`7Ex4evzE3X(haH8 zvgu2w&L2Pk(P`1-f}I-y(K%mq=PUL&RTKM^@HYbgv(BbUCwMkhV;+Qqo%w-N*c}Jt z$T_*JvUww)v0TEhF%oG-sTXN=wa|cJV4CTRluHj@McfG4Az%*OLEO-DDvD0yGaQIW z=()d$&_!7__%&7J?E=}zrrvmH_m-`2oM;L;-lxASl>mw8msReWMBx!Nx-s;H{XZ*q&Q}oUQJkDA2X`z{sFYcY$d+)5? z-C@DT5&^63FSt#%p}B9s2YHRnB4%JrE55G_kx;yqMr{n2k!aqrY*ec~Ktmcx!FXt; zOjDE8!Ur;c(E(+usEmH-yqr>ZjScWZ>LK!5?fF37u-yhik}xw{Gjkpk7x1t%PEzF9 zi47AXJu;k-vCb}Lr(jX{;J)k;zSjmW%6_5XanCH~x46c>ji~>6pYZrNL@XH!U)8b4 zvz;=ob?!=&FrFOC6^Pfr5as^7 zK-S%53|#BDBhMNu89TukD2V?J@V9I#RNzBV)0yxO>0x9pQ(8)?Y>ojMN9Q#+6MN=%9fl1}J;Xh#Rar0r zL4i?#4`v5o@YNGq$(~d!t%cm+8$(Zy&v5Z*;Sy}W1nAYhSha@KsYqgzZ({V1vw+p9 zR+Tu`$>acy?i!HDTU*bL&JJ>zkdHqY?eP{ya%C9D`L9C0@#-%s)}#Gl0zA`f@d$sB zxws-GR&!3Nh`#|0gmJ4A9B~FZ&Mw}`o`HGThWoRpv`#8a%?N_Qo7p7Co56J=JiGpg zmsakU^ppp!8=YYDp+-yn&_22!E&beKYZAYPvNK&f4zh&g?8~Fb9|7qydn@RlEHQX>PBp!Rr1I_7*q-(%_yZl=Zs^NUg&X2=*Z;VDzH{tUE zgZztTL4Q4=3aF2e1r(aQh^0?$v%_GPR4=_Jum#c@dKdKu!jS;s_Crbir6n;0 zX9!44Y^ccn)yH_Zn3e%Tl>3M1in1?Z!lP&_+9uj6E4T}(T;~y#O+|-IzT)v`nqj8| z&{Nrz6_t6M+t}J^x$AGn8;cCBe>f|$Hsk8GIKMB(@bR?S9Z7^_p>@{w6W5_6B!T<<{q_ zP{PP5WaMeXl~tbtgNJ`K&}p8_XM2M?yi{Y2z)jrX`zuKl* zJ(t52KLej^417peR-mjM%S^$g&OFjnoDD9~{RLi$_lh7HxrAa+BfNnxW2U>YC(2W; zn6Cr%=7U%&9vSFWBaH2Vm(&o2wK`)2uKD43_zu(D5Y0B4wP3`_DXns2!d@U$2Bw(1o-UVZPQ5XN6()aJ zE9M_cJMBR$?|#Tajaz0)EdPYu`F|TYw-V4sLz!6q&_?OE9MDGNJkYxXTon8zdwSmL zgPkli`+V^Iu{QvyoRpd?>KDO4Vaa1K;htKZeH4lh>A}S83#ymuutJ&_p1|Tg{=n)z zEpPe3!xvzC$Zpfu?oY)mn`OjV6VD+MpRJa}1xH?&WlM~*&^HV5=C}ESPut>MK%NZW267xul*bNf}yg3C7Tz9B9gP`1-i&geShMWN%K|eR zsnXi5%5~2#=;m0(lZ97yj%2Gx+f|UyTn++hb5$6!sA`H8%)XBcO__2~C<`Ms=8Of! zZ>3!=^j(d<4#iFvg?-54ju(eCzSSHn4{P8#u9x;*7s4upvlUa;`T$XX6?06O$!Mj5 z-gJdSPOr@sPozCaIzQJ_o0^jY)H7igKCu5IjQW{o_gVX)g&gk^3v722r1H$~W0_%kB@lj2_@Os zP)xoAv6Rj}R>M%(i9f{+R;rh^ML)6d!lDCh3aQ%e1*N|*Zr?eVw}gT%45oonEc~^A z=}LL)yU0%+6;lW;OBuSxpCMz24U{E_HGQ*o+Wu=mnE5->%jYtK&CFJB2s+)Z?Nj(R zsG(rCE=6O^Tp#zoqEm}13hlD8h_tX!clr68x)Nt+i1q-gFTU(_!igfrP~TN4p$+>9 z$21>X0;Ro<4B_N6xabFZ^L=1CU0*d5p97Y zc*a6KrBpbYluB(S3LFsnj*_iW&6OJQ2ZP^XKE(nLg1UnA4CF~dsD3c@kreEo2(VxA za4I5XoI(Iz_2%>?#_MvTpN{w;uH2!Im9mzN2WaL>%oGtDq?v}}CY>Y^`_~l4C8?6> zZ5`k{MqKqz!e1o++sub~%cJ#mSCsO%P`vO_V95?r$AMW_c;OTZ4%lnG`}LRxmo^&bsuxP|k2_lMlPgyDbxCJC$ z-I+Gfg4tGZ*KKrK(tsJ!f$08u%N2hWVH_**d`Qr1tAeRR0`(TZenNsy|9YiP_KeRk zUi18p7Uo|%PS}F9_$MCE*LO9=G@`Xe~tsJWRb+cJ*)bSN|v0LolgBWC@qI0_}OyT|j z<+CPo{?kdO=xV&HxF?Ng6C=2e)3-?`Wmi|T*Tqd@5qIBazbQ3X#GKS43_(z?OW?Dv zOUUtfQe%80SQqZ!n*&ss@!=8D4ADk~WT#wa#KJi0kG|qS22(;_}JF z9no6`vzo+oQpQYZg_L!pJCGO>b2&tv$#uy80Ak^Uak*b%v>gEqq&>a!uWobV2bUj|NLgOu;t}_dHR8QBXXKKv ze#>ah4-@5KT2e%gc(Hhi$XoW1Xgj$OKl?_dYGLd;U3u$$5JgXq=ui|mIe1nwW*JZ> zJareDqLJA?pQlQoZd3vP_uK}%PxqH$f`JJ$fPww^e*_x#|6fA+tFNpGQMH05Z`UA5 z5if$EM6rhwpvpwy&=J7_sE`^yD5l+BIu6*>}G|Tx{9oDBwV&z{$RwZNKYotJgxe`CgxsSXdPFMftB8rBmkw zcHnRs9-~47J6X%(kqn!vA!H!!o(g=TC)%%%s=^O`$&)czwz>I39_m>rA*G|kkG5DU znbgKxb0MTt8d0O7TXhngxAQu%MEnf~3$ zEHTZW$QtgSrgl#yR;I&iz1s+Sj&9M~Xv(?8H0hBM+WLbuC0A)chWolCg?|ru@z(Y# zDL^VYjqm3kf(rY~Sb}22T(9Tms~>G4Ty*+3l^R0<&|ELtnVFI{Cp23}l^$Dl&cKOz zt9xvlp}?B-7YBn-o#J&QF7wTkhc)v4@l)Aw6k48s#w%uDXngs zT)-dlwW%#`Z#$E;N#}@8?~l;7V<%k3&l=-F(_~bbn`UIlSqI_%l;n&I2mTYUgsx?? zX|hh+(IinE5z~9LC~oTS>NiXr*RoZaO{xDKEjDU`6O`{|LXFRg!;)`!OW_;PO(})# z_t%%w%coAn3SS>9=I=`Mgypt&t%)i>YVDt)3l1{!n@N$*b;6L-G45;ocl@RI3nT-! z$MWK?N%mcpP~F~0F#<6K08orgtobaYx?@?aS+zO3t3=SPz~-;Ye+(08Z3oUlapIkq zY=(Wpl4NCe$-|CTBqdiyb-8XfkFApu%>*AGdnMCSp4OixqV^4$ZC0=(o$669JRfV_ z$7VtrVWqUy)}f#D_s^Rq3g?o}iI%RR-Eci-okF-_5FUgQ{n@NV4N&c&E9a4u5_!K7 zy}-V0%}N3l-OS;>%dn7H)Y9)<))-A#AK!NAu%gZ$v+}g!xhk%MT>f^Y9nKQZ^43w2 zofH0dD<`8!n}cKIGl!bl)L2CalswtHO#6r~MM48h`x^sYJ2rw9JWy$W8nY+YW<+xv zj-$gW$4P-6Mmv8?3V8g5&lf@gSdz((2E3nI{4}X1ZsZbW=m_0LB88knX?`^m)Yrvo zsXOS@VM>%R9!KjdE$^eiVi^=fz&?)1xx35@`HCS@aS3ZjZw`P% zv3|4^MbP7(Oc+O(>~oYD2J5SrXykf?u^Yqb00(G<&KXas1GmZcz|Z&M`(&_u;d6h7 z<&@-PGdI0Hklp^ZLMkF}$i;F9DxrbVuO~=W=4ZT>0(&}!k!Xd=AzMD=EP06F=vg(k zTIyOymCLeu(bZ#$#Y3BAXMpg+%|`Lp!gXs$OU(n3qrq?HIp-}keL=C7KF1_z zoF^G1J^gAg0kXz0#ET=u709qIz^MArqc4`gNnwezkl}^F8-b@r9JCix&oQ675AKJxxuJ~#S3@1MTHX^?}rG;^73+9AE~zgJP_yhUL^RgOE0CW(-kuVP7* ze!%fO0R$yvIje$B0F5bT`|ZV6cXur>X{Fl!b(5U64x00f|12`0$K%3gqVSWYsvfunikG0>i)D9<9cVq2D`hk9 zSJqy#YBY7+<7IJ{DQF$2et++3y~2KorF-2o0>c~AGfArbiHsWWk^BX0CzwRu5luWx zr?~EBl}Xja!u)6NM=7ZN)xTJFL%QbkX5mbogBtwlb}Q~3`wfoyUKGuVPvOP)d)0S_ zg;ZW0`=yTkd>YxGtNn#;RA0e;Q*D-IGO7k=L`k_Rgaj$pP?rw}t!EHRv^m<9*{dWr zfg+ZB&hav=x!85m1#Kd1*!JSYD1RNe(}u4G@oajYY^qp%!}Qu;N^iG1qUN1wDL zdwyApe08XkeTQp5vE%$X0P2BBy_kX0$C0l;mSf25=na<$NR&YIx!NK6K@^ zgpLcVKXB!zW-rRm04sXg0=RbWy7>0LfqQ=<0I!Q5)yp|cyB0$nY%lDtk4h!tDcj`gOX&}!2$AQXgEr1@!KjsVid1yar0^`*&X`qKWI z>s98C0#G#VNGCrsuB!*CO^gNjtW@0U(S8?v7u}OksGU42(aB(7W{jin!_a9f|M_{T z8t%|kUfF`gITqJaRF)@1^U*PNbIUg2*8I{&Ez6(&Jp)vEX{7y*|8BS!0=^Vx*|pb- zr0*U-tAFAAN`&~`{H1a(@YOj*5{2_UOj0qk*(j~{N+#p+jYX1pei5wESJRoCjmPCC z8~5G(a)6O8SfQl;mDc#*UHtjrFNUf7Drls1o{Ll!7382wW%GP4Np@)(_2y*XQ*9nH z{UNM=QndOL{_a&20pDT52Kv5IlqOl=U#puxr8qjYqS>|o`l<3DS8kxJL(`W!nCB>> ze9w7q>2wu|DSzdM#M*+QGB!GN3o%|BwwHW4=RVe~|L$MTmzF1Js#hp%^XSW{{!t`n zRB8V{6|Un{OVsWm!}L#HRK-5vB+Puh1dGx$Z%9@Toei}(QF9-vk3_S;>5K4&+l5z0~(A1^;zu zn}fJOs3vKR|75tJ1_t(U7LvBV0uoG#KE3kzh4?n;JIJ)U;q`;StF$b2!Wbi^;22miL>@D6fkb2P^ZUccgfY1A`f^RwhOo zYdSV}C*JgV%pTGIcG5-prpU^OageuX?9x}59p@DWnIlbJfISjBLyLl}=1>KCP6#^G z9V+o$7e6L5E*dSK%2$a1vej3`S5WneT` z%h!g?;L`%NfOaW9S{2u%Z2EDbvr}A=ryo=toUw_T-=bIcX{~ z=#v&F;=W{tr)}>dSjkVu8Cf=uD-SwvZjkTaVV35UN-U{#MT|2-+8;krpwIYu3$yye zJL%szk0%143*3(GhyHdhOK1XF3_=2Nt(nSiN%jW3(`5VD1HD)odk zkhc_wQ+5=H*U(?c9J!jf!y1I6C0h!;%82}`stSc^6lW{zxdq1$i!8Rd4(bhco#J0Y zqWfp+Z#=LmF?<0Jxf4{`RaKVi%4a=Nn&#z10_3R_9T#@L zWh|*Z$Co}Tetigd1MhmV;rv9^bTx4xy(%LSXn9kt?E2LjmL z3OHR1pPJ4jW+S*tURMrGG2&}z{gx@MHE`P&i(C-vXH{z8yMV!0L%(%j$m+hV2A7|fA0&|ozh$gO! zqOS>TgoW`~`$7|Hk*HbOt37iQy}X1-lzFL*W)50rTH+!~9KzNV*t2rL<5Bg!DdQ^{ z*u#gI)x#1hv9pGYQmGZ~Cdw4jz*aPQfw3D^L}sVpL795`A6W2-aM2VG9E_o9D>5R?OVnGF~DDgtr@^SdO=F%SvaCSsrMfeXvS~53y&48wgz6 zu!0mv=P=n?#cr5WYG;Ar#Kz%IXz}kM5eFj0cm7e7bn0I;NSDW{$baQc>j((Ef#SEK zT|;UHu0fP+|E-~{V%|+?tK6{uTvk@USKk`WV3MY91yvvPt2IEXr$`Ls47ds@_@RrJ z2Sk~hzV&4wnm$Z2CywOeB=h3B;ERi64zVMkLQ|1)YLW7Ckur`}wK@QnVeH#($5xjE zZzmu!72Jb}!@!k7HhY*aDk7O1fx4C%-H|L*k_7S%Vwmb@dsSlW0Lu%D45^|}hYXyi zaws{8Nep#E?JXI$sVxe0J2tOH`T=K6hdKLE)uyV7x%gln4v&JA9A2jZ2KY>$>(cI! z1D}Prmp+>+EZvu15Cqpn;8Fkfqnf}>?ODkS!oR$u+c>uiW_A-KDg1z-n6>@1j0mQ`wA*5m2DSV6uU{_%t|Sz%Vg*@ zrDB)pX(Mf0rANu3%sqCU_`3CV7v$QA4&-0t>r_ai<~ODOJ_vFR!nRPk;$$+tm_6uc z+(G*49KQ4tUgxtRR~CeX0~Vs#GY$T8jmmPpLmVjSd9AN9V0l;^n0g;y<)Wi^oBo(Y#mVlCC?7JpF(k4VyKJ0IOFfs$Ed3}go z{ovEuGn=3%ydG>A5a6UYy#V$RAwxmtp=Td43V&F$U^pn2%*qtN2E|zLOYlS`F1Q#!y4a$nqgI210_ z1*q?eAZbYO_`-5nZi^B>6aOi2_dnG>=!aS~itsT4-!f{CV;MS0QWq;1dC0(O{n?V3 zR3;SzrE}kyaRh95H-Ve<{TEZmoEy<#Q*O7oW}4@T0nZ?eotO_0_e{}KoH96axmrIF zh3ki2(h>Nn(01_7q5b8NaMAN_~Tv*y0J zJI$C`nNP#~bv=UB1H!HWilLZq2#KGV@-0V3Qj-|p5`Rad~&D<7n5>d-PCWqTeCP-qo>lqJ(%JeJ8v$J2I3pg+Y1|=-^IN4(VC8 z+X|3ZXSPVxiI>;?x||NFB888bw*oCVvIP-hJ3;O+$nNK#V6UDJn=W7WN(4>##$<1> zmb>Mo4?w1)i)WKO6l2i$0^ou4{&9TofyO&PlBH-`csJoo|BdjY$A$o5+_4AoGhJHL z^pc}#nw&TN3%Q@Tj#%j%%8TixbIET_41G7Dt=Y?1>K(Kx^4%c}q}Z|N6@s{jls@Rt zFHXMaK~gyr8(Al%)u3LAZ>uri)3;=#Dh=O@GnzZ zB^9)slZA1n@h!fW4)nCF-+__1BzZ0{J4&7XD$CXy=1sv4U}h7PQ`7e&;#nt=>1U@R;a<0hM~OFBeE~z6e8|Su z0>`5AvgC34cYF;J9L^trM>!Z&95c4siDZGXxI-;IEq+iy_{Vq@;dVw4wY_iteR&Yg zE#CT#(yA}p0|759+ej7vUw`@rBK3!Y5Kv`Pc32oyAh#^O{to-b3!20h3v!f8A_-fB znwC1G-(j=dF5JDbhT^7-oX7)uy@TBnRT|G(&o zgQdYtuePzXT}!(D6y>mU_n?!{kHbCT2-8X}TA9(Lo%Ce+C)^CTPlZqG&%8mJF(Ahv zvuZ{%x9zTa81G?v5^Eq&!~Ja@U9}6-Ik{HLD6^$4g>I#JdHw{q=`C`pbd~9Z9)k$O z=CSrlXwN~rGL%;gSFR{D6@T|exslpQ-s0BVvtH|Osm3C-;N`CR zni7PMehR&uQ_@fg6i8=*vi%-yQy}$+5ix*;R+M-p^iWnz9Rr51(#Ykw(MG#mNq%u& z_qH7-#-AYyge3XaVh#&1P>xgxB>zUt*gYGVa`H45!mqKiM%Iz(5NWeRBpX4s(ClF! zG7E}+@V5Nfw@|V+?(L)?Z1_j@l}a0>aCn)rdPcZ$bAe!j`HpV=OR@huoYvlCX^kc> zU?zv}!*5u!2H^Bm z&J#A5>Bs?7$jwSyV~rkYF>v|~FqT{rFA&dRX(jixk+WGAea>jGITzLHiN!9%>@1t^ z{8C`}wZq4jVNZ(lQuKW7*YjTyBGc>i^Zklz7s46-JH=UOm5&)-VMs$iRhsrr`9uWA z$$W(x4BAe7S$A>NFiHkh{ha#$La5I}cwR{Q*nwZfz?n$Ag@nvb32J^kE3u>Sd>$I2X)JjV! zg+D7W%JOsfwXF`U>9wW}PwBC*K9MM$!6%NhEF)f&r4)!k$!)73lXkF@?z7uOw5$M&^3h|bYDjzsSyY6M3_joyhGy+l8V5F9O{ zw-LSf-dmKYQKKF;dWlX*2od5t*K_av-F!2D%vx)|YwvmXJoC)jd)9i%wKf_$XsTz0 z%whZ%$sZGJI6Zo_g@v^RgsSHDw+GAk?NTjwBps^k8fUb$58F?kMKpAKFQyGHa2ZVS zQOv-^E-Q6oI+#|WbyrU0=sacy5OVM+N1|w%3w;k(;90LK^1%Qh(lG1_yTWG|5#PvQ z%KKyVpq==!p{0rYr%Bn525e#GI|}Cw)sx>`^z<9J3yKRHa$n4YLSQUPBVvWt&IPrt zH>}sDPQzP!4z1!GlJxg}dFH4(Z%q?EmS7kbdO}I*$b#T0bGUdjX;Esm(IVPzSJv_+ z1eV=8LCP*F6=J@l7f97ZvO;FufY2JoPw|!NTr(C{I?G+2U_B<}#2|T0PET^g%rc@f zFz_4wg;6Nlb}|t!BsxXU3kXN3=|_FXr6GIuw2vm8;)IFD?&?_|@Jg|dbIVFRoO|hT zX@K7^P|tExBinHKllkO?BGz=miPp?d8b8%13WFC|RjkKKG#%!LJb&-u3=s8LCz`KkBbv%Dgqps89@S z@}1o@Ce%R#9dw7b6+ha^-pXS(OIj;Li&Msx-V?TGct^?~1@9WGpUFFtNNq6&?I-{3!@#rnwF zY-!6PlJpCsY1l3n@k~nR9kUz@$pOKYI>T7F*6JUmB6w7}HCNG$*xF{*~+u zu*7Ypoybre3SrisSQ2*4Up&hV8BggH?$!CtWhC7%oIec_wPVqnpx7-mqnDJXfC-&u z;vVhXoy()0&)ZiV?8_M!CaNUD#Fi2|)!ADnV3e^SxKcmLLAxgxm>b*6+GjMz>z%%z zxs*R=L?0u1%#FDAOjY+@rI*$e%iuva?Fq_Sho9cd!@j-kxSN1+#t6IkP&; z+@sZ^<&RpT0=Q|Q=_>IJtxbIO5PHoXA?Pm5kGRSjfxhLi=nD1MBJhN^a^@GN=9z2$V z<4CD8i#cB1Io5w(02ulBhT&+mp6BEwktl1E9!l#h>bZW!cMEUMgvo`aF>vX-$E^^z zvg~J8815r_SY}h7LXu8Uk@DehY~xe$4eU_ob1%`s8f}ZstEtx|7*q!(W-P@CuT89fTx)CHKaTcPd zb0@oPG7q55?u?{WjIQ`=7yGtl%!~@2)5A?n{4Zr`H-z#z(_tOujQ6tPaQpqu(FPHZ zqOc1x%Too$6Y_!=g#BT<8-7Lmy-4-_Fh<^`>(d*Grl%3F#(A_ZJ13*O(dce4>YQ|| znDCe>tY0gkh-5l&0X2IHKr)^bQ1xa)aKNg0)YZXXLn(52>aj?w{iWVTkmEg3I9_Qq z-j|wZS&;R?%IenZlnGKazbZOOiF6%x3NSZpq$a&dAO4i?{Na(9z-zzXzrRs*((5t{ zGEF{})|SF&BsHf#HODy@33+scKT?bt%@>Ug-5_mCPM}|7=x2)NxD)eJkq0xE0I{U7 zG$0EPNgv^gQ#OfWKCR%Ha>y~> zr^3V2j}n0sXm{s`w5M1MdZv3IrS>YlzYs1M^y#>fulboWJlzcvl@2;g=6?lo_d*jU-=E(g)e!NSO*M z>WgYio1M;EwOEw?#L;y9iG#D8@~=)z53E1CHMQ|YH=^! z>Z|hRsR(?7xv25J<{iNfjcto+^mpdC_vWE(4tMF8_vz{8H%KedX2KDdM1$0oz(d+I zx_0_SK{d?BooBd5$2L=~$Ap;$zrWgwp*<&#D`Xh>G12UaW_OLYe5Rh<_~Ey-EHYD8 zcifiD)PbbJ0r!ym4Vqz1F{UG%yf&)~{*ug-awp`FEc#oLPP*=020M(o`a1xIb{s^60F_;+;0W^?VNXrTfv{py_}1)nfC`?NVbrFfGtTB^l6>2QEzy11qw znj8566w_&#K$A?)KmI#tjqVjW^^d1c=Ci7s4>H!q-XF}@{W>gym0f?&dhUnu;O$#} zRf`i$LM8r?>VY_b!AxI{GO4FIunc-Hd<3t*RK1l|8qwzwP0O&j+03#bED_J=?-AV= z$u2B{2lb@6%y5qM_6afLcAkHy{86{5%v-Juk|I>5t2J`iX13?4(^|RkXwpPjx#xYi zi`(S$YY#%bwx!&pw9l5YGv$sMYYAWn!53CbABqyon8UVsR4SZG8ySA6&zZud+~{ZLBPhMNE*QQuvAfkXTy z!SLoqu-Ulb>km8Q42FilPx-y37loy%@02HM2(|^4w9zKcYzg7#e7nzSi6yD?wSb{>>LF?IK}A0E@+e zulMRg`xq@tfcvL+i}O+P3|XC$btdfKY1gAjT^|F@i-kHW=Y2ogfw~uSc-B};vuU8mE3AUy><{b>#jXdR3 zL^Q5d7AM9>u3@A*8(iZUqY7s<<8RQh z>M-AQfCzmKG)Ko#8H21OXlO8C!j~C1eG5g5JlpjoLlM)o3y)YdY+%LAg;cjHK7@tyovN)WXVJKRBD!&;}A|Dli9Fhy6VpE@V z;oLPJ_<^?=_}0ryraRB)n)>-;lK{4A<8DCtG9ehX6lThPCS7Tk(q8G9tbjX4VtI&( zUwO?r;v1X_(2#>U7O5c_lps^{i`J4V(}C3PPIGc{sg6g^9aZbs9tv9{K}PPn`w z-D@U*LCXy(%x5D0#k=4pWAc-wlBp+couOTF$O5ZNwqJ+|*HH;#Jvt@j zgwPk1L&WuDCgUSJY_}__#kZ`HPd2ucm#ebiQgC7QD;hN%n*gqJ20=pjd@ESJZoaMK zk+ZUl4JOVzu_1$6cJYi1v%W5eq=X3PTK&}dQb(378+yXC^jY0>$sziUbt~*rH%Xi1 znZaX=lscg0bnV2Lx7!M8en;3ZMj}GHi_Sm`5zdbjw6RUjg>|$E>-6i!A z9l251yS4-JW&8%;3ekhbis{sSLMn{5TO|c8Oo&P zpCPkB#k|CoX`1d;m&TeEhU-%$xJsVdNVtyPLT*`ViFJHaih&ld*R0cGdA~wk(g|K! zlTugN98Y!alJ;2_gQsDlGTj8!W1ul4DmYX9p?)Le@tYlM+$xT_APp?z9qno=d-Aqu zA<|`VbAEACD`9_*(YNn!5XwXbU2TCbGhX*x4{JtcG_Zc16b3huw?%o9w?!=B5v{_o zzPd4gZb5R)WXDM75N%H85;}NY@ zcNW;pkzpAW>5l-RTjc&iBgH&8f}{C`STBlZON$A&OUsedjw1~Y2*|}pe1mK!NX5uk z=#+~cp;kGz&|XIpRWkE_Yat~$VG;<#+$4vi_mfsjiaWNre#X>ywhjWEau!mpVgYko zO6`eITeX7nxPyf3I8K^T&dL>J;XCJ|&P%)Vp8|I66b9mzVx#L;T#1^*N6EX&96N(0 za$l@)V2uNIDdO*25&jzyW2J3ozNK*r#5DcQF;<%Fmx1Tcb$q3N<5#oNmMOcXfa^lz zM%MNedQ%oz?@an{9ls8gNnL*V(pTEgpc+XQwwU*Xp{C4{3syh6Dwrk^_PS~mvyK%# zw08!(V$Gp|=aKOo|L?sZ#SRh3PQNlu>8sv}lJKJRCS>;amxxr4WmPw@wwgO{7X=Pd zaRZ>&Be7(=zTn7f7v_x4W%ed0xRxgo4Xm|2!0DdoV~WjHkq3v3vYGxgi;<_Tz-K@= zzdzJ_S37)`PpvHgQbSA?di{)Xxpz9au6sMu-i2p17d-@uv4gFVC@gkC`9We&3V8?Z7_wX2T{8HateSGg~yjj4hY!@muh=srF zgCUCH`0&+d45g=;f+2PCd|50pfZKy`{eJbOF%yaHaZ3b?b=}W(H|+>%^^a7UrXOv{ zaZC=^@ZR~Ky5r_IOpZQ{ll;s!KNn1+8VHYML}%(E#>z6W-CO{7sa8A7Ydn0Svl6#W z>CPO1kNBc5r)l5fQ;T()3F+}#Hk00w?8Dl&39PMlQSTev-*JMnwJIEliyE;X!zj;s zQ~O*dx)6w)Z}Z%Di_(YIGSa%r4$a=+G#ssVASP?Xx2JS#q3a@B;m%;P6)kNHsV`j+PYEr#^(FGL)xQbWC6qr)fo1! zTCrI8;OLA^w%HLj4c@iH4h=Wba8HpAQf=F&J8QE$h=RK82gGZ3la&T(ae z93^yxzgR9(l1D{;$hNgC#}Ak5J2aU%F1hC!8%bJzez6?JI!0*LyP6$$q&M#88hTUe zGOoJP{^t`BvWF7^I+I@)diHK6xX~zV0SaxGMXbh`s+H< zuPF{Xo($G4GfC&SLW1>JYqC$Z1IWXXL2Tbt4 z#weWIMidDgz&A(*{evFv9~A1EAK-%f5AeTiaX`rs7e(>+{!qWlH|-g$>AjdI0j*xmBYS32vwCe?MH%&_w?5<9>Dv zlo%ldrY_lA3OekfQb$M2E#z%X18LNE*(%c!U;;XzEzJ$WI*ri{uZ$T%~281 zF&sFyVF7T)puor|5lGz)P`Bkmshpq`H$ZSq3^d>dxQ-cvv|gevG=PIWGe9`b3Br60 zOuU5x^e$9@14>Qxxm6EriBYX!067jpzeE+4(gFAr!XU|DR3j5$JwXNi-)0ILn%E!r z&h_RNr0NX;wgLlghQuRG!vwBt{E>sK=g0a3@^nvN%h zpnrspfA|f?15c24pwpCF>=;rO^gSJTlF9)*o{|JzWB~zF79gCwTMO~D7znEXB?{dR z5jPD1p%ko3jE{PXGsf41YZ< hFv}l(fhGZoel|*VB`j2!jD{wRI`>e6%1{2K{U6dpN*w?I delta 20005 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 nvEpR}Ffj;rE}sKLhd|1%F5dI!=ox-rLy2 Date: Thu, 24 Sep 2020 15:17:58 -0500 Subject: [PATCH 003/198] Add ability to verify claim presence (#442) * add ability to verify claim presence only * remove commented-out method * Use singleton class to mark a claim should have only presence checked * minor refactoring to claim verification logic for readability * add test for null claim name --- .../main/java/com/auth0/jwt/JWTVerifier.java | 88 +++++++---- .../auth0/jwt/interfaces/Verification.java | 8 + .../java/com/auth0/jwt/JWTVerifierTest.java | 141 ++++++++++++++++++ 3 files changed, 212 insertions(+), 25 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 487addaf..a311ab33 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -3,6 +3,7 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.*; import com.auth0.jwt.impl.JWTParser; +import com.auth0.jwt.impl.NullClaim; import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Clock; @@ -115,6 +116,13 @@ public Verification withJWTId(String jwtId) { return this; } + @Override + public Verification withClaimPresence(String name) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, NonEmptyClaim.getInstance()); + return this; + } + @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { assertNonNull(name); @@ -289,35 +297,49 @@ private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws private void verifyClaims(DecodedJWT jwt, Map claims) throws TokenExpiredException, InvalidClaimException { for (Map.Entry entry : claims.entrySet()) { - switch (entry.getKey()) { - case PublicClaims.AUDIENCE: - assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); - break; - case PublicClaims.EXPIRES_AT: - assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); - break; - case PublicClaims.ISSUED_AT: - assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); - break; - case PublicClaims.NOT_BEFORE: - assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); - break; - case PublicClaims.ISSUER: - assertValidIssuerClaim(jwt.getIssuer(), (List) entry.getValue()); - break; - case PublicClaims.JWT_ID: - assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); - break; - case PublicClaims.SUBJECT: - assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); - break; - default: - assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); - break; + if (entry.getValue() instanceof NonEmptyClaim) { + assertClaimPresent(jwt.getClaim(entry.getKey()), entry.getKey()); + } else { + verifyClaimValues(jwt, entry); } } } + private void verifyClaimValues(DecodedJWT jwt, Map.Entry entry) { + switch (entry.getKey()) { + case PublicClaims.AUDIENCE: + assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); + break; + case PublicClaims.EXPIRES_AT: + assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); + break; + case PublicClaims.ISSUED_AT: + assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); + break; + case PublicClaims.NOT_BEFORE: + assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); + break; + case PublicClaims.ISSUER: + assertValidIssuerClaim(jwt.getIssuer(), (List) entry.getValue()); + break; + case PublicClaims.JWT_ID: + assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); + break; + case PublicClaims.SUBJECT: + assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); + break; + default: + assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); + break; + } + } + + private void assertClaimPresent(Claim claim, String claimName) { + if (claim instanceof NullClaim) { + throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", claimName)); + } + } + private void assertValidClaim(Claim claim, String claimName, Object value) { boolean isValid = false; if (value instanceof String) { @@ -400,4 +422,20 @@ private void assertValidIssuerClaim(String issuer, List value) { throw new InvalidClaimException("The Claim 'iss' value doesn't match the required issuer."); } } + + /** + * Simple singleton used to mark that a claim should only be verified for presence. + */ + private static class NonEmptyClaim { + private static NonEmptyClaim nonEmptyClaim; + + private NonEmptyClaim() {} + + public static NonEmptyClaim getInstance() { + if (nonEmptyClaim == null) { + nonEmptyClaim = new NonEmptyClaim(); + } + return nonEmptyClaim; + } + } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 72ae35d2..e23f4340 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -80,6 +80,14 @@ public interface Verification { */ Verification withJWTId(String jwtId); + /** + * Require a claim to be present, with any value. + * @param name the Claim's name. + * @return this same Verification instance + * @throws IllegalArgumentException if the name is null. + */ + Verification withClaimPresence(String name) throws IllegalArgumentException; + /** * Require a specific Claim value. * diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index d57ca187..156a39a6 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -790,4 +790,145 @@ public void shouldSkipClaimValidationsIfNoClaimsRequired() throws Exception { assertThat(jwt, is(notNullValue())); } + + @Test + public void shouldThrowWhenVerifyingClaimPresenceButClaimNotPresent() { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'missing' is not present in the JWT."); + + String jwt = JWTCreator.init() + .withClaim("custom", "") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("missing") + .build(); + + verifier.verify(jwt); + } + + @Test + public void shouldThrowWhenVerifyingClaimPresenceWhenClaimNameIsNull() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("The Custom Claim's name can't be null."); + + String jwt = JWTCreator.init() + .withClaim("custom", "value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence(null); + } + + @Test + public void shouldVerifyStringClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", "") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyBooleanClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", true) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyIntegerClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", 123) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyLongClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", 922337203685477600L) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyDoubleClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", 12.34) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyListClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", Collections.singletonList("item")) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyMapClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", Collections.singletonMap("key", "value")) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyStandardClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("aud", "any value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("aud") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } } From c5b785aca15c5c3265314f4f4d5fa7263594c205 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 25 Sep 2020 13:54:23 -0500 Subject: [PATCH 004/198] Release 3.11.0 --- CHANGELOG.md | 11 +++++++++++ README.md | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21095ff7..39da33e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Change Log +## [3.11.0](https://github.com/auth0/java-jwt/tree/3.11.0) (2020-09-25) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.10.3...3.11.0) + +**Added** +- Add ability to verify claim presence [\#442](https://github.com/auth0/java-jwt/pull/442) ([jimmyjames](https://github.com/jimmyjames)) +- Add Support for secp256k1 algorithms (AKA ES256K) [\#439](https://github.com/auth0/java-jwt/pull/439) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- Fix and document thread-safety [\#427](https://github.com/auth0/java-jwt/pull/427) ([lbalmaceda](https://github.com/lbalmaceda)) +- Wrap IllegalArgumentException into JWTDecodeException [\#426](https://github.com/auth0/java-jwt/pull/426) ([lbalmaceda](https://github.com/lbalmaceda)) + ## [3.10.3](https://github.com/auth0/java-jwt/tree/3.10.3) (2020-04-24) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.10.2...3.10.3) diff --git a/README.md b/README.md index 1cdbe422..d1b5948c 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.10.3 + 3.11.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.10.3' +implementation 'com.auth0:java-jwt:3.11.0' ``` ## Available Algorithms From dd6bc25d9a7c2857bae29757a9198b798ac5734f Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 27 Oct 2020 08:54:20 -0300 Subject: [PATCH 005/198] Setup the CODEOWNERS for pull request reviews (#448) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c9ff4921..60f116c0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @auth0/dx-sdks-approver +* @auth0/dx-sdks-engineer From a26284a3228e867e47dc96c2a31b92ee8dda0d83 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 28 Oct 2020 13:49:09 -0500 Subject: [PATCH 006/198] update issue templates --- .github/ISSUE_TEMPLATE.md | 38 ---------------- .github/ISSUE_TEMPLATE/config.yml | 8 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 39 ++++++++++++++++ .github/ISSUE_TEMPLATE/report-a-bug.md | 55 +++++++++++++++++++++++ 4 files changed, 102 insertions(+), 38 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/report-a-bug.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 009ccd24..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,38 +0,0 @@ -In order to efficiently and accurately address your issue or feature request, please read through the template below and answer all relevant questions. Your additional work here is greatly appreciated and will help us respond as quickly as possible. Please delete any sections or questions below that do not pertain to this request. - -For general support or usage questions, please use the [Auth0 Community](https://community.auth0.com/) or [Auth0 Support](https://support.auth0.com/). - -### Description - -Description of the bug or feature request and why it's a problem. Consider including: - -- The use case or overall problem you're trying to solve -- Information about when the problem started - -### Prerequisites - -- [ ] I have checked the documentation for this library in the README. -- [ ] I have checked the [Auth0 Community](https://community.auth0.com/) for related posts. -- [ ] I have checked for related or duplicate [Issues](https://github.com/auth0/java-jwt/issues) and [PRs](https://github.com/auth0/java-jwt/pulls). -- [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md). -- [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). -- [ ] I am reporting this to the correct repository (note this library is used by many libraries, such as [auth0-java](https://github.com/auth0/auth0-java)). - -### Environment - -Please provide the following: - -- Version of this library used: -- Version of Java framework used: -- Additional libraries that might be affecting your instance: - -### Reproduction - -Detail the steps taken to reproduce this error and note if this issue can be reproduced consistently or if it is intermittent. - -Please include: - -- Code sample to reproduce the issue -- Log files (redact/remove sensitive information) -- Application settings (redact/remove sensitive information) -- Screenshots, if helpful diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..77c79de9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Auth0 Community + url: https://community.auth0.com/c/sdks + about: Discuss this SDK in the Auth0 Community forums + - name: Library Documentation + url: https://github.com/auth0/java-jwt/blob/master/README.md + about: Read the library documentation diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..5e9e90bf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,39 @@ +--- +name: Feature request +about: Suggest an idea or a feature for this project +title: '' +labels: feature request +assignees: '' +--- + + + +### Describe the problem you'd like to have solved + + + +### Describe the ideal solution + + + +## Alternatives and current work-arounds + + + +### Additional information, if any + + diff --git a/.github/ISSUE_TEMPLATE/report-a-bug.md b/.github/ISSUE_TEMPLATE/report-a-bug.md new file mode 100644 index 00000000..60505a3f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/report-a-bug.md @@ -0,0 +1,55 @@ +--- +name: Report a bug +about: Have you found a bug or issue? Create a bug report for this SDK +title: '' +labels: bug report +assignees: '' +--- + + + +### Describe the problem + + + +### What was the expected behavior? + + + +### Reproduction + + +- Step 1.. +- Step 2.. +- ... + +### Environment + + + +- **Version of this library used:** +- **Version of Java used:** +- **Other modules/plugins/libraries that might be involved:** +- **Any other relevant information you think would be useful:** From 0dba806b221187b064e72c9f4a204c0140aecfcc Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 30 Oct 2020 09:35:40 -0300 Subject: [PATCH 007/198] Revert "Add license scan report and status" This reverts commit 984eaaf0fb74cba344d3f743aa0fa0d0724edcec. --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d1b5948c..a6cab21c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ + + # Java JWT [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](http://doge.mit-license.org) [![Javadoc](https://javadoc.io/badge2/com.auth0/java-jwt/javadoc.svg)](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt?ref=badge_shield) A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). @@ -433,6 +434,3 @@ If you have found a bug or if you have a feature request, please report them at ## License This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info. - - -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt?ref=badge_large) \ No newline at end of file From 2ba767459fd01f870fbf3375550f3a2c1147dc76 Mon Sep 17 00:00:00 2001 From: TeeVenDick Date: Tue, 3 Nov 2020 16:55:28 +0800 Subject: [PATCH 008/198] Fix broken docs links in README.md (#453) --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a6cab21c..eefe1ccd 100644 --- a/README.md +++ b/README.md @@ -411,12 +411,12 @@ If the values can't be converted to the given **Class Type** a `JWTDecodeExcepti Auth0 helps you to: -* Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, among others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**. -* Add authentication through more traditional **[username/password databases](https://docs.auth0.com/mysql-connection-tutorial)**. -* Add support for **[linking different user accounts](https://docs.auth0.com/link-accounts)** with the same user. -* Support for generating signed [Json Web Tokens](https://docs.auth0.com/jwt) to call your APIs and **flow the user identity** securely. +* Add authentication with [multiple authentication sources](https://auth0.com/docs/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, among others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**. +* Add authentication through more traditional **[username/password databases](https://auth0.com/docs/connections/database)**. +* Add support for **[linking different user accounts](https://auth0.com/docs/users/user-account-linking)** with the same user. +* Support for generating signed [Json Web Tokens](https://auth0.com/docs/tokens/json-web-tokens) to call your APIs and **flow the user identity** securely. * Analytics of how, when and where users are logging in. -* Pull data from other sources and add it to the user profile, through [JavaScript rules](https://docs.auth0.com/rules). +* Pull data from other sources and add it to the user profile, through [JavaScript rules](https://auth0.com/docs/rules). ## Create a free account in Auth0 From 4471c5960e8a0f94e2d34181b4654d8f9bbfe681 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 18 Nov 2020 11:06:21 -0300 Subject: [PATCH 009/198] Setup pull-request and issue templates (#458) * Setup pull-request and issue templates * Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 4 +- .github/ISSUE_TEMPLATE/report_a_bug.md | 55 +++++++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/report_a_bug.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 77c79de9..3cd7aa53 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: false contact_links: - name: Auth0 Community - url: https://community.auth0.com/c/sdks + url: https://community.auth0.com/c/sdks/5 about: Discuss this SDK in the Auth0 Community forums - name: Library Documentation url: https://github.com/auth0/java-jwt/blob/master/README.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 5e9e90bf..68352ba2 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -17,7 +17,7 @@ By submitting an Issue to this repository, you agree to the terms within the Aut ### Describe the problem you'd like to have solved ### Describe the ideal solution @@ -36,4 +36,4 @@ By submitting an Issue to this repository, you agree to the terms within the Aut +--> \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/report_a_bug.md b/.github/ISSUE_TEMPLATE/report_a_bug.md new file mode 100644 index 00000000..50b9fa7e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/report_a_bug.md @@ -0,0 +1,55 @@ +--- +name: Report a bug +about: Have you found a bug or issue? Create a bug report for this SDK +title: '' +labels: bug report +assignees: '' +--- + + + +### Describe the problem + + + +### What was the expected behavior? + + + +### Reproduction + + +- Step 1.. +- Step 2.. +- ... + +### Environment + + + +- **Version of this library used:** +- **Which framework are you using, if applicable:** +- **Other modules/plugins/libraries that might be involved:** +- **Any other relevant information you think would be useful:** \ No newline at end of file From 45775cb8071ce2d1f48491c510991fdff27d9829 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 18 Nov 2020 17:21:42 -0600 Subject: [PATCH 010/198] Add notice to README about Java 8 upcoming requirement --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index eefe1ccd..712cb07d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.or If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. +> This library currently supports Java 7. Beginning soon, it will require Java 8 as the minimum supported Java version. See [this issue](https://github.com/auth0/java-jwt/issues/457) for additional information and timelines. + ## Installation The library is available on both Maven Central and Bintray, and the Javadoc is published [here](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html). From a787e2d79f890429fd7b7dc802f3f874db266bc2 Mon Sep 17 00:00:00 2001 From: LeeHainie <30332409+LeeHainie@users.noreply.github.com> Date: Mon, 23 Nov 2020 17:22:49 +0800 Subject: [PATCH 011/198] Thread-safe classes should be Shared statically Thread-safe classes should be Shared statically,This improves performance and reduces class creation costs. --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index bd0dff9d..340528ad 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -30,15 +30,21 @@ public final class JWTCreator { private final Algorithm algorithm; private final String headerJson; private final String payloadJson; + + private static final ObjectMapper mapper; + private static final SimpleModule module; + + static { + mapper = new ObjectMapper(); + module = new SimpleModule(); + module.addSerializer(ClaimsHolder.class, new PayloadSerializer()); + mapper.registerModule(module); + mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); + } private JWTCreator(Algorithm algorithm, Map headerClaims, Map payloadClaims) throws JWTCreationException { this.algorithm = algorithm; try { - ObjectMapper mapper = new ObjectMapper(); - SimpleModule module = new SimpleModule(); - module.addSerializer(ClaimsHolder.class, new PayloadSerializer()); - mapper.registerModule(module); - mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); headerJson = mapper.writeValueAsString(headerClaims); payloadJson = mapper.writeValueAsString(new ClaimsHolder(payloadClaims)); } catch (JsonProcessingException e) { From 0f9ee305334c6513e58014b113871a531d5738c9 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 7 Dec 2020 18:10:11 +0100 Subject: [PATCH 012/198] Update jackson-databind to 2.10.5.1 (fixes CVE-2020-25649) --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index dcd0d615..9ea22404 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,7 +34,7 @@ compileJava { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.3' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.5.1' implementation 'commons-codec:commons-codec:1.14' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' From f616badb0a30963822d3fee8a789c133c73db547 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 15 Dec 2020 21:30:11 +0100 Subject: [PATCH 013/198] Target Java 8 (#455) * update gradle wrapper to 6.7 * change required java version to 8 * Update to Gradle 6.7.1 Co-authored-by: Jim Anderson --- gradle/wrapper/gradle-wrapper.jar | Bin 58695 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 ++ gradlew.bat | 25 +++++++---------------- lib/build.gradle | 17 ++++++++++----- settings.gradle | 11 ++++++++++ 6 files changed, 33 insertions(+), 24 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2faf2fc91d853cd5d4242b5547257070..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 12842 zcmY+q1ymhDmoSOmQr324hm`&0SZbS3R18r37J8Z0F-T>@gIK` zatajf2b1lO#&E_RnGDF51uk%8Wcxm3`%Yhe7Psx?*?eZ?8M9_+G(?L=s^OG1n#S(NP3gF?2Mr2^f5E7sM~iVC`Rn;(-^MZ zZu*ZXB;XmgvPls(e#)MMTObsEx9oNz-K?AmQ8pP&P7vqx*=5zxjU+ye_1R<%KSg1? z7H&Yh))(Ke!Pa+aVuWxPKa_~Qo_IH}*;tV8n~O*Xa?t3P^9=L%=wOL1=~{LVv}mU8Q#e6s>v}iV8cDP|EdY)`dp≶7^21 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

XiM^r_M1uFTw)(UPqqUJ zed7DJQvOpplYJoNl`A3a2Zr~v)Y}K|*%d7?8;dC*Af}0=(EXrk7hP8PM4v)ZawEv0 z5j5q_6vilv4*ppZ3Wke7%8b`odJu26#YseA?$sP8?@d?TpACTX`G^?%K=Hly9Y$Tj z5`i!}-WH0lQ3yt-&Pf&Z)OxjKfAZ3X`YI2)5o@9EiOA}?kX`^rk;yaOh^Li4VHcT& zd6ztJz^`H!8>cL)a%?Lnofzo0$WPrgkNcP#u@{%~EvA7g#go z+Q4$Q^o_MJTE0G_&9@cgp)WF>2zo>AG}C|(~E^_?ijc2EQ>> zFH1Lxc@i!gDHxvEsrfc+Kiz3Q6Z*NM+U6DH6*-Fv{YDY4>U&eegGH~Xmk~!sd6fw2 z!6^4(MZWhzf(xs6BHy=oS+dir0_JW(j*AIu$9&&p@}T;&6s7(yYidEdkp6pk9}Z(F zk6lG`d!HcJWP{7X)&P5FW;XWU6;zke2e+g*#1kW4L0Auj5pVA45Bhzt{8lj)XyMHu z0p;Q}t*NLnXbGQOTC9WdOQsX;_yLThOP$mzcEbqSBifmDecV;Fqhtm#KxfJTMhY!K zw{1L9za2QtuIWXu0ZYm@Uy72(9^vYajuP$(VAKn+&Jp z!%S;#BqO;02)n~6pFYK8oRC6wXx@kyb?VENM7l?NFg)%^=q4aZ* zwVCZxA8lalF&n`vk#gxD@!9~Aktc+h2UUUax3q2XKQLuL@CpVEpx2IyiH3bALY+Oi zDydtacE1Z|$z5qsCG=%F{}8_ozwieWuM4VcDesuu+wX&YjHm^Ryx)pVtiSLpch7<` zIw9QM%I;(VG9~#Rw2JNt+;?_=O2lA#YSudpgsM`e&(}1 zGdj7U)St4`+Cj#Vn>a;Bj~Q3S1G54;gzq)`b_Hh~ip(`BK#GMkI*RUcK+6ycjl?QN zP%sH5*R$$6A&8j8O4iFJhuHMXLJm|drH+=!_{`60&+tC8*m3Z=YsR@~%eaZyWQI|W zie7;BRL6`LR+4B{%FY~;-^*1GGTCLp!U6VoS36GUAWv$Rcc#|a7DD01{QPB=VyT)? z+1ZV>P=bP1;>v-+j0;BwenO`EKcIbmB3$r*@a~=?5#KB_R$3bTg zOjEutZmWuylIJQEM?28D!4uA#0CZEJHar0TFAW|NwP;WL|EIb_L2>;}e*N!K9E61S zH&u@m!n#CH{C_j}{#ybCRU8z7`Cs{b>@bxSkp3lOm`qAYgB>npnw%V>w}t_+S_Z)s zFhGKq^e%m@=^@sD0CJdAK=`Upbr}?}N zf;snqpt#dOwhxFg82(%Tw=ND+@`O0JGeOWd7-3R8A%Yu%FhiaYXD>p?t2+o%_1CKE z{g(>=g%}X(O%M!}KZK%$7-F<34wD-24`y$WLDv6z?ty=_b)Oi*x&?v}3j0f`AV3HL zWPAYw!XNs-1EmZ9=d=$6LAJISVJ4&gQM5=bh{!f0OmFNz8oMnGgOl_RK5VPP3@87C zpLS$muCo5YTmOslSjFLb`AXIJ@1|O{pm5r z9MxUb-8HMe*|TpFa%dE?+7}MVQ-A-N8wvtq85ROU2%KYt4etC52X0%W08haQgGSU| zvw=K$djTYSLy@4My_Te_8JcZp8Oozg{-ey>*Nm7BkGrX(M~HU6N16U>DSj<`;cy`u zR?0B23zx|*o1V=#fLZ=wd6*NfWjC`pqA^mt>DTZjGBeX`0ZK>Qgxz+37Dyb#NT4WT zl@7Lmh{WdY*h%e_bo6(oXKw=`(9=mft10fOR4< zCOUungtJZPAM~qw4Ydy9PhvYTi1ZRqR5nY)?OX2O3FJ6VXy&`&H%^U|d>N>@f zQ}Cd;DV}_bNiVTW8HcUJDRvXCUwGx|(r2-KbXY>jpd!~jmt(

p*eWM!w;MI3hb2}DlKZV3$r+FsPh`1u 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 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/lib/build.gradle b/lib/build.gradle index 9ea22404..eebfe917 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,7 +1,8 @@ plugins { - id "com.jfrog.bintray" version "1.8.4" - id "com.auth0.gradle.oss-library.java" version "0.11.0" - id "jacoco" + id 'java' + id 'jacoco' + id 'com.jfrog.bintray' + id 'com.auth0.gradle.oss-library.java' } logger.lifecycle("Using version ${version} for ${group}.${name}") @@ -28,9 +29,15 @@ oss { } } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} + compileJava { - sourceCompatibility '1.7' - targetCompatibility '1.7' + sourceCompatibility '1.8' + targetCompatibility '1.8' } dependencies { diff --git a/settings.gradle b/settings.gradle index ce7f0a64..d3c7fb14 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,13 @@ +pluginManagement { + repositories { + gradlePluginPortal() + jcenter() + } + plugins { + id 'com.jfrog.bintray' version '1.8.5' + id 'com.auth0.gradle.oss-library.java' version '0.12.1' + } +} + include ':java-jwt' project(':java-jwt').projectDir = new File(rootProject.projectDir, '/lib') \ No newline at end of file From 58439f41bed7ead6911e47cf71dfb3de59d5cb12 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 18 Dec 2020 08:27:03 -0600 Subject: [PATCH 014/198] Release 3.12.0 --- CHANGELOG.md | 12 ++++++++++++ README.md | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39da33e9..12ab335a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## [3.12.0](https://github.com/auth0/java-jwt/tree/3.12.0) (2020-12-18) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.11.0...3.12.0) + +**Changed** +- Thread-safe classes should be Shared statically [\#462](https://github.com/auth0/java-jwt/pull/462) ([LeeHainie](https://github.com/LeeHainie)) + +**Security** +- Update jackson-databind to 2.10.5.1 (fixes CVE-2020-25649) [\#463](https://github.com/auth0/java-jwt/pull/463) ([overheadhunter](https://github.com/overheadhunter)) + +**Breaking changes** +- Target Java 8 [\#455](https://github.com/auth0/java-jwt/pull/455) ([lbalmaceda](https://github.com/lbalmaceda)) + ## [3.11.0](https://github.com/auth0/java-jwt/tree/3.11.0) (2020-09-25) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.10.3...3.11.0) diff --git a/README.md b/README.md index 712cb07d..618fa12e 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.11.0 + 3.12.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.11.0' +implementation 'com.auth0:java-jwt:3.12.0' ``` ## Available Algorithms From 63812af383245d526c0f89ec02abf861967af031 Mon Sep 17 00:00:00 2001 From: darveshsingh <56809416+darveshsingh@users.noreply.github.com> Date: Wed, 16 Dec 2020 19:09:15 -0500 Subject: [PATCH 015/198] Fix: updated jackson-databind to 2.10.0.pr3 to block CVE-2020-25649 ## Changes Version bump of jackson-databind to 2.11.0 ## References: [CVE-2020-25649](https://bugzilla.redhat.com/show_bug.cgi?id=1887664) --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index eebfe917..fd920e2c 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -41,7 +41,7 @@ compileJava { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.5.1' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.0' implementation 'commons-codec:commons-codec:1.14' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' From 3b87927a8e449d867a1312086164afd08c56bda7 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 20 Jan 2021 13:00:52 -0600 Subject: [PATCH 016/198] Release 3.12.1 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12ab335a..fd2e477e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.12.1](https://github.com/auth0/java-jwt/tree/3.12.1) (2021-01-20) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.12.0...3.12.1) + +**Changed** +- Update jackson-databind to 2.11.0 [\#464](https://github.com/auth0/java-jwt/pull/464) ([darveshsingh](https://github.com/darveshsingh)) + ## [3.12.0](https://github.com/auth0/java-jwt/tree/3.12.0) (2020-12-18) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.11.0...3.12.0) diff --git a/README.md b/README.md index 618fa12e..90c71bbf 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.12.0 + 3.12.1 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.12.0' +implementation 'com.auth0:java-jwt:3.12.1' ``` ## Available Algorithms From 9e951cd9fd63a98b0a9eb5a0367f4cf840952306 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Thu, 28 Jan 2021 15:33:46 -0600 Subject: [PATCH 017/198] Add toString to Claim objects --- lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java | 5 +++++ lib/src/main/java/com/auth0/jwt/impl/NullClaim.java | 5 +++++ .../test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java | 7 +++++++ lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java | 4 ++++ 4 files changed, 21 insertions(+) diff --git a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java index 4e2aef63..ae266798 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -128,6 +128,11 @@ public boolean isNull() { return false; } + @Override + public String toString() { + return data.toString(); + } + /** * Helper method to extract a Claim from the given JsonNode tree. * diff --git a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java index 93bedbb1..8d10ca10 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java @@ -65,4 +65,9 @@ public Map asMap() throws JWTDecodeException { public T as(Class tClazz) throws JWTDecodeException { return null; } + + @Override + public String toString() { + return "Null Claim"; + } } diff --git a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java index 1a82fcec..84d0be4b 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -431,4 +431,11 @@ public void shouldReturnNonNullClaimWhenParsingBooleanValue() throws Exception { assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); } + + @Test + public void shouldDelegateToJsonNodeToString() { + JsonNode value = mapper.valueToTree(new UserPojo("john", 123)); + Claim claim = claimFromNode(value); + assertThat(claim.toString(), is(value.toString())); + } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java index d8ddd516..b15f3dad 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java @@ -70,4 +70,8 @@ public void shouldGetAsCustomClass() throws Exception { assertThat(claim.as(Object.class), is(nullValue())); } + @Test + public void shouldHaveToString() { + assertThat(claim.toString(), is("Null Claim")); + } } \ No newline at end of file From 7fe888ca1d3f9d5918f6247a17763d70c52b5607 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 1 Feb 2021 11:43:46 -0600 Subject: [PATCH 018/198] Update acceptedIssuedAt JavaDocs --- .../java/com/auth0/jwt/interfaces/Verification.java | 5 +++++ lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index e23f4340..f9904aac 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -64,6 +64,11 @@ public interface Verification { /** * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. + * This method overrides the value set with {@link #acceptLeeway(long)}. + * If Issued At verification has been disabled with {@link #ignoreIssuedAt()}, no verification of the Issued At + * claim will be performed, and this method has no effect. + * + * Issued At Date is verified by default when the value is present, unless disabled * Issued At Date is always verified when the value is present. This method overrides the value set with acceptLeeway * * @param leeway the window in seconds in which the Issued At Claim will still be valid. diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 156a39a6..b8fd2f5e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -672,12 +672,14 @@ public void shouldThrowOnInvalidIssuedAtIfPresent() throws Exception { @Test public void shouldOverrideAcceptIssuedAtWhenIgnoreIssuedAtFlagPassedAndSkipTheVerification() throws Exception { Clock clock = mock(Clock.class); - when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); + when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 10000)); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - DecodedJWT jwt = verification.acceptIssuedAt(20).ignoreIssuedAt() - .build() + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")) + .acceptIssuedAt(1) + .ignoreIssuedAt(); + DecodedJWT jwt = verification + .build(clock) .verify(token); assertThat(jwt, is(notNullValue())); From 3766cd8c4d0ed87b91f29ed314744f0db8e58f37 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Tue, 2 Feb 2021 12:23:20 -0600 Subject: [PATCH 019/198] Update JavaDoc to remove duplicated information, improve clarity --- .../main/java/com/auth0/jwt/interfaces/Verification.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index f9904aac..6821f3ae 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -65,11 +65,8 @@ public interface Verification { /** * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. * This method overrides the value set with {@link #acceptLeeway(long)}. - * If Issued At verification has been disabled with {@link #ignoreIssuedAt()}, no verification of the Issued At - * claim will be performed, and this method has no effect. - * - * Issued At Date is verified by default when the value is present, unless disabled - * Issued At Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * By default, the Issued At claim is always verified when the value is present, unless disabled with {@link #ignoreIssuedAt()}. + * If Issued At verification has been disabled, no verification of the Issued At claim will be performed, and this method has no effect. * * @param leeway the window in seconds in which the Issued At Claim will still be valid. * @return this same Verification instance. From 2ee2b3c8e131210b417e66ce7acdd88fdc06c0d6 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 1 Feb 2021 14:48:04 -0600 Subject: [PATCH 020/198] Add ability to verify an audience contains at least one --- .../main/java/com/auth0/jwt/JWTVerifier.java | 57 ++++++- .../auth0/jwt/interfaces/Verification.java | 24 ++- .../java/com/auth0/jwt/JWTVerifierTest.java | 158 +++++++++++++++++- 3 files changed, 232 insertions(+), 7 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index a311ab33..2393f76c 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -11,6 +11,7 @@ import com.auth0.jwt.interfaces.Verification; import java.util.*; +import java.util.stream.Collectors; /** * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, but also it's signature matches. @@ -23,12 +24,14 @@ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { final Map claims; private final Clock clock; private final JWTParser parser; + private final AudienceVerificationStrategy audienceVerificationStrategy; - JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { + JWTVerifier(Algorithm algorithm, Map claims, Clock clock, AudienceVerificationStrategy audienceVerificationStrategy) { this.algorithm = algorithm; this.claims = Collections.unmodifiableMap(claims); this.clock = clock; this.parser = new JWTParser(); + this.audienceVerificationStrategy = audienceVerificationStrategy; } /** @@ -47,6 +50,7 @@ public static class BaseVerification implements Verification { private final Map claims; private long defaultLeeway; private boolean ignoreIssuedAt; + private AudienceVerificationStrategy audienceVerificationStrategy = AudienceVerificationStrategy.UNSET; BaseVerification(Algorithm algorithm) throws IllegalArgumentException { if (algorithm == null) { @@ -72,6 +76,20 @@ public Verification withSubject(String subject) { @Override public Verification withAudience(String... audience) { + if (audienceVerificationStrategy == AudienceVerificationStrategy.CONTAINS) { + throw new IllegalStateException("Audience validation behavior has already been configured."); + } + audienceVerificationStrategy = AudienceVerificationStrategy.EXACT; + requireClaim(PublicClaims.AUDIENCE, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); + return this; + } + + @Override + public Verification withAnyOfAudience(String... audience) { + if (audienceVerificationStrategy == AudienceVerificationStrategy.EXACT) { + throw new IllegalStateException("Audience validation behavior has already been configured."); + } + audienceVerificationStrategy = AudienceVerificationStrategy.CONTAINS; requireClaim(PublicClaims.AUDIENCE, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); return this; } @@ -200,7 +218,7 @@ public JWTVerifier build() { */ public JWTVerifier build(Clock clock) { addLeewayToDateClaims(); - return new JWTVerifier(algorithm, claims, clock); + return new JWTVerifier(algorithm, claims, clock, audienceVerificationStrategy); } private void assertPositive(long leeway) { @@ -412,8 +430,19 @@ private void assertDateIsPast(Date date, long leeway, Date today) { } private void assertValidAudienceClaim(List audience, List value) { - if (audience == null || !audience.containsAll(value)) { - throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); + String invalidMessage = "The Claim 'aud' value doesn't contain the required audience."; + if (audience == null) { + throw new InvalidClaimException(invalidMessage); + } + + if (audienceVerificationStrategy == AudienceVerificationStrategy.CONTAINS) { + if (Collections.disjoint(audience, value)) { + throw new InvalidClaimException(invalidMessage); + } + } else { + if (!audience.containsAll(value)) { + throw new InvalidClaimException(invalidMessage); + } } } @@ -438,4 +467,24 @@ public static NonEmptyClaim getInstance() { return nonEmptyClaim; } } + + /** + * Represents how the audience will be validated. + */ + enum AudienceVerificationStrategy { + /** + * No audience validation configured. + */ + UNSET, + + /** + * The JWT audience must match the expected audiences exactly. + */ + EXACT, + + /** + * The JWT audience must contain at least one of the expected audiences. + */ + CONTAINS + } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 6821f3ae..2130fcd1 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -25,13 +25,35 @@ public interface Verification { Verification withSubject(String subject); /** - * Require a specific Audience ("aud") claim. + * Require a specific Audience ("aud") claim. If multiple audiences are specified, they must all be present + * in the "aud" claim. + * An {@linkplain IllegalStateException} will be thrown if this Verification instance has already been + * configured with {@link #withAnyOfAudience(String...)} * * @param audience the required Audience value * @return this same Verification instance. */ Verification withAudience(String... audience); + /** + * Require that the Audience ("aud") claim contain at least one of the specified audiences. + * An {@linkplain IllegalStateException} will be thrown if this Verification instance has already been + * configured with {@link #withAudience(String...)} + * + * @apiNote This method was added after the interface was released. + * It is defined as a default method for compatibility reasons. + * From version 4.0 on, the method will be abstract and all implementations of this interface + * will have to provide their own implementation. + * + * @implSpec The default implementation throws an {@linkplain UnsupportedOperationException}. + * + * @param audience the required Audience value for which the "aud" claim must contain at least one value. + * @return this same Verification instance. + */ + default Verification withAnyOfAudience(String... audience) { + throw new UnsupportedOperationException("withAnyOfAudience"); + } + /** * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. * Setting a specific leeway value on a given Claim will override this value for that Claim. diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index b8fd2f5e..1847824e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -111,6 +111,7 @@ public void shouldThrowOnInvalidSubject() throws Exception { @Test public void shouldValidateAudience() throws Exception { + // Token 'aud': ["Mark"] String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) .withAudience("Mark") @@ -128,6 +129,32 @@ public void shouldValidateAudience() throws Exception { assertThat(jwtArr, is(notNullValue())); } + @Test + public void shouldThrowIfConfiguringAllOfAfterAnyOfAudienceValidation() { + exception.expect(IllegalStateException.class); + exception.expectMessage("Audience validation behavior has already been configured."); + + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("Mark") + .withAudience("Mark") + .build() + .verify(token); + } + + @Test + public void shouldThrowIfConfiguringAnyOfAfterAllOfAudienceValidation() { + exception.expect(IllegalStateException.class); + exception.expectMessage("Audience validation behavior has already been configured."); + + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience("Mark") + .withAnyOfAudience("Mark") + .build() + .verify(token); + } + @Test public void shouldAcceptPartialAudience() throws Exception { //Token 'aud' = ["Mark", "David", "John"] @@ -141,9 +168,70 @@ public void shouldAcceptPartialAudience() throws Exception { } @Test - public void shouldThrowOnInvalidAudience() throws Exception { + public void shouldAcceptOneOfAudience() { + // Token 'aud': ["Mark"] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("Mark") + .build() + .verify(token); + + assertThat(jwt, is(notNullValue())); + + // Token 'aud' = ["Mark", "David", "John"] + String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("John", "Jim") + .build() + .verify(tokenArr); + + assertThat(jwtArr, is(notNullValue())); + } + + @Test + public void shouldAcceptAudienceWhenAnAudienceAndAllContained() { + // Token 'aud' = ["Mark", "David", "John"] + String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("Mark", "David", "John") + .build() + .verify(tokenArr); + + assertThat(jwtArr, is(notNullValue())); + } + + @Test + public void shouldThrowWhenAudienceHasNoneOfAcceptOneOfAudience() { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); + + // Token 'aud' = ["Mark", "David", "John"] + String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("Joe", "Jim") + .build() + .verify(tokenArr); + } + + @Test + public void shouldThrowWhenAudienceClaimDoesNotContainAllExpected() throws Exception { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); + + // Token 'aud' = ["Mark", "David", "John"] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience("Mark", "Joe") + .build() + .verify(token); + } + + @Test + public void shouldThrowWhenAudienceClaimIsNull() throws Exception { exception.expect(InvalidClaimException.class); exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); + + // Token 'aud': null String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; JWTVerifier.init(Algorithm.HMAC256("secret")) .withAudience("nope") @@ -151,6 +239,19 @@ public void shouldThrowOnInvalidAudience() throws Exception { .verify(token); } + @Test + public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() throws Exception { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); + + // Token 'aud': null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("nope") + .build() + .verify(token); + } + @Test public void shouldRemoveAudienceWhenPassingNullReference() throws Exception { Algorithm algorithm = mock(Algorithm.class); @@ -184,6 +285,39 @@ public void shouldRemoveAudienceWhenPassingNullReference() throws Exception { assertThat(verifier.claims, hasEntry("aud", Collections.singletonList(emptyAud))); } + @Test + public void shouldRemoveAudienceWhenPassingNullReferenceWithAnyOfAudience() throws Exception { + Algorithm algorithm = mock(Algorithm.class); + JWTVerifier verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience((String) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience((String[]) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience() + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + String emptyAud = " "; + verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience(emptyAud) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, hasEntry("aud", Collections.singletonList(emptyAud))); + } + @Test public void shouldRemoveAudienceWhenPassingNull() throws Exception { Algorithm algorithm = mock(Algorithm.class); @@ -204,6 +338,26 @@ public void shouldRemoveAudienceWhenPassingNull() throws Exception { assertThat(verifier.claims, not(hasKey("aud"))); } + @Test + public void shouldRemoveAudienceWhenPassingNullWithAnyAudience() throws Exception { + Algorithm algorithm = mock(Algorithm.class); + JWTVerifier verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience("John") + .withAnyOfAudience((String) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience("John") + .withAnyOfAudience((String[]) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + } + @Test public void shouldThrowOnNullCustomClaimName() throws Exception { exception.expect(IllegalArgumentException.class); @@ -297,7 +451,7 @@ public void shouldThrowOnInvalidCustomClaimValue() throws Exception { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; Map map = new HashMap<>(); map.put("name", new Object()); - JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, new ClockImpl()); + JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, new ClockImpl(), JWTVerifier.AudienceVerificationStrategy.EXACT); verifier.verify(token); } From cbb00491d4b05a5f57d0d5f9c83209a9a9f09f64 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 3 Feb 2021 16:35:58 -0600 Subject: [PATCH 021/198] Manage audience validation strategy with internal map, allow strategies to be overridden --- .../main/java/com/auth0/jwt/JWTVerifier.java | 89 ++++++----------- .../auth0/jwt/interfaces/Verification.java | 10 +- .../java/com/auth0/jwt/JWTVerifierTest.java | 99 +++++++++---------- 3 files changed, 84 insertions(+), 114 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 2393f76c..16a7c89f 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -24,14 +24,15 @@ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { final Map claims; private final Clock clock; private final JWTParser parser; - private final AudienceVerificationStrategy audienceVerificationStrategy; - JWTVerifier(Algorithm algorithm, Map claims, Clock clock, AudienceVerificationStrategy audienceVerificationStrategy) { + static final String AUDIENCE_EXACT = "AUDIENCE_EXACT"; + static final String AUDIENCE_CONTAINS = "AUDIENCE_CONTAINS"; + + JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { this.algorithm = algorithm; this.claims = Collections.unmodifiableMap(claims); this.clock = clock; this.parser = new JWTParser(); - this.audienceVerificationStrategy = audienceVerificationStrategy; } /** @@ -50,7 +51,6 @@ public static class BaseVerification implements Verification { private final Map claims; private long defaultLeeway; private boolean ignoreIssuedAt; - private AudienceVerificationStrategy audienceVerificationStrategy = AudienceVerificationStrategy.UNSET; BaseVerification(Algorithm algorithm) throws IllegalArgumentException { if (algorithm == null) { @@ -76,21 +76,15 @@ public Verification withSubject(String subject) { @Override public Verification withAudience(String... audience) { - if (audienceVerificationStrategy == AudienceVerificationStrategy.CONTAINS) { - throw new IllegalStateException("Audience validation behavior has already been configured."); - } - audienceVerificationStrategy = AudienceVerificationStrategy.EXACT; - requireClaim(PublicClaims.AUDIENCE, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); + claims.remove(AUDIENCE_CONTAINS); + requireClaim(AUDIENCE_EXACT, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); return this; } @Override public Verification withAnyOfAudience(String... audience) { - if (audienceVerificationStrategy == AudienceVerificationStrategy.EXACT) { - throw new IllegalStateException("Audience validation behavior has already been configured."); - } - audienceVerificationStrategy = AudienceVerificationStrategy.CONTAINS; - requireClaim(PublicClaims.AUDIENCE, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); + claims.remove(AUDIENCE_EXACT); + requireClaim(AUDIENCE_CONTAINS, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); return this; } @@ -218,7 +212,7 @@ public JWTVerifier build() { */ public JWTVerifier build(Clock clock) { addLeewayToDateClaims(); - return new JWTVerifier(algorithm, claims, clock, audienceVerificationStrategy); + return new JWTVerifier(algorithm, claims, clock); } private void assertPositive(long leeway) { @@ -323,31 +317,36 @@ private void verifyClaims(DecodedJWT jwt, Map claims) throws Tok } } - private void verifyClaimValues(DecodedJWT jwt, Map.Entry entry) { - switch (entry.getKey()) { - case PublicClaims.AUDIENCE: - assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); + private void verifyClaimValues(DecodedJWT jwt, Map.Entry expectedClaim) { + switch (expectedClaim.getKey()) { + // We use custom keys for audience in the expected claims to differentiate between validating that the audience + // contains all expected values, or validating that the audience contains at least one of the expected values. + case AUDIENCE_EXACT: + assertValidAudienceClaim(jwt.getAudience(), (List) expectedClaim.getValue(), true); + break; + case AUDIENCE_CONTAINS: + assertValidAudienceClaim(jwt.getAudience(), (List) expectedClaim.getValue(), false); break; case PublicClaims.EXPIRES_AT: - assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); + assertValidDateClaim(jwt.getExpiresAt(), (Long) expectedClaim.getValue(), true); break; case PublicClaims.ISSUED_AT: - assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); + assertValidDateClaim(jwt.getIssuedAt(), (Long) expectedClaim.getValue(), false); break; case PublicClaims.NOT_BEFORE: - assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); + assertValidDateClaim(jwt.getNotBefore(), (Long) expectedClaim.getValue(), false); break; case PublicClaims.ISSUER: - assertValidIssuerClaim(jwt.getIssuer(), (List) entry.getValue()); + assertValidIssuerClaim(jwt.getIssuer(), (List) expectedClaim.getValue()); break; case PublicClaims.JWT_ID: - assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); + assertValidStringClaim(expectedClaim.getKey(), jwt.getId(), (String) expectedClaim.getValue()); break; case PublicClaims.SUBJECT: - assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); + assertValidStringClaim(expectedClaim.getKey(), jwt.getSubject(), (String) expectedClaim.getValue()); break; default: - assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); + assertValidClaim(jwt.getClaim(expectedClaim.getKey()), expectedClaim.getKey(), expectedClaim.getValue()); break; } } @@ -429,20 +428,10 @@ private void assertDateIsPast(Date date, long leeway, Date today) { } } - private void assertValidAudienceClaim(List audience, List value) { - String invalidMessage = "The Claim 'aud' value doesn't contain the required audience."; - if (audience == null) { - throw new InvalidClaimException(invalidMessage); - } - - if (audienceVerificationStrategy == AudienceVerificationStrategy.CONTAINS) { - if (Collections.disjoint(audience, value)) { - throw new InvalidClaimException(invalidMessage); - } - } else { - if (!audience.containsAll(value)) { - throw new InvalidClaimException(invalidMessage); - } + private void assertValidAudienceClaim(List audience, List values, boolean shouldContainAll) { + if (audience == null || (shouldContainAll && !audience.containsAll(values)) || + (!shouldContainAll && Collections.disjoint(audience, values))) { + throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); } } @@ -467,24 +456,4 @@ public static NonEmptyClaim getInstance() { return nonEmptyClaim; } } - - /** - * Represents how the audience will be validated. - */ - enum AudienceVerificationStrategy { - /** - * No audience validation configured. - */ - UNSET, - - /** - * The JWT audience must match the expected audiences exactly. - */ - EXACT, - - /** - * The JWT audience must contain at least one of the expected audiences. - */ - CONTAINS - } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 2130fcd1..e778d81b 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -27,8 +27,9 @@ public interface Verification { /** * Require a specific Audience ("aud") claim. If multiple audiences are specified, they must all be present * in the "aud" claim. - * An {@linkplain IllegalStateException} will be thrown if this Verification instance has already been - * configured with {@link #withAnyOfAudience(String...)} + * + * If this is used in conjunction with {@link #withAnyOfAudience(String...)}, whichever one is configured last will + * determine the audience validation behavior. * * @param audience the required Audience value * @return this same Verification instance. @@ -37,8 +38,9 @@ public interface Verification { /** * Require that the Audience ("aud") claim contain at least one of the specified audiences. - * An {@linkplain IllegalStateException} will be thrown if this Verification instance has already been - * configured with {@link #withAudience(String...)} + * + * If this is used in conjunction with {@link #withAudience(String...)}, whichever one is configured last will + * determine the audience validation behavior. * * @apiNote This method was added after the interface was released. * It is defined as a default method for compatibility reasons. diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 1847824e..131b389e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -6,6 +6,7 @@ import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.Verification; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -110,7 +111,7 @@ public void shouldThrowOnInvalidSubject() throws Exception { } @Test - public void shouldValidateAudience() throws Exception { + public void shouldAcceptAudienceWhenWithAudienceContainsAll() throws Exception { // Token 'aud': ["Mark"] String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) @@ -120,6 +121,7 @@ public void shouldValidateAudience() throws Exception { assertThat(jwt, is(notNullValue())); + // Token 'aud': ["Mark", "David"] String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIl19.6WfbIt8m61f9WlCYIQn5CThvw4UNyC66qrPaoinfssw"; DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) .withAudience("Mark", "David") @@ -130,58 +132,55 @@ public void shouldValidateAudience() throws Exception { } @Test - public void shouldThrowIfConfiguringAllOfAfterAnyOfAudienceValidation() { - exception.expect(IllegalStateException.class); - exception.expectMessage("Audience validation behavior has already been configured."); + public void shouldAllowWithAnyOfAudienceVerificationToOverrideWithAudience() { + // Token 'aud' = ["Mark", "David", "John"] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + Verification verification = JWTVerifier.init(Algorithm.HMAC256("secret")).withAudience("Mark", "Jim"); - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAnyOfAudience("Mark") - .withAudience("Mark") - .build() - .verify(token); - } + Exception exception = null; + try { + verification.build().verify(token); + } catch (Exception e) { + exception = e; - @Test - public void shouldThrowIfConfiguringAnyOfAfterAllOfAudienceValidation() { - exception.expect(IllegalStateException.class); - exception.expectMessage("Audience validation behavior has already been configured."); + } - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAudience("Mark") - .withAnyOfAudience("Mark") - .build() - .verify(token); + assertThat(exception, is(notNullValue())); + assertThat(exception, is(instanceOf(InvalidClaimException.class))); + assertThat(exception.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + + DecodedJWT jwt = verification.withAnyOfAudience("Mark", "Jim").build().verify(token); + assertThat(jwt, is(notNullValue())); } @Test - public void shouldAcceptPartialAudience() throws Exception { - //Token 'aud' = ["Mark", "David", "John"] - String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; - DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAudience("John") - .build() - .verify(tokenArr); + public void shouldAllowWithAudienceVerificationToOverrideWithAnyOfAudience() { + // Token 'aud' = ["Mark", "David", "John"] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + Verification verification = JWTVerifier.init(Algorithm.HMAC256("secret")).withAnyOfAudience("Jim"); - assertThat(jwtArr, is(notNullValue())); - } + Exception exception = null; + try { + verification.build().verify(token); + } catch (Exception e) { + exception = e; - @Test - public void shouldAcceptOneOfAudience() { - // Token 'aud': ["Mark"] - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAnyOfAudience("Mark") - .build() - .verify(token); + } + assertThat(exception, is(notNullValue())); + assertThat(exception, is(instanceOf(InvalidClaimException.class))); + assertThat(exception.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + + DecodedJWT jwt = verification.withAudience("Mark").build().verify(token); assertThat(jwt, is(notNullValue())); + } + @Test + public void shouldAcceptAudienceWhenWithAudienceAndPartialExpected() throws Exception { // Token 'aud' = ["Mark", "David", "John"] String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAnyOfAudience("John", "Jim") + .withAudience("John") .build() .verify(tokenArr); @@ -189,7 +188,7 @@ public void shouldAcceptOneOfAudience() { } @Test - public void shouldAcceptAudienceWhenAnAudienceAndAllContained() { + public void shouldAcceptAudienceWhenAnyOfAudienceAndAllContained() { // Token 'aud' = ["Mark", "David", "John"] String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) @@ -201,7 +200,7 @@ public void shouldAcceptAudienceWhenAnAudienceAndAllContained() { } @Test - public void shouldThrowWhenAudienceHasNoneOfAcceptOneOfAudience() { + public void shouldThrowWhenAudienceHasNoneOfExpectedAnyOfAudience() { exception.expect(InvalidClaimException.class); exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); @@ -260,21 +259,21 @@ public void shouldRemoveAudienceWhenPassingNullReference() throws Exception { .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); verifier = JWTVerifier.init(algorithm) .withAudience((String[]) null) .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); verifier = JWTVerifier.init(algorithm) .withAudience() .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); String emptyAud = " "; verifier = JWTVerifier.init(algorithm) @@ -282,7 +281,7 @@ public void shouldRemoveAudienceWhenPassingNullReference() throws Exception { .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("aud", Collections.singletonList(emptyAud))); + assertThat(verifier.claims, hasEntry(JWTVerifier.AUDIENCE_EXACT, Collections.singletonList(emptyAud))); } @Test @@ -293,21 +292,21 @@ public void shouldRemoveAudienceWhenPassingNullReferenceWithAnyOfAudience() thro .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); verifier = JWTVerifier.init(algorithm) .withAnyOfAudience((String[]) null) .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); verifier = JWTVerifier.init(algorithm) .withAnyOfAudience() .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); String emptyAud = " "; verifier = JWTVerifier.init(algorithm) @@ -315,7 +314,7 @@ public void shouldRemoveAudienceWhenPassingNullReferenceWithAnyOfAudience() thro .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("aud", Collections.singletonList(emptyAud))); + assertThat(verifier.claims, hasEntry(JWTVerifier.AUDIENCE_CONTAINS, Collections.singletonList(emptyAud))); } @Test @@ -451,7 +450,7 @@ public void shouldThrowOnInvalidCustomClaimValue() throws Exception { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; Map map = new HashMap<>(); map.put("name", new Object()); - JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, new ClockImpl(), JWTVerifier.AudienceVerificationStrategy.EXACT); + JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, new ClockImpl()); verifier.verify(token); } From 9ef20ad39eb9bdb6dc53aff88325553c6630accd Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 5 Feb 2021 09:50:18 -0600 Subject: [PATCH 022/198] Remove apiNote and implSpec JavaDoc tags - not available in java8 --- .../java/com/auth0/jwt/interfaces/Verification.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index e778d81b..654b466e 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -42,12 +42,12 @@ public interface Verification { * If this is used in conjunction with {@link #withAudience(String...)}, whichever one is configured last will * determine the audience validation behavior. * - * @apiNote This method was added after the interface was released. - * It is defined as a default method for compatibility reasons. - * From version 4.0 on, the method will be abstract and all implementations of this interface - * will have to provide their own implementation. + * Note: This method was added after the interface was released. + * It is defined as a default method for compatibility reasons. + * From version 4.0 on, the method will be abstract and all implementations of this interface + * will have to provide their own implementation. * - * @implSpec The default implementation throws an {@linkplain UnsupportedOperationException}. + * The default implementation throws an {@linkplain UnsupportedOperationException}. * * @param audience the required Audience value for which the "aud" claim must contain at least one value. * @return this same Verification instance. From 87a66bf7c94afd4b746d05b303cfd120dca96e4a Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 5 Feb 2021 12:55:29 -0600 Subject: [PATCH 023/198] Release 3.13.0 --- CHANGELOG.md | 7 +++++++ README.md | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd2e477e..887528ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [3.13.0](https://github.com/auth0/java-jwt/tree/3.13.0) (2021-02-05) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.12.1...3.13.0) + +**Added** +- Add ability to verify audience contains at least one of those expected [\#472](https://github.com/auth0/java-jwt/pull/472) ([jimmyjames](https://github.com/jimmyjames)) +- Add toString to Claim objects [SDK-2225] [\#469](https://github.com/auth0/java-jwt/pull/469) ([jimmyjames](https://github.com/jimmyjames)) + ## [3.12.1](https://github.com/auth0/java-jwt/tree/3.12.1) (2021-01-20) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.12.0...3.12.1) diff --git a/README.md b/README.md index 90c71bbf..6f1e0e81 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.12.1 + 3.13.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.12.1' +implementation 'com.auth0:java-jwt:3.13.0' ``` ## Available Algorithms From 94f5fa4291948c0b3274959558854ecbbbb3393f Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 19 Feb 2021 14:10:53 -0600 Subject: [PATCH 024/198] Add withPayload to JWTCreator.Builder --- README.md | 10 ++ .../main/java/com/auth0/jwt/JWTCreator.java | 57 +++++- .../java/com/auth0/jwt/JWTCreatorTest.java | 170 +++++++++++++++++- 3 files changed, 230 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6f1e0e81..295dbd73 100644 --- a/README.md +++ b/README.md @@ -373,6 +373,16 @@ String token = JWT.create() .sign(algorithm); ``` +You can also create a JWT by calling `withPayload()` and passing a map of claim names to values: + +```java +Map payloadClaims = new HashMap<>(); +payloadClaims.put("@context", "https://auth0.com/"); +String token = JWT.create() + .withPayload(payloadClaims) + .sign(algorithm); +``` + You can also verify custom Claims on the `JWT.require()` by calling `withClaim()` and passing both the name and the required value. ```java diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 340528ad..d0d49189 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -13,10 +13,7 @@ import org.apache.commons.codec.binary.Base64; import java.nio.charset.StandardCharsets; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; /** @@ -360,6 +357,58 @@ public Builder withClaim(String name, List list) throws IllegalArgumentExcept return this; } + /** + * Add specific Claims to set as the Payload. If the provided map is null then + * nothing is changed. + *

+ * Accepted types are {@linkplain Map} and {@linkplain List} with basic types + * {@linkplain Boolean}, {@linkplain Integer}, {@linkplain Long}, {@linkplain Double}, + * {@linkplain String} and {@linkplain Date}. {@linkplain Map}s cannot contain null keys or values. + * {@linkplain List}s can contain null elements. + *

+ * + *

+ * If any of the claims are invalid, none will be added. + *

+ * + * @param payloadClaims the values to use as Claims in the token's payload. + * @throws IllegalArgumentException if any of the claim keys or null, or if the values are not of a supported type. + * @return this same Builder instance. + */ + public Builder withPayload(Map payloadClaims) throws IllegalArgumentException { + if (payloadClaims == null) { + return this; + } + + if (!validatePayload(payloadClaims)) { + throw new IllegalArgumentException("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + } + + // add claims only after validating all claims so as not to corrupt the claims map of this builder + for (Map.Entry entry : payloadClaims.entrySet()) { + addClaim(entry.getKey(), entry.getValue()); + } + + return this; + } + + private boolean validatePayload(Map payload) { + for (Map.Entry entry : payload.entrySet()) { + String key = entry.getKey(); + assertNonNull(key); + + Object value = entry.getValue(); + if (value instanceof List && !validateClaim((List) value)) { + return false; + } else if (value instanceof Map && !validateClaim((Map) value)) { + return false; + } else if (value != null && !isSupportedType(value)) { + return false; + } + } + return true; + } + private static boolean validateClaim(Map map) { // do not accept null values in maps for (Entry entry : map.entrySet()) { diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 2d9179ff..a1aea9b5 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -15,9 +15,7 @@ import java.security.interfaces.RSAPrivateKey; import java.util.*; -import static org.hamcrest.Matchers.anEmptyMap; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -717,4 +715,170 @@ public void shouldRefuseCustomListClaimForUnknownArrayType() throws Exception { .sign(Algorithm.HMAC256("secret")); } + @Test + public void withPayloadShouldAddBasicClaim() { + Map payload = new HashMap<>(); + payload.put("asd", 123); + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, JsonMatcher.hasEntry("asd", 123)); + } + + @Test + public void withPayloadShouldCreateJwtWithEmptyBodyIfPayloadNull() { + String jwt = JWTCreator.init() + .withPayload(null) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, is("{}")); + } + + @Test + public void withPayloadShouldOverwriteExistingClaimIfPayloadMapContainsTheSameKey() { + Map payload = new HashMap<>(); + payload.put(PublicClaims.KEY_ID, "xyz"); + + String jwt = JWTCreator.init() + .withKeyId("abc") + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "xyz")); + } + + @Test + public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { + Map payload = new HashMap<>(); + payload.put(PublicClaims.ISSUER, "xyz"); + + String jwt = JWTCreator.init() + .withPayload(payload) + .withIssuer("abc") + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.ISSUER, "abc")); + } + + @Test + public void shouldRemovePayloadIfTheValueIsNull() throws Exception { + String jwt = JWTCreator.init() + .withClaim("key", "stubValue") + .withPayload(Collections.singletonMap("key", (Map) null)) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + + String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + ObjectMapper mapper = new ObjectMapper(); + Map map = (Map) mapper.readValue(body, Map.class); + assertThat(map, anEmptyMap()); + } + + @Test + public void withPayloadShouldNotAllowCustomType() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + + Map payload = new HashMap<>(); + payload.put("entry", "value"); + payload.put("pojo", new UserPojo("name", 42)); + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void withPayloadShouldAllowNullListItems() { + Map payload = new HashMap<>(); + payload.put("list", Arrays.asList("item1", null, "item2")); + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, JsonMatcher.hasEntry("list", Arrays.asList("item1", null, "item2"))); + } + + @Test + public void withPayloadShouldNotAllowListWithCustomType() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + + Map payload = new HashMap<>(); + payload.put("list", Arrays.asList("item1", new UserPojo("name", 42))); + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void withPayloadShouldNotAllowMapWithCustomType() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + + Map payload = new HashMap<>(); + payload.put("entry", "value"); + payload.put("map", Collections.singletonMap("pojo", new UserPojo("name", 42))); + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void withPayloadShouldAllowNestedSupportedTypes() { + /* + JWT: + { + "stringClaim": "string", + "intClaim": 41, + "listClaim": [ + 1, 2, { + "nestedObjKey": true + } + ], + "objClaim": { + "objKey": ["nestedList1", "nestedList2"] + } + } + */ + + List listClaim = Arrays.asList(1, 2, Collections.singletonMap("nestedObjKey", "nestedObjValue")); + Map mapClaim = new HashMap<>(); + mapClaim.put("objKey", Arrays.asList("nestedList1", true)); + + Map payload = new HashMap<>(); + payload.put("stringClaim", "string"); + payload.put("intClaim", 41); + payload.put("listClaim", listClaim); + payload.put("objClaim", mapClaim); + + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, JsonMatcher.hasEntry("stringClaim", "string")); + assertThat(payloadJson, JsonMatcher.hasEntry("intClaim", 41)); + assertThat(payloadJson, JsonMatcher.hasEntry("listClaim", listClaim)); + assertThat(payloadJson, JsonMatcher.hasEntry("objClaim", mapClaim)); + } } From fbbf140efc45854acbdc375fee497ff97fa4ff47 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 19 Feb 2021 14:21:50 -0600 Subject: [PATCH 025/198] Use wildcard instead of Object --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index d0d49189..0280af07 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -375,7 +375,7 @@ public Builder withClaim(String name, List list) throws IllegalArgumentExcept * @throws IllegalArgumentException if any of the claim keys or null, or if the values are not of a supported type. * @return this same Builder instance. */ - public Builder withPayload(Map payloadClaims) throws IllegalArgumentException { + public Builder withPayload(Map payloadClaims) throws IllegalArgumentException { if (payloadClaims == null) { return this; } @@ -385,15 +385,15 @@ public Builder withPayload(Map payloadClaims) throws IllegalArgu } // add claims only after validating all claims so as not to corrupt the claims map of this builder - for (Map.Entry entry : payloadClaims.entrySet()) { + for (Map.Entry entry : payloadClaims.entrySet()) { addClaim(entry.getKey(), entry.getValue()); } return this; } - private boolean validatePayload(Map payload) { - for (Map.Entry entry : payload.entrySet()) { + private boolean validatePayload(Map payload) { + for (Map.Entry entry : payload.entrySet()) { String key = entry.getKey(); assertNonNull(key); From 9987b98237f2988ea4f150e9fc1d91be6b31b582 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 24 Feb 2021 14:42:54 -0600 Subject: [PATCH 026/198] Update README to include HMAC key length recommendations --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 295dbd73..1bff22d1 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,10 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); > Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). +##### HMAC Key Length and Security + +When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recomendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. + #### Using a KeyProvider: By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: From 6b732d3931796e2789254301575f1fc0a8c231a7 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 25 Feb 2021 10:05:01 -0600 Subject: [PATCH 027/198] Add ability to run apiDiff for incompatible changes (#476) * Add ability to run apiDiff for incompatible changes * Add api diff to CI --- .circleci/config.yml | 57 +++++++++++++++++++++++++++++++++++--------- lib/build.gradle | 1 + settings.gradle | 2 +- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9bb01639..78c9dab4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,8 +1,7 @@ -version: 2 -jobs: - build: - docker: - - image: openjdk:8-jdk +version: 2.1 + +commands: + checkout-and-build: steps: - checkout - run: chmod +x gradlew @@ -12,17 +11,53 @@ jobs: - v1-dependencies-{{ checksum "build.gradle" }} # fallback to using the latest cache if no exact match is found - v1-dependencies- - # run tests! - - run: ./gradlew clean check jacocoTestReport --continue --console=plain - - run: - name: Upload Coverage - when: on_success - command: bash <(curl -s https://codecov.io/bash) -Z -C $CIRCLE_SHA1 + - run: ./gradlew clean build - save_cache: paths: - ~/.m2 key: v1-dependencies-{{ checksum "build.gradle" }} + run-tests: + steps: + - run: ./gradlew check jacocoTestReport --continue --console=plain + - run: + name: Upload Coverage + when: on_success + command: bash <(curl -s https://codecov.io/bash) -Z -C $CIRCLE_SHA1 + run-api-diff: + steps: + # run apiDiff task + - run: ./gradlew apiDiff + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.txt + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.html + +jobs: + build: + docker: + - image: openjdk:8-jdk + steps: + - checkout-and-build + - run-tests + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + TERM: dumb + api-diff: + docker: + - image: openjdk:8-jdk + steps: + - checkout-and-build + - run-api-diff environment: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb + +workflows: + build-and-test: + jobs: + - build + api-diff: + jobs: + - api-diff diff --git a/lib/build.gradle b/lib/build.gradle index fd920e2c..209e90ce 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -12,6 +12,7 @@ oss { repository "java-jwt" organization "auth0" description "Java implementation of JSON Web Token (JWT)" + baselineCompareVersion "3.13.0" developers { auth0 { diff --git a/settings.gradle b/settings.gradle index d3c7fb14..2170ea25 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,7 +5,7 @@ pluginManagement { } plugins { id 'com.jfrog.bintray' version '1.8.5' - id 'com.auth0.gradle.oss-library.java' version '0.12.1' + id 'com.auth0.gradle.oss-library.java' version '0.14.0' } } From fb51a2f147cfa95cbb64d7990b775cf11a64e6d6 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 26 Feb 2021 12:22:01 -0600 Subject: [PATCH 028/198] Release 3.14.0 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 887528ab..92116fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.14.0](https://github.com/auth0/java-jwt/tree/3.14.0) (2021-02-26) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.13.0...3.14.0) + +**Added** +- Add withPayload to JWTCreator.Builder [\#475](https://github.com/auth0/java-jwt/pull/475) ([jimmyjames](https://github.com/jimmyjames)) + ## [3.13.0](https://github.com/auth0/java-jwt/tree/3.13.0) (2021-02-05) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.12.1...3.13.0) diff --git a/README.md b/README.md index 1bff22d1..65e033fa 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.13.0 + 3.14.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.13.0' +implementation 'com.auth0:java-jwt:3.14.0' ``` ## Available Algorithms From 7dea6ac54d5b5b8822a9f3ee41cc4666e250cc27 Mon Sep 17 00:00:00 2001 From: XakepSDK Date: Mon, 8 Mar 2021 15:56:13 +0000 Subject: [PATCH 029/198] Move form commons-codec Base64 to j.u.Base64 (#478) * Use Java Base64 encoding and decoding fix last usages of commons-codec Remove codec usages Remove commons-codec * Throw on invalid Base64 encoded tokens * Fix broken tests that became broken after java.util.Base64 transition Co-authored-by: jimmyjames Co-authored-by: Xakep_SDK --- lib/build.gradle | 1 - .../main/java/com/auth0/jwt/JWTCreator.java | 9 ++-- .../main/java/com/auth0/jwt/JWTDecoder.java | 8 +-- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 7 ++- .../auth0/jwt/algorithms/HMACAlgorithm.java | 7 ++- .../auth0/jwt/algorithms/NoneAlgorithm.java | 14 +++-- .../auth0/jwt/algorithms/RSAAlgorithm.java | 7 ++- .../java/com/auth0/jwt/JWTCreatorTest.java | 54 +++++++++---------- .../java/com/auth0/jwt/JWTDecoderTest.java | 27 ++++++++-- lib/src/test/java/com/auth0/jwt/JWTTest.java | 22 ++++---- .../jwt/algorithms/CryptoTestHelper.java | 11 ++-- .../jwt/algorithms/ECDSAAlgorithmTest.java | 53 ++++++++++-------- .../ECDSABouncyCastleProviderTests.java | 45 ++++++++-------- .../jwt/algorithms/HMACAlgorithmTest.java | 16 +++++- .../jwt/algorithms/NoneAlgorithmTest.java | 13 ++++- .../jwt/algorithms/RSAAlgorithmTest.java | 9 ++++ 16 files changed, 183 insertions(+), 120 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 209e90ce..e0894e3d 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -43,7 +43,6 @@ compileJava { dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.0' - implementation 'commons-codec:commons-codec:1.14' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' testImplementation 'net.jodah:concurrentunit:0.4.3' diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 0280af07..26df771f 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; -import org.apache.commons.codec.binary.Base64; import java.nio.charset.StandardCharsets; import java.util.*; @@ -27,7 +26,7 @@ public final class JWTCreator { private final Algorithm algorithm; private final String headerJson; private final String payloadJson; - + private static final ObjectMapper mapper; private static final SimpleModule module; @@ -492,11 +491,11 @@ private void addClaim(String name, Object value) { } private String sign() throws SignatureGenerationException { - String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8)); - String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8)); + String header = Base64.getUrlEncoder().withoutPadding().encodeToString(headerJson.getBytes(StandardCharsets.UTF_8)); + String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8)); byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8)); - String signature = Base64.encodeBase64URLSafeString((signatureBytes)); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString((signatureBytes)); return String.format("%s.%s.%s", header, payload, signature); } diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index 445bf95d..71acbe59 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -6,10 +6,10 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Header; import com.auth0.jwt.interfaces.Payload; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.binary.StringUtils; import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Date; import java.util.List; import java.util.Map; @@ -37,8 +37,8 @@ final class JWTDecoder implements DecodedJWT, Serializable { String headerJson; String payloadJson; try { - headerJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[0])); - payloadJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[1])); + headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); + payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); } catch (NullPointerException e) { throw new JWTDecodeException("The UTF-8 Charset isn't initialized.", e); } catch (IllegalArgumentException e){ diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java index 6d065c9c..c2d08a15 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -4,13 +4,13 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.ECDSAKeyProvider; -import org.apache.commons.codec.binary.Base64; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.util.Base64; /** * Subclass representing an Elliptic Curve signing algorithm @@ -40,9 +40,8 @@ class ECDSAAlgorithm extends Algorithm { @Override public void verify(DecodedJWT jwt) throws SignatureVerificationException { - byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); - try { + byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature()); ECPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId()); if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); @@ -52,7 +51,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (!valid) { throw new SignatureVerificationException(this); } - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException | IllegalArgumentException e) { throw new SignatureVerificationException(this, e); } } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java index 596f907e..c5792457 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java @@ -3,12 +3,12 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.codec.binary.Base64; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.Base64; /** * Subclass representing an Hash-based MAC signing algorithm @@ -48,14 +48,13 @@ static byte[] getSecretBytes(String secret) throws IllegalArgumentException { @Override public void verify(DecodedJWT jwt) throws SignatureVerificationException { - byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); - try { + byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature()); boolean valid = crypto.verifySignatureFor(getDescription(), secret, jwt.getHeader(), jwt.getPayload(), signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } - } catch (IllegalStateException | InvalidKeyException | NoSuchAlgorithmException e) { + } catch (IllegalStateException | InvalidKeyException | NoSuchAlgorithmException | IllegalArgumentException e) { throw new SignatureVerificationException(this, e); } } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java index f70b72b2..076a6b94 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java @@ -3,7 +3,8 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.codec.binary.Base64; + +import java.util.Base64; class NoneAlgorithm extends Algorithm { @@ -13,9 +14,14 @@ class NoneAlgorithm extends Algorithm { @Override public void verify(DecodedJWT jwt) throws SignatureVerificationException { - byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); - if (signatureBytes.length > 0) { - throw new SignatureVerificationException(this); + try { + byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature()); + + if (signatureBytes.length > 0) { + throw new SignatureVerificationException(this); + } + } catch (IllegalArgumentException e) { + throw new SignatureVerificationException(this, e); } } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index a61fc97e..31773a09 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -4,7 +4,6 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.RSAKeyProvider; -import org.apache.commons.codec.binary.Base64; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; @@ -12,6 +11,7 @@ import java.security.SignatureException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import java.util.Base64; /** * Subclass representing an RSA signing algorithm @@ -39,9 +39,8 @@ class RSAAlgorithm extends Algorithm { @Override public void verify(DecodedJWT jwt) throws SignatureVerificationException { - byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); - try { + byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature()); RSAPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId()); if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); @@ -50,7 +49,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (!valid) { throw new SignatureVerificationException(this); } - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalArgumentException | IllegalStateException e) { throw new SignatureVerificationException(this, e); } } diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index a1aea9b5..eabc2f31 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -5,7 +5,7 @@ import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.codec.binary.Base64; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -49,7 +49,7 @@ public void shouldAddHeaderClaim() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("asd", 123)); } @@ -74,7 +74,7 @@ public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() throws assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "xyz")); } @@ -90,7 +90,7 @@ public void shouldOverwriteExistingHeadersWhenSettingSameHeaderKey() throws Exce assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "abc")); } @@ -107,7 +107,7 @@ public void shouldRemoveHeaderIfTheValueIsNull() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.isNotPresent(PublicClaims.KEY_ID)); assertThat(headerJson, JsonMatcher.hasEntry("test2", "isSet")); } @@ -120,7 +120,7 @@ public void shouldAddKeyId() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "56a8bd44da435300010000015f5ed")); } @@ -136,7 +136,7 @@ public void shouldAddKeyIdIfAvailableFromRSAAlgorithms() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -153,7 +153,7 @@ public void shouldNotOverwriteKeyIdIfAddedFromRSAAlgorithms() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -169,7 +169,7 @@ public void shouldAddKeyIdIfAvailableFromECDSAKAlgorithms() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -186,7 +186,7 @@ public void shouldNotOverwriteKeyIdIfAddedFromECDSAKAlgorithms() throws Exceptio assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -202,7 +202,7 @@ public void shouldAddKeyIdIfAvailableFromECDSAAlgorithms() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -219,7 +219,7 @@ public void shouldNotOverwriteKeyIdIfAddedFromECDSAAlgorithms() throws Exception assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -319,7 +319,7 @@ public void shouldSetCorrectAlgorithmInTheHeader() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS256")); } @@ -330,7 +330,7 @@ public void shouldSetDefaultTypeInTheHeader() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); } @@ -343,7 +343,7 @@ public void shouldSetCustomTypeInTheHeader() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("typ", "passport")); } @@ -521,7 +521,7 @@ public void shouldAcceptCustomMapClaimOfBasicObjectTypes() throws Exception { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map map = (Map) mapper.readValue(body, Map.class).get("data"); @@ -574,8 +574,8 @@ public void shouldAcceptCustomListClaimOfBasicObjectTypes() throws Exception { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - - String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); List list = (List) mapper.readValue(body, Map.class).get("data"); @@ -618,7 +618,7 @@ public void shouldAcceptCustomClaimWithNullMapAndRemoveClaim() throws Exception assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map map = (Map) mapper.readValue(body, Map.class); assertThat(map, anEmptyMap()); @@ -635,7 +635,7 @@ public void shouldAcceptCustomClaimWithNullListAndRemoveClaim() throws Exception assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map map = (Map) mapper.readValue(body, Map.class); assertThat(map, anEmptyMap()); @@ -725,7 +725,7 @@ public void withPayloadShouldAddBasicClaim() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, JsonMatcher.hasEntry("asd", 123)); } @@ -737,7 +737,7 @@ public void withPayloadShouldCreateJwtWithEmptyBodyIfPayloadNull() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, is("{}")); } @@ -753,7 +753,7 @@ public void withPayloadShouldOverwriteExistingClaimIfPayloadMapContainsTheSameKe assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "xyz")); } @@ -769,7 +769,7 @@ public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.ISSUER, "abc")); } @@ -783,7 +783,7 @@ public void shouldRemovePayloadIfTheValueIsNull() throws Exception { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map map = (Map) mapper.readValue(body, Map.class); assertThat(map, anEmptyMap()); @@ -812,7 +812,7 @@ public void withPayloadShouldAllowNullListItems() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, JsonMatcher.hasEntry("list", Arrays.asList("item1", null, "item2"))); } @@ -875,7 +875,7 @@ public void withPayloadShouldAllowNestedSupportedTypes() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, JsonMatcher.hasEntry("stringClaim", "string")); assertThat(payloadJson, JsonMatcher.hasEntry("intClaim", 41)); assertThat(payloadJson, JsonMatcher.hasEntry("listClaim", listClaim)); diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index efd1b398..5cfe069e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -4,7 +4,6 @@ import com.auth0.jwt.impl.NullClaim; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.codec.binary.Base64; import org.hamcrest.collection.IsCollectionWithSize; import org.hamcrest.core.IsCollectionContaining; import org.junit.Assert; @@ -14,6 +13,7 @@ import java.io.*; import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Date; import java.util.Map; @@ -35,7 +35,8 @@ public void getSubject() throws Exception { @Test public void shouldThrowIfTheContentIsNotProperlyEncoded() throws Exception { exception.expect(JWTDecodeException.class); - exception.expectMessage("The input is not a valid base 64 encoded string."); + exception.expectMessage(startsWith("The string '")); + exception.expectMessage(endsWith("' doesn't have a valid JSON format.")); JWT.decode("eyJ0eXAiOiJKV1QiLCJhbGciO-corrupted.eyJ0ZXN0IjoxMjN9.sLtFC2rLAzN0-UJ13OLQX6ezNptAQzespaOGwCnpqk"); } @@ -71,6 +72,24 @@ public void shouldThrowIfHeaderHasInvalidJSONFormat() throws Exception { customJWT(invalidJson, validJson, "signature"); } + @Test + public void shouldThrowWhenHeaderNotValidBase64() { + exception.expect(JWTDecodeException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + String jwt = "eyJhbGciOiJub25l+IiwiY3R5IjoiSldUIn0.eyJpc3MiOiJhdXRoMCJ9.Ox-WRXRaGAuWt2KfPvWiGcCrPqZtbp_4OnQzZXaTfss"; + JWT.decode(jwt); + } + + @Test + public void shouldThrowWhenPayloadNotValidBase64() { + exception.expect(JWTDecodeException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + String jwt = "eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJpc3MiOiJhdXRo+MCJ9.Ox-WRXRaGAuWt2KfPvWiGcCrPqZtbp_4OnQzZXaTfss"; + JWT.decode(jwt); + } + // Parts @Test @@ -328,8 +347,8 @@ public void shouldSerializeAndDeserialize() throws Exception { //Helper Methods private DecodedJWT customJWT(String jsonHeader, String jsonPayload, String signature) { - String header = Base64.encodeBase64URLSafeString(jsonHeader.getBytes(StandardCharsets.UTF_8)); - String body = Base64.encodeBase64URLSafeString(jsonPayload.getBytes(StandardCharsets.UTF_8)); + String header = Base64.getUrlEncoder().withoutPadding().encodeToString(jsonHeader.getBytes(StandardCharsets.UTF_8)); + String body = Base64.getUrlEncoder().withoutPadding().encodeToString(jsonPayload.getBytes(StandardCharsets.UTF_8)); return JWT.decode(String.format("%s.%s.%s", header, body, signature)); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index b778be13..bcf62e05 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -3,7 +3,6 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.codec.binary.Base64; import org.hamcrest.collection.IsCollectionWithSize; import org.hamcrest.core.IsCollectionContaining; import org.junit.Rule; @@ -15,6 +14,7 @@ import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAKey; +import java.util.Base64; import java.util.Date; import static org.hamcrest.Matchers.*; @@ -393,7 +393,7 @@ public void shouldCreateAnEmptyHMAC256SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS256")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -409,7 +409,7 @@ public void shouldCreateAnEmptyHMAC384SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS384")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -425,7 +425,7 @@ public void shouldCreateAnEmptyHMAC512SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS512")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -441,7 +441,7 @@ public void shouldCreateAnEmptyRSA256SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "RS256")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -457,7 +457,7 @@ public void shouldCreateAnEmptyRSA384SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "RS384")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -473,7 +473,7 @@ public void shouldCreateAnEmptyRSA512SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "RS512")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -489,7 +489,7 @@ public void shouldCreateAnEmptyECDSA256SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES256")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -508,7 +508,7 @@ public void shouldCreateAnEmptyECDSA256KSignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES256K")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -524,7 +524,7 @@ public void shouldCreateAnEmptyECDSA384SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES384")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -540,7 +540,7 @@ public void shouldCreateAnEmptyECDSA512SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES512")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java b/lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java index f977e42a..25b08d70 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java @@ -1,14 +1,13 @@ package com.auth0.jwt.algorithms; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.commons.codec.binary.Base64; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public abstract class CryptoTestHelper { @@ -16,7 +15,7 @@ public abstract class CryptoTestHelper { public static String asJWT(Algorithm algorithm, String header, String payload) { byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8)); - String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes); return String.format("%s.%s.%s", header, payload, jwtSignature); } diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java index 7f7b9c94..39c9dabf 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -4,8 +4,6 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.ECDSAKeyProvider; - -import org.apache.commons.codec.binary.Base64; import org.hamcrest.Matchers; import org.hamcrest.collection.IsIn; import org.junit.Assert; @@ -20,13 +18,15 @@ import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.util.Arrays; +import java.util.Base64; import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; import static com.auth0.jwt.algorithms.CryptoTestHelper.asJWT; import static com.auth0.jwt.algorithms.CryptoTestHelper.assertSignaturePresent; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.isA; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; import static org.mockito.ArgumentMatchers.any; @@ -156,7 +156,7 @@ public void shouldFailECDSA256VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[63]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -169,7 +169,7 @@ public void shouldFailECDSA256VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[64]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -183,7 +183,7 @@ public void shouldFailECDSA256VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[64]; bytes[0] = 0x30; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -260,8 +260,8 @@ public void shouldFailECDSA256KVerificationWhenUsingPrivateKey() throws Exceptio public void shouldFailECDSA256KVerificationOnInvalidJOSESignatureLength() throws Exception { exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); - exception.expectCause(isA(SignatureException.class)); - exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + exception.expectCause(isA(IllegalArgumentException.class)); + exception.expectCause(hasMessage(is("Last unit does not have enough valid bits"))); String jwt = ES256K_JWT.substring(0,ES256K_JWT.length()-1); Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); @@ -275,7 +275,7 @@ public void shouldFailECDSA256KVerificationOnInvalidJOSESignature() throws Excep byte[] bytes = new byte[64]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); algorithm.verify(JWT.decode(jwt)); @@ -289,7 +289,7 @@ public void shouldFailECDSA256KVerificationOnInvalidDERSignature() throws Except byte[] bytes = new byte[64]; bytes[0] = 0x30; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); algorithm.verify(JWT.decode(jwt)); @@ -386,7 +386,7 @@ public void shouldFailECDSA384VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[95]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -399,7 +399,7 @@ public void shouldFailECDSA384VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[96]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -413,7 +413,7 @@ public void shouldFailECDSA384VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[96]; new SecureRandom().nextBytes(bytes); bytes[0] = 0x30; - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -511,7 +511,7 @@ public void shouldFailECDSA512VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[131]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -524,7 +524,7 @@ public void shouldFailECDSA512VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[132]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -538,7 +538,7 @@ public void shouldFailECDSA512VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[132]; new SecureRandom().nextBytes(bytes); bytes[0] = 0x30; - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -553,7 +553,7 @@ public void shouldFailJOSEToDERConversionOnInvalidJOSESignatureLength() throws E byte[] bytes = new byte[256]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); @@ -617,7 +617,18 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception algorithm.verify(JWT.decode(jwt)); } - //Sign + @Test + public void shouldThrowWhenSignatureNotValidBase64() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0UW726GsDVCsb4RTFeUTTrKaHZHtHPRoTuTEHCuerwvxo4+EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0mmWFhVCR1YNg"; + ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); + Algorithm algorithm = Algorithm.ECDSA512(key); + algorithm.verify(JWT.decode(jwt)); + } + + //Sign private static final String ES256Header = "eyJhbGciOiJFUzI1NiJ9"; private static final String ES384Header = "eyJhbGciOiJFUzM4NCJ9"; private static final String ES512Header = "eyJhbGciOiJFUzUxMiJ9"; @@ -643,7 +654,7 @@ public void shouldDoECDSA256Signing() throws Exception { public void shouldDoECDSA256SigningWithBothKeys() throws Exception { Algorithm algorithm = Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); byte[] signatureBytes = algorithm.sign(ES256HeaderBytes, auth0IssPayloadBytes); - String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes); String jwt = String.format("%s.%s.%s", ES256Header, auth0IssPayload, jwtSignature); assertSignaturePresent(jwt); @@ -1267,12 +1278,12 @@ public void shouldBeEqualSignatureMethodDecodeResults() throws Exception { bout.write('.'); bout.write(payloadBytes); - String jwtSignature1 = Base64.encodeBase64URLSafeString(algorithm.sign(bout.toByteArray())); + String jwtSignature1 = Base64.getUrlEncoder().withoutPadding().encodeToString(algorithm.sign(bout.toByteArray())); String jwt1 = String.format("%s.%s.%s", header, payload, jwtSignature1); algorithm.verify(JWT.decode(jwt1)); - String jwtSignature2 = Base64.encodeBase64URLSafeString(algorithm.sign(headerBytes, payloadBytes)); + String jwtSignature2 = Base64.getUrlEncoder().withoutPadding().encodeToString(algorithm.sign(headerBytes, payloadBytes)); String jwt2 = String.format("%s.%s.%s", header, payload, jwtSignature2); algorithm.verify(JWT.decode(jwt2)); diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java index dbecae1e..68fe6e50 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java @@ -1,11 +1,9 @@ package com.auth0.jwt.algorithms; -import static com.auth0.jwt.algorithms.CryptoTestHelper.*; import com.auth0.jwt.JWT; import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.ECDSAKeyProvider; -import org.apache.commons.codec.binary.Base64; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -18,9 +16,12 @@ import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.util.Base64; import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; +import static com.auth0.jwt.algorithms.CryptoTestHelper.asJWT; +import static com.auth0.jwt.algorithms.CryptoTestHelper.assertSignaturePresent; import static com.auth0.jwt.algorithms.ECDSAAlgorithmTest.*; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.*; @@ -150,8 +151,8 @@ public void shouldFailECDSA256KVerificationWhenUsingPrivateKey() throws Exceptio public void shouldFailECDSA256KVerificationOnInvalidJOSESignatureLength() throws Exception { exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); - exception.expectCause(isA(SignatureException.class)); - exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + exception.expectCause(isA(IllegalArgumentException.class)); + exception.expectCause(hasMessage(is("Last unit does not have enough valid bits"))); String jwt = ES256K_JWT.substring(0,ES256K_JWT.length()-1); Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); @@ -165,7 +166,7 @@ public void shouldFailECDSA256KVerificationOnInvalidJOSESignature() throws Excep byte[] bytes = new byte[64]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); algorithm.verify(JWT.decode(jwt)); @@ -179,7 +180,7 @@ public void shouldFailECDSA256KVerificationOnInvalidDERSignature() throws Except byte[] bytes = new byte[64]; bytes[0] = 0x30; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); algorithm.verify(JWT.decode(jwt)); @@ -200,7 +201,7 @@ public void shouldThrowOnECDSA256VerificationWithDERSignature() throws Exception exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jShFPj0hpCWn7x1nhxPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); Algorithm algorithm = Algorithm.ECDSA256(key); algorithm.verify(JWT.decode(jwt)); @@ -220,7 +221,7 @@ public void shouldThrowOnECDSA256VerificationWithDERSignatureWithBothKeys() thro exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jShFPj0hpCWn7x1nhxPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; Algorithm algorithm = Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); } @@ -277,7 +278,7 @@ public void shouldFailECDSA256VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[63]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -290,7 +291,7 @@ public void shouldFailECDSA256VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[64]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -304,7 +305,7 @@ public void shouldFailECDSA256VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[64]; bytes[0] = 0x30; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -325,7 +326,7 @@ public void shouldThrowOnECDSA384VerificationWithDERSignature() throws Exception exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXB/KRjyNAEqm+4dmh7ohkEmbk2+gHxtH6GdGDq2L4Idua+hG2Ut+ccCMH8CE2v/HCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAur+DEv8w=="; + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXBKRjyNAEqm4dmh7ohkEmbk2gHxtH6GdGDq2L4IduahG2UtccCMH8CE2vHCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAurDEv8w"; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); Algorithm algorithm = Algorithm.ECDSA384(key); algorithm.verify(JWT.decode(jwt)); @@ -345,7 +346,7 @@ public void shouldThrowOnECDSA384VerificationWithDERSignatureWithBothKeys() thro exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXB/KRjyNAEqm+4dmh7ohkEmbk2+gHxtH6GdGDq2L4Idua+hG2Ut+ccCMH8CE2v/HCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAur+DEv8w=="; + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXBKRjyNAEqm4dmh7ohkEmbk2gHxtH6GdGDq2L4IduahG2UtccCMH8CE2vHCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAurDEv8w"; Algorithm algorithm = Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); } @@ -402,7 +403,7 @@ public void shouldFailECDSA384VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[95]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -415,7 +416,7 @@ public void shouldFailECDSA384VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[96]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -429,7 +430,7 @@ public void shouldFailECDSA384VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[96]; new SecureRandom().nextBytes(bytes); bytes[0] = 0x30; - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -450,7 +451,7 @@ public void shouldThrowOnECDSA512VerificationWithDERSignature() throws Exception exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0UW726GsDVCsb4RTFeUTTrKaHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0mmWFhVCR1YNg"; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); Algorithm algorithm = Algorithm.ECDSA512(key); algorithm.verify(JWT.decode(jwt)); @@ -470,7 +471,7 @@ public void shouldThrowECDSA512VerificationWithDERSignatureWithBothKeys() throws exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0UW726GsDVCsb4RTFeUTTrKaHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0mmWFhVCR1YNg"; Algorithm algorithm = Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); } @@ -527,7 +528,7 @@ public void shouldFailECDSA512VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[131]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -540,7 +541,7 @@ public void shouldFailECDSA512VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[132]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -554,7 +555,7 @@ public void shouldFailECDSA512VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[132]; new SecureRandom().nextBytes(bytes); bytes[0] = 0x30; - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -569,7 +570,7 @@ public void shouldFailJOSEToDERConversionOnInvalidJOSESignatureLength() throws E byte[] bytes = new byte[256]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java index 38aaf301..fd5aed71 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java @@ -293,4 +293,18 @@ public void shouldBeEqualSignatureMethodResults() throws Exception { assertThat(algorithm.sign(bout.toByteArray()), is(algorithm.sign(header, payload))); } -} + + @Test + public void shouldThrowWhenSignatureNotValidBase64() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + CryptoHelper crypto = mock(CryptoHelper.class); + when(crypto.verifySignatureFor(anyString(), any(byte[].class), any(String.class), any(String.class), any(byte[].class))) + .thenThrow(NoSuchAlgorithmException.class); + + Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes(StandardCharsets.UTF_8)); + String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWm+i903JuUoDRZDBPB7HwkS4nVyWH1M"; + algorithm.verify(JWT.decode(jwt)); + } +} \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java index f6e72b84..9c528f68 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java @@ -7,8 +7,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; public class NoneAlgorithmTest { @@ -45,4 +44,14 @@ public void shouldFailNoneVerificationWhenSignatureIsPresent() throws Exception public void shouldReturnNullSigningKeyId() throws Exception { assertThat(Algorithm.none().getSigningKeyId(), is(nullValue())); } + + @Test + public void shouldThrowWhenSignatureNotValidBase64() { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + String jwt = "eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJpc3MiOiJhdXRoMCJ9.Ox-WRXRaGAuWt2KfPvW+iGcCrPqZtbp_4OnQzZXaTfss"; + Algorithm algorithm = Algorithm.none(); + algorithm.verify(JWT.decode(jwt)); + } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java index b24365e3..a4902781 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java @@ -569,4 +569,13 @@ public void shouldFailOnRSA256SigningWithDeprecatedMethodWhenProvidedPrivateKeyI algorithm.sign(new byte[0]); } + @Test + public void shouldThrowWhenSignatureNotValidBase64() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNu+LAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; + Algorithm algorithm = Algorithm.RSA256((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); + algorithm.verify(JWT.decode(jwt)); + } } \ No newline at end of file From 523b2d6cc091ab0026bef4ad2b72f7f502babd68 Mon Sep 17 00:00:00 2001 From: Josiah Boning Date: Thu, 25 Mar 2021 22:35:10 -0700 Subject: [PATCH 030/198] Update Java version in README.md Java 8 has been required since #457. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 65e033fa..871bdcfa 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.or If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. -> This library currently supports Java 7. Beginning soon, it will require Java 8 as the minimum supported Java version. See [this issue](https://github.com/auth0/java-jwt/issues/457) for additional information and timelines. +> This library requires Java 8 or higher. The last version that supported Java 7 was 3.11.0. ## Installation From 603e2b738f185be6a9a4036c2c27634d5d750029 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Wed, 31 Mar 2021 01:33:13 +0200 Subject: [PATCH 031/198] Improve README formatting --- README.md | 98 +++++++++++++++++++++++++++---------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 871bdcfa..b86ad957 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) -[![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](http://doge.mit-license.org) +[![License](https://img.shields.io/:license-mit-blue.svg?style=flat)](https://doge.mit-license.org) [![Javadoc](https://javadoc.io/badge2/com.auth0/java-jwt/javadoc.svg)](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). @@ -122,31 +122,31 @@ You'll first need to create a `JWTCreator` instance by calling `JWT.create()`. U * Example using `HS256` -```java -try { - Algorithm algorithm = Algorithm.HMAC256("secret"); - String token = JWT.create() - .withIssuer("auth0") - .sign(algorithm); -} catch (JWTCreationException exception){ - //Invalid Signing configuration / Couldn't convert Claims. -} -``` + ```java + try { + Algorithm algorithm = Algorithm.HMAC256("secret"); + String token = JWT.create() + .withIssuer("auth0") + .sign(algorithm); + } catch (JWTCreationException exception){ + //Invalid Signing configuration / Couldn't convert Claims. + } + ``` * Example using `RS256` -```java -RSAPublicKey publicKey = //Get the key instance -RSAPrivateKey privateKey = //Get the key instance -try { - Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); - String token = JWT.create() - .withIssuer("auth0") - .sign(algorithm); -} catch (JWTCreationException exception){ - //Invalid Signing configuration / Couldn't convert Claims. -} -``` + ```java + RSAPublicKey publicKey = //Get the key instance + RSAPrivateKey privateKey = //Get the key instance + try { + Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); + String token = JWT.create() + .withIssuer("auth0") + .sign(algorithm); + } catch (JWTCreationException exception){ + //Invalid Signing configuration / Couldn't convert Claims. + } + ``` If a Claim couldn't be converted to JSON or the Key used in the signing process was invalid a `JWTCreationException` will raise. @@ -157,35 +157,35 @@ You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` * Example using `HS256` -```java -String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; -try { - Algorithm algorithm = Algorithm.HMAC256("secret"); - JWTVerifier verifier = JWT.require(algorithm) - .withIssuer("auth0") - .build(); //Reusable verifier instance - DecodedJWT jwt = verifier.verify(token); -} catch (JWTVerificationException exception){ - //Invalid signature/claims -} -``` + ```java + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + try { + Algorithm algorithm = Algorithm.HMAC256("secret"); + JWTVerifier verifier = JWT.require(algorithm) + .withIssuer("auth0") + .build(); //Reusable verifier instance + DecodedJWT jwt = verifier.verify(token); + } catch (JWTVerificationException exception){ + //Invalid signature/claims + } + ``` * Example using `RS256` -```java -String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; -RSAPublicKey publicKey = //Get the key instance -RSAPrivateKey privateKey = //Get the key instance -try { - Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); - JWTVerifier verifier = JWT.require(algorithm) - .withIssuer("auth0") - .build(); //Reusable verifier instance - DecodedJWT jwt = verifier.verify(token); -} catch (JWTVerificationException exception){ - //Invalid signature/claims -} -``` + ```java + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + RSAPublicKey publicKey = //Get the key instance + RSAPrivateKey privateKey = //Get the key instance + try { + Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); + JWTVerifier verifier = JWT.require(algorithm) + .withIssuer("auth0") + .build(); //Reusable verifier instance + DecodedJWT jwt = verifier.verify(token); + } catch (JWTVerificationException exception){ + //Invalid signature/claims + } + ``` If the token has an invalid signature or the Claim requirement is not met, a `JWTVerificationException` will raise. From 35570e7ef3c036fc3e13a2fb8372a095f73d9f94 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Tue, 9 Mar 2021 19:35:05 -0600 Subject: [PATCH 032/198] remove jcenter --- build.gradle | 2 +- settings.gradle | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index bf6dfe4f..c938b200 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,6 @@ allprojects { group = 'com.auth0' repositories { - jcenter() + mavenCentral() } } diff --git a/settings.gradle b/settings.gradle index 2170ea25..962399d3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,6 @@ pluginManagement { repositories { gradlePluginPortal() - jcenter() } plugins { id 'com.jfrog.bintray' version '1.8.5' From 6ce1991f9b35a69c1e49dba2b12909f1fc86fb4c Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 5 Apr 2021 12:03:53 -0500 Subject: [PATCH 033/198] Release 3.15.0 --- CHANGELOG.md | 7 +++++++ README.md | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92116fa2..3cf74f8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [3.15.0](https://github.com/auth0/java-jwt/tree/3.15.0) (2021-04-05) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.14.0...3.15.0) + +**Changed** +- Remove jcenter [\#482](https://github.com/auth0/java-jwt/pull/482) ([jimmyjames](https://github.com/jimmyjames)) +- Move form commons-codec Base64 to j.u.Base64 [\#478](https://github.com/auth0/java-jwt/pull/478) ([XakepSDK](https://github.com/XakepSDK)) + ## [3.14.0](https://github.com/auth0/java-jwt/tree/3.14.0) (2021-02-26) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.13.0...3.14.0) diff --git a/README.md b/README.md index b86ad957..2c26f951 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.14.0 + 3.15.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.14.0' +implementation 'com.auth0:java-jwt:3.15.0' ``` ## Available Algorithms From 75272c3343d7e058843e164abc29e3f42a202409 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 5 Apr 2021 12:20:40 -0500 Subject: [PATCH 034/198] Update OSS plugin version --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index 962399d3..7faf41ff 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,7 @@ pluginManagement { } plugins { id 'com.jfrog.bintray' version '1.8.5' - id 'com.auth0.gradle.oss-library.java' version '0.14.0' + id 'com.auth0.gradle.oss-library.java' version '0.15.1' } } From 6ded84d9c490b06d9f9f6be09c34f441b2317798 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 8 Apr 2021 18:04:47 +0200 Subject: [PATCH 035/198] delete duplicated template --- .github/ISSUE_TEMPLATE/report_a_bug.md | 55 -------------------------- 1 file changed, 55 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/report_a_bug.md diff --git a/.github/ISSUE_TEMPLATE/report_a_bug.md b/.github/ISSUE_TEMPLATE/report_a_bug.md deleted file mode 100644 index 50b9fa7e..00000000 --- a/.github/ISSUE_TEMPLATE/report_a_bug.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: Report a bug -about: Have you found a bug or issue? Create a bug report for this SDK -title: '' -labels: bug report -assignees: '' ---- - - - -### Describe the problem - - - -### What was the expected behavior? - - - -### Reproduction - - -- Step 1.. -- Step 2.. -- ... - -### Environment - - - -- **Version of this library used:** -- **Which framework are you using, if applicable:** -- **Other modules/plugins/libraries that might be involved:** -- **Any other relevant information you think would be useful:** \ No newline at end of file From a643cebdc713acdcf790b7f17f57c4aea2211987 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 25 Apr 2021 18:04:30 +0200 Subject: [PATCH 036/198] Add Eclipse files to .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 5ab9b34a..b5766efd 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,11 @@ crashlytics.properties crashlytics-build.properties fabric.properties +# Eclipse IDE +.classpath +.project +.settings/ + ### Java template *.class From 85bf1534586c8e8858b7bdf1a5dbd5f200574f52 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 25 Apr 2021 18:14:51 +0200 Subject: [PATCH 037/198] Add package-info.java for internal `impl` package --- lib/src/main/java/com/auth0/jwt/impl/NullClaim.java | 2 +- lib/src/main/java/com/auth0/jwt/impl/package-info.java | 7 +++++++ lib/src/main/java/com/auth0/jwt/interfaces/Header.java | 3 ++- lib/src/main/java/com/auth0/jwt/interfaces/Payload.java | 3 ++- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 lib/src/main/java/com/auth0/jwt/impl/package-info.java diff --git a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java index 8d10ca10..2e9a38c2 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java @@ -8,7 +8,7 @@ import java.util.Map; /** - * The {@link NullClaim} class is a Claim implementation that returns null when any of it's methods it's called. + * The {@code NullClaim} class is a Claim implementation that returns null when any of it's methods is called. */ public class NullClaim implements Claim { @Override diff --git a/lib/src/main/java/com/auth0/jwt/impl/package-info.java b/lib/src/main/java/com/auth0/jwt/impl/package-info.java new file mode 100644 index 00000000..334ccb8a --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/impl/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains parts of the internal implementation of this library. + * + *

Do not use any of the classes in this package. They might be removed + * or changed at any point without prior warning. + */ +package com.auth0.jwt.impl; diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Header.java b/lib/src/main/java/com/auth0/jwt/interfaces/Header.java index 0a83d1ce..030643d0 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Header.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Header.java @@ -34,7 +34,8 @@ public interface Header { String getKeyId(); /** - * Get a Private Claim given it's name. If the Claim wasn't specified in the Header, a NullClaim will be returned. + * Get a Private Claim given it's name. If the Claim wasn't specified in the Header, a 'null claim' will be + * returned. All of the methods of that claim will return {@code null}. * * @param name the name of the Claim to retrieve. * @return a non-null Claim. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java b/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java index 0f639ab9..b999ad43 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java @@ -59,7 +59,8 @@ public interface Payload { String getId(); /** - * Get a Claim given it's name. If the Claim wasn't specified in the Payload, a NullClaim will be returned. + * Get a Claim given it's name. If the Claim wasn't specified in the Payload, a 'null claim' + * will be returned. All of the methods of that claim will return {@code null}. * * @param name the name of the Claim to retrieve. * @return a non-null Claim. From e745182d847eb162a96f3967265aba9b916c842d Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Wed, 31 Mar 2021 02:01:11 +0200 Subject: [PATCH 038/198] Use JDK 16 for javadoc generation This allows using the latest javadoc features (such as a search bar) without having to the change the lowest Java version supported by the project. --- lib/build.gradle | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index e0894e3d..e394a18e 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -30,15 +30,30 @@ oss { } } +def javaVersion = 8 java { toolchain { - languageVersion = JavaLanguageVersion.of(8) + languageVersion = JavaLanguageVersion.of(javaVersion) } } compileJava { - sourceCompatibility '1.8' - targetCompatibility '1.8' + sourceCompatibility "$javaVersion" + targetCompatibility "$javaVersion" +} + +// Use latest JDK for javadoc generation +tasks.withType(Javadoc).configureEach { + javadocTool = javaToolchains.javadocToolFor { + languageVersion = JavaLanguageVersion.of(16) + } +} + +javadoc { + // Specify Java version this project uses + // Otherwise would use version of javadoc toolchain by default which could + // cause compilation error mismatch between compileJava and javadoc creation + options.addStringOption('-release', "$javaVersion") } dependencies { From 64cb404a816cc9f5f8523f7b2a348547cf024c80 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 25 Apr 2021 18:48:13 +0200 Subject: [PATCH 039/198] Exclude internal `impl` package from javadoc --- lib/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/build.gradle b/lib/build.gradle index e394a18e..851ed055 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -50,6 +50,8 @@ tasks.withType(Javadoc).configureEach { } javadoc { + // Exclude internal implementation package from javadoc + excludes = ['com/auth0/jwt/impl'] // Specify Java version this project uses // Otherwise would use version of javadoc toolchain by default which could // cause compilation error mismatch between compileJava and javadoc creation From 7d1035cf79a21d1fc3c068b527bd230f07ac8ca4 Mon Sep 17 00:00:00 2001 From: Jeffrey Swan <61218880+plan-do-break-fix@users.noreply.github.com> Date: Thu, 29 Apr 2021 09:04:14 -0500 Subject: [PATCH 040/198] fix(docs) - corrects typo in project documentation (#494) Co-authored-by: Jim Anderson --- .github/ISSUE_TEMPLATE/report-a-bug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/report-a-bug.md b/.github/ISSUE_TEMPLATE/report-a-bug.md index 60505a3f..e5cf8c46 100644 --- a/.github/ISSUE_TEMPLATE/report-a-bug.md +++ b/.github/ISSUE_TEMPLATE/report-a-bug.md @@ -29,7 +29,7 @@ By submitting an Issue to this repository, you agree to the terms within the Aut ### Reproduction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/build.gradle b/lib/build.gradle index a672afaf..001963bd 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -2,6 +2,12 @@ plugins { id 'java' id 'jacoco' id 'com.auth0.gradle.oss-library.java' + id 'checkstyle' +} + +checkstyle { + toolVersion '10.0' + checkstyleTest.enabled = false //We are disabling lint checks for tests } logger.lifecycle("Using version ${version} for ${group}.${name}") diff --git a/lib/src/main/java/com/auth0/jwt/JWT.java b/lib/src/main/java/com/auth0/jwt/JWT.java index ca05deff..9d719d1e 100644 --- a/lib/src/main/java/com/auth0/jwt/JWT.java +++ b/lib/src/main/java/com/auth0/jwt/JWT.java @@ -6,6 +6,9 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; +/** + * Exposes all the JWT functionalities. + */ @SuppressWarnings("WeakerAccess") public class JWT { @@ -22,11 +25,13 @@ public JWT() { /** * Decode a given Json Web Token. *

- * Note that this method doesn't verify the token's signature! Use it only if you trust the token or you already verified it. + * Note that this method doesn't verify the token's signature! + * Use it only if you trust the token or you already verified it. * * @param token with jwt format as string. * @return a decoded JWT. - * @throws JWTDecodeException if any part of the token contained an invalid jwt or JSON format of each of the jwt parts. + * @throws JWTDecodeException if any part of the token contained an invalid jwt + * or JSON format of each of the jwt parts. */ public DecodedJWT decodeJwt(String token) throws JWTDecodeException { return new JWTDecoder(parser, token); @@ -35,11 +40,13 @@ public DecodedJWT decodeJwt(String token) throws JWTDecodeException { /** * Decode a given Json Web Token. *

- * Note that this method doesn't verify the token's signature! Use it only if you trust the token or you already verified it. + * Note that this method doesn't verify the token's signature! + * Use it only if you trust the token or you already verified it. * * @param token with jwt format as string. * @return a decoded JWT. - * @throws JWTDecodeException if any part of the token contained an invalid jwt or JSON format of each of the jwt parts. + * @throws JWTDecodeException if any part of the token contained an invalid jwt + * or JSON format of each of the jwt parts. */ public static DecodedJWT decode(String token) throws JWTDecodeException { return new JWTDecoder(token); @@ -57,7 +64,7 @@ public static Verification require(Algorithm algorithm) { } /** - * Returns a Json Web Token builder used to create and sign tokens + * Returns a Json Web Token builder used to create and sign tokens. * * @return a token builder. */ diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index ca6e3918..6b355b36 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -15,7 +15,8 @@ import java.util.Map.Entry; /** - * The JWTCreator class holds the sign method to generate a complete JWT (with Signature) from a given Header and Payload content. + * The JWTCreator class holds the sign method to generate a complete JWT (with Signature) + * from a given Header and Payload content. *

* This class is thread-safe. */ @@ -38,7 +39,8 @@ public final class JWTCreator { mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); } - private JWTCreator(Algorithm algorithm, Map headerClaims, Map payloadClaims) throws JWTCreationException { + private JWTCreator(Algorithm algorithm, Map headerClaims, Map payloadClaims) + throws JWTCreationException { this.algorithm = algorithm; try { headerJson = mapper.writeValueAsString(new HeaderClaimsHolder(headerClaims)); @@ -96,7 +98,8 @@ public Builder withHeader(Map headerClaims) { /** * Add a specific Key Id ("kid") claim to the Header. - * If the {@link Algorithm} used to sign this token was instantiated with a KeyProvider, the 'kid' value will be taken from that provider and this one will be ignored. + * If the {@link Algorithm} used to sign this token was instantiated with a KeyProvider, + * the 'kid' value will be taken from that provider and this one will be ignored. * * @param keyId the Key Id value. * @return this same Builder instance. @@ -322,48 +325,6 @@ public Builder withClaim(String name, Instant value) throws IllegalArgumentExcep return this; } - /** - * Add a custom Array Claim with the given items. - * - * @param name the Claim's name. - * @param items the Claim's value. - * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null. - */ - public Builder withArrayClaim(String name, String[] items) throws IllegalArgumentException { - assertNonNull(name); - addClaim(name, items); - return this; - } - - /** - * Add a custom Array Claim with the given items. - * - * @param name the Claim's name. - * @param items the Claim's value. - * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null. - */ - public Builder withArrayClaim(String name, Integer[] items) throws IllegalArgumentException { - assertNonNull(name); - addClaim(name, items); - return this; - } - - /** - * Add a custom Array Claim with the given items. - * - * @param name the Claim's name. - * @param items the Claim's value. - * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null - */ - public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentException { - assertNonNull(name); - addClaim(name, items); - return this; - } - /** * Add a custom Map Claim with the given items. *

@@ -381,7 +342,8 @@ public Builder withClaim(String name, Map map) throws IllegalArgument assertNonNull(name); // validate map contents if (map != null && !validateClaim(map)) { - throw new IllegalArgumentException("Expected map containing Map, List, Boolean, Integer, Long, Double, String and Date"); + throw new IllegalArgumentException("Expected map containing Map, List, Boolean, Integer, " + + "Long, Double, String and Date"); } addClaim(name, map); return this; @@ -405,12 +367,55 @@ public Builder withClaim(String name, List list) throws IllegalArgumentExcept assertNonNull(name); // validate list contents if (list != null && !validateClaim(list)) { - throw new IllegalArgumentException("Expected list containing Map, List, Boolean, Integer, Long, Double, String and Date"); + throw new IllegalArgumentException("Expected list containing Map, List, Boolean, Integer, " + + "Long, Double, String and Date"); } addClaim(name, list); return this; } + /** + * Add a custom Array Claim with the given items. + * + * @param name the Claim's name. + * @param items the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withArrayClaim(String name, String[] items) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, items); + return this; + } + + /** + * Add a custom Array Claim with the given items. + * + * @param name the Claim's name. + * @param items the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withArrayClaim(String name, Integer[] items) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, items); + return this; + } + + /** + * Add a custom Array Claim with the given items. + * + * @param name the Claim's name. + * @param items the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null + */ + public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, items); + return this; + } + /** * Add specific Claims to set as the Payload. If the provided map is null then * nothing is changed. @@ -426,8 +431,9 @@ public Builder withClaim(String name, List list) throws IllegalArgumentExcept *

* * @param payloadClaims the values to use as Claims in the token's payload. - * @throws IllegalArgumentException if any of the claim keys or null, or if the values are not of a supported type. * @return this same Builder instance. + * @throws IllegalArgumentException if any of the claim keys or null, + * or if the values are not of a supported type. */ public Builder withPayload(Map payloadClaims) throws IllegalArgumentException { if (payloadClaims == null) { @@ -435,7 +441,8 @@ public Builder withPayload(Map payloadClaims) throws IllegalArgumentE } if (!validatePayload(payloadClaims)) { - throw new IllegalArgumentException("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + throw new IllegalArgumentException("Claim values must only be of types Map, List, Boolean, Integer, " + + "Long, Double, String and Date"); } // add claims only after validating all claims so as not to corrupt the claims map of this builder @@ -504,16 +511,18 @@ private static boolean isBasicType(Object value) { if (c.isArray()) { return c == Integer[].class || c == Long[].class || c == String[].class; } - return c == String.class || c == Integer.class || c == Long.class || c == Double.class || c == Date.class || c == Instant.class || c == Boolean.class; + return c == String.class || c == Integer.class || c == Long.class || c == Double.class + || c == Date.class || c == Instant.class || c == Boolean.class; } /** - * Creates a new JWT and signs is with the given algorithm + * Creates a new JWT and signs is with the given algorithm. * * @param algorithm used to sign the JWT * @return a new JWT token * @throws IllegalArgumentException if the provided algorithm is null. - * @throws JWTCreationException if the claims could not be converted to a valid JSON or there was a problem with the signing key. + * @throws JWTCreationException if the claims could not be converted to a valid JSON + * or there was a problem with the signing key. */ public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCreationException { if (algorithm == null) { @@ -546,10 +555,13 @@ private void addClaim(String name, Object value) { } private String sign() throws SignatureGenerationException { - String header = Base64.getUrlEncoder().withoutPadding().encodeToString(headerJson.getBytes(StandardCharsets.UTF_8)); - String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8)); + String header = Base64.getUrlEncoder().withoutPadding() + .encodeToString(headerJson.getBytes(StandardCharsets.UTF_8)); + String payload = Base64.getUrlEncoder().withoutPadding() + .encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8)); - byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8)); + byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), + payload.getBytes(StandardCharsets.UTF_8)); String signature = Base64.getUrlEncoder().withoutPadding().encodeToString((signatureBytes)); return String.format("%s.%s.%s", header, payload, signature); diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index 55855241..cc283095 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -42,7 +42,7 @@ final class JWTDecoder implements DecodedJWT, Serializable { payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); } catch (NullPointerException e) { throw new JWTDecodeException("The UTF-8 Charset isn't initialized.", e); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { throw new JWTDecodeException("The input is not a valid base 64 encoded string.", e); } header = converter.parseHeader(headerJson); diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 5bcc7bc7..77dfef4e 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -16,11 +16,13 @@ import java.util.*; /** - * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, but also its signature matches. + * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, + * but also its signature matches. *

* This class is thread-safe. + * + * @see com.auth0.jwt.interfaces.JWTVerifier */ -@SuppressWarnings("WeakerAccess") public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { private final Algorithm algorithm; final Map claims; @@ -48,6 +50,9 @@ static Verification init(Algorithm algorithm) throws IllegalArgumentException { return new BaseVerification(algorithm); } + /** + * {@link Verification} implementation that accepts all the expected Claim values for verification. + */ public static class BaseVerification implements Verification { private final Algorithm algorithm; private final Map claims; @@ -180,7 +185,8 @@ public Verification withClaim(String name, Date value) throws IllegalArgumentExc @Override public Verification withClaim(String name, Instant value) throws IllegalArgumentException { assertNonNull(name); - // Since date-time claims are serialized as epoch seconds, we need to compare them with only seconds-granularity + // Since date-time claims are serialized as epoch seconds, + // we need to compare them with only seconds-granularity requireClaim(name, value != null ? value.truncatedTo(ChronoUnit.SECONDS) : null); return this; } @@ -280,7 +286,8 @@ private static boolean isNullOrEmpty(String[] args) { * * @param token to verify. * @return a verified and decoded JWT. - * @throws AlgorithmMismatchException if the algorithm stated in the token's header is not equal to the one defined in the {@link JWTVerifier}. + * @throws AlgorithmMismatchException if the algorithm stated in the token's header is not equal to + * the one defined in the {@link JWTVerifier}. * @throws SignatureVerificationException if the signature is invalid. * @throws TokenExpiredException if the token has expired. * @throws InvalidClaimException if a claim contained a different value than the expected one. @@ -296,7 +303,8 @@ public DecodedJWT verify(String token) throws JWTVerificationException { * * @param jwt to verify. * @return a verified and decoded JWT. - * @throws AlgorithmMismatchException if the algorithm stated in the token's header is not equal to the one defined in the {@link JWTVerifier}. + * @throws AlgorithmMismatchException if the algorithm stated in the token's header is not equal to + * the one defined in the {@link JWTVerifier}. * @throws SignatureVerificationException if the signature is invalid. * @throws TokenExpiredException if the token has expired. * @throws InvalidClaimException if a claim contained a different value than the expected one. @@ -311,11 +319,13 @@ public DecodedJWT verify(DecodedJWT jwt) throws JWTVerificationException { private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException { if (!expectedAlgorithm.getName().equals(jwt.getAlgorithm())) { - throw new AlgorithmMismatchException("The provided Algorithm doesn't match the one defined in the JWT's Header."); + throw new AlgorithmMismatchException( + "The provided Algorithm doesn't match the one defined in the JWT's Header."); } } - private void verifyClaims(DecodedJWT jwt, Map claims) throws TokenExpiredException, InvalidClaimException { + private void verifyClaims(DecodedJWT jwt, Map claims) + throws TokenExpiredException, InvalidClaimException { for (Map.Entry entry : claims.entrySet()) { if (entry.getValue() instanceof NonEmptyClaim) { assertClaimPresent(jwt.getClaim(entry.getKey()), entry.getKey()); @@ -327,8 +337,9 @@ private void verifyClaims(DecodedJWT jwt, Map claims) throws Tok private void verifyClaimValues(DecodedJWT jwt, Map.Entry expectedClaim) { switch (expectedClaim.getKey()) { - // We use custom keys for audience in the expected claims to differentiate between validating that the audience - // contains all expected values, or validating that the audience contains at least one of the expected values. + // We use custom keys for audience in the expected claims to differentiate between + // validating that the audience contains all expected values, or validating that the audience contains + // at least one of the expected values. case AUDIENCE_EXACT: assertValidAudienceClaim(jwt.getAudience(), (List) expectedClaim.getValue(), true); break; @@ -354,7 +365,8 @@ private void verifyClaimValues(DecodedJWT jwt, Map.Entry expecte assertValidStringClaim(expectedClaim.getKey(), jwt.getSubject(), (String) expectedClaim.getValue()); break; default: - assertValidClaim(jwt.getClaim(expectedClaim.getKey()), expectedClaim.getKey(), expectedClaim.getValue()); + assertValidClaim(jwt.getClaim(expectedClaim.getKey()), expectedClaim.getKey(), + expectedClaim.getValue()); break; } } @@ -402,13 +414,15 @@ private void assertValidClaim(Claim claim, String claimName, Object value) { } if (!isValid) { - throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); + throw new InvalidClaimException( + String.format("The Claim '%s' value doesn't match the required one.", claimName)); } } private void assertValidStringClaim(String claimName, String value, String expectedValue) { if (!expectedValue.equals(value)) { - throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); + throw new InvalidClaimException( + String.format("The Claim '%s' value doesn't match the required one.", claimName)); } } @@ -434,8 +448,8 @@ private void assertInstantIsPast(Instant claimVal, long leeway, Instant now) { } private void assertValidAudienceClaim(List audience, List values, boolean shouldContainAll) { - if (audience == null || (shouldContainAll && !audience.containsAll(values)) || - (!shouldContainAll && Collections.disjoint(audience, values))) { + if (audience == null || (shouldContainAll && !audience.containsAll(values)) + || (!shouldContainAll && Collections.disjoint(audience, values))) { throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); } } @@ -452,7 +466,8 @@ private void assertValidIssuerClaim(String issuer, List value) { private static class NonEmptyClaim { private static NonEmptyClaim nonEmptyClaim; - private NonEmptyClaim() {} + private NonEmptyClaim() { + } public static NonEmptyClaim getInstance() { if (nonEmptyClaim == null) { diff --git a/lib/src/main/java/com/auth0/jwt/TokenUtils.java b/lib/src/main/java/com/auth0/jwt/TokenUtils.java index cb6cff3e..0a76028b 100644 --- a/lib/src/main/java/com/auth0/jwt/TokenUtils.java +++ b/lib/src/main/java/com/auth0/jwt/TokenUtils.java @@ -18,7 +18,8 @@ static String[] splitToken(String token) throws JWTDecodeException { parts = new String[]{parts[0], parts[1], ""}; } if (parts.length != 3) { - throw new JWTDecodeException(String.format("The token was expected to have 3 parts, but got %s.", parts.length)); + throw new JWTDecodeException( + String.format("The token was expected to have 3 parts, but got %s.", parts.length)); } return parts; } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index ff10a9aa..a5247005 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -139,47 +139,47 @@ public static Algorithm HMAC256(String secret) throws IllegalArgumentException { } /** - * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". + * Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256". * - * @param secret the secret to use in the verify or signing instance. - * @return a valid HMAC384 Algorithm. + * @param secret the secret bytes to use in the verify or signing instance. + * @return a valid HMAC256 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ - public static Algorithm HMAC384(String secret) throws IllegalArgumentException { - return new HMACAlgorithm("HS384", "HmacSHA384", secret); + public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException { + return new HMACAlgorithm("HS256", "HmacSHA256", secret); } /** - * Creates a new Algorithm instance using HmacSHA512. Tokens specify this as "HS512". + * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". * * @param secret the secret to use in the verify or signing instance. - * @return a valid HMAC512 Algorithm. + * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ - public static Algorithm HMAC512(String secret) throws IllegalArgumentException { - return new HMACAlgorithm("HS512", "HmacSHA512", secret); + public static Algorithm HMAC384(String secret) throws IllegalArgumentException { + return new HMACAlgorithm("HS384", "HmacSHA384", secret); } /** - * Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256". + * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". * * @param secret the secret bytes to use in the verify or signing instance. - * @return a valid HMAC256 Algorithm. + * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ - public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException { - return new HMACAlgorithm("HS256", "HmacSHA256", secret); + public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException { + return new HMACAlgorithm("HS384", "HmacSHA384", secret); } /** - * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". + * Creates a new Algorithm instance using HmacSHA512. Tokens specify this as "HS512". * - * @param secret the secret bytes to use in the verify or signing instance. - * @return a valid HMAC384 Algorithm. + * @param secret the secret to use in the verify or signing instance. + * @return a valid HMAC512 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ - public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException { - return new HMACAlgorithm("HS384", "HmacSHA384", secret); + public static Algorithm HMAC512(String secret) throws IllegalArgumentException { + return new HMACAlgorithm("HS512", "HmacSHA512", secret); } /** @@ -192,8 +192,7 @@ public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException { public static Algorithm HMAC512(byte[] secret) throws IllegalArgumentException { return new HMACAlgorithm("HS512", "HmacSHA512", secret); } - - + /** * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256". @@ -314,7 +313,8 @@ protected Algorithm(String name, String description) { } /** - * Getter for the Id of the Private Key used to sign the tokens. This is usually specified as the `kid` claim in the Header. + * Getter for the Id of the Private Key used to sign the tokens. + * This is usually specified as the `kid` claim in the Header. * * @return the Key Id that identifies the Signing Key or null if it's not specified. */ @@ -332,7 +332,8 @@ public String getName() { } /** - * Getter for the description of this Algorithm, required when instantiating a Mac or Signature object. i.e. "HmacSHA256" + * Getter for the description of this Algorithm, + * required when instantiating a Mac or Signature object. i.e. "HmacSHA256" * * @return the algorithm description. */ @@ -349,15 +350,19 @@ public String toString() { * Verify the given token using this Algorithm instance. * * @param jwt the already decoded JWT that it's going to be verified. - * @throws SignatureVerificationException if the Token's Signature is invalid, meaning that it doesn't match the signatureBytes, or if the Key is invalid. + * @throws SignatureVerificationException if the Token's Signature is invalid, + * meaning that it doesn't match the signatureBytes, + * or if the Key is invalid. */ public abstract void verify(DecodedJWT jwt) throws SignatureVerificationException; /** * Sign the given content using this Algorithm instance. * - * @param headerBytes an array of bytes representing the base64 encoded header content to be verified against the signature. - * @param payloadBytes an array of bytes representing the base64 encoded payload content to be verified against the signature. + * @param headerBytes an array of bytes representing the base64 encoded header content + * to be verified against the signature. + * @param payloadBytes an array of bytes representing the base64 encoded payload content + * to be verified against the signature. * @return the signature in a base64 encoded array of bytes * @throws SignatureGenerationException if the Key is invalid. */ @@ -376,7 +381,8 @@ public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGene * Sign the given content using this Algorithm instance. * To get the correct JWT Signature, ensure the content is in the format {HEADER}.{PAYLOAD} * - * @param contentBytes an array of bytes representing the base64 encoded content to be verified against the signature. + * @param contentBytes an array of bytes representing the base64 encoded content + * to be verified against the signature. * @return the signature in a base64 encoded array of bytes * @throws SignatureGenerationException if the Key is invalid. */ diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java b/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java index 26a87bc4..7b8c5c2a 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java @@ -12,93 +12,98 @@ */ class CryptoHelper { - private static final byte JWT_PART_SEPARATOR = (byte)46; + private static final byte JWT_PART_SEPARATOR = (byte) 46; /** * Verify signature for JWT header and payload. * - * @param algorithm algorithm name. - * @param secretBytes algorithm secret. - * @param header JWT header. - * @param payload JWT payload. + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. + * @param header JWT header. + * @param payload JWT payload. * @param signatureBytes JWT signature. * @return true if signature is valid. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - boolean verifySignatureFor(String algorithm, byte[] secretBytes, String header, String payload, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException { - return verifySignatureFor(algorithm, secretBytes, header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8), signatureBytes); + boolean verifySignatureFor( + String algorithm, + byte[] secretBytes, + String header, + String payload, + byte[] signatureBytes + ) throws NoSuchAlgorithmException, InvalidKeyException { + return verifySignatureFor(algorithm, secretBytes, + header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8), signatureBytes); } /** * Verify signature for JWT header and payload. * - * @param algorithm algorithm name. - * @param secretBytes algorithm secret. - * @param headerBytes JWT header. - * @param payloadBytes JWT payload. + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. + * @param headerBytes JWT header. + * @param payloadBytes JWT payload. * @param signatureBytes JWT signature. * @return true if signature is valid. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - boolean verifySignatureFor(String algorithm, byte[] secretBytes, byte[] headerBytes, byte[] payloadBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException { - return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, headerBytes, payloadBytes), signatureBytes); - } - - /** - * Create signature for JWT header and payload. - * - * @param algorithm algorithm name. - * @param secretBytes algorithm secret. - * @param headerBytes JWT header. - * @param payloadBytes JWT payload. - * @return the signature bytes. - * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. - */ - - byte[] createSignatureFor(String algorithm, byte[] secretBytes, byte[] headerBytes, byte[] payloadBytes) throws NoSuchAlgorithmException, InvalidKeyException { - final Mac mac = Mac.getInstance(algorithm); - mac.init(new SecretKeySpec(secretBytes, algorithm)); - mac.update(headerBytes); - mac.update(JWT_PART_SEPARATOR); - return mac.doFinal(payloadBytes); + boolean verifySignatureFor( + String algorithm, + byte[] secretBytes, + byte[] headerBytes, + byte[] payloadBytes, + byte[] signatureBytes + ) throws NoSuchAlgorithmException, InvalidKeyException { + return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, headerBytes, payloadBytes), + signatureBytes); } /** * Verify signature for JWT header and payload. * - * @param algorithm algorithm name. - * @param publicKey algorithm public key. - * @param header JWT header. - * @param payload JWT payload. + * @param algorithm algorithm name. + * @param publicKey algorithm public key. + * @param header JWT header. + * @param payload JWT payload. * @param signatureBytes JWT signature. * @return true if signature is valid. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - - boolean verifySignatureFor(String algorithm, PublicKey publicKey, String header, String payload, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - return verifySignatureFor(algorithm, publicKey, header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8), signatureBytes); + boolean verifySignatureFor( + String algorithm, + PublicKey publicKey, + String header, + String payload, + byte[] signatureBytes + ) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + return verifySignatureFor(algorithm, publicKey, header.getBytes(StandardCharsets.UTF_8), + payload.getBytes(StandardCharsets.UTF_8), signatureBytes); } /** * Verify signature for JWT header and payload using a public key. * - * @param algorithm algorithm name. - * @param publicKey the public key to use for verification. - * @param headerBytes JWT header. - * @param payloadBytes JWT payload. + * @param algorithm algorithm name. + * @param publicKey the public key to use for verification. + * @param headerBytes JWT header. + * @param payloadBytes JWT payload. * @param signatureBytes JWT signature. * @return true if signature is valid. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - - boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] headerBytes, byte[] payloadBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + boolean verifySignatureFor( + String algorithm, + PublicKey publicKey, + byte[] headerBytes, + byte[] payloadBytes, + byte[] signatureBytes + ) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { final Signature s = Signature.getInstance(algorithm); s.initVerify(publicKey); s.update(headerBytes); @@ -110,17 +115,22 @@ boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] headerB /** * Create signature for JWT header and payload using a private key. * - * @param algorithm algorithm name. - * @param privateKey the private key to use for signing. - * @param headerBytes JWT header. + * @param algorithm algorithm name. + * @param privateKey the private key to use for signing. + * @param headerBytes JWT header. * @param payloadBytes JWT payload. * @return the signature bytes. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. - * @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws SignatureException if this signature object is not initialized properly + * or if this signature algorithm is unable to process the input data provided. */ - - byte[] createSignatureFor(String algorithm, PrivateKey privateKey, byte[] headerBytes, byte[] payloadBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + byte[] createSignatureFor( + String algorithm, + PrivateKey privateKey, + byte[] headerBytes, + byte[] payloadBytes + ) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { final Signature s = Signature.getInstance(algorithm); s.initSign(privateKey); s.update(headerBytes); @@ -130,75 +140,66 @@ byte[] createSignatureFor(String algorithm, PrivateKey privateKey, byte[] header } /** - * Verify signature. - * For valid verification, ensure the content is in the format {HEADER}.{PAYLOAD} + * Create signature for JWT header and payload. * - * @param algorithm algorithm name. - * @param secretBytes algorithm secret. - * @param contentBytes the content to which the signature applies. - * @param signatureBytes JWT signature. - * @return true if signature is valid. + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. + * @param headerBytes JWT header. + * @param payloadBytes JWT payload. + * @return the signature bytes. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - - boolean verifySignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException { - return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, contentBytes), signatureBytes); + byte[] createSignatureFor( + String algorithm, + byte[] secretBytes, + byte[] headerBytes, + byte[] payloadBytes + ) throws NoSuchAlgorithmException, InvalidKeyException { + final Mac mac = Mac.getInstance(algorithm); + mac.init(new SecretKeySpec(secretBytes, algorithm)); + mac.update(headerBytes); + mac.update(JWT_PART_SEPARATOR); + return mac.doFinal(payloadBytes); } /** * Create signature. * To get the correct JWT Signature, ensure the content is in the format {HEADER}.{PAYLOAD} * - * @param algorithm algorithm name. - * @param secretBytes algorithm secret. + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. * @param contentBytes the content to be signed. * @return the signature bytes. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - - byte[] createSignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes) throws NoSuchAlgorithmException, InvalidKeyException { + byte[] createSignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes) + throws NoSuchAlgorithmException, InvalidKeyException { final Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(secretBytes, algorithm)); return mac.doFinal(contentBytes); } - /** - * Verify signature using a public key. - * For valid verification, ensure the content is in the format {HEADER}.{PAYLOAD} - * - * @param algorithm algorithm name. - * @param publicKey algorithm public key. - * @param contentBytes the content to which the signature applies. - * @param signatureBytes JWT signature. - * @return the signature bytes. - * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. - * @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided. - */ - - boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - final Signature s = Signature.getInstance(algorithm); - s.initVerify(publicKey); - s.update(contentBytes); - return s.verify(signatureBytes); - } - /** * Create signature using a private key. * To get the correct JWT Signature, ensure the content is in the format {HEADER}.{PAYLOAD} * - * @param algorithm algorithm name. - * @param privateKey the private key to use for signing. + * @param algorithm algorithm name. + * @param privateKey the private key to use for signing. * @param contentBytes the content to be signed. * @return the signature bytes. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. - * @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws SignatureException if this signature object is not initialized properly + * or if this signature algorithm is unable to process the input data provided. */ - byte[] createSignatureFor(String algorithm, PrivateKey privateKey, byte[] contentBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + byte[] createSignatureFor( + String algorithm, + PrivateKey privateKey, + byte[] contentBytes + ) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { final Signature s = Signature.getInstance(algorithm); s.initSign(privateKey); s.update(contentBytes); diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java index 51fa70fa..26700a2a 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -24,7 +24,8 @@ class ECDSAAlgorithm extends Algorithm { private final int ecNumberSize; //Visible for testing - ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) throws IllegalArgumentException { + ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) + throws IllegalArgumentException { super(id, algorithm); if (keyProvider == null) { throw new IllegalArgumentException("The Key Provider cannot be null."); @@ -34,7 +35,8 @@ class ECDSAAlgorithm extends Algorithm { this.ecNumberSize = ecNumberSize; } - ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) throws IllegalArgumentException { + ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) + throws IllegalArgumentException { this(new CryptoHelper(), id, algorithm, ecNumberSize, keyProvider); } @@ -46,12 +48,14 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } - boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), JOSEToDER(signatureBytes)); + boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), + jwt.getPayload(), JOSEToDER(signatureBytes)); if (!valid) { throw new SignatureVerificationException(this); } - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException | IllegalArgumentException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException + | IllegalStateException | IllegalArgumentException e) { throw new SignatureVerificationException(this, e); } } @@ -116,25 +120,27 @@ byte[] DERToJOSE(byte[] derSignature) throws SignatureException { offset++; //Obtain R number length (Includes padding) and skip it - int rLength = derSignature[offset++]; - if (rLength > ecNumberSize + 1) { + int rlength = derSignature[offset++]; + if (rlength > ecNumberSize + 1) { throw new SignatureException("Invalid DER signature format."); } - int rPadding = ecNumberSize - rLength; + int rpadding = ecNumberSize - rlength; //Retrieve R number - System.arraycopy(derSignature, offset + Math.max(-rPadding, 0), joseSignature, Math.max(rPadding, 0), rLength + Math.min(rPadding, 0)); + System.arraycopy(derSignature, offset + Math.max(-rpadding, 0), + joseSignature, Math.max(rpadding, 0), rlength + Math.min(rpadding, 0)); //Skip R number and 0x02 - offset += rLength + 1; + offset += rlength + 1; //Obtain S number length. (Includes padding) - int sLength = derSignature[offset++]; - if (sLength > ecNumberSize + 1) { + int slength = derSignature[offset++]; + if (slength > ecNumberSize + 1) { throw new SignatureException("Invalid DER signature format."); } - int sPadding = ecNumberSize - sLength; + int spadding = ecNumberSize - slength; //Retrieve R number - System.arraycopy(derSignature, offset + Math.max(-sPadding, 0), joseSignature, ecNumberSize + Math.max(sPadding, 0), sLength + Math.min(sPadding, 0)); + System.arraycopy(derSignature, offset + Math.max(-spadding, 0), joseSignature, + ecNumberSize + Math.max(spadding, 0), slength + Math.min(spadding, 0)); return joseSignature; } @@ -146,12 +152,12 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { } // Retrieve R and S number's length and padding. - int rPadding = countPadding(joseSignature, 0, ecNumberSize); - int sPadding = countPadding(joseSignature, ecNumberSize, joseSignature.length); - int rLength = ecNumberSize - rPadding; - int sLength = ecNumberSize - sPadding; + int rpadding = countPadding(joseSignature, 0, ecNumberSize); + int spadding = countPadding(joseSignature, ecNumberSize, joseSignature.length); + int rlength = ecNumberSize - rpadding; + int slength = ecNumberSize - spadding; - int length = 2 + rLength + 2 + sLength; + int length = 2 + rlength + 2 + slength; if (length > 255) { throw new SignatureException("Invalid JOSE signature format."); } @@ -174,31 +180,32 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { // Header with "min R" number length derSignature[offset++] = (byte) 0x02; - derSignature[offset++] = (byte) rLength; + derSignature[offset++] = (byte) rlength; // R number - if (rPadding < 0) { + if (rpadding < 0) { //Sign derSignature[offset++] = (byte) 0x00; System.arraycopy(joseSignature, 0, derSignature, offset, ecNumberSize); offset += ecNumberSize; } else { - int copyLength = Math.min(ecNumberSize, rLength); - System.arraycopy(joseSignature, rPadding, derSignature, offset, copyLength); + int copyLength = Math.min(ecNumberSize, rlength); + System.arraycopy(joseSignature, rpadding, derSignature, offset, copyLength); offset += copyLength; } // Header with "min S" number length derSignature[offset++] = (byte) 0x02; - derSignature[offset++] = (byte) sLength; + derSignature[offset++] = (byte) slength; // S number - if (sPadding < 0) { + if (spadding < 0) { //Sign derSignature[offset++] = (byte) 0x00; System.arraycopy(joseSignature, ecNumberSize, derSignature, offset, ecNumberSize); } else { - System.arraycopy(joseSignature, ecNumberSize + sPadding, derSignature, offset, Math.min(ecNumberSize, sLength)); + System.arraycopy(joseSignature, ecNumberSize + spadding, derSignature, offset, + Math.min(ecNumberSize, slength)); } return derSignature; diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java index 26ad6a87..0306e7c4 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java @@ -21,7 +21,8 @@ class HMACAlgorithm extends Algorithm { private final byte[] secret; //Visible for testing - HMACAlgorithm(CryptoHelper crypto, String id, String algorithm, byte[] secretBytes) throws IllegalArgumentException { + HMACAlgorithm(CryptoHelper crypto, String id, String algorithm, byte[] secretBytes) + throws IllegalArgumentException { super(id, algorithm); if (secretBytes == null) { throw new IllegalArgumentException("The Secret cannot be null"); @@ -50,7 +51,8 @@ static byte[] getSecretBytes(String secret) throws IllegalArgumentException { public void verify(DecodedJWT jwt) throws SignatureVerificationException { try { byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature()); - boolean valid = crypto.verifySignatureFor(getDescription(), secret, jwt.getHeader(), jwt.getPayload(), signatureBytes); + boolean valid = crypto.verifySignatureFor( + getDescription(), secret, jwt.getHeader(), jwt.getPayload(), signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java index ade07b0e..5c6c0fc5 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java @@ -3,7 +3,6 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; - import java.util.Base64; class NoneAlgorithm extends Algorithm { diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index cd1e97a5..0c7a5b57 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -24,7 +24,8 @@ class RSAAlgorithm extends Algorithm { private final CryptoHelper crypto; //Visible for testing - RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAKeyProvider keyProvider) throws IllegalArgumentException { + RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAKeyProvider keyProvider) + throws IllegalArgumentException { super(id, algorithm); if (keyProvider == null) { throw new IllegalArgumentException("The Key Provider cannot be null."); @@ -45,11 +46,13 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } - boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), signatureBytes); + boolean valid = crypto.verifySignatureFor( + getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalArgumentException | IllegalStateException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException + | IllegalArgumentException | IllegalStateException e) { throw new SignatureVerificationException(this, e); } } @@ -66,7 +69,7 @@ public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGene throw new SignatureGenerationException(this, e); } } - + @Override public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/AlgorithmMismatchException.java b/lib/src/main/java/com/auth0/jwt/exceptions/AlgorithmMismatchException.java index 1d7ba1ce..d6b71205 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/AlgorithmMismatchException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/AlgorithmMismatchException.java @@ -1,5 +1,8 @@ package com.auth0.jwt.exceptions; +/** + * The exception that will be thrown if the exception doesn't match the one mentioned in the JWT Header. + */ public class AlgorithmMismatchException extends JWTVerificationException { public AlgorithmMismatchException(String message) { super(message); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/InvalidClaimException.java b/lib/src/main/java/com/auth0/jwt/exceptions/InvalidClaimException.java index ab348323..f80fb752 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/InvalidClaimException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/InvalidClaimException.java @@ -1,6 +1,8 @@ package com.auth0.jwt.exceptions; - +/** + * The exception that will be thrown while verifying Claims of a JWT. + */ public class InvalidClaimException extends JWTVerificationException { public InvalidClaimException(String message) { super(message); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/JWTCreationException.java b/lib/src/main/java/com/auth0/jwt/exceptions/JWTCreationException.java index 5bf4facb..c7e162ea 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/JWTCreationException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/JWTCreationException.java @@ -1,5 +1,8 @@ package com.auth0.jwt.exceptions; +/** + * The exception that is thrown when a JWT cannot be created. + */ public class JWTCreationException extends RuntimeException { public JWTCreationException(String message, Throwable cause) { super(message, cause); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/JWTDecodeException.java b/lib/src/main/java/com/auth0/jwt/exceptions/JWTDecodeException.java index 93799d31..448714e9 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/JWTDecodeException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/JWTDecodeException.java @@ -1,5 +1,8 @@ package com.auth0.jwt.exceptions; +/** + * The exception that is thrown when any part of the token contained an invalid JWT or JSON format. + */ public class JWTDecodeException extends JWTVerificationException { public JWTDecodeException(String message) { this(message, null); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/JWTVerificationException.java b/lib/src/main/java/com/auth0/jwt/exceptions/JWTVerificationException.java index 2ccfcccf..dd36dcd3 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/JWTVerificationException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/JWTVerificationException.java @@ -1,5 +1,8 @@ package com.auth0.jwt.exceptions; +/** + * Parent to all the exception thrown while verifying a JWT. + */ public class JWTVerificationException extends RuntimeException { public JWTVerificationException(String message) { this(message, null); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/SignatureGenerationException.java b/lib/src/main/java/com/auth0/jwt/exceptions/SignatureGenerationException.java index 3637f97a..4b7668a0 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/SignatureGenerationException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/SignatureGenerationException.java @@ -2,6 +2,9 @@ import com.auth0.jwt.algorithms.Algorithm; +/** + * The exception that is thrown when signature is not able to be generated. + */ public class SignatureGenerationException extends JWTCreationException { public SignatureGenerationException(Algorithm algorithm, Throwable cause) { super("The Token's Signature couldn't be generated when signing using the Algorithm: " + algorithm, cause); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/SignatureVerificationException.java b/lib/src/main/java/com/auth0/jwt/exceptions/SignatureVerificationException.java index 12bd3429..fa7c3cab 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/SignatureVerificationException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/SignatureVerificationException.java @@ -2,6 +2,9 @@ import com.auth0.jwt.algorithms.Algorithm; +/** + * The exception that is thrown if the Signature verification fails. + */ public class SignatureVerificationException extends JWTVerificationException { public SignatureVerificationException(Algorithm algorithm) { this(algorithm, null); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java b/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java index f5d2e67a..66f28285 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java @@ -1,5 +1,8 @@ package com.auth0.jwt.exceptions; +/** + * The exception that is thrown if the token is expired. + */ public class TokenExpiredException extends JWTVerificationException { private static final long serialVersionUID = -7076928975713577708L; diff --git a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java index 0a782268..3746dcd2 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java +++ b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java @@ -24,8 +24,15 @@ class BasicHeader implements Header, Serializable { private final String keyId; private final Map tree; private final ObjectReader objectReader; - - BasicHeader(String algorithm, String type, String contentType, String keyId, Map tree, ObjectReader objectReader) { + + BasicHeader( + String algorithm, + String type, + String contentType, + String keyId, + Map tree, + ObjectReader objectReader + ) { this.algorithm = algorithm; this.type = type; this.contentType = contentType; diff --git a/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java index 4db20119..b1f8e6d3 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java @@ -32,12 +32,12 @@ public void serialize(T holder, JsonGenerator gen, SerializerProvider provider) /** * Writes the given entry to the JSON representation. Custom claim serialization handling can override this method - * to provide use-case specific serialization. Implementors who override this method must write the field name and the - * field value. + * to provide use-case specific serialization. Implementors who override this method must write + * the field name and the field value. * * @param entry The entry that corresponds to the JSON field to write * @param gen The {@code JsonGenerator} to use - * @throws IOException + * @throws IOException if there is either an underlying I/O problem or encoding issue at format layer */ protected void writeClaim(Map.Entry entry, JsonGenerator gen) throws IOException { gen.writeFieldName(entry.getKey()); diff --git a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java index 6bba2caa..8a6cbcf8 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java @@ -13,10 +13,10 @@ /** * Jackson deserializer implementation for converting from JWT Header parts. - * - * @see JWTParser *

* This class is thread-safe. + * + * @see JWTParser */ class HeaderDeserializer extends StdDeserializer { diff --git a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java index 84a8b867..fe1600bd 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java @@ -9,9 +9,12 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; - import java.io.IOException; +/** + * This class helps in decoding the Header and Payload of the JWT using + * {@link HeaderSerializer} and {@link PayloadSerializer}. + */ public class JWTParser implements JWTPartsParser { private final ObjectReader payloadReader; private final ObjectReader headerReader; diff --git a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java index 0d816128..54a09575 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -74,24 +74,24 @@ public Instant asInstant() { @Override @SuppressWarnings("unchecked") - public T[] asArray(Class tClazz) throws JWTDecodeException { + public T[] asArray(Class clazz) throws JWTDecodeException { if (!data.isArray()) { return null; } - T[] arr = (T[]) Array.newInstance(tClazz, data.size()); + T[] arr = (T[]) Array.newInstance(clazz, data.size()); for (int i = 0; i < data.size(); i++) { try { - arr[i] = objectReader.treeToValue(data.get(i), tClazz); + arr[i] = objectReader.treeToValue(data.get(i), clazz); } catch (JsonProcessingException e) { - throw new JWTDecodeException("Couldn't map the Claim's array contents to " + tClazz.getSimpleName(), e); + throw new JWTDecodeException("Couldn't map the Claim's array contents to " + clazz.getSimpleName(), e); } } return arr; } @Override - public List asList(Class tClazz) throws JWTDecodeException { + public List asList(Class clazz) throws JWTDecodeException { if (!data.isArray()) { return null; } @@ -99,9 +99,9 @@ public List asList(Class tClazz) throws JWTDecodeException { List list = new ArrayList<>(); for (int i = 0; i < data.size(); i++) { try { - list.add(objectReader.treeToValue(data.get(i), tClazz)); + list.add(objectReader.treeToValue(data.get(i), clazz)); } catch (JsonProcessingException e) { - throw new JWTDecodeException("Couldn't map the Claim's array contents to " + tClazz.getSimpleName(), e); + throw new JWTDecodeException("Couldn't map the Claim's array contents to " + clazz.getSimpleName(), e); } } return list; @@ -124,11 +124,11 @@ public Map asMap() throws JWTDecodeException { } @Override - public T as(Class tClazz) throws JWTDecodeException { + public T as(Class clazz) throws JWTDecodeException { try { - return objectReader.treeAsTokens(data).readValueAs(tClazz); + return objectReader.treeAsTokens(data).readValueAs(clazz); } catch (IOException e) { - throw new JWTDecodeException("Couldn't map the Claim value to " + tClazz.getSimpleName(), e); + throw new JWTDecodeException("Couldn't map the Claim value to " + clazz.getSimpleName(), e); } } diff --git a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java index 664328de..2975b51b 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java @@ -53,12 +53,12 @@ public Instant asInstant() { } @Override - public T[] asArray(Class tClazz) throws JWTDecodeException { + public T[] asArray(Class clazz) throws JWTDecodeException { return null; } @Override - public List asList(Class tClazz) throws JWTDecodeException { + public List asList(Class clazz) throws JWTDecodeException { return null; } @@ -68,7 +68,7 @@ public Map asMap() throws JWTDecodeException { } @Override - public T as(Class tClazz) throws JWTDecodeException { + public T as(Class clazz) throws JWTDecodeException { return null; } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 46bd7ebe..09009267 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -16,10 +16,10 @@ /** * Jackson deserializer implementation for converting from JWT Payload parts. - * - * @see JWTParser *

* This class is thread-safe. + * + * @see JWTParser */ class PayloadDeserializer extends StdDeserializer { @@ -80,7 +80,8 @@ Instant getInstantFromSeconds(Map tree, String claimName) { return null; } if (!node.canConvertToLong()) { - throw new JWTDecodeException(String.format("The claim '%s' contained a non-numeric date value.", claimName)); + throw new JWTDecodeException( + String.format("The claim '%s' contained a non-numeric date value.", claimName)); } return Instant.ofEpochSecond(node.asLong()); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java index a446741c..75e79474 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java @@ -13,10 +13,10 @@ /** * Decoder of string JSON Web Tokens into their POJO representations. - * - * @see Payload *

* This class is thread-safe. + * + * @see Payload */ class PayloadImpl implements Payload, Serializable { @@ -32,7 +32,17 @@ class PayloadImpl implements Payload, Serializable { private final Map tree; private final ObjectReader objectReader; - PayloadImpl(String issuer, String subject, List audience, Instant expiresAt, Instant notBefore, Instant issuedAt, String jwtId, Map tree, ObjectReader objectReader) { + PayloadImpl( + String issuer, + String subject, + List audience, + Instant expiresAt, + Instant notBefore, + Instant issuedAt, + String jwtId, + Map tree, + ObjectReader objectReader + ) { this.issuer = issuer; this.subject = subject; this.audience = audience != null ? Collections.unmodifiableList(audience) : null; diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index 497a3a94..dc138c08 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -10,10 +10,10 @@ /** * Jackson serializer implementation for converting into JWT Payload parts. - * - * @see com.auth0.jwt.JWTCreator *

* This class is thread-safe. + * + * @see com.auth0.jwt.JWTCreator */ public class PayloadSerializer extends ClaimsSerializer { public PayloadSerializer() { @@ -47,7 +47,7 @@ private void writeAudience(JsonGenerator gen, Map.Entry e) throw List audList = (List) e.getValue(); for (Object aud : audList) { if (aud instanceof String) { - audArray.add((String)aud); + audArray.add((String) aud); } } } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java b/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java index cc2b3db8..ea166b0d 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java @@ -1,6 +1,8 @@ package com.auth0.jwt.impl; - +/** + * Contains the claim name for all Public claims. + */ public interface PublicClaims { //Header diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index cc5256f4..fdfac715 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -81,23 +81,24 @@ default Instant asInstant() { /** * Get this Claim as an Array of type T. * If the value isn't an Array, null will be returned. + * * @param type - * @param tClazz the type class + * @param clazz the type class * @return the value as an Array or null. * @throws JWTDecodeException if the values inside the Array can't be converted to a class T. */ - T[] asArray(Class tClazz) throws JWTDecodeException; + T[] asArray(Class clazz) throws JWTDecodeException; /** * Get this Claim as a List of type T. * If the value isn't an Array, null will be returned. * * @param type - * @param tClazz the type class + * @param clazz the type class * @return the value as a List or null. * @throws JWTDecodeException if the values inside the List can't be converted to a class T. */ - List asList(Class tClazz) throws JWTDecodeException; + List asList(Class clazz) throws JWTDecodeException; /** * Get this Claim as a generic Map of values. @@ -111,9 +112,9 @@ default Instant asInstant() { * Get this Claim as a custom type T. * * @param type - * @param tClazz the type class + * @param clazz the type class * @return the value as instance of T. * @throws JWTDecodeException if the value can't be converted to a class T. */ - T as(Class tClazz) throws JWTDecodeException; + T as(Class clazz) throws JWTDecodeException; } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java index 520e35c8..e1c6efcc 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java @@ -3,7 +3,8 @@ import com.auth0.jwt.exceptions.JWTDecodeException; /** - * The JWTPartsParser class defines which parts of the JWT should be converted to it's specific Object representation instance. + * The JWTPartsParser class defines which parts of the JWT should be converted + * to it's specific Object representation instance. */ public interface JWTPartsParser { diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java index a335a83e..3555878e 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java @@ -3,10 +3,13 @@ import com.auth0.jwt.exceptions.JWTVerificationException; +/** + * Used to verify the JWT for it's signature and claims. + */ public interface JWTVerifier { /** - * Performs the verification against the given Token + * Performs the verification against the given Token. * * @param token to verify. * @return a verified and decoded JWT. @@ -15,7 +18,7 @@ public interface JWTVerifier { DecodedJWT verify(String token) throws JWTVerificationException; /** - * Performs the verification against the given decoded JWT + * Performs the verification against the given decoded JWT. * * @param jwt to verify. * @return a verified and decoded JWT. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java index fa3b13c9..fd8baea5 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java @@ -14,7 +14,8 @@ interface KeyProvider { /** * Getter for the Public Key instance with the given Id. Used to verify the signature on the JWT verification stage. * - * @param keyId the Key Id specified in the Token's Header or null if none is available. Provides a hint on which Public Key to use to verify the token's signature. + * @param keyId the Key Id specified in the Token's Header or null if none is available. + * Provides a hint on which Public Key to use to verify the token's signature. * @return the Public Key instance */ U getPublicKeyById(String keyId); @@ -27,7 +28,8 @@ interface KeyProvider { R getPrivateKey(); /** - * Getter for the Id of the Private Key used to sign the tokens. This represents the `kid` claim and will be placed in the Header. + * Getter for the Id of the Private Key used to sign the tokens. + * This represents the `kid` claim and will be placed in the Header. * * @return the Key Id that identifies the Private Key or null if it's not specified. */ diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 1fb6aece..071b3e9c 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -60,7 +60,7 @@ default Verification withIssuer(String issuer) { * will have to provide their own implementation. * * The default implementation throws an {@linkplain UnsupportedOperationException}. - * + * * @param audience the required Audience value for which the "aud" claim must contain at least one value. * @return this same Verification instance. */ @@ -69,8 +69,8 @@ default Verification withAnyOfAudience(String... audience) { } /** - * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. - * Setting a specific leeway value on a given Claim will override this value for that Claim. + * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims + * will still be valid. Setting a specific leeway value on a given Claim will override this value for that Claim. * * @param leeway the window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. * @return this same Verification instance. @@ -80,7 +80,8 @@ default Verification withAnyOfAudience(String... audience) { /** * Set a specific leeway window in seconds in which the Expires At ("exp") Claim will still be valid. - * Expiration Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * Expiration Date is always verified when the value is present. + * This method overrides the value set with acceptLeeway * * @param leeway the window in seconds in which the Expires At Claim will still be valid. * @return this same Verification instance. @@ -90,7 +91,8 @@ default Verification withAnyOfAudience(String... audience) { /** * Set a specific leeway window in seconds in which the Not Before ("nbf") Claim will still be valid. - * Not Before Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * Not Before Date is always verified when the value is present. + * This method overrides the value set with acceptLeeway * * @param leeway the window in seconds in which the Not Before Claim will still be valid. * @return this same Verification instance. @@ -101,8 +103,10 @@ default Verification withAnyOfAudience(String... audience) { /** * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. * This method overrides the value set with {@link #acceptLeeway(long)}. - * By default, the Issued At claim is always verified when the value is present, unless disabled with {@link #ignoreIssuedAt()}. - * If Issued At verification has been disabled, no verification of the Issued At claim will be performed, and this method has no effect. + * By default, the Issued At claim is always verified when the value is present, + * unless disabled with {@link #ignoreIssuedAt()}. + * If Issued At verification has been disabled, no verification of the Issued At claim will be performed, + * and this method has no effect. * * @param leeway the window in seconds in which the Issued At Claim will still be valid. * @return this same Verification instance. @@ -120,6 +124,7 @@ default Verification withAnyOfAudience(String... audience) { /** * Require a claim to be present, with any value. + * * @param name the Claim's name. * @return this same Verification instance * @throws IllegalArgumentException if the name is null. @@ -177,8 +182,8 @@ default Verification withAnyOfAudience(String... audience) { Verification withClaim(String name, String value) throws IllegalArgumentException; /** - * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; when verifying - * date-time claim value, any time units more granular than seconds will not be considered. + * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; + * when verifying date-time claim value, any time units more granular than seconds will not be considered. * * @param name the Claim's name. * @param value the Claim's value. @@ -188,8 +193,8 @@ default Verification withAnyOfAudience(String... audience) { Verification withClaim(String name, Date value) throws IllegalArgumentException; /** - * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; when verifying - * a date-time claim value, any time units more granular than seconds will not be considered. + * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; + * when verifying a date-time claim value, any time units more granular than seconds will not be considered. * * @param name the Claim's name. * @param value the Claim's value. diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java index 120f6a9d..77d573bc 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java @@ -71,12 +71,12 @@ public Date asDate() { } @Override - public T[] asArray(Class tClazz) throws JWTDecodeException { + public T[] asArray(Class clazz) throws JWTDecodeException { return null; } @Override - public List asList(Class tClazz) throws JWTDecodeException { + public List asList(Class clazz) throws JWTDecodeException { return null; } From 0029a6363f89c1af43db64a76a002e8169c2656c Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 25 Mar 2022 16:25:50 +0530 Subject: [PATCH 080/198] [SDK-3155] Predicate based Claim verification (#562) * Rename claims -> expectedClaims in verifier for clarity * added method to validate claim value using predicate * Using Predicates for Verification (#560) * Use predicates for verification * Code review changes * Add additional tests for predicate based verification * Fixed Lint issues --- .../main/java/com/auth0/jwt/JWTVerifier.java | 366 ++++++++---------- .../auth0/jwt/interfaces/Verification.java | 12 + .../java/com/auth0/jwt/JWTVerifierTest.java | 207 +++++----- .../jwt/interfaces/VerificationTest.java | 6 + 4 files changed, 283 insertions(+), 308 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 77dfef4e..8b14ff86 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -14,6 +14,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.*; +import java.util.function.BiPredicate; /** * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, @@ -25,17 +26,12 @@ */ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { private final Algorithm algorithm; - final Map claims; - private final Clock clock; + final Map> expectedChecks; private final JWTParser parser; - static final String AUDIENCE_EXACT = "AUDIENCE_EXACT"; - static final String AUDIENCE_CONTAINS = "AUDIENCE_CONTAINS"; - - JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { + JWTVerifier(Algorithm algorithm, Map> expectedChecks) { this.algorithm = algorithm; - this.claims = Collections.unmodifiableMap(claims); - this.clock = clock; + this.expectedChecks = Collections.unmodifiableMap(expectedChecks); this.parser = new JWTParser(); } @@ -55,9 +51,11 @@ static Verification init(Algorithm algorithm) throws IllegalArgumentException { */ public static class BaseVerification implements Verification { private final Algorithm algorithm; - private final Map claims; + private final Map> expectedChecks; private long defaultLeeway; + private final Map customLeeways; private boolean ignoreIssuedAt; + private Clock clock; BaseVerification(Algorithm algorithm) throws IllegalArgumentException { if (algorithm == null) { @@ -65,33 +63,42 @@ public static class BaseVerification implements Verification { } this.algorithm = algorithm; - this.claims = new HashMap<>(); + this.expectedChecks = new LinkedHashMap<>(); + this.customLeeways = new HashMap<>(); this.defaultLeeway = 0; } @Override public Verification withIssuer(String... issuer) { - requireClaim(PublicClaims.ISSUER, isNullOrEmpty(issuer) ? null : Arrays.asList(issuer)); + List value = isNullOrEmpty(issuer) ? null : Arrays.asList(issuer); + checkIfNeedToRemove(PublicClaims.ISSUER, value, ((claim, decodedJWT) -> { + if (value == null || !value.contains(claim.asString())) { + throw new InvalidClaimException("The Claim 'iss' value doesn't match the required issuer."); + } + return true; + })); return this; } @Override public Verification withSubject(String subject) { - requireClaim(PublicClaims.SUBJECT, subject); + checkIfNeedToRemove(PublicClaims.SUBJECT, subject, (claim, decodedJWT) -> subject.equals(claim.asString())); return this; } @Override public Verification withAudience(String... audience) { - claims.remove(AUDIENCE_CONTAINS); - requireClaim(AUDIENCE_EXACT, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); + List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); + checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> + assertValidAudienceClaim(decodedJWT.getAudience(), value, true))); return this; } @Override public Verification withAnyOfAudience(String... audience) { - claims.remove(AUDIENCE_EXACT); - requireClaim(AUDIENCE_CONTAINS, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); + List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); + checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> + assertValidAudienceClaim(decodedJWT.getAudience(), value, false))); return this; } @@ -105,21 +112,21 @@ public Verification acceptLeeway(long leeway) throws IllegalArgumentException { @Override public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - requireClaim(PublicClaims.EXPIRES_AT, leeway); + customLeeways.put(PublicClaims.EXPIRES_AT, leeway); return this; } @Override public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { assertPositive(leeway); - requireClaim(PublicClaims.NOT_BEFORE, leeway); + customLeeways.put(PublicClaims.NOT_BEFORE, leeway); return this; } @Override public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - requireClaim(PublicClaims.ISSUED_AT, leeway); + customLeeways.put(PublicClaims.ISSUED_AT, leeway); return this; } @@ -131,49 +138,54 @@ public Verification ignoreIssuedAt() { @Override public Verification withJWTId(String jwtId) { - requireClaim(PublicClaims.JWT_ID, jwtId); + checkIfNeedToRemove(PublicClaims.JWT_ID, jwtId, ((claim, decodedJWT) -> jwtId.equals(claim.asString()))); return this; } @Override public Verification withClaimPresence(String name) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, NonEmptyClaim.getInstance()); + withClaim(name, ((claim, decodedJWT) -> { + if (claim instanceof NullClaim) { + throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", name)); + } + return true; + })); return this; } @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, value); + checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asBoolean()))); return this; } @Override public Verification withClaim(String name, Integer value) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, value); + checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asInt()))); return this; } @Override public Verification withClaim(String name, Long value) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, value); + checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asLong()))); return this; } @Override public Verification withClaim(String name, Double value) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, value); + checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asDouble()))); return this; } @Override public Verification withClaim(String name, String value) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, value); + checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asString()))); return this; } @@ -187,28 +199,37 @@ public Verification withClaim(String name, Instant value) throws IllegalArgument assertNonNull(name); // Since date-time claims are serialized as epoch seconds, // we need to compare them with only seconds-granularity - requireClaim(name, value != null ? value.truncatedTo(ChronoUnit.SECONDS) : null); + checkIfNeedToRemove(name, value, + ((claim, decodedJWT) -> value.truncatedTo(ChronoUnit.SECONDS).equals(claim.asInstant()))); + return this; + } + + @Override + public Verification withClaim(String name, BiPredicate predicate) + throws IllegalArgumentException { + assertNonNull(name); + checkIfNeedToRemove(name, predicate, predicate); return this; } @Override public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, items); + checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); return this; } @Override public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, items); + checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); return this; } @Override public Verification withArrayClaim(String name, Long... items) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, items); + checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); return this; } @@ -225,59 +246,122 @@ public JWTVerifier build() { * @return a new JWTVerifier instance with a custom {@link java.time.Clock} */ public JWTVerifier build(Clock clock) { - addLeewayToDateClaims(); - return new JWTVerifier(algorithm, claims, clock); + this.clock = clock; + addMandatoryClaimChecks(); + return new JWTVerifier(algorithm, expectedChecks); } - private void assertPositive(long leeway) { - if (leeway < 0) { - throw new IllegalArgumentException("Leeway value can't be negative."); + /** + * Fetches the Leeway set for claim or returns the {@link BaseVerification#defaultLeeway}. + * + * @param name Claim for which leeway is fetched + * @return Leeway value set for the claim + */ + public long getLeewayFor(String name) { + return customLeeways.getOrDefault(name, defaultLeeway); + } + + private void addMandatoryClaimChecks() { + long expiresAtLeeway = getLeewayFor(PublicClaims.EXPIRES_AT); + long notBeforeLeeway = getLeewayFor(PublicClaims.NOT_BEFORE); + long issuedAtLeeway = getLeewayFor(PublicClaims.ISSUED_AT); + + checkIfNeedToRemove(PublicClaims.EXPIRES_AT, expiresAtLeeway, ((claim, decodedJWT) -> + assertValidInstantClaim(claim.asInstant(), expiresAtLeeway, true))); + checkIfNeedToRemove(PublicClaims.NOT_BEFORE, notBeforeLeeway, ((claim, decodedJWT) -> + assertValidInstantClaim(claim.asInstant(), notBeforeLeeway, false))); + if (!ignoreIssuedAt) { + checkIfNeedToRemove(PublicClaims.ISSUED_AT, issuedAtLeeway, ((claim, decodedJWT) -> + assertValidInstantClaim(claim.asInstant(), issuedAtLeeway, false))); } } - private void assertNonNull(String name) { - if (name == null) { - throw new IllegalArgumentException("The Custom Claim's name can't be null."); + private boolean assertValidCollectionClaim(Claim claim, Object[] expectedClaimValue) { + List claimArr; + Object[] claimAsObject = claim.as(Object[].class); + + // Jackson uses 'natural' mapping which uses Integer if value fits in 32 bits. + if (expectedClaimValue instanceof Long[]) { + // convert Integers to Longs for comparison with equals + claimArr = new ArrayList<>(claimAsObject.length); + for (Object cao : claimAsObject) { + if (cao instanceof Integer) { + claimArr.add(((Integer) cao).longValue()); + } else { + claimArr.add(cao); + } + } + } else { + claimArr = claim.isNull() ? Collections.emptyList() : Arrays.asList(claim.as(Object[].class)); } + List valueArr = Arrays.asList(expectedClaimValue); + return claimArr.containsAll(valueArr); } - private void addLeewayToDateClaims() { - if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { - claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); + private boolean assertValidInstantClaim(Instant claimVal, long leeway, boolean shouldBeFuture) { + Instant now = clock.instant().truncatedTo(ChronoUnit.SECONDS); + if (shouldBeFuture) { + return assertInstantIsFuture(claimVal, leeway, now); + } else { + return assertInstantIsPast(claimVal, leeway, now); } - if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { - claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); + } + + private boolean assertInstantIsFuture(Instant claimVal, long leeway, Instant now) { + if (claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)) { + throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal)); } - if (ignoreIssuedAt) { - claims.remove(PublicClaims.ISSUED_AT); - return; + return true; + } + + private boolean assertInstantIsPast(Instant claimVal, long leeway, Instant now) { + if (claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)) { + throw new InvalidClaimException(String.format("The Token can't be used before %s.", claimVal)); + } + return true; + } + + private boolean assertValidAudienceClaim(List audience, List values, boolean shouldContainAll) { + if (audience == null || (shouldContainAll && !audience.containsAll(values)) + || (!shouldContainAll && Collections.disjoint(audience, values))) { + throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); } - if (!claims.containsKey(PublicClaims.ISSUED_AT)) { - claims.put(PublicClaims.ISSUED_AT, defaultLeeway); + return true; + } + + private void assertPositive(long leeway) { + if (leeway < 0) { + throw new IllegalArgumentException("Leeway value can't be negative."); + } + } + + private void assertNonNull(String name) { + if (name == null) { + throw new IllegalArgumentException("The Custom Claim's name can't be null."); } } - private void requireClaim(String name, Object value) { + private void checkIfNeedToRemove(String name, Object value, BiPredicate predicate) { if (value == null) { - claims.remove(name); + expectedChecks.remove(name); return; } - claims.put(name, value); + expectedChecks.put(name, predicate); } - } - private static boolean isNullOrEmpty(String[] args) { - if (args == null || args.length == 0) { - return true; - } - boolean isAllNull = true; - for (String arg : args) { - if (arg != null) { - isAllNull = false; - break; + private boolean isNullOrEmpty(String[] args) { + if (args == null || args.length == 0) { + return true; + } + boolean isAllNull = true; + for (String arg : args) { + if (arg != null) { + isAllNull = false; + break; + } } + return isAllNull; } - return isAllNull; } @@ -313,7 +397,7 @@ public DecodedJWT verify(String token) throws JWTVerificationException { public DecodedJWT verify(DecodedJWT jwt) throws JWTVerificationException { verifyAlgorithm(jwt, algorithm); algorithm.verify(jwt); - verifyClaims(jwt, claims); + verifyClaims(jwt, expectedChecks); return jwt; } @@ -324,156 +408,20 @@ private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws } } - private void verifyClaims(DecodedJWT jwt, Map claims) + private void verifyClaims(DecodedJWT jwt, Map> claims) throws TokenExpiredException, InvalidClaimException { - for (Map.Entry entry : claims.entrySet()) { - if (entry.getValue() instanceof NonEmptyClaim) { - assertClaimPresent(jwt.getClaim(entry.getKey()), entry.getKey()); - } else { - verifyClaimValues(jwt, entry); - } - } - } - - private void verifyClaimValues(DecodedJWT jwt, Map.Entry expectedClaim) { - switch (expectedClaim.getKey()) { - // We use custom keys for audience in the expected claims to differentiate between - // validating that the audience contains all expected values, or validating that the audience contains - // at least one of the expected values. - case AUDIENCE_EXACT: - assertValidAudienceClaim(jwt.getAudience(), (List) expectedClaim.getValue(), true); - break; - case AUDIENCE_CONTAINS: - assertValidAudienceClaim(jwt.getAudience(), (List) expectedClaim.getValue(), false); - break; - case PublicClaims.EXPIRES_AT: - assertValidInstantClaim(jwt.getExpiresAtAsInstant(), (Long) expectedClaim.getValue(), true); - break; - case PublicClaims.ISSUED_AT: - assertValidInstantClaim(jwt.getIssuedAtAsInstant(), (Long) expectedClaim.getValue(), false); - break; - case PublicClaims.NOT_BEFORE: - assertValidInstantClaim(jwt.getNotBeforeAsInstant(), (Long) expectedClaim.getValue(), false); - break; - case PublicClaims.ISSUER: - assertValidIssuerClaim(jwt.getIssuer(), (List) expectedClaim.getValue()); - break; - case PublicClaims.JWT_ID: - assertValidStringClaim(expectedClaim.getKey(), jwt.getId(), (String) expectedClaim.getValue()); - break; - case PublicClaims.SUBJECT: - assertValidStringClaim(expectedClaim.getKey(), jwt.getSubject(), (String) expectedClaim.getValue()); - break; - default: - assertValidClaim(jwt.getClaim(expectedClaim.getKey()), expectedClaim.getKey(), - expectedClaim.getValue()); - break; - } - } - - private void assertClaimPresent(Claim claim, String claimName) { - if (claim instanceof NullClaim) { - throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", claimName)); - } - } - - private void assertValidClaim(Claim claim, String claimName, Object value) { - boolean isValid = false; - if (value instanceof String) { - isValid = value.equals(claim.asString()); - } else if (value instanceof Integer) { - isValid = value.equals(claim.asInt()); - } else if (value instanceof Long) { - isValid = value.equals(claim.asLong()); - } else if (value instanceof Boolean) { - isValid = value.equals(claim.asBoolean()); - } else if (value instanceof Double) { - isValid = value.equals(claim.asDouble()); - } else if (value instanceof Instant) { - isValid = value.equals(claim.asInstant()); - } else if (value instanceof Object[]) { - List claimArr; - Object[] claimAsObject = claim.as(Object[].class); - - // Jackson uses 'natural' mapping which uses Integer if value fits in 32 bits. - if (value instanceof Long[]) { - // convert Integers to Longs for comparison with equals - claimArr = new ArrayList<>(claimAsObject.length); - for (Object cao : claimAsObject) { - if (cao instanceof Integer) { - claimArr.add(((Integer) cao).longValue()); - } else { - claimArr.add(cao); - } - } - } else { - claimArr = claim.isNull() ? Collections.emptyList() : Arrays.asList(claim.as(Object[].class)); - } - List valueArr = Arrays.asList((Object[]) value); - isValid = claimArr.containsAll(valueArr); - } - - if (!isValid) { - throw new InvalidClaimException( - String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void assertValidStringClaim(String claimName, String value, String expectedValue) { - if (!expectedValue.equals(value)) { - throw new InvalidClaimException( - String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void assertValidInstantClaim(Instant claimVal, long leeway, boolean shouldBeFuture) { - Instant now = clock.instant().truncatedTo(ChronoUnit.SECONDS); - if (shouldBeFuture) { - assertInstantIsFuture(claimVal, leeway, now); - } else { - assertInstantIsPast(claimVal, leeway, now); - } - } - - private void assertInstantIsFuture(Instant claimVal, long leeway, Instant now) { - if (claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)) { - throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal)); - } - } - - private void assertInstantIsPast(Instant claimVal, long leeway, Instant now) { - if (claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)) { - throw new InvalidClaimException(String.format("The Token can't be used before %s.", claimVal)); - } - } - - private void assertValidAudienceClaim(List audience, List values, boolean shouldContainAll) { - if (audience == null || (shouldContainAll && !audience.containsAll(values)) - || (!shouldContainAll && Collections.disjoint(audience, values))) { - throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); - } - } + for (Map.Entry> entry : claims.entrySet()) { + boolean isValid; + String claimName = entry.getKey(); + BiPredicate expectedCheck = entry.getValue(); + Claim claim = jwt.getClaim(claimName); - private void assertValidIssuerClaim(String issuer, List value) { - if (issuer == null || !value.contains(issuer)) { - throw new InvalidClaimException("The Claim 'iss' value doesn't match the required issuer."); - } - } - - /** - * Simple singleton used to mark that a claim should only be verified for presence. - */ - private static class NonEmptyClaim { - private static NonEmptyClaim nonEmptyClaim; - - private NonEmptyClaim() { - } + isValid = expectedCheck.test(claim, jwt); - public static NonEmptyClaim getInstance() { - if (nonEmptyClaim == null) { - nonEmptyClaim = new NonEmptyClaim(); + if (!isValid) { + throw new InvalidClaimException( + String.format("The Claim '%s' value doesn't match the required one.", claimName)); } - return nonEmptyClaim; } } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 071b3e9c..ffe0b920 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -4,6 +4,7 @@ import java.time.Instant; import java.util.Date; +import java.util.function.BiPredicate; /** * Holds the Claims and claim-based configurations required for a JWT to be considered valid. @@ -205,6 +206,17 @@ default Verification withClaim(String name, Instant value) throws IllegalArgumen return withClaim(name, value != null ? Date.from(value) : null); } + /** + * Executes the predicate provided during the verification + * and passes the verification if the predicate returns true. + * + * @param name the Claim's name + * @param predicate the predicate check to be done. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + Verification withClaim(String name, BiPredicate predicate) throws IllegalArgumentException; + /** * Require a specific Array Claim to contain at least the given items. * diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 93a7bcd1..1d80d802 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -4,6 +4,8 @@ import com.auth0.jwt.exceptions.AlgorithmMismatchException; import com.auth0.jwt.exceptions.InvalidClaimException; import com.auth0.jwt.exceptions.TokenExpiredException; +import com.auth0.jwt.impl.PublicClaims; +import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; import org.junit.Rule; @@ -16,11 +18,11 @@ import java.time.ZoneId; import java.util.Collections; import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import java.util.function.BiPredicate; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.mockito.ArgumentMatchers.isNotNull; import static org.mockito.Mockito.mock; public class JWTVerifierTest { @@ -263,63 +265,29 @@ public void shouldRemoveAudienceWhenPassingNullReference() { .withAudience((String) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); verifier = JWTVerifier.init(algorithm) .withAudience((String[]) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); verifier = JWTVerifier.init(algorithm) .withAudience() .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); String emptyAud = " "; verifier = JWTVerifier.init(algorithm) .withAudience(emptyAud) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry(JWTVerifier.AUDIENCE_EXACT, Collections.singletonList(emptyAud))); - } - - @Test - public void shouldRemoveAudienceWhenPassingNullReferenceWithAnyOfAudience() { - Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience((String) null) - .build(); - - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); - - verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience((String[]) null) - .build(); - - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); - - verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience() - .build(); - - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); - - String emptyAud = " "; - verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience(emptyAud) - .build(); - - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry(JWTVerifier.AUDIENCE_CONTAINS, Collections.singletonList(emptyAud))); + assertThat(verifier.expectedChecks, is(notNullValue())); } @Test @@ -330,16 +298,16 @@ public void shouldRemoveAudienceWhenPassingNull() { .withAudience((String) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("aud"))); verifier = JWTVerifier.init(algorithm) .withAudience("John") .withAudience((String[]) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("aud"))); } @Test @@ -350,16 +318,16 @@ public void shouldRemoveAudienceWhenPassingNullWithAnyAudience() { .withAnyOfAudience((String) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("aud"))); verifier = JWTVerifier.init(algorithm) .withAnyOfAudience("John") .withAnyOfAudience((String[]) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("aud"))); } @Test @@ -453,10 +421,10 @@ public void shouldThrowOnInvalidCustomClaimValue() { exception.expect(InvalidClaimException.class); exception.expectMessage("The Claim 'name' value doesn't match the required one."); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - Map map = new HashMap<>(); - map.put("name", new Object()); - JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, Clock.systemUTC()); - verifier.verify(token); + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", "check") + .build() + .verify(token); } @Test @@ -533,8 +501,8 @@ public void shouldRemoveCustomClaimOfTypeDateWhenNull() { .withClaim("name", (Date) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); } @Test @@ -593,76 +561,76 @@ public void shouldValidateCustomArrayClaimOfTypeLongWhenValueIsIntegerAndLong() } // Generic Delta - @SuppressWarnings("RedundantCast") @Test public void shouldAddDefaultLeewayToDateClaims() { Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm); + JWTVerifier verifier = verification .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iat", (Object) 0L)); - assertThat(verifier.claims, hasEntry("exp", (Object) 0L)); - assertThat(verifier.claims, hasEntry("nbf", (Object) 0L)); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(0L)); + assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(0L)); + assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(0L)); } - @SuppressWarnings("RedundantCast") @Test public void shouldAddCustomLeewayToDateClaims() { Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm); + JWTVerifier verifier = verification .acceptLeeway(1234L) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iat", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("exp", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("nbf", (Object) 1234L)); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); } - @SuppressWarnings("RedundantCast") @Test public void shouldOverrideDefaultIssuedAtLeeway() { Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm); + JWTVerifier verifier = verification .acceptLeeway(1234L) .acceptIssuedAt(9999L) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iat", (Object) 9999L)); - assertThat(verifier.claims, hasEntry("exp", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("nbf", (Object) 1234L)); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(9999L)); + assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); } - @SuppressWarnings("RedundantCast") @Test public void shouldOverrideDefaultExpiresAtLeeway() { Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm); + JWTVerifier verifier = verification .acceptLeeway(1234L) .acceptExpiresAt(9999L) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iat", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("exp", (Object) 9999L)); - assertThat(verifier.claims, hasEntry("nbf", (Object) 1234L)); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(9999L)); + assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); } - @SuppressWarnings("RedundantCast") @Test public void shouldOverrideDefaultNotBeforeLeeway() { Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm); + JWTVerifier verifier = verification .acceptLeeway(1234L) .acceptNotBefore(9999L) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iat", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("exp", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("nbf", (Object) 9999L)); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(9999L)); } @Test @@ -860,16 +828,16 @@ public void shouldRemoveClaimWhenPassingNull() { .withIssuer((String) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); verifier = JWTVerifier.init(algorithm) .withIssuer("iss") .withIssuer((String[]) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); } @Test @@ -879,30 +847,29 @@ public void shouldRemoveIssuerWhenPassingNullReference() { .withIssuer((String) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); verifier = JWTVerifier.init(algorithm) .withIssuer((String[]) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); verifier = JWTVerifier.init(algorithm) .withIssuer() .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); String emptyIss = " "; verifier = JWTVerifier.init(algorithm) .withIssuer(emptyIss) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iss", Collections.singletonList(emptyIss))); + assertThat(verifier.expectedChecks, is(notNullValue())); } @Test @@ -1055,4 +1022,46 @@ public void shouldVerifyStandardClaimPresence() { DecodedJWT decodedJWT = verifier.verify(jwt); assertThat(decodedJWT, is(notNullValue())); } + + @Test + public void shouldSuccessfullyVerifyClaimWithPredicate() { + String jwt = JWTCreator.init() + .withClaim("claimName", "claimValue") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("claimName", (claim, decodedJWT) -> "claimValue".equals(claim.asString())) + .withClaim(PublicClaims.ISSUED_AT, ((claim, decodedJWT) -> false)) + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldThrowWhenPredicateReturnsFalse() { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'claimName' value doesn't match the required one."); + + String jwt = JWTCreator.init() + .withClaim("claimName", "claimValue") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString())) + .build() + .verify(jwt); + } + + @Test + public void shouldRemovePredicateCheckForNull() { + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString())) + .withClaim("claimName", (BiPredicate) null) + .build(); + + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("claimName"))); + } + } diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java index e9f4f815..c7f89763 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java @@ -7,6 +7,7 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.function.BiPredicate; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasEntry; @@ -149,6 +150,11 @@ public Verification withArrayClaim(String name, Long... items) throws IllegalArg return null; } + @Override + public Verification withClaim(String name, BiPredicate predicate) throws IllegalArgumentException { + return null; + } + @Override public Verification ignoreIssuedAt() { return null; From e37301aad52681174bcd2dc39844ab56408e7b8a Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 25 Mar 2022 16:38:56 +0530 Subject: [PATCH 081/198] [SDK-3158] Null claim handling (#564) * Handle claim difference between missing and null * Verification with null claims * JWT creation support for null values * Test cases for JWT verification and construction * Add JWT decode test cases * Fix broken tests * Fixed Lint issues * Fixed formatting errors * Add test case to check Claim toString conversion --- .../main/java/com/auth0/jwt/JWTCreator.java | 45 ++++-- .../main/java/com/auth0/jwt/JWTVerifier.java | 13 +- .../com/auth0/jwt/impl/JsonNodeClaim.java | 39 +++-- .../java/com/auth0/jwt/impl/NullClaim.java | 79 --------- .../java/com/auth0/jwt/interfaces/Claim.java | 10 ++ .../auth0/jwt/interfaces/Verification.java | 9 ++ .../java/com/auth0/jwt/JWTCreatorTest.java | 152 +++++++++--------- .../java/com/auth0/jwt/JWTDecoderTest.java | 21 ++- .../java/com/auth0/jwt/JWTVerifierTest.java | 43 +++++ .../com/auth0/jwt/impl/BasicHeaderTest.java | 3 +- .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 45 +++++- .../com/auth0/jwt/impl/NullClaimTest.java | 82 ---------- .../com/auth0/jwt/impl/PayloadImplTest.java | 3 +- .../com/auth0/jwt/interfaces/ClaimTest.java | 5 + .../jwt/interfaces/VerificationTest.java | 5 + 15 files changed, 273 insertions(+), 281 deletions(-) delete mode 100644 lib/src/main/java/com/auth0/jwt/impl/NullClaim.java delete mode 100644 lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 6b355b36..e1159ae4 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -75,7 +75,6 @@ public static class Builder { /** * Add specific Claims to set as the Header. * If provided map is null then nothing is changed - * If provided map contains a claim with null value then that claim will be removed from the header * * @param headerClaims the values to use as Claims in the token's Header. * @return this same Builder instance. @@ -362,7 +361,6 @@ public Builder withClaim(String name, Map map) throws IllegalArgument * @return this same Builder instance. * @throws IllegalArgumentException if the name is null, or if the list contents does not validate. */ - public Builder withClaim(String name, List list) throws IllegalArgumentException { assertNonNull(name); // validate list contents @@ -374,6 +372,19 @@ public Builder withClaim(String name, List list) throws IllegalArgumentExcept return this; } + /** + * Add a custom claim with null value. + * + * @param name the Claim's name. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null + */ + public Builder withNullClaim(String name) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, null); + return this; + } + /** * Add a custom Array Claim with the given items. * @@ -422,8 +433,8 @@ public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentE *

* Accepted types are {@linkplain Map} and {@linkplain List} with basic types * {@linkplain Boolean}, {@linkplain Integer}, {@linkplain Long}, {@linkplain Double}, - * {@linkplain String} and {@linkplain Date}. {@linkplain Map}s cannot contain null keys or values. - * {@linkplain List}s can contain null elements. + * {@linkplain String} and {@linkplain Date}. + * {@linkplain Map}s and {@linkplain List}s can contain null elements. *

* *

@@ -442,7 +453,7 @@ public Builder withPayload(Map payloadClaims) throws IllegalArgumentE if (!validatePayload(payloadClaims)) { throw new IllegalArgumentException("Claim values must only be of types Map, List, Boolean, Integer, " - + "Long, Double, String and Date"); + + "Long, Double, String, Date and Null"); } // add claims only after validating all claims so as not to corrupt the claims map of this builder @@ -463,7 +474,7 @@ private boolean validatePayload(Map payload) { return false; } else if (value instanceof Map && !validateClaim((Map) value)) { return false; - } else if (value != null && !isSupportedType(value)) { + } else if (!isSupportedType(value)) { return false; } } @@ -474,7 +485,7 @@ private static boolean validateClaim(Map map) { // do not accept null values in maps for (Entry entry : map.entrySet()) { Object value = entry.getValue(); - if (value == null || !isSupportedType(value)) { + if (!isSupportedType(value)) { return false; } @@ -488,7 +499,7 @@ private static boolean validateClaim(Map map) { private static boolean validateClaim(List list) { // accept null values in list for (Object object : list) { - if (object != null && !isSupportedType(object)) { + if (!isSupportedType(object)) { return false; } } @@ -506,13 +517,17 @@ private static boolean isSupportedType(Object value) { } private static boolean isBasicType(Object value) { - Class c = value.getClass(); + if (value == null) { + return true; + } else { + Class c = value.getClass(); - if (c.isArray()) { - return c == Integer[].class || c == Long[].class || c == String[].class; + if (c.isArray()) { + return c == Integer[].class || c == Long[].class || c == String[].class; + } + return c == String.class || c == Integer.class || c == Long.class || c == Double.class + || c == Date.class || c == Instant.class || c == Boolean.class; } - return c == String.class || c == Integer.class || c == Long.class || c == Double.class - || c == Date.class || c == Instant.class || c == Boolean.class; } /** @@ -546,10 +561,6 @@ private void assertNonNull(String name) { } private void addClaim(String name, Object value) { - if (value == null) { - payloadClaims.remove(name); - return; - } payloadClaims.put(name, value); } } diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 8b14ff86..947c9e25 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -3,7 +3,6 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.*; import com.auth0.jwt.impl.JWTParser; -import com.auth0.jwt.impl.NullClaim; import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; @@ -146,7 +145,7 @@ public Verification withJWTId(String jwtId) { public Verification withClaimPresence(String name) throws IllegalArgumentException { assertNonNull(name); withClaim(name, ((claim, decodedJWT) -> { - if (claim instanceof NullClaim) { + if (claim.isMissing()) { throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", name)); } return true; @@ -154,6 +153,13 @@ public Verification withClaimPresence(String name) throws IllegalArgumentExcepti return this; } + @Override + public Verification withNullClaim(String name) throws IllegalArgumentException { + assertNonNull(name); + withClaim(name, ((claim, decodedJWT) -> claim.isNull())); + return this; + } + @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { assertNonNull(name); @@ -292,7 +298,8 @@ private boolean assertValidCollectionClaim(Claim claim, Object[] expectedClaimVa } } } else { - claimArr = claim.isNull() ? Collections.emptyList() : Arrays.asList(claim.as(Object[].class)); + claimArr = claim.isNull() || claim.isMissing() + ? Collections.emptyList() : Arrays.asList(claim.as(Object[].class)); } List valueArr = Arrays.asList(expectedClaimValue); return claimArr.containsAll(valueArr); diff --git a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java index 54a09575..5bb3dbbc 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -31,32 +31,32 @@ private JsonNodeClaim(JsonNode node, ObjectReader objectReader) { @Override public Boolean asBoolean() { - return !data.isBoolean() ? null : data.asBoolean(); + return isMissing() || isNull() || !data.isBoolean() ? null : data.asBoolean(); } @Override public Integer asInt() { - return !data.isNumber() ? null : data.asInt(); + return isMissing() || isNull() || !data.isNumber() ? null : data.asInt(); } @Override public Long asLong() { - return !data.isNumber() ? null : data.asLong(); + return isMissing() || isNull() || !data.isNumber() ? null : data.asLong(); } @Override public Double asDouble() { - return !data.isNumber() ? null : data.asDouble(); + return isMissing() || isNull() || !data.isNumber() ? null : data.asDouble(); } @Override public String asString() { - return !data.isTextual() ? null : data.asText(); + return isMissing() || isNull() || !data.isTextual() ? null : data.asText(); } @Override public Date asDate() { - if (!data.canConvertToLong()) { + if (isMissing() || isNull() || !data.canConvertToLong()) { return null; } long seconds = data.asLong(); @@ -65,7 +65,7 @@ public Date asDate() { @Override public Instant asInstant() { - if (!data.canConvertToLong()) { + if (isMissing() || isNull() || !data.canConvertToLong()) { return null; } long seconds = data.asLong(); @@ -75,7 +75,7 @@ public Instant asInstant() { @Override @SuppressWarnings("unchecked") public T[] asArray(Class clazz) throws JWTDecodeException { - if (!data.isArray()) { + if (isMissing() || isNull() || !data.isArray()) { return null; } @@ -92,7 +92,7 @@ public T[] asArray(Class clazz) throws JWTDecodeException { @Override public List asList(Class clazz) throws JWTDecodeException { - if (!data.isArray()) { + if (isMissing() || isNull() || !data.isArray()) { return null; } @@ -109,7 +109,7 @@ public List asList(Class clazz) throws JWTDecodeException { @Override public Map asMap() throws JWTDecodeException { - if (!data.isObject()) { + if (isMissing() || isNull() || !data.isObject()) { return null; } @@ -126,6 +126,9 @@ public Map asMap() throws JWTDecodeException { @Override public T as(Class clazz) throws JWTDecodeException { try { + if (isMissing() || isNull()) { + return null; + } return objectReader.treeAsTokens(data).readValueAs(clazz); } catch (IOException e) { throw new JWTDecodeException("Couldn't map the Claim value to " + clazz.getSimpleName(), e); @@ -134,11 +137,21 @@ public T as(Class clazz) throws JWTDecodeException { @Override public boolean isNull() { - return false; + return !isMissing() && data.isNull(); + } + + @Override + public boolean isMissing() { + return data == null || data.isMissingNode(); } @Override public String toString() { + if (isMissing()) { + return "Missing claim"; + } else if (isNull()) { + return "Null claim"; + } return data.toString(); } @@ -161,10 +174,8 @@ static Claim extractClaim(String claimName, Map tree, ObjectRe * @return a valid Claim instance. If the node is null or missing, a NullClaim will be returned. */ static Claim claimFromNode(JsonNode node, ObjectReader objectReader) { - if (node == null || node.isNull() || node.isMissingNode()) { - return new NullClaim(); - } return new JsonNodeClaim(node, objectReader); } } +//todo test all as* methods in JsonNodeClaim to ensure isMissing isNull calls are made \ No newline at end of file diff --git a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java deleted file mode 100644 index 2975b51b..00000000 --- a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.auth0.jwt.impl; - -import com.auth0.jwt.exceptions.JWTDecodeException; -import com.auth0.jwt.interfaces.Claim; - -import java.time.Instant; -import java.util.Date; -import java.util.List; -import java.util.Map; - -/** - * The {@code NullClaim} class is a Claim implementation that returns null when any of it's methods is called. - */ -public class NullClaim implements Claim { - @Override - public boolean isNull() { - return true; - } - - @Override - public Boolean asBoolean() { - return null; - } - - @Override - public Integer asInt() { - return null; - } - - @Override - public Long asLong() { - return null; - } - - @Override - public Double asDouble() { - return null; - } - - @Override - public String asString() { - return null; - } - - @Override - public Date asDate() { - return null; - } - - @Override - public Instant asInstant() { - return null; - } - - @Override - public T[] asArray(Class clazz) throws JWTDecodeException { - return null; - } - - @Override - public List asList(Class clazz) throws JWTDecodeException { - return null; - } - - @Override - public Map asMap() throws JWTDecodeException { - return null; - } - - @Override - public T as(Class clazz) throws JWTDecodeException { - return null; - } - - @Override - public String toString() { - return "Null Claim"; - } -} diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index fdfac715..f6ccfdcd 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -14,11 +14,20 @@ public interface Claim { /** * Whether this Claim has a null value or not. + * If the claim is not present, it will return false hence checking {@link Claim#isMissing} is advised as well * * @return whether this Claim has a null value or not. */ boolean isNull(); + /** + * Can be used to verify whether the Claim is found or not. + * This will be true even if the Claim has null value associated to it. + * + * @return whether this Claim is present or not + */ + boolean isMissing(); + /** * Get this Claim as a Boolean. * If the value isn't of type Boolean or it can't be converted to a Boolean, null will be returned. @@ -110,6 +119,7 @@ default Instant asInstant() { /** * Get this Claim as a custom type T. + * This method will return null if {@link Claim#isMissing()} or {@link Claim#isNull()} is true * * @param type * @param clazz the type class diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index ffe0b920..8b0416dc 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -132,6 +132,15 @@ default Verification withAnyOfAudience(String... audience) { */ Verification withClaimPresence(String name) throws IllegalArgumentException; + /** + * Require a specific Claim value to be null. + * + * @param name the Claim's name. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + Verification withNullClaim(String name) throws IllegalArgumentException; + /** * Require a specific Claim value. * diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 6c870656..3cccbf7b 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -326,17 +326,6 @@ public void shouldAddJWTId() { assertThat(TokenUtils.splitToken(signed)[1], is("eyJqdGkiOiJqd3RfaWRfMTIzIn0")); } - @Test - public void shouldRemoveClaimWhenPassingNull() { - String signed = JWTCreator.init() - .withIssuer("iss") - .withIssuer(null) - .sign(Algorithm.HMAC256("secret")); - - assertThat(signed, is(notNullValue())); - assertThat(TokenUtils.splitToken(signed)[1], is("e30")); - } - @Test public void shouldSetCorrectAlgorithmInTheHeader() { String signed = JWTCreator.init() @@ -615,7 +604,7 @@ public void shouldAcceptCustomListClaimOfBasicObjectTypes() throws Exception { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); List list = (List) mapper.readValue(body, Map.class).get("data"); @@ -648,52 +637,6 @@ public void shouldAcceptCustomClaimForNullListItem() { .sign(Algorithm.HMAC256("secret")); } - @Test - @SuppressWarnings("unchecked") - public void shouldAcceptCustomClaimWithNullMapAndRemoveClaim() throws Exception { - String jwt = JWTCreator.init() - .withClaim("map", "stubValue") - .withClaim("map", (Map) null) - .sign(Algorithm.HMAC256("secret")); - - assertThat(jwt, is(notNullValue())); - String[] parts = jwt.split("\\."); - - String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - ObjectMapper mapper = new ObjectMapper(); - Map map = (Map) mapper.readValue(body, Map.class); - assertThat(map, anEmptyMap()); - } - - @Test - @SuppressWarnings("unchecked") - public void shouldAcceptCustomClaimWithNullListAndRemoveClaim() throws Exception { - String jwt = JWTCreator.init() - .withClaim("list", "stubValue") - .withClaim("list", (List) null) - .sign(Algorithm.HMAC256("secret")); - - assertThat(jwt, is(notNullValue())); - String[] parts = jwt.split("\\."); - - String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - ObjectMapper mapper = new ObjectMapper(); - Map map = (Map) mapper.readValue(body, Map.class); - assertThat(map, anEmptyMap()); - } - - @Test - public void shouldRefuseCustomClaimForNullMapValue() { - Map data = new HashMap<>(); - data.put("subKey", null); - - exception.expect(IllegalArgumentException.class); - - JWTCreator.init() - .withClaim("pojo", data) - .sign(Algorithm.HMAC256("secret")); - } - @Test public void shouldRefuseCustomClaimForNullMapKey() { Map data = new HashMap<>(); @@ -814,26 +757,10 @@ public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.ISSUER, "abc")); } - @Test - public void shouldRemovePayloadIfTheValueIsNull() throws Exception { - String jwt = JWTCreator.init() - .withClaim("key", "stubValue") - .withPayload(Collections.singletonMap("key", (Map) null)) - .sign(Algorithm.HMAC256("secret")); - - assertThat(jwt, is(notNullValue())); - String[] parts = jwt.split("\\."); - - String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - ObjectMapper mapper = new ObjectMapper(); - Map map = (Map) mapper.readValue(body, Map.class); - assertThat(map, anEmptyMap()); - } - @Test public void withPayloadShouldNotAllowCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); Map payload = new HashMap<>(); payload.put("entry", "value"); @@ -860,7 +787,7 @@ public void withPayloadShouldAllowNullListItems() { @Test public void withPayloadShouldNotAllowListWithCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); Map payload = new HashMap<>(); payload.put("list", Arrays.asList("item1", new UserPojo("name", 42))); @@ -872,7 +799,7 @@ public void withPayloadShouldNotAllowListWithCustomType() { @Test public void withPayloadShouldNotAllowMapWithCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); Map payload = new HashMap<>(); payload.put("entry", "value"); @@ -922,4 +849,75 @@ public void withPayloadShouldAllowNestedSupportedTypes() { assertThat(payloadJson, JsonMatcher.hasEntry("listClaim", listClaim)); assertThat(payloadJson, JsonMatcher.hasEntry("objClaim", mapClaim)); } + + @Test + public void withPayloadShouldSupportNullValuesEverywhere() { + /* + JWT: + { + "listClaim": [ + "answer to ultimate question of life", + 42, + null + ], + "claim": null, + "listNestedClaim": [ + 1, + 2, + { + "nestedObjKey": null + } + ], + "objClaim": { + "nestedObjKey": null, + "objObjKey": { + "nestedObjKey": null, + "objListKey": [ + null, + "nestedList2" + ] + }, + "objListKey": [ + null, + "nestedList2" + ] + } + } + */ + + List listClaim = Arrays.asList("answer to ultimate question of life", 42, null); + List listNestedClaim = Arrays.asList(1, 2, Collections.singletonMap("nestedObjKey", null)); + List objListKey = Arrays.asList(null, "nestedList2"); + HashMap objClaim = new HashMap<>(); + objClaim.put("nestedObjKey", null); + objClaim.put("objListKey", objListKey); + objClaim.put("objObjKey", new HashMap<>(objClaim)); + + + Map payload = new HashMap<>(); + payload.put("claim", null); + payload.put("listClaim", listClaim); + payload.put("listNestedClaim", listNestedClaim); + payload.put("objClaim", objClaim); + + String jwt = JWTCreator.init() + .withPayload(payload) + .withHeader(payload) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); + + assertThat(payloadJson, JsonMatcher.hasEntry("claim", null)); + assertThat(payloadJson, JsonMatcher.hasEntry("listClaim", listClaim)); + assertThat(payloadJson, JsonMatcher.hasEntry("listNestedClaim", listNestedClaim)); + assertThat(payloadJson, JsonMatcher.hasEntry("objClaim", objClaim)); + + assertThat(headerJson, JsonMatcher.hasEntry("claim", null)); + assertThat(headerJson, JsonMatcher.hasEntry("listClaim", listClaim)); + assertThat(headerJson, JsonMatcher.hasEntry("listNestedClaim", listNestedClaim)); + assertThat(headerJson, JsonMatcher.hasEntry("objClaim", objClaim)); + } } diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index 32db703e..5ea2b4ef 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -1,7 +1,6 @@ package com.auth0.jwt; import com.auth0.jwt.exceptions.JWTDecodeException; -import com.auth0.jwt.impl.NullClaim; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import org.hamcrest.collection.IsCollectionWithSize; @@ -216,7 +215,8 @@ public void shouldGetMissingClaimIfClaimDoesNotExist() { DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.e30.K17vlwhE8FCMShdl1_65jEYqsQqBOVMPUU9IgG-QlTM"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getClaim("notExisting"), is(notNullValue())); - assertThat(jwt.getClaim("notExisting"), is(instanceOf(NullClaim.class))); + assertThat(jwt.getClaim("notExisting").isMissing(), is(true)); + assertThat(jwt.getClaim("notExisting").isNull(), is(false)); } @Test @@ -295,13 +295,28 @@ public void shouldGetCustomArrayClaimOfTypeInteger() { @Test public void shouldGetCustomMapClaim() { - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp7InN0cmluZyI6InZhbHVlIiwibnVtYmVyIjoxLCJib29sZWFuIjp0cnVlfX0.-8aIaXd2-rp1lLuDEQmCeisCBX9X_zbqdPn2llGxNoc"; + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp7InN0cmluZyI6InZhbHVlIiwibnVtYmVyIjoxLCJib29sZWFuIjp0cnVlLCJlbXB0eSI6bnVsbH19.6xkCuYZnu4RA0xZSxlYSYAqzy9JDWsDtIWqSCUZlPt8"; DecodedJWT jwt = JWT.decode(token); assertThat(jwt, is(notNullValue())); Map map = jwt.getClaim("name").asMap(); assertThat(map, hasEntry("string", "value")); assertThat(map, hasEntry("number", 1)); assertThat(map, hasEntry("boolean", true)); + assertThat(map, hasEntry("empty", null)); + } + + @Test + public void shouldGetCustomNullClaim() { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpudWxsfQ.X4ALHe7uYqEcXWFBnwBUNRKwmwrtDEGZ2aynRYYUx8c"; + DecodedJWT jwt = JWT.decode(token); + assertThat(jwt.getClaim("name").isNull(), is(true)); + } + + @Test + public void shouldGetListClaim() { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbbnVsbCwiaGVsbG8iXX0.SpcuQRBGdTV0ofHdxBSnhWEUsQi89noZUXin2Thwb70"; + DecodedJWT jwt = JWT.decode(token); + assertThat(jwt.getClaim("name").asList(String.class), contains(null, "hello")); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 1d80d802..7d2123a9 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -1064,4 +1064,47 @@ public void shouldRemovePredicateCheckForNull() { assertThat(verifier.expectedChecks, not(hasKey("claimName"))); } + @Test + public void shouldSuccessfullyVerifyClaimWithNull() { + String jwt = JWTCreator.init() + .withNullClaim("claimName") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withNullClaim("claimName") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldThrowWhenNullClaimHasValue() { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'claimName' value doesn't match the required one."); + + String jwt = JWTCreator.init() + .withClaim("claimName", "value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withNullClaim("claimName") + .build() + .verify(jwt); + } + + @Test + public void shouldThrowWhenNullClaimIsMissing() { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'anotherClaimName' value doesn't match the required one."); + + String jwt = JWTCreator.init() + .withClaim("claimName", "value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withNullClaim("anotherClaimName") + .build() + .verify(jwt); + } } diff --git a/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java b/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java index e3d04c77..c4a04d81 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java @@ -135,6 +135,7 @@ public void shouldGetNotNullExtraClaimIfMissing() { assertThat(header, is(notNullValue())); assertThat(header.getHeaderClaim("missing"), is(notNullValue())); - assertThat(header.getHeaderClaim("missing"), is(instanceOf(NullClaim.class))); + assertThat(header.getHeaderClaim("missing").isMissing(), is(true)); + assertThat(header.getHeaderClaim("missing").isNull(), is(false)); } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java index 3c744487..44ef2b9f 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -331,8 +331,8 @@ public void shouldReturnBaseClaimWhenParsingMissingNode() { Claim claim = claimFromNode(value); assertThat(claim, is(notNullValue())); - assertThat(claim, is(instanceOf(NullClaim.class))); - assertThat(claim.isNull(), is(true)); + assertThat(claim.isMissing(), is(true)); + assertThat(claim.isNull(), is(false)); } @Test @@ -341,8 +341,8 @@ public void shouldReturnBaseClaimWhenParsingNullNode() { Claim claim = claimFromNode(value); assertThat(claim, is(notNullValue())); - assertThat(claim, is(instanceOf(NullClaim.class))); assertThat(claim.isNull(), is(true)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -351,8 +351,8 @@ public void shouldReturnBaseClaimWhenParsingNullValue() { Claim claim = claimFromNode(value); assertThat(claim, is(notNullValue())); - assertThat(claim, is(instanceOf(NullClaim.class))); assertThat(claim.isNull(), is(true)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -363,6 +363,7 @@ public void shouldReturnNonNullClaimWhenParsingObject() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -373,6 +374,7 @@ public void shouldReturnNonNullClaimWhenParsingArray() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -383,6 +385,7 @@ public void shouldReturnNonNullClaimWhenParsingList() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -393,6 +396,7 @@ public void shouldReturnNonNullClaimWhenParsingStringValue() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -403,6 +407,7 @@ public void shouldReturnNonNullClaimWhenParsingIntValue() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -413,6 +418,7 @@ public void shouldReturnNonNullClaimWhenParsingDoubleValue() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -423,6 +429,7 @@ public void shouldReturnNonNullClaimWhenParsingDateValue() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -433,6 +440,18 @@ public void shouldReturnNonNullClaimWhenParsingBooleanValue() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); + } + + @Test + public void shouldReturnNullIsTrue() { + JsonNode value = mapper.valueToTree(null); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + assertThat(claim, is(instanceOf(JsonNodeClaim.class))); + assertThat(claim.isNull(), is(true)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -441,4 +460,22 @@ public void shouldDelegateToJsonNodeToString() { Claim claim = claimFromNode(value); assertThat(claim.toString(), is(value.toString())); } + + @Test + public void shouldConvertToString() { + JsonNode value = mapper.valueToTree(new UserPojo("john", 123)); + JsonNode nullValue = mapper.valueToTree(null); + JsonNode missingValue = MissingNode.getInstance(); + + Claim claim = claimFromNode(value); + Claim nullClaim = claimFromNode(nullValue); + Claim missingClaim = claimFromNode(missingValue); + + assertThat(claim.toString(), is("{\"name\":\"john\",\"id\":123}")); + assertThat(nullClaim.isNull(), is(true)); + assertThat(nullClaim.toString(), is("Null claim")); + assertThat(missingClaim.isMissing(), is(true)); + assertThat(missingClaim.toString(), is("Missing claim")); + + } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java deleted file mode 100644 index bd478130..00000000 --- a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.auth0.jwt.impl; - -import org.junit.Before; -import org.junit.Test; - -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; - -public class NullClaimTest { - private NullClaim claim; - - @Before - public void setUp() { - claim = new NullClaim(); - } - - @Test - public void shouldBeNull() { - assertThat(claim.isNull(), is(true)); - } - - @Test - public void shouldGetAsBoolean() { - assertThat(claim.asBoolean(), is(nullValue())); - } - - @Test - public void shouldGetAsInt() { - assertThat(claim.asInt(), is(nullValue())); - } - - @Test - public void shouldGetAsLong() { - assertThat(claim.asLong(), is(nullValue())); - } - - @Test - public void shouldGetAsDouble() { - assertThat(claim.asDouble(), is(nullValue())); - } - - @Test - public void shouldGetAsString() { - assertThat(claim.asString(), is(nullValue())); - } - - @Test - public void shouldGetAsDate() { - assertThat(claim.asDate(), is(nullValue())); - } - - @Test - public void shouldGetAsInstant() { - assertThat(claim.asInstant(), is(nullValue())); - } - - @Test - public void shouldGetAsArray() { - assertThat(claim.asArray(Object.class), is(nullValue())); - } - - @Test - public void shouldGetAsList() { - assertThat(claim.asList(Object.class), is(nullValue())); - } - - @Test - public void shouldGetAsMap() { - assertThat(claim.asMap(), is(nullValue())); - } - - @Test - public void shouldGetAsCustomClass() { - assertThat(claim.as(Object.class), is(nullValue())); - } - - @Test - public void shouldHaveToString() { - assertThat(claim.toString(), is("Null Claim")); - } -} \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java index 9e2e6902..da0c880e 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java @@ -166,7 +166,8 @@ public void shouldGetNotNullExtraClaimIfMissing() { PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); assertThat(payload, is(notNullValue())); assertThat(payload.getClaim("missing"), is(notNullValue())); - assertThat(payload.getClaim("missing"), is(instanceOf(NullClaim.class))); + assertThat(payload.getClaim("missing").isMissing(), is(true)); + assertThat(payload.getClaim("missing").isNull(), is(false)); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java index 77d573bc..61541ccf 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java @@ -40,6 +40,11 @@ public boolean isNull() { return false; } + @Override + public boolean isMissing() { + return false; + } + @Override public Boolean asBoolean() { return null; diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java index c7f89763..fb0c74a9 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java @@ -104,6 +104,11 @@ public Verification withClaimPresence(String name) throws IllegalArgumentExcepti return null; } + @Override + public Verification withNullClaim(String name) throws IllegalArgumentException { + return null; + } + @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { return null; From 67df3d476024cbe697b3a54893f5383945c7670d Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 29 Mar 2022 13:08:11 +0530 Subject: [PATCH 082/198] [SDK-3154] Improved Exception Handling (#568) * Improved exception handling * Fixed issues thrown by Linter * Fixed Lint Issue (Missing Period) * Improved Code Coverage --- .../main/java/com/auth0/jwt/JWTVerifier.java | 85 ++++++---- .../exceptions/IncorrectClaimException.java | 44 +++++ .../jwt/exceptions/InvalidClaimException.java | 2 +- .../jwt/exceptions/MissingClaimException.java | 23 +++ .../jwt/exceptions/TokenExpiredException.java | 11 +- .../com/auth0/jwt/impl/JsonNodeClaim.java | 3 +- .../java/com/auth0/jwt/JWTVerifierTest.java | 159 ++++++++++++++---- .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 43 +++++ .../auth0/jwt/matchers/CustomMatchers.java | 114 +++++++++++++ 9 files changed, 410 insertions(+), 74 deletions(-) create mode 100644 lib/src/main/java/com/auth0/jwt/exceptions/IncorrectClaimException.java create mode 100644 lib/src/main/java/com/auth0/jwt/exceptions/MissingClaimException.java create mode 100644 lib/src/test/java/com/auth0/jwt/matchers/CustomMatchers.java diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 947c9e25..ba19feb9 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -72,7 +72,8 @@ public Verification withIssuer(String... issuer) { List value = isNullOrEmpty(issuer) ? null : Arrays.asList(issuer); checkIfNeedToRemove(PublicClaims.ISSUER, value, ((claim, decodedJWT) -> { if (value == null || !value.contains(claim.asString())) { - throw new InvalidClaimException("The Claim 'iss' value doesn't match the required issuer."); + throw new IncorrectClaimException( + "The Claim 'iss' value doesn't match the required issuer.", PublicClaims.ISSUER, claim); } return true; })); @@ -89,7 +90,7 @@ public Verification withSubject(String subject) { public Verification withAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> - assertValidAudienceClaim(decodedJWT.getAudience(), value, true))); + assertValidAudienceClaim(claim, decodedJWT.getAudience(), value, true))); return this; } @@ -97,7 +98,7 @@ public Verification withAudience(String... audience) { public Verification withAnyOfAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> - assertValidAudienceClaim(decodedJWT.getAudience(), value, false))); + assertValidAudienceClaim(claim, decodedJWT.getAudience(), value, false))); return this; } @@ -144,12 +145,7 @@ public Verification withJWTId(String jwtId) { @Override public Verification withClaimPresence(String name) throws IllegalArgumentException { assertNonNull(name); - withClaim(name, ((claim, decodedJWT) -> { - if (claim.isMissing()) { - throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", name)); - } - return true; - })); + withClaim(name, ((claim, decodedJWT) -> assertClaimPresence(name, claim))); return this; } @@ -272,13 +268,13 @@ private void addMandatoryClaimChecks() { long notBeforeLeeway = getLeewayFor(PublicClaims.NOT_BEFORE); long issuedAtLeeway = getLeewayFor(PublicClaims.ISSUED_AT); - checkIfNeedToRemove(PublicClaims.EXPIRES_AT, expiresAtLeeway, ((claim, decodedJWT) -> - assertValidInstantClaim(claim.asInstant(), expiresAtLeeway, true))); - checkIfNeedToRemove(PublicClaims.NOT_BEFORE, notBeforeLeeway, ((claim, decodedJWT) -> - assertValidInstantClaim(claim.asInstant(), notBeforeLeeway, false))); + expectedChecks.put(PublicClaims.EXPIRES_AT, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.EXPIRES_AT, claim, expiresAtLeeway, true)); + expectedChecks.put(PublicClaims.NOT_BEFORE, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.NOT_BEFORE, claim, notBeforeLeeway, false)); if (!ignoreIssuedAt) { - checkIfNeedToRemove(PublicClaims.ISSUED_AT, issuedAtLeeway, ((claim, decodedJWT) -> - assertValidInstantClaim(claim.asInstant(), issuedAtLeeway, false))); + expectedChecks.put(PublicClaims.ISSUED_AT, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.ISSUED_AT, claim, issuedAtLeeway, false)); } } @@ -305,33 +301,50 @@ private boolean assertValidCollectionClaim(Claim claim, Object[] expectedClaimVa return claimArr.containsAll(valueArr); } - private boolean assertValidInstantClaim(Instant claimVal, long leeway, boolean shouldBeFuture) { + private boolean assertValidInstantClaim(String claimName, Claim claim, long leeway, boolean shouldBeFuture) { + Instant claimVal = claim.asInstant(); Instant now = clock.instant().truncatedTo(ChronoUnit.SECONDS); + boolean isValid; if (shouldBeFuture) { - return assertInstantIsFuture(claimVal, leeway, now); + isValid = assertInstantIsFuture(claimVal, leeway, now); + if (!isValid) { + throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal), claimVal); + } } else { - return assertInstantIsPast(claimVal, leeway, now); + isValid = assertInstantIsPast(claimVal, leeway, now); + if (!isValid) { + throw new IncorrectClaimException( + String.format("The Token can't be used before %s.", claimVal), claimName, claim); + } } + return true; } private boolean assertInstantIsFuture(Instant claimVal, long leeway, Instant now) { - if (claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)) { - throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal)); - } - return true; + return !(claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)); } private boolean assertInstantIsPast(Instant claimVal, long leeway, Instant now) { - if (claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)) { - throw new InvalidClaimException(String.format("The Token can't be used before %s.", claimVal)); - } - return true; + return !(claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)); } - private boolean assertValidAudienceClaim(List audience, List values, boolean shouldContainAll) { + private boolean assertValidAudienceClaim( + Claim claim, + List audience, + List values, + boolean shouldContainAll + ) { if (audience == null || (shouldContainAll && !audience.containsAll(values)) || (!shouldContainAll && Collections.disjoint(audience, values))) { - throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); + throw new IncorrectClaimException( + "The Claim 'aud' value doesn't contain the required audience.", PublicClaims.AUDIENCE, claim); + } + return true; + } + + private boolean assertClaimPresence(String name, Claim claim) { + if (claim.isMissing()) { + throw new MissingClaimException(name); } return true; } @@ -353,7 +366,8 @@ private void checkIfNeedToRemove(String name, Object value, BiPredicate assertClaimPresence(name, claim) + && predicate.test(claim, decodedJWT)); } private boolean isNullOrEmpty(String[] args) { @@ -381,7 +395,8 @@ private boolean isNullOrEmpty(String[] args) { * the one defined in the {@link JWTVerifier}. * @throws SignatureVerificationException if the signature is invalid. * @throws TokenExpiredException if the token has expired. - * @throws InvalidClaimException if a claim contained a different value than the expected one. + * @throws MissingClaimException if a claim to be verified is missing. + * @throws IncorrectClaimException if a claim contained a different value than the expected one. */ @Override public DecodedJWT verify(String token) throws JWTVerificationException { @@ -398,7 +413,8 @@ public DecodedJWT verify(String token) throws JWTVerificationException { * the one defined in the {@link JWTVerifier}. * @throws SignatureVerificationException if the signature is invalid. * @throws TokenExpiredException if the token has expired. - * @throws InvalidClaimException if a claim contained a different value than the expected one. + * @throws MissingClaimException if a claim to be verified is missing. + * @throws IncorrectClaimException if a claim contained a different value than the expected one. */ @Override public DecodedJWT verify(DecodedJWT jwt) throws JWTVerificationException { @@ -426,8 +442,11 @@ private void verifyClaims(DecodedJWT jwt, Map hasMissingClaimName(final String claimName) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("MissingClaimException with claim name: "+claimName); + } + + @Override + protected boolean matchesSafely(MissingClaimException item) { + return item.getClaimName().equals(claimName); + } + }; + } + + public static Matcher hasTokenExpiredOn(final Instant instant) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("TokenExpiredException with expired time as: "+instant.getEpochSecond()); + } + + @Override + protected boolean matchesSafely(TokenExpiredException item) { + return item.getExpiredOn().equals(instant); + } + }; + } + + public static Matcher hasClaimName(final String claimName) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("IncorrectClaimException with claim name: "+claimName); + } + + @Override + protected boolean matchesSafely(IncorrectClaimException item) { + return item.getClaimName().equals(claimName); + } + }; + } + + public static Matcher hasClaimValue(final Object value, final Class clazz) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("IncorrectClaimException with claim : "+value); + } + + @Override + protected boolean matchesSafely(IncorrectClaimException item) { + return item.getClaimValue().as(clazz).equals(value); + } + }; + } + + public static Matcher hasClaimInstant(final Instant value, final Class clazz) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("IncorrectClaimException with claim : "+value); + } + + @Override + protected boolean matchesSafely(IncorrectClaimException item) { + return item.getClaimValue().as(clazz).equals(value); + } + }; + } + + public static Matcher hasClaimValueArray(final Object value, final Class clazz) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("IncorrectClaimException with claim : "+value); + } + + @Override + protected boolean matchesSafely(IncorrectClaimException item) { + return Arrays.equals((Object[]) item.getClaimValue().as(clazz), (Object[])value); + } + }; + } + + public static Matcher hasNullClaim() { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("IncorrectClaimException with claim as null"); + } + + @Override + protected boolean matchesSafely(IncorrectClaimException item) { + boolean a = item.getClaimValue().isNull(); + String b = item.getClaimValue().toString(); + return item.getClaimValue().isNull(); + } + }; + } +} From dd22f32d5cfcffd6066a5ce949bcc58481cc2f5a Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 30 Mar 2022 03:35:54 -0500 Subject: [PATCH 083/198] Security: Bump `jackson-databind` to 2.13.2.2 (#566) * Security: Bump `jackson-databind` to 2.13.2.1 This PR bumps the `jackson-databind` dependency to 2.13.2.1 to address [CVE-2020-36518](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-36518) in that library * Bump to 2.13.2.2 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index 55f531b8..fbc88df8 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -47,7 +47,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' testImplementation 'net.jodah:concurrentunit:0.4.3' From 9793478af856483af7b1e75cd8d66096df77b18f Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 30 Mar 2022 14:09:19 +0530 Subject: [PATCH 084/198] Release 3.19.1 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84686b4b..a022328d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.19.1](https://github.com/auth0/java-jwt/tree/3.19.1) (2022-03-30) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.0...3.19.1) + +**Security** +- Security: Bump `jackson-databind` to 2.13.2.2 [\#566](https://github.com/auth0/java-jwt/pull/566) ([evansims](https://github.com/evansims)) + ## [3.19.0](https://github.com/auth0/java-jwt/tree/3.19.0) (2022-03-14) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.18.3...3.19.0) diff --git a/README.md b/README.md index a26170d2..260f6d6e 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.19.0 + 3.19.1 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.19.0' +implementation 'com.auth0:java-jwt:3.19.1' ``` ## Available Algorithms From ed58ef33d1ed800b84678acee2cab172684198d1 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 1 Apr 2022 15:35:37 +0530 Subject: [PATCH 085/198] Improved README structure --- README.md | 193 +++++++++++++++++++++++++++++------------------------- 1 file changed, 103 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 0f847cca..fb489d9b 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,29 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o > This library requires Java 8 or higher. The last version that supported Java 7 was 3.11.0. +## Table of Contents +- [**Installation**](#installation) +- [**Available Algorithms**](#available-algorithms) +- [**Quickstart**](#quickstart) + + [**Create and Sign a Token**](#create-and-sign-a-token) + + [**Verify a Token**](#verify-a-token) + + [**Decode a Token**](#decode-a-token) +- [**Usage**](#usage) + + [**Pick the algorithm**](#pick-the-algorithm) + + [**Time Validation**](#time-validation) + + [**Header Claims**](#header-claims) + + [**Payload Claims**](#payload-claims) + + [**Claim Class**](#claim-class) +- [**Javadoc**](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) + ## Installation -The library is available on both Maven Central and Bintray, and the Javadoc is published [here](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html). - +### Gradle + +```gradle +implementation 'com.auth0:java-jwt:3.19.0' +``` + ### Maven ```xml @@ -27,12 +46,6 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p ``` -### Gradle - -```gradle -implementation 'com.auth0:java-jwt:3.19.0' -``` - ## Available Algorithms The library implements JWT Verification and Signing using the following algorithms: @@ -49,73 +62,9 @@ The library implements JWT Verification and Signing using the following algorith | ES384 | ECDSA384 | ECDSA with curve P-384 and SHA-384 | | ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 | -⚠️ Note - ECDSA with curve secp256k1 and SHA-256 will not be supported for Java 15+ by this library since it has been (disabled in Java 15)[https://www.oracle.com/java/technologies/javase/15-relnote-issues.html#JDK-8237219] - -## Usage - -### Pick the Algorithm - -The Algorithm defines how a token is signed and verified. It can be instantiated with the raw value of the secret in the case of HMAC algorithms, or the key pairs or `KeyProvider` in the case of RSA and ECDSA algorithms. Once created, the instance is reusable for token signing and verification operations. - -When using RSA or ECDSA algorithms and you just need to **sign** JWTs you can avoid specifying a Public Key by passing a `null` value. The same can be done with the Private Key when you just need to **verify** JWTs. - - -#### Using static secrets or keys: - -```java -//HMAC -Algorithm algorithmHS = Algorithm.HMAC256("secret"); - -//RSA -RSAPublicKey publicKey = //Get the key instance -RSAPrivateKey privateKey = //Get the key instance -Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); -``` - -> Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). - -##### HMAC Key Length and Security - -When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recomendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. - -#### Using a KeyProvider: - -By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: - -- `getPublicKeyById(String kid)`: Its called during token signature verification and it should return the key used to verify the token. If key rotation is being used, e.g. [JWK](https://tools.ietf.org/html/rfc7517) it can fetch the correct rotation key using the id. (Or just return the same key all the time). -- `getPrivateKey()`: Its called during token signing and it should return the key that will be used to sign the JWT. -- `getPrivateKeyId()`: Its called during token signing and it should return the id of the key that identifies the one returned by `getPrivateKey()`. This value is preferred over the one set in the `JWTCreator.Builder#withKeyId(String)` method. If you don't need to set a `kid` value avoid instantiating an Algorithm using a `KeyProvider`. - - -The following example shows how this would work with `JwkStore`, an imaginary [JWK Set](https://auth0.com/docs/jwks) implementation. For simple key rotation using JWKS, try the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. - -```java -final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); -final RSAPrivateKey privateKey = //Get the key instance -final String privateKeyId = //Create an Id for the above key +> Note - Support for ECDSA with curve secp256k1 and SHA-256 (ES256K) has been dropped since it has been [disabled in Java 15](https://www.oracle.com/java/technologies/javase/15-relnote-issues.html#JDK-8237219) -RSAKeyProvider keyProvider = new RSAKeyProvider() { - @Override - public RSAPublicKey getPublicKeyById(String kid) { - //Received 'kid' value might be null if it wasn't defined in the Token's header - RSAPublicKey publicKey = jwkStore.get(kid); - return (RSAPublicKey) publicKey; - } - - @Override - public RSAPrivateKey getPrivateKey() { - return privateKey; - } - - @Override - public String getPrivateKeyId() { - return privateKeyId; - } -}; - -Algorithm algorithm = Algorithm.RSA256(keyProvider); -//Use the Algorithm to create and verify JWTs. -``` +## Quickstart ### Create and Sign a Token @@ -151,7 +100,6 @@ You'll first need to create a `JWTCreator` instance by calling `JWT.create()`. U If a Claim couldn't be converted to JSON or the Key used in the signing process was invalid a `JWTCreationException` will raise. - ### Verify a Token You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` and passing the `Algorithm` instance. If you require the token to have specific Claim values, use the builder to define them. The instance returned by the method `build()` is reusable, so you can define it once and use it to verify different tokens. Finally call `verifier.verify()` passing the token. @@ -190,8 +138,87 @@ You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` If the token has an invalid signature or the Claim requirement is not met, a `JWTVerificationException` will raise. +### Decode a Token -#### Time Validation +```java +String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; +try { + DecodedJWT jwt = JWT.decode(token); +} catch (JWTDecodeException exception){ + //Invalid token +} +``` + +If the token has an invalid syntax or the header or payload are not JSONs, a `JWTDecodeException` will raise. + +## Usage + +### Pick the Algorithm + +The Algorithm defines how a token is signed and verified. It can be instantiated with the raw value of the secret in the case of HMAC algorithms, or the key pairs or `KeyProvider` in the case of RSA and ECDSA algorithms. Once created, the instance is reusable for token signing and verification operations. + +When using RSA or ECDSA algorithms and you just need to **sign** JWTs you can avoid specifying a Public Key by passing a `null` value. The same can be done with the Private Key when you just need to **verify** JWTs. + + +#### Using static secrets or keys: + +```java +//HMAC +Algorithm algorithmHS = Algorithm.HMAC256("secret"); + +//RSA +RSAPublicKey publicKey = //Get the key instance +RSAPrivateKey privateKey = //Get the key instance +Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); +``` + +> Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). + +##### HMAC Key Length and Security + +When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recomendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. + +#### Using a KeyProvider: +//todo need to be updated +By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: + +- `getPublicKeyById(String kid)`: Its called during token signature verification and it should return the key used to verify the token. If key rotation is being used, e.g. [JWK](https://tools.ietf.org/html/rfc7517) it can fetch the correct rotation key using the id. (Or just return the same key all the time). +- `getPrivateKey()`: Its called during token signing and it should return the key that will be used to sign the JWT. +- `getPrivateKeyId()`: Its called during token signing and it should return the id of the key that identifies the one returned by `getPrivateKey()`. This value is preferred over the one set in the `JWTCreator.Builder#withKeyId(String)` method. If you don't need to set a `kid` value avoid instantiating an Algorithm using a `KeyProvider`. + + +The following example shows how this would work with `JwkStore`, an imaginary [JWK Set](https://auth0.com/docs/jwks) implementation. For simple key rotation using JWKS, try the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. + +```java +final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); +final RSAPrivateKey privateKey = //Get the key instance +final String privateKeyId = //Create an Id for the above key + +RSAKeyProvider keyProvider = new RSAKeyProvider() { + @Override + public RSAPublicKey getPublicKeyById(String kid) { + //Received 'kid' value might be null if it wasn't defined in the Token's header + RSAPublicKey publicKey = jwkStore.get(kid); + return (RSAPublicKey) publicKey; + } + + @Override + public RSAPrivateKey getPrivateKey() { + return privateKey; + } + + @Override + public String getPrivateKeyId() { + return privateKeyId; + } +}; + +Algorithm algorithm = Algorithm.RSA256(keyProvider); +//Use the Algorithm to create and verify JWTs. +``` + + +### Time Validation The JWT token may include DateNumber fields that can be used to validate that: * The token was issued in a past date `"iat" < TODAY` @@ -227,20 +254,6 @@ Clock clock = new CustomClock(); //Must implement Clock interface JWTVerifier verifier = verification.build(clock); ``` -### Decode a Token - -```java -String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; -try { - DecodedJWT jwt = JWT.decode(token); -} catch (JWTDecodeException exception){ - //Invalid token -} -``` - -If the token has an invalid syntax or the header or payload are not JSONs, a `JWTDecodeException` will raise. - - ### Header Claims #### Algorithm ("alg") From c1942944b3b906d749128279808ad86cd24b8b9f Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 12 Apr 2022 18:55:31 +0530 Subject: [PATCH 086/198] Add README instructions for new APIs --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb489d9b..065899f1 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,6 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recomendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. #### Using a KeyProvider: -//todo need to be updated By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: - `getPublicKeyById(String kid)`: Its called during token signature verification and it should return the key used to verify the token. If key rotation is being used, e.g. [JWK](https://tools.ietf.org/html/rfc7517) it can fetch the correct rotation key using the id. (Or just return the same key all the time). @@ -388,6 +387,7 @@ When creating a Token with the `JWT.create()` you can specify a custom Claim by String token = JWT.create() .withClaim("name", 123) .withArrayClaim("array", new Integer[]{1, 2, 3}) + .withNullClaim("claim_name") .sign(algorithm); ``` @@ -407,6 +407,9 @@ You can also verify custom Claims on the `JWT.require()` by calling `withClaim() JWTVerifier verifier = JWT.require(algorithm) .withClaim("name", 123) .withArrayClaim("array", 1, 2, 3) + .withNullClaim("null_value") //checks if the claim name provided has null value + .withClaimPresence("claim_presence") //checks if the claim name provided is in the payload + .withClaim("predicate", (claim, decodedJWT) -> "custom_check".equals(claim.asString())) //can be used to run custom verification .build(); DecodedJWT jwt = verifier.verify("my.jwt.token"); ``` @@ -423,7 +426,10 @@ The Claim class is a wrapper for the Claim values. It allows you to get the Clai * **asDouble()**: Returns the Double value or null if it can't be converted. * **asLong()**: Returns the Long value or null if it can't be converted. * **asString()**: Returns the String value or null if it can't be converted. -* **asDate()**: Returns the Date value or null if it can't be converted. This must be a NumericDate (Unix Epoch/Timestamp). Note that the [JWT Standard](https://tools.ietf.org/html/rfc7519#section-2) specified that all the *NumericDate* values must be in seconds. +* **asInstant()**: Returns the Instant value or null if it can't be converted. +* **asDate()**: Returns the Date value or null if it can't be converted. + +> For `asInstant()` and `asDate()` the value must be a NumericDate (Unix Epoch/Timestamp). Note that the [JWT Standard](https://tools.ietf.org/html/rfc7519#section-2) specified that all the *NumericDate* values must be in seconds. #### Custom Classes and Collections To obtain a Claim as a Collection you'll need to provide the **Class Type** of the contents to convert from. From 0f8a9bf20f215f9cdb78c50d7edbfe9d811cfdbc Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 12 Apr 2022 19:18:46 +0530 Subject: [PATCH 087/198] Replace expected exception with assertThrows (#572) * Replace expected exception with assertThrows --- .../java/com/auth0/jwt/JWTVerifierTest.java | 550 +++++++++--------- .../auth0/jwt/matchers/CustomMatchers.java | 114 ---- 2 files changed, 275 insertions(+), 389 deletions(-) delete mode 100644 lib/src/test/java/com/auth0/jwt/matchers/CustomMatchers.java diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 1cf79c8f..9dcb2cb1 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -19,9 +19,9 @@ import java.util.Date; import java.util.function.BiPredicate; -import static com.auth0.jwt.matchers.CustomMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.mock; public class JWTVerifierTest { @@ -35,17 +35,18 @@ public class JWTVerifierTest { @Test public void shouldThrowWhenInitializedWithoutAlgorithm() { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("The Algorithm cannot be null"); - JWTVerifier.init(null); + IllegalArgumentException e = assertThrows(null, IllegalArgumentException.class, () -> + JWTVerifier.init(null)); + assertThat(e.getMessage(), is("The Algorithm cannot be null.")); } @Test public void shouldThrowWhenAlgorithmDoesntMatchTheTokensAlgorithm() { - exception.expect(AlgorithmMismatchException.class); - exception.expectMessage("The provided Algorithm doesn't match the one defined in the JWT's Header."); - JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC512("secret")).build(); - verifier.verify("eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.s69x7Mmu4JqwmdxiK6sesALO7tcedbFsKEEITUxw9ho"); + AlgorithmMismatchException e = assertThrows(null, AlgorithmMismatchException.class, () -> { + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC512("secret")).build(); + verifier.verify("eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.s69x7Mmu4JqwmdxiK6sesALO7tcedbFsKEEITUxw9ho"); + }); + assertThat(e.getMessage(), is("The provided Algorithm doesn't match the one defined in the JWT's Header.")); } @Test @@ -73,45 +74,45 @@ public void shouldValidateMultipleIssuers() { @Test public void shouldThrowOnInvalidIssuer() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'iss' value doesn't match the required issuer."); - exception.expect(hasClaimName(PublicClaims.ISSUER)); - exception.expect(hasClaimValue("auth0", String.class)); - - String token = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withIssuer("invalid") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer("invalid") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); + assertThat(e.getClaimName(), is(PublicClaims.ISSUER)); + assertThat(e.getClaimValue().asString(), is("auth0")); } @Test public void shouldThrowOnNullIssuer() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'iss' value doesn't match the required issuer."); - exception.expect(hasClaimName(PublicClaims.ISSUER)); - exception.expect(hasNullClaim()); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOm51bGx9.OoiCLipSfflWxkFX2rytvtwEiJ8eAL0opkdXY_ap0qA"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withIssuer("auth0") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOm51bGx9.OoiCLipSfflWxkFX2rytvtwEiJ8eAL0opkdXY_ap0qA"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer("auth0") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); + assertThat(e.getClaimName(), is(PublicClaims.ISSUER)); + assertThat(e.getClaimValue().isNull(), is(true)); } @Test public void shouldThrowOnMissingIssuer() { - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'iss' is not present in the JWT."); - exception.expect(hasMissingClaimName("iss")); - - String jwt = JWTCreator.init() - .sign(Algorithm.HMAC256("secret")); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String jwt = JWTCreator.init() + .sign(Algorithm.HMAC256("secret")); - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withIssuer("nope") - .build() - .verify(jwt); + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer("nope") + .build() + .verify(jwt); + }); + assertThat(e.getMessage(), is("The Claim 'iss' is not present in the JWT.")); + assertThat(e.getClaimName(), is("iss")); } @Test @@ -127,16 +128,16 @@ public void shouldValidateSubject() { @Test public void shouldThrowOnInvalidSubject() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'sub' value doesn't match the required one."); - exception.expect(hasClaimName(PublicClaims.SUBJECT)); - exception.expect(hasClaimValue("1234567890", String.class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withSubject("invalid") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withSubject("invalid") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'sub' value doesn't match the required one.")); + assertThat(e.getClaimName(), is(PublicClaims.SUBJECT)); + assertThat(e.getClaimValue().asString(), is("1234567890")); } @Test @@ -230,76 +231,75 @@ public void shouldAcceptAudienceWhenAnyOfAudienceAndAllContained() { @Test public void shouldThrowWhenAudienceHasNoneOfExpectedAnyOfAudience() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); - exception.expect(hasClaimName(PublicClaims.AUDIENCE)); - exception.expect(hasClaimValueArray(new String[] {"Mark","David","John"}, String[].class)); - - // Token 'aud' = ["Mark", "David", "John"] - String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAnyOfAudience("Joe", "Jim") - .build() - .verify(tokenArr); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + // Token 'aud' = ["Mark", "David", "John"] + String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("Joe", "Jim") + .build() + .verify(tokenArr); + }); + assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @Test public void shouldThrowWhenAudienceClaimDoesNotContainAllExpected() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); - exception.expect(hasClaimName(PublicClaims.AUDIENCE)); - exception.expect(hasClaimValueArray(new String[] {"Mark","David","John"}, String[].class)); - - // Token 'aud' = ["Mark", "David", "John"] - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAudience("Mark", "Joe") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + // Token 'aud' = ["Mark", "David", "John"] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience("Mark", "Joe") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @Test public void shouldThrowWhenAudienceClaimIsNull() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); - exception.expect(hasClaimName(PublicClaims.AUDIENCE)); - exception.expect(hasNullClaim()); - - // Token 'aud': null - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpudWxsfQ.bpPyquk3b8KepErKgTidjJ1ZwiOGuoTxam2_x7cElKI"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAudience("nope") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + // Token 'aud': null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpudWxsfQ.bpPyquk3b8KepErKgTidjJ1ZwiOGuoTxam2_x7cElKI"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience("nope") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimValue().isNull(), is(true)); } @Test public void shouldThrowWhenAudienceClaimIsMissing(){ - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'aud' is not present in the JWT."); - exception.expect(hasMissingClaimName("aud")); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; - - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAudience("nope") - .build() - .verify(token); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience("nope") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'aud' is not present in the JWT.")); + assertThat(e.getClaimName(), is("aud")); } @Test public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); - exception.expect(hasClaimName(PublicClaims.AUDIENCE)); - exception.expect(hasClaimValueArray(new String[] {null}, String[].class)); - - // Token 'aud': [null] - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpbbnVsbF19.2cBf7FbkX52h8Vmjnl1DY1PYe_J_YP0KsyeoeYmuca8"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAnyOfAudience("nope") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + // Token 'aud': [null] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpbbnVsbF19.2cBf7FbkX52h8Vmjnl1DY1PYe_J_YP0KsyeoeYmuca8"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("nope") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {null})); } @Test @@ -384,113 +384,113 @@ public void shouldThrowOnNullCustomClaimName() { @Test public void shouldThrowWhenExpectedArrayClaimIsMissing() { - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'missing' is not present in the JWT."); - exception.expect(hasMissingClaimName("missing")); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcnJheSI6WzEsMiwzXX0.wKNFBcMdwIpdF9rXRxvexrzSM6umgSFqRO1WZj992YM"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withArrayClaim("missing", 1, 2, 3) - .build() - .verify(token); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcnJheSI6WzEsMiwzXX0.wKNFBcMdwIpdF9rXRxvexrzSM6umgSFqRO1WZj992YM"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("missing", 1, 2, 3) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'missing' is not present in the JWT.")); + assertThat(e.getClaimName(), is("missing")); } @Test public void shouldThrowWhenExpectedClaimIsMissing() { - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'missing' is not present in the JWT."); - exception.expect(hasMissingClaimName("missing")); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbSI6InRleHQifQ.aZ27Ze35VvTqxpaSIK5ZcnYHr4SrvANlUbDR8fw9qsQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("missing", "text") - .build() - .verify(token); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbSI6InRleHQifQ.aZ27Ze35VvTqxpaSIK5ZcnYHr4SrvANlUbDR8fw9qsQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("missing", "text") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'missing' is not present in the JWT.")); + assertThat(e.getClaimName(), is("missing")); } @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeString() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", "value") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", "value") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeInteger() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", 123) - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", 123) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeDouble() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", 23.45) - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", 23.45) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeBoolean() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", true) - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", true) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeDate() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", new Date()) - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", new Date()) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test public void shouldThrowOnInvalidCustomClaimValue() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", "check") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", "check") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test @@ -734,15 +734,15 @@ public void shouldValidateExpiresAtIfPresent() { @Test public void shouldThrowOnInvalidExpiresAtIfPresent() { - exception.expect(TokenExpiredException.class); - exception.expectMessage(startsWith("The Token has expired on")); - exception.expect(hasTokenExpiredOn(Instant.ofEpochSecond(1477592L))); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - verification - .build(mockOneSecondLater) - .verify(token); + TokenExpiredException e = assertThrows(null, TokenExpiredException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification + .build(mockOneSecondLater) + .verify(token); + }); + assertThat(e.getMessage(), is("The Token has expired on 1970-01-18T02:26:32Z.")); + assertThat(e.getExpiredOn(), is(Instant.ofEpochSecond(1477592L))); } @Test @@ -769,16 +769,16 @@ public void shouldValidateNotBeforeWithLeeway() { @Test public void shouldThrowOnInvalidNotBeforeIfPresent() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Token can't be used before 1970-01-18T02:26:32Z."); - exception.expect(hasClaimName(PublicClaims.NOT_BEFORE)); - exception.expect(hasClaimValue(1477592L, Long.class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0Nzc1OTJ9.wq4ZmnSF2VOxcQBxPLfeh1J2Ozy1Tj5iUaERm3FKaw8"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - verification - .build(mockOneSecondEarlier) - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0Nzc1OTJ9.wq4ZmnSF2VOxcQBxPLfeh1J2Ozy1Tj5iUaERm3FKaw8"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification + .build(mockOneSecondEarlier) + .verify(token); + }); + assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); + assertThat(e.getClaimName(), is(PublicClaims.NOT_BEFORE)); + assertThat(e.getClaimValue().asLong(), is(1477592L)); } @Test @@ -804,16 +804,16 @@ public void shouldThrowOnNegativeNotBeforeLeeway() { // Issued At with future date @Test public void shouldThrowOnFutureIssuedAt() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Token can't be used before 1970-01-18T02:26:32Z."); - exception.expect(hasClaimName(PublicClaims.ISSUED_AT)); - exception.expect(hasClaimValue(1477592L, Long.class)); - - String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - DecodedJWT jwt = verification.build(mockOneSecondEarlier).verify(token); - assertThat(jwt, is(notNullValue())); + DecodedJWT jwt = verification.build(mockOneSecondEarlier).verify(token); + assertThat(jwt, is(notNullValue())); + }); + assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); + assertThat(e.getClaimName(), is(PublicClaims.ISSUED_AT)); + assertThat(e.getClaimValue().asLong(), is(1477592L)); } // Issued At with future date and ignore flag @@ -829,16 +829,16 @@ public void shouldSkipIssuedAtVerificationWhenFlagIsPassed() { @Test public void shouldThrowOnInvalidIssuedAtIfPresent() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Token can't be used before 1970-01-18T02:26:32Z."); - exception.expect(hasClaimName(PublicClaims.ISSUED_AT)); - exception.expect(hasClaimValue(1477592L, Long.class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - verification - .build(mockOneSecondEarlier) - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification + .build(mockOneSecondEarlier) + .verify(token); + }); + assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); + assertThat(e.getClaimName(), is(PublicClaims.ISSUED_AT)); + assertThat(e.getClaimValue().asLong(), is(1477592L)); } @Test @@ -887,16 +887,16 @@ public void shouldValidateJWTId() { @Test public void shouldThrowOnInvalidJWTId() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'jti' value doesn't match the required one."); - exception.expect(hasClaimName("jti")); - exception.expect(hasClaimValue("jwt_id_123", String.class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJqd3RfaWRfMTIzIn0.0kegfXUvwOYioP8PDaLMY1IlV8HOAzSVz3EGL7-jWF4"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withJWTId("invalid") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJqd3RfaWRfMTIzIn0.0kegfXUvwOYioP8PDaLMY1IlV8HOAzSVz3EGL7-jWF4"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withJWTId("invalid") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'jti' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("jti")); + assertThat(e.getClaimValue().asString(), is("jwt_id_123")); } @Test @@ -963,19 +963,19 @@ public void shouldSkipClaimValidationsIfNoClaimsRequired() { @Test public void shouldThrowWhenVerifyingClaimPresenceButClaimNotPresent() { - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'missing' is not present in the JWT."); - exception.expect(hasMissingClaimName("missing")); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String jwt = JWTCreator.init() + .withClaim("custom", "") + .sign(Algorithm.HMAC256("secret")); - String jwt = JWTCreator.init() - .withClaim("custom", "") - .sign(Algorithm.HMAC256("secret")); - - JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaimPresence("missing") - .build(); + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("missing") + .build(); - verifier.verify(jwt); + verifier.verify(jwt); + }); + assertThat(e.getMessage(), is("The Claim 'missing' is not present in the JWT.")); + assertThat(e.getClaimName(), is("missing")); } @Test @@ -1120,19 +1120,19 @@ public void shouldSuccessfullyVerifyClaimWithPredicate() { @Test public void shouldThrowWhenPredicateReturnsFalse() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'claimName' value doesn't match the required one."); - exception.expect(hasClaimName("claimName")); - exception.expect(hasClaimValue("claimValue", String.class)); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String jwt = JWTCreator.init() + .withClaim("claimName", "claimValue") + .sign(Algorithm.HMAC256("secret")); - String jwt = JWTCreator.init() - .withClaim("claimName", "claimValue") - .sign(Algorithm.HMAC256("secret")); - - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString())) - .build() - .verify(jwt); + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString())) + .build() + .verify(jwt); + }); + assertThat(e.getMessage(), is("The Claim 'claimName' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("claimName")); + assertThat(e.getClaimValue().asString(), is("claimValue")); } @Test @@ -1162,34 +1162,34 @@ public void shouldSuccessfullyVerifyClaimWithNull() { @Test public void shouldThrowWhenNullClaimHasValue() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'claimName' value doesn't match the required one."); - exception.expect(hasClaimName("claimName")); - exception.expect(hasClaimValue("value", String.class)); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String jwt = JWTCreator.init() + .withClaim("claimName", "value") + .sign(Algorithm.HMAC256("secret")); - String jwt = JWTCreator.init() - .withClaim("claimName", "value") - .sign(Algorithm.HMAC256("secret")); - - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withNullClaim("claimName") - .build() - .verify(jwt); + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withNullClaim("claimName") + .build() + .verify(jwt); + }); + assertThat(e.getMessage(), is("The Claim 'claimName' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("claimName")); + assertThat(e.getClaimValue().asString(), is("value")); } @Test public void shouldThrowWhenNullClaimIsMissing() { - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'anotherClaimName' is not present in the JWT."); - exception.expect(hasMissingClaimName("anotherClaimName")); - - String jwt = JWTCreator.init() - .withClaim("claimName", "value") - .sign(Algorithm.HMAC256("secret")); - - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withNullClaim("anotherClaimName") - .build() - .verify(jwt); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String jwt = JWTCreator.init() + .withClaim("claimName", "value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withNullClaim("anotherClaimName") + .build() + .verify(jwt); + }); + assertThat(e.getMessage(), is("The Claim 'anotherClaimName' is not present in the JWT.")); + assertThat(e.getClaimName(), is("anotherClaimName")); } } diff --git a/lib/src/test/java/com/auth0/jwt/matchers/CustomMatchers.java b/lib/src/test/java/com/auth0/jwt/matchers/CustomMatchers.java deleted file mode 100644 index acff9b0f..00000000 --- a/lib/src/test/java/com/auth0/jwt/matchers/CustomMatchers.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.auth0.jwt.matchers; - -import com.auth0.jwt.exceptions.IncorrectClaimException; -import com.auth0.jwt.exceptions.MissingClaimException; -import com.auth0.jwt.exceptions.TokenExpiredException; -import com.auth0.jwt.interfaces.Claim; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; - -import java.time.Instant; -import java.util.Arrays; - -public class CustomMatchers { - public static Matcher hasMissingClaimName(final String claimName) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("MissingClaimException with claim name: "+claimName); - } - - @Override - protected boolean matchesSafely(MissingClaimException item) { - return item.getClaimName().equals(claimName); - } - }; - } - - public static Matcher hasTokenExpiredOn(final Instant instant) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("TokenExpiredException with expired time as: "+instant.getEpochSecond()); - } - - @Override - protected boolean matchesSafely(TokenExpiredException item) { - return item.getExpiredOn().equals(instant); - } - }; - } - - public static Matcher hasClaimName(final String claimName) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("IncorrectClaimException with claim name: "+claimName); - } - - @Override - protected boolean matchesSafely(IncorrectClaimException item) { - return item.getClaimName().equals(claimName); - } - }; - } - - public static Matcher hasClaimValue(final Object value, final Class clazz) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("IncorrectClaimException with claim : "+value); - } - - @Override - protected boolean matchesSafely(IncorrectClaimException item) { - return item.getClaimValue().as(clazz).equals(value); - } - }; - } - - public static Matcher hasClaimInstant(final Instant value, final Class clazz) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("IncorrectClaimException with claim : "+value); - } - - @Override - protected boolean matchesSafely(IncorrectClaimException item) { - return item.getClaimValue().as(clazz).equals(value); - } - }; - } - - public static Matcher hasClaimValueArray(final Object value, final Class clazz) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("IncorrectClaimException with claim : "+value); - } - - @Override - protected boolean matchesSafely(IncorrectClaimException item) { - return Arrays.equals((Object[]) item.getClaimValue().as(clazz), (Object[])value); - } - }; - } - - public static Matcher hasNullClaim() { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("IncorrectClaimException with claim as null"); - } - - @Override - protected boolean matchesSafely(IncorrectClaimException item) { - boolean a = item.getClaimValue().isNull(); - String b = item.getClaimValue().toString(); - return item.getClaimValue().isNull(); - } - }; - } -} From af04b229327f863cdde7c41c77c076e39a6b3742 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 13 Apr 2022 15:05:02 +0530 Subject: [PATCH 088/198] [SDK-3231] Added support for multiple checks on a single claim (#573) * Added support for multiple checks on a single claim * Fix codecov CI failure * Allow pseudo comparison for codecov * Remove newly added parameters and check codecov disabled * Reenabled changes check in codecov * Trigger Build * Refactor ExpectedCheckHolder from interfaces to impl package * Refactored code to improve coverage report --- .../main/java/com/auth0/jwt/JWTVerifier.java | 153 ++++++----- .../auth0/jwt/impl/ExpectedCheckHolder.java | 25 ++ .../java/com/auth0/jwt/JWTVerifierTest.java | 241 ++++++++++++------ 3 files changed, 282 insertions(+), 137 deletions(-) create mode 100644 lib/src/main/java/com/auth0/jwt/impl/ExpectedCheckHolder.java diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index ba19feb9..801dac24 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -6,6 +6,7 @@ import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.impl.ExpectedCheckHolder; import com.auth0.jwt.interfaces.Verification; import java.time.Clock; @@ -25,12 +26,12 @@ */ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { private final Algorithm algorithm; - final Map> expectedChecks; + final List expectedChecks; private final JWTParser parser; - JWTVerifier(Algorithm algorithm, Map> expectedChecks) { + JWTVerifier(Algorithm algorithm, List expectedChecks) { this.algorithm = algorithm; - this.expectedChecks = Collections.unmodifiableMap(expectedChecks); + this.expectedChecks = Collections.unmodifiableList(expectedChecks); this.parser = new JWTParser(); } @@ -50,7 +51,7 @@ static Verification init(Algorithm algorithm) throws IllegalArgumentException { */ public static class BaseVerification implements Verification { private final Algorithm algorithm; - private final Map> expectedChecks; + private final List expectedChecks; private long defaultLeeway; private final Map customLeeways; private boolean ignoreIssuedAt; @@ -62,7 +63,7 @@ public static class BaseVerification implements Verification { } this.algorithm = algorithm; - this.expectedChecks = new LinkedHashMap<>(); + this.expectedChecks = new ArrayList<>(); this.customLeeways = new HashMap<>(); this.defaultLeeway = 0; } @@ -70,7 +71,10 @@ public static class BaseVerification implements Verification { @Override public Verification withIssuer(String... issuer) { List value = isNullOrEmpty(issuer) ? null : Arrays.asList(issuer); - checkIfNeedToRemove(PublicClaims.ISSUER, value, ((claim, decodedJWT) -> { + addCheck(PublicClaims.ISSUER, ((claim, decodedJWT) -> { + if (verifyNull(claim, value)) { + return true; + } if (value == null || !value.contains(claim.asString())) { throw new IncorrectClaimException( "The Claim 'iss' value doesn't match the required issuer.", PublicClaims.ISSUER, claim); @@ -82,23 +86,40 @@ public Verification withIssuer(String... issuer) { @Override public Verification withSubject(String subject) { - checkIfNeedToRemove(PublicClaims.SUBJECT, subject, (claim, decodedJWT) -> subject.equals(claim.asString())); + addCheck(PublicClaims.SUBJECT, (claim, decodedJWT) -> + verifyNull(claim, subject) || subject.equals(claim.asString())); return this; } @Override public Verification withAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> - assertValidAudienceClaim(claim, decodedJWT.getAudience(), value, true))); + addCheck(PublicClaims.AUDIENCE, ((claim, decodedJWT) -> { + if (verifyNull(claim, value)) { + return true; + } + if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, true)) { + throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", + PublicClaims.AUDIENCE, claim); + } + return true; + })); return this; } @Override public Verification withAnyOfAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> - assertValidAudienceClaim(claim, decodedJWT.getAudience(), value, false))); + addCheck(PublicClaims.AUDIENCE, ((claim, decodedJWT) -> { + if (verifyNull(claim, value)) { + return true; + } + if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, false)) { + throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", + PublicClaims.AUDIENCE, claim); + } + return true; + })); return this; } @@ -138,14 +159,16 @@ public Verification ignoreIssuedAt() { @Override public Verification withJWTId(String jwtId) { - checkIfNeedToRemove(PublicClaims.JWT_ID, jwtId, ((claim, decodedJWT) -> jwtId.equals(claim.asString()))); + addCheck(PublicClaims.JWT_ID, ((claim, decodedJWT) -> + verifyNull(claim, jwtId) || jwtId.equals(claim.asString()))); return this; } @Override public Verification withClaimPresence(String name) throws IllegalArgumentException { assertNonNull(name); - withClaim(name, ((claim, decodedJWT) -> assertClaimPresence(name, claim))); + //since addCheck already checks presence, we just return true + withClaim(name, ((claim, decodedJWT) -> true)); return this; } @@ -159,35 +182,40 @@ public Verification withNullClaim(String name) throws IllegalArgumentException { @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asBoolean()))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value) + || value.equals(claim.asBoolean()))); return this; } @Override public Verification withClaim(String name, Integer value) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asInt()))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value) + || value.equals(claim.asInt()))); return this; } @Override public Verification withClaim(String name, Long value) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asLong()))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value) + || value.equals(claim.asLong()))); return this; } @Override public Verification withClaim(String name, Double value) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asDouble()))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value) + || value.equals(claim.asDouble()))); return this; } @Override public Verification withClaim(String name, String value) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asString()))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value) + || value.equals(claim.asString()))); return this; } @@ -201,8 +229,9 @@ public Verification withClaim(String name, Instant value) throws IllegalArgument assertNonNull(name); // Since date-time claims are serialized as epoch seconds, // we need to compare them with only seconds-granularity - checkIfNeedToRemove(name, value, - ((claim, decodedJWT) -> value.truncatedTo(ChronoUnit.SECONDS).equals(claim.asInstant()))); + addCheck(name, + ((claim, decodedJWT) -> verifyNull(claim, value) + || value.truncatedTo(ChronoUnit.SECONDS).equals(claim.asInstant()))); return this; } @@ -210,28 +239,32 @@ public Verification withClaim(String name, Instant value) throws IllegalArgument public Verification withClaim(String name, BiPredicate predicate) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, predicate, predicate); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, predicate) + || predicate.test(claim, decodedJWT))); return this; } @Override public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, items) + || assertValidCollectionClaim(claim, items))); return this; } @Override public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, items) + || assertValidCollectionClaim(claim, items))); return this; } @Override public Verification withArrayClaim(String name, Long... items) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, items) + || assertValidCollectionClaim(claim, items))); return this; } @@ -268,13 +301,13 @@ private void addMandatoryClaimChecks() { long notBeforeLeeway = getLeewayFor(PublicClaims.NOT_BEFORE); long issuedAtLeeway = getLeewayFor(PublicClaims.ISSUED_AT); - expectedChecks.put(PublicClaims.EXPIRES_AT, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.EXPIRES_AT, claim, expiresAtLeeway, true)); - expectedChecks.put(PublicClaims.NOT_BEFORE, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.NOT_BEFORE, claim, notBeforeLeeway, false)); + expectedChecks.add(constructExpectedCheck(PublicClaims.EXPIRES_AT, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.EXPIRES_AT, claim, expiresAtLeeway, true))); + expectedChecks.add(constructExpectedCheck(PublicClaims.NOT_BEFORE, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.NOT_BEFORE, claim, notBeforeLeeway, false))); if (!ignoreIssuedAt) { - expectedChecks.put(PublicClaims.ISSUED_AT, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.ISSUED_AT, claim, issuedAtLeeway, false)); + expectedChecks.add(constructExpectedCheck(PublicClaims.ISSUED_AT, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.ISSUED_AT, claim, issuedAtLeeway, false))); } } @@ -294,8 +327,7 @@ private boolean assertValidCollectionClaim(Claim claim, Object[] expectedClaimVa } } } else { - claimArr = claim.isNull() || claim.isMissing() - ? Collections.emptyList() : Arrays.asList(claim.as(Object[].class)); + claimArr = Arrays.asList(claim.as(Object[].class)); } List valueArr = Arrays.asList(expectedClaimValue); return claimArr.containsAll(valueArr); @@ -329,24 +361,12 @@ private boolean assertInstantIsPast(Instant claimVal, long leeway, Instant now) } private boolean assertValidAudienceClaim( - Claim claim, List audience, List values, boolean shouldContainAll ) { - if (audience == null || (shouldContainAll && !audience.containsAll(values)) - || (!shouldContainAll && Collections.disjoint(audience, values))) { - throw new IncorrectClaimException( - "The Claim 'aud' value doesn't contain the required audience.", PublicClaims.AUDIENCE, claim); - } - return true; - } - - private boolean assertClaimPresence(String name, Claim claim) { - if (claim.isMissing()) { - throw new MissingClaimException(name); - } - return true; + return !(audience == null || (shouldContainAll && !audience.containsAll(values)) + || (!shouldContainAll && Collections.disjoint(audience, values))); } private void assertPositive(long leeway) { @@ -361,13 +381,31 @@ private void assertNonNull(String name) { } } - private void checkIfNeedToRemove(String name, Object value, BiPredicate predicate) { - if (value == null) { - expectedChecks.remove(name); - return; - } - expectedChecks.put(name, (claim, decodedJWT) -> assertClaimPresence(name, claim) - && predicate.test(claim, decodedJWT)); + private void addCheck(String name, BiPredicate predicate) { + expectedChecks.add(constructExpectedCheck(name, (claim, decodedJWT) -> { + if (claim.isMissing()) { + throw new MissingClaimException(name); + } + return predicate.test(claim, decodedJWT); + })); + } + + private ExpectedCheckHolder constructExpectedCheck(String claimName, BiPredicate check) { + return new ExpectedCheckHolder() { + @Override + public String getClaimName() { + return claimName; + } + + @Override + public boolean verify(Claim claim, DecodedJWT decodedJWT) { + return check.test(claim, decodedJWT); + } + }; + } + + private boolean verifyNull(Claim claim, Object value) { + return value == null && claim.isNull(); } private boolean isNullOrEmpty(String[] args) { @@ -431,15 +469,14 @@ private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws } } - private void verifyClaims(DecodedJWT jwt, Map> claims) + private void verifyClaims(DecodedJWT jwt, List expectedChecks) throws TokenExpiredException, InvalidClaimException { - for (Map.Entry> entry : claims.entrySet()) { + for (ExpectedCheckHolder expectedCheck : expectedChecks) { boolean isValid; - String claimName = entry.getKey(); - BiPredicate expectedCheck = entry.getValue(); + String claimName = expectedCheck.getClaimName(); Claim claim = jwt.getClaim(claimName); - isValid = expectedCheck.test(claim, jwt); + isValid = expectedCheck.verify(claim, jwt); if (!isValid) { throw new IncorrectClaimException( diff --git a/lib/src/main/java/com/auth0/jwt/impl/ExpectedCheckHolder.java b/lib/src/main/java/com/auth0/jwt/impl/ExpectedCheckHolder.java new file mode 100644 index 00000000..6737031c --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/impl/ExpectedCheckHolder.java @@ -0,0 +1,25 @@ +package com.auth0.jwt.impl; + +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; + +/** + * This holds the checks that are run to verify a JWT. + */ +public interface ExpectedCheckHolder { + /** + * The claim name that will be checked. + * + * @return the claim name + */ + String getClaimName(); + + /** + * The verification that will be run. + * + * @param claim the claim for which verification is done + * @param decodedJWT the JWT on which verification is done + * @return whether the verification passed or not + */ + boolean verify(Claim claim, DecodedJWT decodedJWT); +} diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 9dcb2cb1..72e5656f 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -56,8 +56,18 @@ public void shouldValidateIssuer() { .withIssuer("auth0") .build() .verify(token); - assertThat(jwt, is(notNullValue())); + + // "iss": ["auth0", "okta"] + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, ()-> { + String token1 = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer((String[]) null) + .build() + .verify(token1); + }); + + assertThat(e.getClaimName(), is("iss")); } @Test @@ -179,7 +189,7 @@ public void shouldAllowWithAnyOfAudienceVerificationToOverrideWithAudience() { assertThat(exception, is(instanceOf(IncorrectClaimException.class))); assertThat(exception.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - DecodedJWT jwt = verification.withAnyOfAudience("Mark", "Jim").build().verify(token); + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")).withAnyOfAudience("Mark", "Jim").build().verify(token); assertThat(jwt, is(notNullValue())); } @@ -201,7 +211,7 @@ public void shouldAllowWithAudienceVerificationToOverrideWithAnyOfAudience() { assertThat(exception, is(instanceOf(IncorrectClaimException.class))); assertThat(exception.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - DecodedJWT jwt = verification.withAudience("Mark").build().verify(token); + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")).withAudience("Mark").build().verify(token); assertThat(jwt, is(notNullValue())); } @@ -303,75 +313,15 @@ public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() { } @Test - public void shouldRemoveAudienceWhenPassingNullReference() { - Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) - .withAudience((String) null) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); - - verifier = JWTVerifier.init(algorithm) + public void shouldNotReplaceWhenMultipleChecksAreAdded() { + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) .withAudience((String[]) null) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); - - verifier = JWTVerifier.init(algorithm) .withAudience() - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); - - String emptyAud = " "; - verifier = JWTVerifier.init(algorithm) - .withAudience(emptyAud) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - } - - @Test - public void shouldRemoveAudienceWhenPassingNull() { - Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) - .withAudience("John") - .withAudience((String) null) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("aud"))); - - verifier = JWTVerifier.init(algorithm) - .withAudience("John") - .withAudience((String[]) null) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("aud"))); - } - - @Test - public void shouldRemoveAudienceWhenPassingNullWithAnyAudience() { - Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience("John") - .withAnyOfAudience((String) null) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("aud"))); - - verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience("John") .withAnyOfAudience((String[]) null) + .withAnyOfAudience() .build(); - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("aud"))); + assertThat(verifier.expectedChecks.size(), is(7)); //3 extra mandatory checks exp, nbf, iat } @Test @@ -561,14 +511,14 @@ public void shouldValidateCustomClaimOfTypeDate() { } @Test - public void shouldRemoveCustomClaimOfTypeDateWhenNull() { + public void shouldNotRemoveCustomClaimOfTypeDateWhenNull() { JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) .withClaim("name", new Date()) .withClaim("name", (Date) null) .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(5)); } @Test @@ -900,7 +850,7 @@ public void shouldThrowOnInvalidJWTId() { } @Test - public void shouldRemoveClaimWhenPassingNull() { + public void shouldNotRemoveClaimWhenPassingNull() { Algorithm algorithm = mock(Algorithm.class); JWTVerifier verifier = JWTVerifier.init(algorithm) .withIssuer("iss") @@ -908,7 +858,7 @@ public void shouldRemoveClaimWhenPassingNull() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(5)); verifier = JWTVerifier.init(algorithm) .withIssuer("iss") @@ -916,32 +866,32 @@ public void shouldRemoveClaimWhenPassingNull() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(5)); } @Test - public void shouldRemoveIssuerWhenPassingNullReference() { + public void shouldNotRemoveIssuerWhenPassingNullReference() { Algorithm algorithm = mock(Algorithm.class); JWTVerifier verifier = JWTVerifier.init(algorithm) .withIssuer((String) null) .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(4)); verifier = JWTVerifier.init(algorithm) .withIssuer((String[]) null) .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(4)); verifier = JWTVerifier.init(algorithm) .withIssuer() .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(4)); String emptyIss = " "; verifier = JWTVerifier.init(algorithm) @@ -1111,7 +1061,6 @@ public void shouldSuccessfullyVerifyClaimWithPredicate() { JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) .withClaim("claimName", (claim, decodedJWT) -> "claimValue".equals(claim.asString())) - .withClaim(PublicClaims.ISSUED_AT, ((claim, decodedJWT) -> false)) .build(); DecodedJWT decodedJWT = verifier.verify(jwt); @@ -1136,14 +1085,14 @@ public void shouldThrowWhenPredicateReturnsFalse() { } @Test - public void shouldRemovePredicateCheckForNull() { + public void shouldNotRemovePredicateCheckForNull() { JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString())) .withClaim("claimName", (BiPredicate) null) .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("claimName"))); + assertThat(verifier.expectedChecks.size(), is(5)); } @Test @@ -1192,4 +1141,138 @@ public void shouldThrowWhenNullClaimIsMissing() { assertThat(e.getMessage(), is("The Claim 'anotherClaimName' is not present in the JWT.")); assertThat(e.getClaimName(), is("anotherClaimName")); } + + @Test + public void shouldCheckForNullValuesForSubject() { + // sub = null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOm51bGx9.y5brmQQ05OYwVvlTg83njUrz6tfpdyWNh17LHU6DxmI"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withSubject(null) + .build() + .verify(token); + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldCheckForNullValuesInIssuer() { + // iss = null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOm51bGx9.OoiCLipSfflWxkFX2rytvtwEiJ8eAL0opkdXY_ap0qA"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer((String) null) + .withIssuer((String[]) null) + .withIssuer() + .build() + .verify(token); + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldCheckForNullValuesInJwtId() { + // jti = null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOm51bGx9.z_MDyl8uPGH0q0jeB54wbYt3bwKXamU_3MO8LofGvZs"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withJWTId(null) + .build() + .verify(token); + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldCheckForNullValuesInCustomClaims() { + // jti = null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOm51bGx9.inAuN3Q9UZ6WgbB63O43B1ero2MTqnfzzumr_5qYIls"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("custom", (Boolean) null) + .withClaim("custom", (Integer) null) + .withClaim("custom", (Long) null) + .withClaim("custom", (Double) null) + .withClaim("custom", (String) null) + .withClaim("custom", (Date) null) + .withClaim("custom", (Instant) null) + .withClaim("custom", (BiPredicate) null) + .withArrayClaim("custom", (String[]) null) + .withArrayClaim("custom", (Integer[]) null) + .withArrayClaim("custom", (Long[]) null) + .build() + .verify(token); + assertThat(jwt, is(notNullValue())); + } + + + @Test + public void shouldCheckForNullValuesForAudience() { + // aud = null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpudWxsfQ.bpPyquk3b8KepErKgTidjJ1ZwiOGuoTxam2_x7cElKI"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience((String[]) null) + .withAudience((String) null) + .withAudience() + .withAnyOfAudience((String[]) null) + .withAnyOfAudience((String) null) + .withAnyOfAudience() + .build() + .verify(token); + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldCheckForClaimPresenceEvenForNormalClaimChecks() { + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpudWxsfQ.bpPyquk3b8KepErKgTidjJ1ZwiOGuoTxam2_x7cElKI"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("custom", true) + .build() + .verify(token); + }); + assertThat(e.getClaimName(), is("custom")); + } + + @Test + public void shouldCheckForWrongLongClaim() { + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOjF9.00btiK0sv8pQ2T-hOr9GC5x2osi7--Bsk4pS5cTikqQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("custom", 2L) + .build() + .verify(token); + }); + assertThat(e.getClaimName(), is("custom")); + assertThat(e.getClaimValue().asLong(), is(1L)); + } + + @Test + public void shouldCheckForWrongLongArrayClaim() { + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOlsxXX0.R9ZSmgtJng062rcEc59u4VKCq89Yk5VlkN9BuMTMvr0"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("custom", 2L) + .build() + .verify(token); + }); + assertThat(e.getClaimName(), is("custom")); + } + + @Test + public void shouldCheckForWrongStringArrayClaim() { + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOlsxXX0.R9ZSmgtJng062rcEc59u4VKCq89Yk5VlkN9BuMTMvr0"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("custom", "2L") + .build() + .verify(token); + }); + assertThat(e.getClaimName(), is("custom")); + } + + @Test + public void shouldCheckForWrongIntegerArrayClaim() { + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOlsxXX0.R9ZSmgtJng062rcEc59u4VKCq89Yk5VlkN9BuMTMvr0"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("custom", 2) + .build() + .verify(token); + }); + assertThat(e.getClaimName(), is("custom")); + } } From 05c925edc68eb072e6910055891d1317be2325a2 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 13 Apr 2022 09:58:21 -0500 Subject: [PATCH 089/198] [SDK-3226] Expose claim header constants --- lib/src/main/java/HeaderParams.java | 29 +++++++++++ .../main/java/com/auth0/jwt/JWTCreator.java | 28 +++++----- .../main/java/com/auth0/jwt/JWTVerifier.java | 43 ++++++++------- .../java/com/auth0/jwt/StandardClaims.java | 48 +++++++++++++++++ .../auth0/jwt/impl/HeaderDeserializer.java | 9 ++-- .../auth0/jwt/impl/PayloadDeserializer.java | 15 +++--- .../com/auth0/jwt/impl/PayloadSerializer.java | 3 +- .../java/com/auth0/jwt/impl/PublicClaims.java | 23 -------- .../java/com/auth0/jwt/JWTCreatorTest.java | 21 ++++---- .../java/com/auth0/jwt/JWTDecoderTest.java | 4 +- lib/src/test/java/com/auth0/jwt/JWTTest.java | 6 +-- .../java/com/auth0/jwt/JWTVerifierTest.java | 52 +++++++++---------- 12 files changed, 166 insertions(+), 115 deletions(-) create mode 100644 lib/src/main/java/HeaderParams.java create mode 100644 lib/src/main/java/com/auth0/jwt/StandardClaims.java delete mode 100644 lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java diff --git a/lib/src/main/java/HeaderParams.java b/lib/src/main/java/HeaderParams.java new file mode 100644 index 00000000..e15877a2 --- /dev/null +++ b/lib/src/main/java/HeaderParams.java @@ -0,0 +1,29 @@ +package com.auth0.jwt; + +/** + * Contains constants representing the JWT header parameter names. + */ +public final class HeaderParams { + + private HeaderParams() {} + + /** + * The algorithm used to sign a JWT. + */ + public static String ALGORITHM = "alg"; + + /** + * The content type of a JWT. + */ + public static String CONTENT_TYPE = "cty"; + + /** + * The media type of a JWT. + */ + public static String TYPE = "typ"; + + /** + * The key ID of a JWT used to specify the key for signature validation. + */ + public static String KEY_ID = "kid"; +} diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index e1159ae4..f0299d22 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -104,7 +104,7 @@ public Builder withHeader(Map headerClaims) { * @return this same Builder instance. */ public Builder withKeyId(String keyId) { - this.headerClaims.put(PublicClaims.KEY_ID, keyId); + this.headerClaims.put(HeaderParams.KEY_ID, keyId); return this; } @@ -115,7 +115,7 @@ public Builder withKeyId(String keyId) { * @return this same Builder instance. */ public Builder withIssuer(String issuer) { - addClaim(PublicClaims.ISSUER, issuer); + addClaim(StandardClaims.ISSUER, issuer); return this; } @@ -126,7 +126,7 @@ public Builder withIssuer(String issuer) { * @return this same Builder instance. */ public Builder withSubject(String subject) { - addClaim(PublicClaims.SUBJECT, subject); + addClaim(StandardClaims.SUBJECT, subject); return this; } @@ -137,7 +137,7 @@ public Builder withSubject(String subject) { * @return this same Builder instance. */ public Builder withAudience(String... audience) { - addClaim(PublicClaims.AUDIENCE, audience); + addClaim(StandardClaims.AUDIENCE, audience); return this; } @@ -149,7 +149,7 @@ public Builder withAudience(String... audience) { * @return this same Builder instance. */ public Builder withExpiresAt(Date expiresAt) { - addClaim(PublicClaims.EXPIRES_AT, expiresAt); + addClaim(StandardClaims.EXPIRES_AT, expiresAt); return this; } @@ -161,7 +161,7 @@ public Builder withExpiresAt(Date expiresAt) { * @return this same Builder instance. */ public Builder withExpiresAt(Instant expiresAt) { - addClaim(PublicClaims.EXPIRES_AT, expiresAt); + addClaim(StandardClaims.EXPIRES_AT, expiresAt); return this; } @@ -173,7 +173,7 @@ public Builder withExpiresAt(Instant expiresAt) { * @return this same Builder instance. */ public Builder withNotBefore(Date notBefore) { - addClaim(PublicClaims.NOT_BEFORE, notBefore); + addClaim(StandardClaims.NOT_BEFORE, notBefore); return this; } @@ -185,7 +185,7 @@ public Builder withNotBefore(Date notBefore) { * @return this same Builder instance. */ public Builder withNotBefore(Instant notBefore) { - addClaim(PublicClaims.NOT_BEFORE, notBefore); + addClaim(StandardClaims.NOT_BEFORE, notBefore); return this; } @@ -197,7 +197,7 @@ public Builder withNotBefore(Instant notBefore) { * @return this same Builder instance. */ public Builder withIssuedAt(Date issuedAt) { - addClaim(PublicClaims.ISSUED_AT, issuedAt); + addClaim(StandardClaims.ISSUED_AT, issuedAt); return this; } @@ -209,7 +209,7 @@ public Builder withIssuedAt(Date issuedAt) { * @return this same Builder instance. */ public Builder withIssuedAt(Instant issuedAt) { - addClaim(PublicClaims.ISSUED_AT, issuedAt); + addClaim(StandardClaims.ISSUED_AT, issuedAt); return this; } @@ -220,7 +220,7 @@ public Builder withIssuedAt(Instant issuedAt) { * @return this same Builder instance. */ public Builder withJWTId(String jwtId) { - addClaim(PublicClaims.JWT_ID, jwtId); + addClaim(StandardClaims.JWT_ID, jwtId); return this; } @@ -543,9 +543,9 @@ public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCrea if (algorithm == null) { throw new IllegalArgumentException("The Algorithm cannot be null."); } - headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName()); - if (!headerClaims.containsKey(PublicClaims.TYPE)) { - headerClaims.put(PublicClaims.TYPE, "JWT"); + headerClaims.put(HeaderParams.ALGORITHM, algorithm.getName()); + if (!headerClaims.containsKey(HeaderParams.TYPE)) { + headerClaims.put(HeaderParams.TYPE, "JWT"); } String signingKeyId = algorithm.getSigningKeyId(); if (signingKeyId != null) { diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 801dac24..223de7f5 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -3,7 +3,6 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.*; import com.auth0.jwt.impl.JWTParser; -import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.impl.ExpectedCheckHolder; @@ -71,13 +70,13 @@ public static class BaseVerification implements Verification { @Override public Verification withIssuer(String... issuer) { List value = isNullOrEmpty(issuer) ? null : Arrays.asList(issuer); - addCheck(PublicClaims.ISSUER, ((claim, decodedJWT) -> { + addCheck(StandardClaims.ISSUER, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (value == null || !value.contains(claim.asString())) { throw new IncorrectClaimException( - "The Claim 'iss' value doesn't match the required issuer.", PublicClaims.ISSUER, claim); + "The Claim 'iss' value doesn't match the required issuer.", StandardClaims.ISSUER, claim); } return true; })); @@ -86,7 +85,7 @@ public Verification withIssuer(String... issuer) { @Override public Verification withSubject(String subject) { - addCheck(PublicClaims.SUBJECT, (claim, decodedJWT) -> + addCheck(StandardClaims.SUBJECT, (claim, decodedJWT) -> verifyNull(claim, subject) || subject.equals(claim.asString())); return this; } @@ -94,13 +93,13 @@ public Verification withSubject(String subject) { @Override public Verification withAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - addCheck(PublicClaims.AUDIENCE, ((claim, decodedJWT) -> { + addCheck(StandardClaims.AUDIENCE, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, true)) { throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", - PublicClaims.AUDIENCE, claim); + StandardClaims.AUDIENCE, claim); } return true; })); @@ -110,13 +109,13 @@ public Verification withAudience(String... audience) { @Override public Verification withAnyOfAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - addCheck(PublicClaims.AUDIENCE, ((claim, decodedJWT) -> { + addCheck(StandardClaims.AUDIENCE, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, false)) { throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", - PublicClaims.AUDIENCE, claim); + StandardClaims.AUDIENCE, claim); } return true; })); @@ -133,21 +132,21 @@ public Verification acceptLeeway(long leeway) throws IllegalArgumentException { @Override public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(PublicClaims.EXPIRES_AT, leeway); + customLeeways.put(StandardClaims.EXPIRES_AT, leeway); return this; } @Override public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(PublicClaims.NOT_BEFORE, leeway); + customLeeways.put(StandardClaims.NOT_BEFORE, leeway); return this; } @Override public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(PublicClaims.ISSUED_AT, leeway); + customLeeways.put(StandardClaims.ISSUED_AT, leeway); return this; } @@ -159,7 +158,7 @@ public Verification ignoreIssuedAt() { @Override public Verification withJWTId(String jwtId) { - addCheck(PublicClaims.JWT_ID, ((claim, decodedJWT) -> + addCheck(StandardClaims.JWT_ID, ((claim, decodedJWT) -> verifyNull(claim, jwtId) || jwtId.equals(claim.asString()))); return this; } @@ -297,17 +296,17 @@ public long getLeewayFor(String name) { } private void addMandatoryClaimChecks() { - long expiresAtLeeway = getLeewayFor(PublicClaims.EXPIRES_AT); - long notBeforeLeeway = getLeewayFor(PublicClaims.NOT_BEFORE); - long issuedAtLeeway = getLeewayFor(PublicClaims.ISSUED_AT); - - expectedChecks.add(constructExpectedCheck(PublicClaims.EXPIRES_AT, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.EXPIRES_AT, claim, expiresAtLeeway, true))); - expectedChecks.add(constructExpectedCheck(PublicClaims.NOT_BEFORE, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.NOT_BEFORE, claim, notBeforeLeeway, false))); + long expiresAtLeeway = getLeewayFor(StandardClaims.EXPIRES_AT); + long notBeforeLeeway = getLeewayFor(StandardClaims.NOT_BEFORE); + long issuedAtLeeway = getLeewayFor(StandardClaims.ISSUED_AT); + + expectedChecks.add(constructExpectedCheck(StandardClaims.EXPIRES_AT, (claim, decodedJWT) -> + assertValidInstantClaim(StandardClaims.EXPIRES_AT, claim, expiresAtLeeway, true))); + expectedChecks.add(constructExpectedCheck(StandardClaims.NOT_BEFORE, (claim, decodedJWT) -> + assertValidInstantClaim(StandardClaims.NOT_BEFORE, claim, notBeforeLeeway, false))); if (!ignoreIssuedAt) { - expectedChecks.add(constructExpectedCheck(PublicClaims.ISSUED_AT, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.ISSUED_AT, claim, issuedAtLeeway, false))); + expectedChecks.add(constructExpectedCheck(StandardClaims.ISSUED_AT, (claim, decodedJWT) -> + assertValidInstantClaim(StandardClaims.ISSUED_AT, claim, issuedAtLeeway, false))); } } diff --git a/lib/src/main/java/com/auth0/jwt/StandardClaims.java b/lib/src/main/java/com/auth0/jwt/StandardClaims.java new file mode 100644 index 00000000..4fe82d64 --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/StandardClaims.java @@ -0,0 +1,48 @@ +package com.auth0.jwt; + +/** + * Contains constants representing the name of the Registered Claim Names as defined in Section 4.1.1 of + * RFC 7529 + */ +public final class StandardClaims { + + private StandardClaims() { + } + + /** + * The "iss" (issuer) claim identifies the principal that issued the JWT. + */ + public static String ISSUER = "iss"; + + /** + * The "sub" (subject) claim identifies the principal that is the subject of the JWT. + */ + public static String SUBJECT = "sub"; + + /** + * The "aud" (audience) claim identifies the recipients that the JWT is intended for. + */ + public static String AUDIENCE = "aud"; + + /** + * The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be + * accepted for processing. + */ + public static String EXPIRES_AT = "exp"; + + /** + * The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. + */ + public static String NOT_BEFORE = "nbf"; + + /** + * The "iat" (issued at) claim identifies the time at which the JWT was issued. + */ + public static String ISSUED_AT = "iat"; + + /** + * The "jti" (JWT ID) claim provides a unique identifier for the JWT. + */ + public static String JWT_ID = "jti"; + +} diff --git a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java index 8a6cbcf8..9293fd4d 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java @@ -1,5 +1,6 @@ package com.auth0.jwt.impl; +import com.auth0.jwt.HeaderParams; import com.auth0.jwt.exceptions.JWTDecodeException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; @@ -40,10 +41,10 @@ public BasicHeader deserialize(JsonParser p, DeserializationContext ctxt) throws throw new JWTDecodeException("Parsing the Header's JSON resulted on a Null map"); } - String algorithm = getString(tree, PublicClaims.ALGORITHM); - String type = getString(tree, PublicClaims.TYPE); - String contentType = getString(tree, PublicClaims.CONTENT_TYPE); - String keyId = getString(tree, PublicClaims.KEY_ID); + String algorithm = getString(tree, HeaderParams.ALGORITHM); + String type = getString(tree, HeaderParams.TYPE); + String contentType = getString(tree, HeaderParams.CONTENT_TYPE); + String keyId = getString(tree, HeaderParams.KEY_ID); return new BasicHeader(algorithm, type, contentType, keyId, tree, objectReader); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 09009267..4d058826 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -1,5 +1,6 @@ package com.auth0.jwt.impl; +import com.auth0.jwt.StandardClaims; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.Payload; import com.fasterxml.jackson.core.JsonParser; @@ -43,13 +44,13 @@ public Payload deserialize(JsonParser p, DeserializationContext ctxt) throws IOE throw new JWTDecodeException("Parsing the Payload's JSON resulted on a Null map"); } - String issuer = getString(tree, PublicClaims.ISSUER); - String subject = getString(tree, PublicClaims.SUBJECT); - List audience = getStringOrArray(tree, PublicClaims.AUDIENCE); - Instant expiresAt = getInstantFromSeconds(tree, PublicClaims.EXPIRES_AT); - Instant notBefore = getInstantFromSeconds(tree, PublicClaims.NOT_BEFORE); - Instant issuedAt = getInstantFromSeconds(tree, PublicClaims.ISSUED_AT); - String jwtId = getString(tree, PublicClaims.JWT_ID); + String issuer = getString(tree, StandardClaims.ISSUER); + String subject = getString(tree, StandardClaims.SUBJECT); + List audience = getStringOrArray(tree, StandardClaims.AUDIENCE); + Instant expiresAt = getInstantFromSeconds(tree, StandardClaims.EXPIRES_AT); + Instant notBefore = getInstantFromSeconds(tree, StandardClaims.NOT_BEFORE); + Instant issuedAt = getInstantFromSeconds(tree, StandardClaims.ISSUED_AT); + String jwtId = getString(tree, StandardClaims.JWT_ID); return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, objectReader); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index dc138c08..1d2ada43 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -1,5 +1,6 @@ package com.auth0.jwt.impl; +import com.auth0.jwt.StandardClaims; import com.fasterxml.jackson.core.JsonGenerator; import java.io.IOException; @@ -22,7 +23,7 @@ public PayloadSerializer() { @Override protected void writeClaim(Map.Entry entry, JsonGenerator gen) throws IOException { - if (PublicClaims.AUDIENCE.equals(entry.getKey())) { + if (StandardClaims.AUDIENCE.equals(entry.getKey())) { writeAudience(gen, entry); } else { super.writeClaim(entry, gen); diff --git a/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java b/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java deleted file mode 100644 index ea166b0d..00000000 --- a/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.auth0.jwt.impl; - -/** - * Contains the claim name for all Public claims. - */ -public interface PublicClaims { - - //Header - String ALGORITHM = "alg"; - String CONTENT_TYPE = "cty"; - String TYPE = "typ"; - String KEY_ID = "kid"; - - //Payload - String ISSUER = "iss"; - String SUBJECT = "sub"; - String EXPIRES_AT = "exp"; - String NOT_BEFORE = "nbf"; - String ISSUED_AT = "iat"; - String JWT_ID = "jti"; - String AUDIENCE = "aud"; - -} diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 3cccbf7b..b28035f8 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -1,7 +1,6 @@ package com.auth0.jwt; import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; import com.fasterxml.jackson.databind.ObjectMapper; @@ -92,7 +91,7 @@ public void shouldReturnBuilderIfNullMapIsProvided() { @Test public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() { Map header = new HashMap<>(); - header.put(PublicClaims.KEY_ID, "xyz"); + header.put(HeaderParams.KEY_ID, "xyz"); String signed = JWTCreator.init() .withKeyId("abc") @@ -102,13 +101,13 @@ public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); - assertThat(headerJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "xyz")); + assertThat(headerJson, JsonMatcher.hasEntry(HeaderParams.KEY_ID, "xyz")); } @Test public void shouldOverwriteExistingHeadersWhenSettingSameHeaderKey() { Map header = new HashMap<>(); - header.put(PublicClaims.KEY_ID, "xyz"); + header.put(HeaderParams.KEY_ID, "xyz"); String signed = JWTCreator.init() .withHeader(header) @@ -118,13 +117,13 @@ public void shouldOverwriteExistingHeadersWhenSettingSameHeaderKey() { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); - assertThat(headerJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "abc")); + assertThat(headerJson, JsonMatcher.hasEntry(HeaderParams.KEY_ID, "abc")); } @Test public void shouldRemoveHeaderIfTheValueIsNull() { Map header = new HashMap<>(); - header.put(PublicClaims.KEY_ID, null); + header.put(HeaderParams.KEY_ID, null); header.put("test2", "isSet"); String signed = JWTCreator.init() @@ -135,7 +134,7 @@ public void shouldRemoveHeaderIfTheValueIsNull() { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); - assertThat(headerJson, JsonMatcher.isNotPresent(PublicClaims.KEY_ID)); + assertThat(headerJson, JsonMatcher.isNotPresent(HeaderParams.KEY_ID)); assertThat(headerJson, JsonMatcher.hasEntry("test2", "isSet")); } @@ -728,7 +727,7 @@ public void withPayloadShouldCreateJwtWithEmptyBodyIfPayloadNull() { @Test public void withPayloadShouldOverwriteExistingClaimIfPayloadMapContainsTheSameKey() { Map payload = new HashMap<>(); - payload.put(PublicClaims.KEY_ID, "xyz"); + payload.put(HeaderParams.KEY_ID, "xyz"); String jwt = JWTCreator.init() .withKeyId("abc") @@ -738,13 +737,13 @@ public void withPayloadShouldOverwriteExistingClaimIfPayloadMapContainsTheSameKe assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "xyz")); + assertThat(payloadJson, JsonMatcher.hasEntry(HeaderParams.KEY_ID, "xyz")); } @Test public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { Map payload = new HashMap<>(); - payload.put(PublicClaims.ISSUER, "xyz"); + payload.put(StandardClaims.ISSUER, "xyz"); String jwt = JWTCreator.init() .withPayload(payload) @@ -754,7 +753,7 @@ public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.ISSUER, "abc")); + assertThat(payloadJson, JsonMatcher.hasEntry(StandardClaims.ISSUER, "abc")); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index 5ea2b4ef..82dc895c 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -121,7 +121,7 @@ public void shouldGetSignature() { assertThat(jwt.getSignature(), is("XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ")); } - // Public PublicClaims + // Standard Claims @Test public void shouldGetIssuer() { @@ -208,7 +208,7 @@ public void shouldGetAlgorithm() { assertThat(jwt.getAlgorithm(), is("HS256")); } - //Private PublicClaims + // Private Claims @Test public void shouldGetMissingClaimIfClaimDoesNotExist() { diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index 958e89fb..b9f56a2e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -10,8 +10,6 @@ import java.nio.charset.StandardCharsets; import java.security.interfaces.ECKey; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAKey; import java.time.Clock; import java.time.Instant; @@ -19,8 +17,8 @@ import java.util.Base64; import java.util.Date; -import static org.hamcrest.Matchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; public class JWTTest { @@ -199,7 +197,7 @@ public void shouldAcceptECDSA512Algorithm() throws Exception { } - // Public Claims + // Standard Claims @Test public void shouldGetAlgorithm() { diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 72e5656f..d2715262 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -2,7 +2,6 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.*; -import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; @@ -14,7 +13,6 @@ import java.time.Duration; import java.time.Instant; import java.time.ZoneId; -import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.function.BiPredicate; @@ -92,7 +90,7 @@ public void shouldThrowOnInvalidIssuer() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); - assertThat(e.getClaimName(), is(PublicClaims.ISSUER)); + assertThat(e.getClaimName(), is(StandardClaims.ISSUER)); assertThat(e.getClaimValue().asString(), is("auth0")); } @@ -106,7 +104,7 @@ public void shouldThrowOnNullIssuer() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); - assertThat(e.getClaimName(), is(PublicClaims.ISSUER)); + assertThat(e.getClaimName(), is(StandardClaims.ISSUER)); assertThat(e.getClaimValue().isNull(), is(true)); } @@ -146,7 +144,7 @@ public void shouldThrowOnInvalidSubject() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'sub' value doesn't match the required one.")); - assertThat(e.getClaimName(), is(PublicClaims.SUBJECT)); + assertThat(e.getClaimName(), is(StandardClaims.SUBJECT)); assertThat(e.getClaimValue().asString(), is("1234567890")); } @@ -250,7 +248,7 @@ public void shouldThrowWhenAudienceHasNoneOfExpectedAnyOfAudience() { .verify(tokenArr); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @@ -265,7 +263,7 @@ public void shouldThrowWhenAudienceClaimDoesNotContainAllExpected() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @@ -280,7 +278,7 @@ public void shouldThrowWhenAudienceClaimIsNull() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); assertThat(e.getClaimValue().isNull(), is(true)); } @@ -308,7 +306,7 @@ public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {null})); } @@ -585,9 +583,9 @@ public void shouldAddDefaultLeewayToDateClaims() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(0L)); - assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(0L)); - assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(0L)); + assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(0L)); + assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(0L)); + assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(0L)); } @Test @@ -599,9 +597,9 @@ public void shouldAddCustomLeewayToDateClaims() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); } @Test @@ -614,9 +612,9 @@ public void shouldOverrideDefaultIssuedAtLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(9999L)); - assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(9999L)); + assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); } @Test @@ -629,9 +627,9 @@ public void shouldOverrideDefaultExpiresAtLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(9999L)); - assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(9999L)); + assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); } @Test @@ -644,9 +642,9 @@ public void shouldOverrideDefaultNotBeforeLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(9999L)); + assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(9999L)); } @Test @@ -727,7 +725,7 @@ public void shouldThrowOnInvalidNotBeforeIfPresent() { .verify(token); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(PublicClaims.NOT_BEFORE)); + assertThat(e.getClaimName(), is(StandardClaims.NOT_BEFORE)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } @@ -762,7 +760,7 @@ public void shouldThrowOnFutureIssuedAt() { assertThat(jwt, is(notNullValue())); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(PublicClaims.ISSUED_AT)); + assertThat(e.getClaimName(), is(StandardClaims.ISSUED_AT)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } @@ -787,7 +785,7 @@ public void shouldThrowOnInvalidIssuedAtIfPresent() { .verify(token); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(PublicClaims.ISSUED_AT)); + assertThat(e.getClaimName(), is(StandardClaims.ISSUED_AT)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } From 5da2806b592daeef57b37e5d5461de6d0ef2bbe9 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 13 Apr 2022 12:55:40 -0500 Subject: [PATCH 090/198] fix packaging --- lib/src/main/java/{ => com/auth0/jwt}/HeaderParams.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/src/main/java/{ => com/auth0/jwt}/HeaderParams.java (100%) diff --git a/lib/src/main/java/HeaderParams.java b/lib/src/main/java/com/auth0/jwt/HeaderParams.java similarity index 100% rename from lib/src/main/java/HeaderParams.java rename to lib/src/main/java/com/auth0/jwt/HeaderParams.java From 035f323dc4e0c4654c661c050843f521028aec21 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 13 Apr 2022 13:01:04 -0500 Subject: [PATCH 091/198] Update class names --- .../main/java/com/auth0/jwt/JWTCreator.java | 20 ++++---- .../main/java/com/auth0/jwt/JWTVerifier.java | 42 ++++++++-------- ...ndardClaims.java => RegisteredClaims.java} | 4 +- .../auth0/jwt/impl/PayloadDeserializer.java | 16 +++--- .../com/auth0/jwt/impl/PayloadSerializer.java | 4 +- .../java/com/auth0/jwt/JWTCreatorTest.java | 4 +- .../java/com/auth0/jwt/JWTVerifierTest.java | 50 +++++++++---------- 7 files changed, 70 insertions(+), 70 deletions(-) rename lib/src/main/java/com/auth0/jwt/{StandardClaims.java => RegisteredClaims.java} (94%) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index f0299d22..cab474f7 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -115,7 +115,7 @@ public Builder withKeyId(String keyId) { * @return this same Builder instance. */ public Builder withIssuer(String issuer) { - addClaim(StandardClaims.ISSUER, issuer); + addClaim(RegisteredClaims.ISSUER, issuer); return this; } @@ -126,7 +126,7 @@ public Builder withIssuer(String issuer) { * @return this same Builder instance. */ public Builder withSubject(String subject) { - addClaim(StandardClaims.SUBJECT, subject); + addClaim(RegisteredClaims.SUBJECT, subject); return this; } @@ -137,7 +137,7 @@ public Builder withSubject(String subject) { * @return this same Builder instance. */ public Builder withAudience(String... audience) { - addClaim(StandardClaims.AUDIENCE, audience); + addClaim(RegisteredClaims.AUDIENCE, audience); return this; } @@ -149,7 +149,7 @@ public Builder withAudience(String... audience) { * @return this same Builder instance. */ public Builder withExpiresAt(Date expiresAt) { - addClaim(StandardClaims.EXPIRES_AT, expiresAt); + addClaim(RegisteredClaims.EXPIRES_AT, expiresAt); return this; } @@ -161,7 +161,7 @@ public Builder withExpiresAt(Date expiresAt) { * @return this same Builder instance. */ public Builder withExpiresAt(Instant expiresAt) { - addClaim(StandardClaims.EXPIRES_AT, expiresAt); + addClaim(RegisteredClaims.EXPIRES_AT, expiresAt); return this; } @@ -173,7 +173,7 @@ public Builder withExpiresAt(Instant expiresAt) { * @return this same Builder instance. */ public Builder withNotBefore(Date notBefore) { - addClaim(StandardClaims.NOT_BEFORE, notBefore); + addClaim(RegisteredClaims.NOT_BEFORE, notBefore); return this; } @@ -185,7 +185,7 @@ public Builder withNotBefore(Date notBefore) { * @return this same Builder instance. */ public Builder withNotBefore(Instant notBefore) { - addClaim(StandardClaims.NOT_BEFORE, notBefore); + addClaim(RegisteredClaims.NOT_BEFORE, notBefore); return this; } @@ -197,7 +197,7 @@ public Builder withNotBefore(Instant notBefore) { * @return this same Builder instance. */ public Builder withIssuedAt(Date issuedAt) { - addClaim(StandardClaims.ISSUED_AT, issuedAt); + addClaim(RegisteredClaims.ISSUED_AT, issuedAt); return this; } @@ -209,7 +209,7 @@ public Builder withIssuedAt(Date issuedAt) { * @return this same Builder instance. */ public Builder withIssuedAt(Instant issuedAt) { - addClaim(StandardClaims.ISSUED_AT, issuedAt); + addClaim(RegisteredClaims.ISSUED_AT, issuedAt); return this; } @@ -220,7 +220,7 @@ public Builder withIssuedAt(Instant issuedAt) { * @return this same Builder instance. */ public Builder withJWTId(String jwtId) { - addClaim(StandardClaims.JWT_ID, jwtId); + addClaim(RegisteredClaims.JWT_ID, jwtId); return this; } diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 223de7f5..8b4639ac 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -70,13 +70,13 @@ public static class BaseVerification implements Verification { @Override public Verification withIssuer(String... issuer) { List value = isNullOrEmpty(issuer) ? null : Arrays.asList(issuer); - addCheck(StandardClaims.ISSUER, ((claim, decodedJWT) -> { + addCheck(RegisteredClaims.ISSUER, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (value == null || !value.contains(claim.asString())) { throw new IncorrectClaimException( - "The Claim 'iss' value doesn't match the required issuer.", StandardClaims.ISSUER, claim); + "The Claim 'iss' value doesn't match the required issuer.", RegisteredClaims.ISSUER, claim); } return true; })); @@ -85,7 +85,7 @@ public Verification withIssuer(String... issuer) { @Override public Verification withSubject(String subject) { - addCheck(StandardClaims.SUBJECT, (claim, decodedJWT) -> + addCheck(RegisteredClaims.SUBJECT, (claim, decodedJWT) -> verifyNull(claim, subject) || subject.equals(claim.asString())); return this; } @@ -93,13 +93,13 @@ public Verification withSubject(String subject) { @Override public Verification withAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - addCheck(StandardClaims.AUDIENCE, ((claim, decodedJWT) -> { + addCheck(RegisteredClaims.AUDIENCE, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, true)) { throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", - StandardClaims.AUDIENCE, claim); + RegisteredClaims.AUDIENCE, claim); } return true; })); @@ -109,13 +109,13 @@ public Verification withAudience(String... audience) { @Override public Verification withAnyOfAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - addCheck(StandardClaims.AUDIENCE, ((claim, decodedJWT) -> { + addCheck(RegisteredClaims.AUDIENCE, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, false)) { throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", - StandardClaims.AUDIENCE, claim); + RegisteredClaims.AUDIENCE, claim); } return true; })); @@ -132,21 +132,21 @@ public Verification acceptLeeway(long leeway) throws IllegalArgumentException { @Override public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(StandardClaims.EXPIRES_AT, leeway); + customLeeways.put(RegisteredClaims.EXPIRES_AT, leeway); return this; } @Override public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(StandardClaims.NOT_BEFORE, leeway); + customLeeways.put(RegisteredClaims.NOT_BEFORE, leeway); return this; } @Override public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(StandardClaims.ISSUED_AT, leeway); + customLeeways.put(RegisteredClaims.ISSUED_AT, leeway); return this; } @@ -158,7 +158,7 @@ public Verification ignoreIssuedAt() { @Override public Verification withJWTId(String jwtId) { - addCheck(StandardClaims.JWT_ID, ((claim, decodedJWT) -> + addCheck(RegisteredClaims.JWT_ID, ((claim, decodedJWT) -> verifyNull(claim, jwtId) || jwtId.equals(claim.asString()))); return this; } @@ -296,17 +296,17 @@ public long getLeewayFor(String name) { } private void addMandatoryClaimChecks() { - long expiresAtLeeway = getLeewayFor(StandardClaims.EXPIRES_AT); - long notBeforeLeeway = getLeewayFor(StandardClaims.NOT_BEFORE); - long issuedAtLeeway = getLeewayFor(StandardClaims.ISSUED_AT); - - expectedChecks.add(constructExpectedCheck(StandardClaims.EXPIRES_AT, (claim, decodedJWT) -> - assertValidInstantClaim(StandardClaims.EXPIRES_AT, claim, expiresAtLeeway, true))); - expectedChecks.add(constructExpectedCheck(StandardClaims.NOT_BEFORE, (claim, decodedJWT) -> - assertValidInstantClaim(StandardClaims.NOT_BEFORE, claim, notBeforeLeeway, false))); + long expiresAtLeeway = getLeewayFor(RegisteredClaims.EXPIRES_AT); + long notBeforeLeeway = getLeewayFor(RegisteredClaims.NOT_BEFORE); + long issuedAtLeeway = getLeewayFor(RegisteredClaims.ISSUED_AT); + + expectedChecks.add(constructExpectedCheck(RegisteredClaims.EXPIRES_AT, (claim, decodedJWT) -> + assertValidInstantClaim(RegisteredClaims.EXPIRES_AT, claim, expiresAtLeeway, true))); + expectedChecks.add(constructExpectedCheck(RegisteredClaims.NOT_BEFORE, (claim, decodedJWT) -> + assertValidInstantClaim(RegisteredClaims.NOT_BEFORE, claim, notBeforeLeeway, false))); if (!ignoreIssuedAt) { - expectedChecks.add(constructExpectedCheck(StandardClaims.ISSUED_AT, (claim, decodedJWT) -> - assertValidInstantClaim(StandardClaims.ISSUED_AT, claim, issuedAtLeeway, false))); + expectedChecks.add(constructExpectedCheck(RegisteredClaims.ISSUED_AT, (claim, decodedJWT) -> + assertValidInstantClaim(RegisteredClaims.ISSUED_AT, claim, issuedAtLeeway, false))); } } diff --git a/lib/src/main/java/com/auth0/jwt/StandardClaims.java b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java similarity index 94% rename from lib/src/main/java/com/auth0/jwt/StandardClaims.java rename to lib/src/main/java/com/auth0/jwt/RegisteredClaims.java index 4fe82d64..d60d9fc5 100644 --- a/lib/src/main/java/com/auth0/jwt/StandardClaims.java +++ b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java @@ -4,9 +4,9 @@ * Contains constants representing the name of the Registered Claim Names as defined in Section 4.1.1 of * RFC 7529 */ -public final class StandardClaims { +public final class RegisteredClaims { - private StandardClaims() { + private RegisteredClaims() { } /** diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 4d058826..37e70f7a 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -1,6 +1,6 @@ package com.auth0.jwt.impl; -import com.auth0.jwt.StandardClaims; +import com.auth0.jwt.RegisteredClaims; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.Payload; import com.fasterxml.jackson.core.JsonParser; @@ -44,13 +44,13 @@ public Payload deserialize(JsonParser p, DeserializationContext ctxt) throws IOE throw new JWTDecodeException("Parsing the Payload's JSON resulted on a Null map"); } - String issuer = getString(tree, StandardClaims.ISSUER); - String subject = getString(tree, StandardClaims.SUBJECT); - List audience = getStringOrArray(tree, StandardClaims.AUDIENCE); - Instant expiresAt = getInstantFromSeconds(tree, StandardClaims.EXPIRES_AT); - Instant notBefore = getInstantFromSeconds(tree, StandardClaims.NOT_BEFORE); - Instant issuedAt = getInstantFromSeconds(tree, StandardClaims.ISSUED_AT); - String jwtId = getString(tree, StandardClaims.JWT_ID); + String issuer = getString(tree, RegisteredClaims.ISSUER); + String subject = getString(tree, RegisteredClaims.SUBJECT); + List audience = getStringOrArray(tree, RegisteredClaims.AUDIENCE); + Instant expiresAt = getInstantFromSeconds(tree, RegisteredClaims.EXPIRES_AT); + Instant notBefore = getInstantFromSeconds(tree, RegisteredClaims.NOT_BEFORE); + Instant issuedAt = getInstantFromSeconds(tree, RegisteredClaims.ISSUED_AT); + String jwtId = getString(tree, RegisteredClaims.JWT_ID); return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, objectReader); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index 1d2ada43..c8df2bb9 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -1,6 +1,6 @@ package com.auth0.jwt.impl; -import com.auth0.jwt.StandardClaims; +import com.auth0.jwt.RegisteredClaims; import com.fasterxml.jackson.core.JsonGenerator; import java.io.IOException; @@ -23,7 +23,7 @@ public PayloadSerializer() { @Override protected void writeClaim(Map.Entry entry, JsonGenerator gen) throws IOException { - if (StandardClaims.AUDIENCE.equals(entry.getKey())) { + if (RegisteredClaims.AUDIENCE.equals(entry.getKey())) { writeAudience(gen, entry); } else { super.writeClaim(entry, gen); diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index b28035f8..efe6b94b 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -743,7 +743,7 @@ public void withPayloadShouldOverwriteExistingClaimIfPayloadMapContainsTheSameKe @Test public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { Map payload = new HashMap<>(); - payload.put(StandardClaims.ISSUER, "xyz"); + payload.put(RegisteredClaims.ISSUER, "xyz"); String jwt = JWTCreator.init() .withPayload(payload) @@ -753,7 +753,7 @@ public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - assertThat(payloadJson, JsonMatcher.hasEntry(StandardClaims.ISSUER, "abc")); + assertThat(payloadJson, JsonMatcher.hasEntry(RegisteredClaims.ISSUER, "abc")); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index d2715262..6d8ba201 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -90,7 +90,7 @@ public void shouldThrowOnInvalidIssuer() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); - assertThat(e.getClaimName(), is(StandardClaims.ISSUER)); + assertThat(e.getClaimName(), is(RegisteredClaims.ISSUER)); assertThat(e.getClaimValue().asString(), is("auth0")); } @@ -104,7 +104,7 @@ public void shouldThrowOnNullIssuer() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); - assertThat(e.getClaimName(), is(StandardClaims.ISSUER)); + assertThat(e.getClaimName(), is(RegisteredClaims.ISSUER)); assertThat(e.getClaimValue().isNull(), is(true)); } @@ -144,7 +144,7 @@ public void shouldThrowOnInvalidSubject() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'sub' value doesn't match the required one.")); - assertThat(e.getClaimName(), is(StandardClaims.SUBJECT)); + assertThat(e.getClaimName(), is(RegisteredClaims.SUBJECT)); assertThat(e.getClaimValue().asString(), is("1234567890")); } @@ -248,7 +248,7 @@ public void shouldThrowWhenAudienceHasNoneOfExpectedAnyOfAudience() { .verify(tokenArr); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @@ -263,7 +263,7 @@ public void shouldThrowWhenAudienceClaimDoesNotContainAllExpected() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @@ -278,7 +278,7 @@ public void shouldThrowWhenAudienceClaimIsNull() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE)); assertThat(e.getClaimValue().isNull(), is(true)); } @@ -306,7 +306,7 @@ public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {null})); } @@ -583,9 +583,9 @@ public void shouldAddDefaultLeewayToDateClaims() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(0L)); - assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(0L)); - assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(0L)); + assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(0L)); + assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(0L)); + assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(0L)); } @Test @@ -597,9 +597,9 @@ public void shouldAddCustomLeewayToDateClaims() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(1234L)); } @Test @@ -612,9 +612,9 @@ public void shouldOverrideDefaultIssuedAtLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(9999L)); - assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(9999L)); + assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(1234L)); } @Test @@ -627,9 +627,9 @@ public void shouldOverrideDefaultExpiresAtLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(9999L)); - assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(9999L)); + assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(1234L)); } @Test @@ -642,9 +642,9 @@ public void shouldOverrideDefaultNotBeforeLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(9999L)); + assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(9999L)); } @Test @@ -725,7 +725,7 @@ public void shouldThrowOnInvalidNotBeforeIfPresent() { .verify(token); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(StandardClaims.NOT_BEFORE)); + assertThat(e.getClaimName(), is(RegisteredClaims.NOT_BEFORE)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } @@ -760,7 +760,7 @@ public void shouldThrowOnFutureIssuedAt() { assertThat(jwt, is(notNullValue())); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(StandardClaims.ISSUED_AT)); + assertThat(e.getClaimName(), is(RegisteredClaims.ISSUED_AT)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } @@ -785,7 +785,7 @@ public void shouldThrowOnInvalidIssuedAtIfPresent() { .verify(token); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(StandardClaims.ISSUED_AT)); + assertThat(e.getClaimName(), is(RegisteredClaims.ISSUED_AT)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } From 537f9863b8d3297d7aa7660094a690e5909afdf8 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Thu, 14 Apr 2022 16:32:58 -0400 Subject: [PATCH 092/198] Add Semgrep to continuous integration tests This PR updates the CircleCI workflow for v4 to add Semgrep automated security testing to the continuous integration tests. --- .circleci/config.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a299cb9d..14f9652e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -53,11 +53,26 @@ jobs: # GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' # _JAVA_OPTIONS: "-Xms512m -Xmx1024m" # TERM: dumb + semgrep: + docker: + - image: returntocorp/semgrep-agent:v1 + environment: + SEMGREP_REPO_NAME: "auth0/java-jwt" + SEMGREP_REPO_URL: "https://github.com/auth0/java-jwt" + steps: + - checkout + - run: + name: Run vulnerabilities tests (Semgrep) + command: | + semgrep-agent --baseline-ref v4-dev --publish-token $SEMGREP_TOKEN workflows: build-and-test: jobs: - build + - semgrep: + context: + - semgrep-env # api-diff: # jobs: -# - api-diff \ No newline at end of file +# - api-diff From 54d97c68ad2e4b57aa21dea6bc3ed6cec0c0479d Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Thu, 14 Apr 2022 16:34:15 -0400 Subject: [PATCH 093/198] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 14f9652e..f47d6efa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,7 +64,7 @@ jobs: - run: name: Run vulnerabilities tests (Semgrep) command: | - semgrep-agent --baseline-ref v4-dev --publish-token $SEMGREP_TOKEN + semgrep-agent --baseline-ref master --publish-token $SEMGREP_TOKEN workflows: build-and-test: From 5d8776d7e3d14f9d454404813ae0656c3a8edf2d Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 18 Apr 2022 17:04:19 -0500 Subject: [PATCH 094/198] [SDK-3244] Add Migration Guide --- MIGRATION_GUIDE.md | 84 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 MIGRATION_GUIDE.md diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md new file mode 100644 index 00000000..1f95f323 --- /dev/null +++ b/MIGRATION_GUIDE.md @@ -0,0 +1,84 @@ +# Migration Guide + +## Upgrading from v3.x -> v4.0 + +The version 4 release contains several improvements: + +- Support for `java.time.Instant` when creating or verifying JWTs with Numeric Date claim values. +- Improvements to JWT claim validation, including support for custom claim validation using Predicates. +- Improved exception handling when validating JWTs, to better inform of the reason for failed validation. +- Consistent handling of `null` claim values both when creating and validation JWTs. + +This guide captures the changes you should be aware of when planning and upgrading to version 4. + +### Compile or runtime breaking changes + +**Classes or methods removed:** +- The `impl` package has been removed as an export in `module-info.java`. This package contains implementation-specific code that may change at any point. +- Support for the ES256K algorithm has been removed, as it is disabled in Java 15+. The `Algorithm#ECDSA256K(ECDSAKeyProvider keyProvider)` and `Algorithm#ECDSA256K(ECPublicKey publicKey, ECPrivateKey privateKey)` methods have been removed. +- `com.auth0.jwt.interfaces.Clock` has been removed. Instead, an implementation of `java.time.Clock` can be passed to the `BaseVerification` for testing purposes. +- `com.auth0.jwt.impl.NullClaim` has been removed. `Claim#isNull` can be used to determine if a claim's value is `null`. +- `com.auth0.jwt.impl.PublicClaims` was removed, and replaced by `com.auth0.jwt.StandardClaims` and `com.auth0.jwt.HeaderParams`. + +### Behavioral potentially breaking changes + +#### JWT creation + +- All date/time claim values are now serialized as **seconds since the epoch**, in both the payload and header. In version 3, date/time claims nested in a list or map, as well as any header parameters with date/time values, were serialized as milliseconds since the epoch. +- When creating a JWT, passing `null` as the value no longer removes the claim if it was previously added to the builder. It now adds the claim with a `null` value. + +#### JWT validation + +- In version 3, specifying multiple claim expectations for the same claim name would override any previous expectations for that claim. In version 4, all expectations for that claim will be validated. +- In version 3, passing `null` for the value of a claim expectation would remove that expectation from the validation. In version 4, passing `null` does not remove that expectation, but instead validates that the claim has the literal value `null`. +- When validating a JWT, if an expected claim is present in the JWT but contains a value different from the one expected, an `IncorrectClaimException` (subclass of `InvalidClaimException`) will now be thrown instead of an `InvalidClaimException`. +- When validating a JWT, if an expected claim is not present in the JWT, an `MissingClaimException` (subclass of `InvalidClaimException`) will now be thrown instead of an `InvalidClaimException`. +- `withClaimPresence(String claimName)` now validates that the claim is present in the JWT, and a claim with a `null` value is considered present. Previously, a claim with a value of `null` would be considered as missing and fail the validation. +- When validating a date/time claim value, the validation no longer checks for strict equality of the claim's value and the provided `Date` (or `Instant`). Instead, the expected `Date` or `Instant` will be compared to the claim's value only considering seconds (because JWT date/time claims are represented as seconds since the epoch). + +#### Claim changes + +- `com.auth0.jwt.interfaces.Claim#isNull()` now returns true only if the claim is present and its value is `null`. Previously, it returned true if the claim was present and its value was `null`, or if the claim was not present in the JWT. To check if the claim is present or not in the JWT, use `isMissing()`. + +### New classes or methods + +#### `IncorrectClaimException` added + +This class extends `InvalidClaimException` and represents that when validating a JWT, an expected claim exists in the JWT but does not match the expected value. + +#### `MissingClaimException` added + +This class extends `InvalidClaimException` and represents that when validating a JWT, an expected claim is missing from the JWT. + +### `HeaderParams` added + +This class contains constants representing common header parameter names. + +### `StandardClaims` added + +This class contains constants representing the standard claim names. + +#### `JWTCreator` new methods + +- `JWTCreator.Builder#withExpiresAt(Instant expiresAt)` - adds the `exp` claim to the JWT from a `java.time.Instant`. +- `JWTCreator.Builder#withNotBefore(Instant notBefore)` - adds the `nbf` claim to the JWT from a `java.time.Instant`. +- `JWTCreator.Builder#withIssuedAt(Instant issuedAt)` - adds the `iat` claim to the JWT from a `java.time.Instant`. +- `JWTCreator.Builder#withClaim(String claimName, Instant value)` - adds a claim to the JWT from a `java.time.Instant`. +- `JWTCreator.Builder#withNullClaim(String claimName)` - adds a claim to the JWT with the literal value `null`. + +#### `DecodedJWT` new methods + +- `Instant getExpiresAtAsInstant()` - Returns a JWT's `exp` claim as a `java.time.Instant`. +- `Instant getNotBeforeAsInstant()` - Returns a JWT's `nbf` claim as a `java.time.Instant`. +- `Instant getIssuedAtAsInstant()` - Returns a JWT's `iat` claim as a `java.time.Instant`. + +#### `Claim` new methods + +- `Instant asInstant()` - Gets a claim as a `java.time.Instant`. +- `boolean isMissing()` - Returns whether the claim is present or not. + +#### `Verification` new methods + +- `Verification withClaim(String name, Instant value)` - Adds an expectation that a claim with the provided name has a value equal to the provided `java.time.Instant`. +- `Verification withClaim(String name, BiPredicate predicate)` - Allows for a claim to be validated with the supplied predicate. +- `Verification withNullClaim(String name)` - Adds an expectation that a claim with the provided name has a value equal to the literal `null`. From e6565cecb1049c43f5dee0df5a0d545cc369c74e Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 20 Apr 2022 19:05:43 +0530 Subject: [PATCH 095/198] JavaDoc updated --- .../main/java/com/auth0/jwt/HeaderParams.java | 4 +- lib/src/main/java/com/auth0/jwt/JWT.java | 8 +- .../main/java/com/auth0/jwt/JWTCreator.java | 2 + .../main/java/com/auth0/jwt/JWTVerifier.java | 4 +- .../java/com/auth0/jwt/RegisteredClaims.java | 9 +- .../com/auth0/jwt/impl/PayloadSerializer.java | 2 - .../java/com/auth0/jwt/interfaces/Claim.java | 20 ++-- .../java/com/auth0/jwt/interfaces/Header.java | 4 +- .../auth0/jwt/interfaces/JWTPartsParser.java | 6 +- .../com/auth0/jwt/interfaces/JWTVerifier.java | 4 +- .../com/auth0/jwt/interfaces/Payload.java | 6 +- .../auth0/jwt/interfaces/Verification.java | 100 ++++++++---------- .../jwt/interfaces/VerificationTest.java | 5 + 13 files changed, 89 insertions(+), 85 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/HeaderParams.java b/lib/src/main/java/com/auth0/jwt/HeaderParams.java index e15877a2..7af3c442 100644 --- a/lib/src/main/java/com/auth0/jwt/HeaderParams.java +++ b/lib/src/main/java/com/auth0/jwt/HeaderParams.java @@ -13,12 +13,12 @@ private HeaderParams() {} public static String ALGORITHM = "alg"; /** - * The content type of a JWT. + * The content type of the JWT. */ public static String CONTENT_TYPE = "cty"; /** - * The media type of a JWT. + * The media type of the JWT. */ public static String TYPE = "typ"; diff --git a/lib/src/main/java/com/auth0/jwt/JWT.java b/lib/src/main/java/com/auth0/jwt/JWT.java index 9d719d1e..696abe40 100644 --- a/lib/src/main/java/com/auth0/jwt/JWT.java +++ b/lib/src/main/java/com/auth0/jwt/JWT.java @@ -26,7 +26,7 @@ public JWT() { * Decode a given Json Web Token. *

* Note that this method doesn't verify the token's signature! - * Use it only if you trust the token or you already verified it. + * Use it only if you trust the token or if you have already verified it. * * @param token with jwt format as string. * @return a decoded JWT. @@ -41,7 +41,7 @@ public DecodedJWT decodeJwt(String token) throws JWTDecodeException { * Decode a given Json Web Token. *

* Note that this method doesn't verify the token's signature! - * Use it only if you trust the token or you already verified it. + * Use it only if you trust the token or if you have already verified it. * * @param token with jwt format as string. * @return a decoded JWT. @@ -53,10 +53,10 @@ public static DecodedJWT decode(String token) throws JWTDecodeException { } /** - * Returns a {@link JWTVerifier} builder with the algorithm to be used to validate token signature. + * Returns a {@link Verification} builder with the algorithm to be used to validate token signature. * * @param algorithm that will be used to verify the token's signature. - * @return {@link JWTVerifier} builder + * @return {@link Verification} builder * @throws IllegalArgumentException if the provided algorithm is null. */ public static Verification require(Algorithm algorithm) { diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index cab474f7..25624982 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -19,6 +19,8 @@ * from a given Header and Payload content. *

* This class is thread-safe. + * + * TODO Poovam - Should we claim thread safety since KeyProvider implementation can cause signing differ */ @SuppressWarnings("WeakerAccess") public final class JWTCreator { diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 8b4639ac..0bc17fb1 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -35,10 +35,10 @@ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { } /** - * Initialize a JWTVerifier instance using the given Algorithm. + * Initialize a {@link Verification} instance using the given Algorithm. * * @param algorithm the Algorithm to use on the JWT verification. - * @return a JWTVerifier.Verification instance to configure. + * @return a {@link Verification} instance to configure. * @throws IllegalArgumentException if the provided algorithm is null. */ static Verification init(Algorithm algorithm) throws IllegalArgumentException { diff --git a/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java index d60d9fc5..8c47eb51 100644 --- a/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java +++ b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java @@ -1,7 +1,7 @@ package com.auth0.jwt; /** - * Contains constants representing the name of the Registered Claim Names as defined in Section 4.1.1 of + * Contains constants representing the name of the Registered Claim Names as defined in Section 4.1 of * RFC 7529 */ public final class RegisteredClaims { @@ -11,37 +11,44 @@ private RegisteredClaims() { /** * The "iss" (issuer) claim identifies the principal that issued the JWT. + * Refer RFC 7529 Section 4.1.1 */ public static String ISSUER = "iss"; /** * The "sub" (subject) claim identifies the principal that is the subject of the JWT. + * Refer RFC 7529 Section 4.1.2 */ public static String SUBJECT = "sub"; /** * The "aud" (audience) claim identifies the recipients that the JWT is intended for. + * Refer RFC 7529 Section 4.1.3 */ public static String AUDIENCE = "aud"; /** * The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be * accepted for processing. + * Refer RFC 7529 Section 4.1.4 */ public static String EXPIRES_AT = "exp"; /** * The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. + * Refer RFC 7529 Section 4.1.5 */ public static String NOT_BEFORE = "nbf"; /** * The "iat" (issued at) claim identifies the time at which the JWT was issued. + * Refer RFC 7529 Section 4.1.6 */ public static String ISSUED_AT = "iat"; /** * The "jti" (JWT ID) claim provides a unique identifier for the JWT. + * Refer RFC 7529 Section 4.1.7 */ public static String JWT_ID = "jti"; diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index c8df2bb9..24fe37b7 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -34,8 +34,6 @@ protected void writeClaim(Map.Entry entry, JsonGenerator gen) th * Audience may be a list of strings or a single string. This is needed to properly handle the aud claim when * added with the {@linkplain com.auth0.jwt.JWTCreator.Builder#withPayload(Map)} method. */ - - // private void writeAudience(JsonGenerator gen, Map.Entry e) throws IOException { if (e.getValue() instanceof String) { gen.writeFieldName(e.getKey()); diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index f6ccfdcd..7c4da4f7 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -22,7 +22,7 @@ public interface Claim { /** * Can be used to verify whether the Claim is found or not. - * This will be true even if the Claim has null value associated to it. + * This will be true even if the Claim has {@code null} value associated to it. * * @return whether this Claim is present or not */ @@ -30,7 +30,7 @@ public interface Claim { /** * Get this Claim as a Boolean. - * If the value isn't of type Boolean or it can't be converted to a Boolean, null will be returned. + * If the value isn't of type Boolean or it can't be converted to a Boolean, {@code null} will be returned. * * @return the value as a Boolean or null. */ @@ -38,7 +38,7 @@ public interface Claim { /** * Get this Claim as an Integer. - * If the value isn't of type Integer or it can't be converted to an Integer, null will be returned. + * If the value isn't of type Integer or it can't be converted to an Integer, {@code null} will be returned. * * @return the value as an Integer or null. */ @@ -46,7 +46,7 @@ public interface Claim { /** * Get this Claim as an Long. - * If the value isn't of type Long or it can't be converted to an Long, null will be returned. + * If the value isn't of type Long or it can't be converted to a Long, {@code null} will be returned. * * @return the value as an Long or null. */ @@ -54,7 +54,7 @@ public interface Claim { /** * Get this Claim as a Double. - * If the value isn't of type Double or it can't be converted to a Double, null will be returned. + * If the value isn't of type Double or it can't be converted to a Double, {@code null} will be returned. * * @return the value as a Double or null. */ @@ -62,7 +62,7 @@ public interface Claim { /** * Get this Claim as a String. - * If the value isn't of type String or it can't be converted to a String, null will be returned. + * If the value isn't of type String or it can't be converted to a String, {@code null} will be returned. * * @return the value as a String or null. */ @@ -70,7 +70,7 @@ public interface Claim { /** * Get this Claim as a Date. - * If the value can't be converted to a Date, null will be returned. + * If the value can't be converted to a Date, {@code null} will be returned. * * @return the value as a Date or null. */ @@ -78,7 +78,7 @@ public interface Claim { /** * Get this Claim as an Instant. - * If the value can't be converted to an Instant, null will be returned. + * If the value can't be converted to an Instant, {@code null} will be returned. * * @return the value as a Date or null. */ @@ -89,7 +89,7 @@ default Instant asInstant() { /** * Get this Claim as an Array of type T. - * If the value isn't an Array, null will be returned. + * If the value isn't an Array, {@code null} will be returned. * * @param type * @param clazz the type class @@ -100,7 +100,7 @@ default Instant asInstant() { /** * Get this Claim as a List of type T. - * If the value isn't an Array, null will be returned. + * If the value isn't an Array, {@code null} will be returned. * * @param type * @param clazz the type class diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Header.java b/lib/src/main/java/com/auth0/jwt/interfaces/Header.java index 030643d0..52b3ba56 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Header.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Header.java @@ -1,7 +1,7 @@ package com.auth0.jwt.interfaces; /** - * The Header class represents the 1st part of the JWT, where the Header value is hold. + * The Header class represents the 1st part of the JWT, where the Header value is held. */ public interface Header { @@ -35,7 +35,7 @@ public interface Header { /** * Get a Private Claim given it's name. If the Claim wasn't specified in the Header, a 'null claim' will be - * returned. All of the methods of that claim will return {@code null}. + * returned. All the methods of that claim will return {@code null}. * * @param name the name of the Claim to retrieve. * @return a non-null Claim. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java index e1c6efcc..33cd0d70 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java @@ -4,12 +4,12 @@ /** * The JWTPartsParser class defines which parts of the JWT should be converted - * to it's specific Object representation instance. + * to its specific Object representation instance. */ public interface JWTPartsParser { /** - * Parses the given JSON into a Payload instance. + * Parses the given JSON into a {@link Payload} instance. * * @param json the content of the Payload in a JSON representation. * @return the Payload. @@ -18,7 +18,7 @@ public interface JWTPartsParser { Payload parsePayload(String json) throws JWTDecodeException; /** - * Parses the given JSON into a Header instance. + * Parses the given JSON into a {@link Header} instance. * * @param json the content of the Header in a JSON representation. * @return the Header. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java index 3555878e..b7030a97 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java @@ -4,7 +4,7 @@ /** - * Used to verify the JWT for it's signature and claims. + * Used to verify the JWT for its signature and claims. */ public interface JWTVerifier { @@ -18,7 +18,7 @@ public interface JWTVerifier { DecodedJWT verify(String token) throws JWTVerificationException; /** - * Performs the verification against the given decoded JWT. + * Performs the verification against the given {@link DecodedJWT}. * * @param jwt to verify. * @return a verified and decoded JWT. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java b/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java index b3bf71f5..feb58c64 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java @@ -6,7 +6,7 @@ import java.util.Map; /** - * The Payload class represents the 2nd part of the JWT, where the Payload value is hold. + * The Payload class represents the 2nd part of the JWT, where the Payload value is held. */ public interface Payload { @@ -87,8 +87,8 @@ default Instant getIssuedAtAsInstant() { String getId(); /** - * Get a Claim given it's name. If the Claim wasn't specified in the Payload, a 'null claim' - * will be returned. All of the methods of that claim will return {@code null}. + * Get a Claim given its name. If the Claim wasn't specified in the Payload, a 'null claim' + * will be returned. All the methods of that claim will return {@code null}. * * @param name the name of the Claim to retrieve. * @return a non-null Claim. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 8b0416dc..4a8a0f84 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -7,12 +7,13 @@ import java.util.function.BiPredicate; /** - * Holds the Claims and claim-based configurations required for a JWT to be considered valid. + * Constructs and holds the checks required for a JWT to be considered valid. */ public interface Verification { /** - * Require a specific Issuer ("iss") claim. + * Verifies whether the JWT contains an Issuer ("iss") claim that equals to the value provided. + * This check is case-sensitive. * * @param issuer the required Issuer value. * @return this same Verification instance. @@ -22,7 +23,8 @@ default Verification withIssuer(String issuer) { } /** - * Require a specific Issuer ("iss") claim. + * Verifies whether the JWT contains an Issuer ("iss") claim that contains all the values provided. + * This check is case-sensitive. An empty array is considered as a {@code null}. * * @param issuer the required Issuer value. If multiple values are given, the claim must at least match one of them * @return this same Verification instance. @@ -30,7 +32,8 @@ default Verification withIssuer(String issuer) { Verification withIssuer(String... issuer); /** - * Require a specific Subject ("sub") claim. + * Verifies whether the JWT contains a Subject ("sub") claim that equals to the value provided. + * This check is case-sensitive. * * @param subject the required Subject value * @return this same Verification instance. @@ -38,11 +41,8 @@ default Verification withIssuer(String issuer) { Verification withSubject(String subject); /** - * Require a specific Audience ("aud") claim. If multiple audiences are specified, they must all be present - * in the "aud" claim. - * - * If this is used in conjunction with {@link #withAnyOfAudience(String...)}, whichever one is configured last will - * determine the audience validation behavior. + * Verifies whether the JWT contains an Audience ("aud") claim that contains all the values provided. + * This check is case-sensitive. An empty array is considered as a {@code null}. * * @param audience the required Audience value * @return this same Verification instance. @@ -50,24 +50,13 @@ default Verification withIssuer(String issuer) { Verification withAudience(String... audience); /** - * Require that the Audience ("aud") claim contain at least one of the specified audiences. - * - * If this is used in conjunction with {@link #withAudience(String...)}, whichever one is configured last will - * determine the audience validation behavior. - * - * Note: This method was added after the interface was released. - * It is defined as a default method for compatibility reasons. - * From version 4.0 on, the method will be abstract and all implementations of this interface - * will have to provide their own implementation. - * - * The default implementation throws an {@linkplain UnsupportedOperationException}. + * Verifies whether the JWT contains an Audience ("aud") claim contain at least one of the specified audiences. + * This check is case-sensitive. An empty array is considered as a {@code null}. * * @param audience the required Audience value for which the "aud" claim must contain at least one value. * @return this same Verification instance. */ - default Verification withAnyOfAudience(String... audience) { - throw new UnsupportedOperationException("withAnyOfAudience"); - } + Verification withAnyOfAudience(String... audience); /** * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims @@ -116,149 +105,152 @@ default Verification withAnyOfAudience(String... audience) { Verification acceptIssuedAt(long leeway) throws IllegalArgumentException; /** - * Require a specific JWT Id ("jti") claim. + * Verifies whether the JWT contains a JWT ID ("jti") claim that equals to the value provided. + * This check is case-sensitive. * - * @param jwtId the required Id value + * @param jwtId the required ID value * @return this same Verification instance. */ Verification withJWTId(String jwtId); /** - * Require a claim to be present, with any value. + * Verifies whether the claim is present in the JWT, with any value including {@code null}. * * @param name the Claim's name. * @return this same Verification instance - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaimPresence(String name) throws IllegalArgumentException; /** - * Require a specific Claim value to be null. + * Verifies whether the claim is present with a {@code null} value. * * @param name the Claim's name. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withNullClaim(String name) throws IllegalArgumentException; /** - * Require a specific Claim value. + * Verifies whether the claim is equal to the given Boolean value. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, Boolean value) throws IllegalArgumentException; /** - * Require a specific Claim value. + * Verifies whether the claim is equal to the given Integer value. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, Integer value) throws IllegalArgumentException; /** - * Require a specific Claim value. + * Verifies whether the claim is equal to the given Long value. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, Long value) throws IllegalArgumentException; /** - * Require a specific Claim value. + * Verifies whether the claim is equal to the given Integer value. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, Double value) throws IllegalArgumentException; /** - * Require a specific Claim value. + * Verifies whether the claim is equal to the given String value. + * This check is case-sensitive. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, String value) throws IllegalArgumentException; /** - * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; + * Verifies whether the claim is equal to the given Date value. + * Note that date-time claims are serialized as seconds since the epoch; * when verifying date-time claim value, any time units more granular than seconds will not be considered. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, Date value) throws IllegalArgumentException; /** - * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; + * Verifies whether the claim is equal to the given Instant value. + * Note that date-time claims are serialized as seconds since the epoch; * when verifying a date-time claim value, any time units more granular than seconds will not be considered. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ default Verification withClaim(String name, Instant value) throws IllegalArgumentException { return withClaim(name, value != null ? Date.from(value) : null); } /** - * Executes the predicate provided during the verification - * and passes the verification if the predicate returns true. + * Executes the predicate provided and the validates the JWT if the predicate returns true. * * @param name the Claim's name * @param predicate the predicate check to be done. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, BiPredicate predicate) throws IllegalArgumentException; /** - * Require a specific Array Claim to contain at least the given items. + * Verifies whether the claim contain at least the given String items. * * @param name the Claim's name. * @param items the items the Claim must contain. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withArrayClaim(String name, String... items) throws IllegalArgumentException; /** - * Require a specific Array Claim to contain at least the given items. + * Verifies whether the claim contain at least the given Integer items. * * @param name the Claim's name. * @param items the items the Claim must contain. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException; /** - * Require a specific Array Claim to contain at least the given items. + * Verifies whether the claim contain at least the given Long items. * * @param name the Claim's name. * @param items the items the Claim must contain. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withArrayClaim(String name, Long ... items) throws IllegalArgumentException; /** - * Skip the Issued At ("iat") date verification. By default, the verification is performed. + * Skip the Issued At ("iat") claim verification. By default, the verification is performed. * * @return this same Verification instance. */ @@ -267,7 +259,7 @@ default Verification withClaim(String name, Instant value) throws IllegalArgumen /** * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. * - * @return a new JWTVerifier instance. + * @return a new {@link com.auth0.jwt.interfaces.JWTVerifier} instance. */ JWTVerifier build(); } diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java index fb0c74a9..bdbd688a 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java @@ -74,6 +74,11 @@ public Verification withAudience(String... audience) { return null; } + @Override + public Verification withAnyOfAudience(String... audience) { + return null; + } + @Override public Verification acceptLeeway(long leeway) throws IllegalArgumentException { return null; From 1979b95f272cc4bffda318a7db097bbed66a5d82 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 20 Apr 2022 19:45:09 +0530 Subject: [PATCH 096/198] Removed test for default audience claim check --- .../java/com/auth0/jwt/interfaces/VerificationTest.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java index bdbd688a..11acb029 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java @@ -38,13 +38,6 @@ public void withInstantClaimShouldUseDefaultImplAndHandleNull() { assertThat(((VerificationImplForTest)verification).expectedClaims, hasEntry("name", null)); } - @Test - public void withAnyOfAudienceDeafultImplShouldThrow() { - assertThrows("withAnyOfAudience", UnsupportedOperationException.class, () -> { - new VerificationImplForTest().withAnyOfAudience(""); - }); - } - @Test public void withIssuerStringDefaultImplShouldDelegate() { Verification verification = new VerificationImplForTest() From fef52cc0bd7b1039436ebe09abbe569123812b02 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 20 Apr 2022 17:27:14 -0500 Subject: [PATCH 097/198] Formatting fixes --- MIGRATION_GUIDE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 1f95f323..7105742d 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -50,11 +50,11 @@ This class extends `InvalidClaimException` and represents that when validating a This class extends `InvalidClaimException` and represents that when validating a JWT, an expected claim is missing from the JWT. -### `HeaderParams` added +#### `HeaderParams` added This class contains constants representing common header parameter names. -### `StandardClaims` added +#### `StandardClaims` added This class contains constants representing the standard claim names. From 3f939842f1d0c6c393b9fd6d2365fa2e45d986cc Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Thu, 21 Apr 2022 18:49:01 +0530 Subject: [PATCH 098/198] Added note on Keyprovider --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 2 -- lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 25624982..cab474f7 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -19,8 +19,6 @@ * from a given Header and Payload content. *

* This class is thread-safe. - * - * TODO Poovam - Should we claim thread safety since KeyProvider implementation can cause signing differ */ @SuppressWarnings("WeakerAccess") public final class JWTCreator { diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java index fd8baea5..30a144a6 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java @@ -5,6 +5,7 @@ /** * Generic Public/Private Key provider. + * While implementing, ensure the Private Key and Private Key ID doesn't change in between signing a token. * * @param the class that represents the Public Key * @param the class that represents the Private Key From 0d3864da70b1831d4a923450d41cd784ba181c91 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Thu, 21 Apr 2022 10:31:43 -0500 Subject: [PATCH 099/198] updates from PR review --- MIGRATION_GUIDE.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 7105742d..c47b95c2 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -18,7 +18,8 @@ This guide captures the changes you should be aware of when planning and upgradi - Support for the ES256K algorithm has been removed, as it is disabled in Java 15+. The `Algorithm#ECDSA256K(ECDSAKeyProvider keyProvider)` and `Algorithm#ECDSA256K(ECPublicKey publicKey, ECPrivateKey privateKey)` methods have been removed. - `com.auth0.jwt.interfaces.Clock` has been removed. Instead, an implementation of `java.time.Clock` can be passed to the `BaseVerification` for testing purposes. - `com.auth0.jwt.impl.NullClaim` has been removed. `Claim#isNull` can be used to determine if a claim's value is `null`. -- `com.auth0.jwt.impl.PublicClaims` was removed, and replaced by `com.auth0.jwt.StandardClaims` and `com.auth0.jwt.HeaderParams`. +- `com.auth0.jwt.impl.PublicClaims` was removed, and replaced by `com.auth0.jwt.RegisteredClaims` and `com.auth0.jwt.HeaderParams`. +- `com.auth0.jwt.interfaces.Verification#withAnyOfAudience` no longer provides a default implementation. ### Behavioral potentially breaking changes @@ -54,9 +55,9 @@ This class extends `InvalidClaimException` and represents that when validating a This class contains constants representing common header parameter names. -#### `StandardClaims` added +#### `RegisteredClaims` added -This class contains constants representing the standard claim names. +This class contains constants representing the registered claim names. #### `JWTCreator` new methods From b6a9a034b3ceac2e644308e376c0f48aefdcc6da Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 25 Apr 2022 19:31:01 -0500 Subject: [PATCH 100/198] Documentation updates --- .codecov.yml | 1 + README.md | 39 ++++++++++++++++--- .../main/java/com/auth0/jwt/JWTCreator.java | 2 +- .../java/com/auth0/jwt/JWTCreatorTest.java | 6 +-- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 51f857af..de278af2 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -11,5 +11,6 @@ coverage: project: default: target: auto + threshold: 1% if_no_uploads: error comment: false \ No newline at end of file diff --git a/README.md b/README.md index 065899f1..624a7f68 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,15 @@ A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.or If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. -> This library requires Java 8 or higher. The last version that supported Java 7 was 3.11.0. +> You are viewing the documentation for the v4 beta release. For the latest stable release, please see the [version 3.x documentation](https://github.com/auth0/java-jwt). ## Table of Contents +- [**Requirements**](#requirements) - [**Installation**](#installation) - [**Available Algorithms**](#available-algorithms) - [**Quickstart**](#quickstart) + [**Create and Sign a Token**](#create-and-sign-a-token) + [**Verify a Token**](#verify-a-token) - + [**Decode a Token**](#decode-a-token) - [**Usage**](#usage) + [**Pick the algorithm**](#pick-the-algorithm) + [**Time Validation**](#time-validation) @@ -28,6 +28,10 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o + [**Claim Class**](#claim-class) - [**Javadoc**](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) +## Requirements + +This library is supported for Java 8, 11, and 17. For issues on non-LTS versions above 8, consideration will be given on a case-by-case basis. + ## Installation ### Gradle @@ -138,8 +142,13 @@ You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` If the token has an invalid signature or the Claim requirement is not met, a `JWTVerificationException` will raise. +

+Need to peek into a JWT without verifying it? (Click to expand) + ### Decode a Token +> __Warning:__ This will __not__ verify whether the signature is valid. You should __not__ use this for untrusted messages. You most likely want to use `JWTVerifier` as documented above instead. + ```java String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { @@ -151,6 +160,8 @@ try { If the token has an invalid syntax or the header or payload are not JSONs, a `JWTDecodeException` will raise. +
+ ## Usage ### Pick the Algorithm @@ -176,7 +187,7 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); ##### HMAC Key Length and Security -When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recomendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. +When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recommendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. #### Using a KeyProvider: By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: @@ -243,13 +254,13 @@ JWTVerifier verifier = JWT.require(algorithm) .build(); ``` -If you need to test this behaviour in your lib/app cast the `Verification` instance to a `BaseVerification` to gain visibility of the `verification.build()` method that accepts a custom `Clock`. e.g.: +If you need to test this behavior in your lib/app cast the `Verification` instance to a `BaseVerification` to gain visibility of the `verification.build()` method that accepts a `java.time.Clock`. e.g.: ```java BaseVerification verification = (BaseVerification) JWT.require(algorithm) .acceptLeeway(1) .acceptExpiresAt(5); -Clock clock = new CustomClock(); //Must implement Clock interface +private final Clock mockNow = Clock.fixed(Instant.ofEpochSecond(1477592), ZoneId.of("UTC")); JWTVerifier verifier = verification.build(clock); ``` @@ -342,6 +353,12 @@ Returns the Expiration Time value or null if it's not defined in the Payload. Date expiresAt = jwt.getExpiresAt(); ``` +If you prefer to work with `java.time.Instant` instead of `java.util.Date`: + +```java +Instant expiresAt = jwt.getExpiresAtAsInstant(); +``` + #### Not Before ("nbf") Returns the Not Before value or null if it's not defined in the Payload. @@ -350,6 +367,12 @@ Returns the Not Before value or null if it's not defined in the Payload. Date notBefore = jwt.getNotBefore(); ``` +If you prefer to work with `java.time.Instant` instead of `java.util.Date`: + +```java +Instant notBefore = jwt.getNotBeforeAsInstant(); +``` + #### Issued At ("iat") Returns the Issued At value or null if it's not defined in the Payload. @@ -358,6 +381,12 @@ Returns the Issued At value or null if it's not defined in the Payload. Date issuedAt = jwt.getIssuedAt(); ``` +If you prefer to work with `java.time.Instant` instead of `java.util.Date`: + +```java +Instant issuedAt = jwt.getIssuedAtAsInstant(); +``` + #### JWT ID ("jti") Returns the JWT ID value or null if it's not defined in the Payload. diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index cab474f7..a99f0fa0 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -453,7 +453,7 @@ public Builder withPayload(Map payloadClaims) throws IllegalArgumentE if (!validatePayload(payloadClaims)) { throw new IllegalArgumentException("Claim values must only be of types Map, List, Boolean, Integer, " - + "Long, Double, String, Date and Null"); + + "Long, Double, String, Date, Instant, and Null"); } // add claims only after validating all claims so as not to corrupt the claims map of this builder diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index efe6b94b..4d1c7314 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -759,7 +759,7 @@ public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { @Test public void withPayloadShouldNotAllowCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date, Instant, and Null"); Map payload = new HashMap<>(); payload.put("entry", "value"); @@ -786,7 +786,7 @@ public void withPayloadShouldAllowNullListItems() { @Test public void withPayloadShouldNotAllowListWithCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date, Instant, and Null"); Map payload = new HashMap<>(); payload.put("list", Arrays.asList("item1", new UserPojo("name", 42))); @@ -798,7 +798,7 @@ public void withPayloadShouldNotAllowListWithCustomType() { @Test public void withPayloadShouldNotAllowMapWithCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date, Instant, and Null"); Map payload = new HashMap<>(); payload.put("entry", "value"); From 05b54499be0cc06478467d66c801d9bc3da3fecc Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 3 May 2022 15:46:47 +0200 Subject: [PATCH 101/198] Added protection against CVE-2022-21449 --- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 69 ++++++++- .../jwt/algorithms/ECDSAAlgorithmTest.java | 139 +++++++++++++++++- .../ECDSABouncyCastleProviderTests.java | 20 ++- 3 files changed, 221 insertions(+), 7 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java index c2d08a15..f96fd576 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -5,6 +5,7 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.ECDSAKeyProvider; +import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; @@ -46,6 +47,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } + validateSignatureStructure(signatureBytes, publicKey); boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), JOSEToDER(signatureBytes)); if (!valid) { @@ -140,13 +142,42 @@ byte[] DERToJOSE(byte[] derSignature) throws SignatureException { return joseSignature; } - //Visible for testing - byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { + /** + * Added check for extra protection against CVE-2022-21449. + * This method ensures the signature's structure is as expected. + * + * @param joseSignature is the signature from the JWT + * @param publicKey public key used to verify the JWT + * @throws SignatureException if the signature's structure is not as per expectation + */ + // Visible for testing + void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) throws SignatureException { + // check signature length, moved this check from JOSEToDER method if (joseSignature.length != ecNumberSize * 2) { throw new SignatureException("Invalid JOSE signature format."); } - // Retrieve R and S number's length and padding. + if (isAllZeros(joseSignature)) { + throw new SignatureException("Invalid Signature: All Zeros."); + } + + // get R + byte[] rBytes = new byte[ecNumberSize]; + System.arraycopy(joseSignature, 0, rBytes, 0, ecNumberSize); + BigInteger r = new BigInteger(1, rBytes); + if(isAllZeros(rBytes)) { + throw new SignatureException("Invalid Signature: All Zeros for R value."); + } + + // get S + byte[] sBytes = new byte[ecNumberSize]; + System.arraycopy(joseSignature, ecNumberSize, sBytes, 0, ecNumberSize); + BigInteger s = new BigInteger(1, sBytes); + if(isAllZeros(sBytes)) { + throw new SignatureException("Invalid Signature: All Zeros for S value."); + } + + //moved this check from JOSEToDER method int rPadding = countPadding(joseSignature, 0, ecNumberSize); int sPadding = countPadding(joseSignature, ecNumberSize, joseSignature.length); int rLength = ecNumberSize - rPadding; @@ -157,6 +188,29 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { throw new SignatureException("Invalid JOSE signature format."); } + // check for 0 r or s here since we have the values + if (BigInteger.ZERO.equals(r) || BigInteger.ZERO.equals(s)) { + throw new SignatureException("R or S value cannot be zero."); + } + + BigInteger order = publicKey.getParams().getOrder(); + + // R and S must be less than N + if (order.compareTo(r) < 1 || order.compareTo(s) < 1) { + throw new SignatureException("The difference between R or S value and order should be greater than one."); + } + } + + //Visible for testing + byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { + // Retrieve R and S number's length and padding. + int rPadding = countPadding(joseSignature, 0, ecNumberSize); + int sPadding = countPadding(joseSignature, ecNumberSize, joseSignature.length); + int rLength = ecNumberSize - rPadding; + int sLength = ecNumberSize - sPadding; + + int length = 2 + rLength + 2 + sLength; + final byte[] derSignature; int offset; if (length > 0x7f) { @@ -205,6 +259,15 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { return derSignature; } + private boolean isAllZeros(byte[] bytes) { + for (byte b : bytes) { + if (b != 0) { + return false; + } + } + return true; + } + private int countPadding(byte[] bytes, int fromIndex, int toIndex) { int padding = 0; while (fromIndex + padding < toIndex && bytes[fromIndex + padding] == 0) { diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java index 39c9dabf..aee8238c 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -4,6 +4,7 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.ECDSAKeyProvider; +import com.auth0.jwt.interfaces.JWTVerifier; import org.hamcrest.Matchers; import org.hamcrest.collection.IsIn; import org.junit.Assert; @@ -12,11 +13,13 @@ import org.junit.rules.ExpectedException; import java.io.ByteArrayOutputStream; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; import java.util.Arrays; import java.util.Base64; @@ -574,6 +577,10 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce .thenThrow(NoSuchAlgorithmException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -592,6 +599,10 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception { .thenThrow(InvalidKeyException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -610,6 +621,10 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception .thenThrow(SignatureException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -939,12 +954,13 @@ public void shouldThrowOnDERSignatureConversionIfSNumberDoesNotHaveExpectedLengt @Test public void shouldThrowOnJOSESignatureConversionIfDoesNotHaveExpectedLength() throws Exception { - ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); byte[] joseSignature = new byte[32 * 2 - 1]; exception.expect(SignatureException.class); exception.expectMessage("Invalid JOSE signature format."); - algorithm256.JOSEToDER(joseSignature); + algorithm256.validateSignatureStructure(joseSignature, publicKey); } @Test @@ -1309,4 +1325,123 @@ public void shouldFailOnECDSA256SigningWithDeprecatedMethodWhenProvidedPrivateKe algorithm.sign(new byte[0]); } + @Test + public void invalidECDSA256SignatureShouldFailTokenVerification() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(SignatureException.class)); + + String jwtWithInvalidSig = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0._____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ"; + + ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); + ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); + ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build(); + JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build(); + JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build(); + JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build(); + verifier256.verify(jwtWithInvalidSig); + verifier384.verify(jwtWithInvalidSig); + verifier512.verify(jwtWithInvalidSig); + verifier256k.verify(jwtWithInvalidSig); + } + + @Test + public void emptyECDSA256SignatureShouldFailTokenVerification() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(SignatureException.class)); + + String jwtWithInvalidSig = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + + ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); + ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); + ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build(); + JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build(); + JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build(); + JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build(); + verifier256.verify(jwtWithInvalidSig); + verifier384.verify(jwtWithInvalidSig); + verifier512.verify(jwtWithInvalidSig); + verifier256k.verify(jwtWithInvalidSig); + } + + @Test + public void signatureWithAllZerosShouldFail() throws Exception { + exception.expect(SignatureException.class); + + ECPublicKey pubKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(pubKey, (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + byte[] signatureBytes = new byte[64]; + algorithm256.validateSignatureStructure(signatureBytes, pubKey); + } + + @Test + public void signatureWithRZeroShouldFail() throws Exception { + exception.expect(SignatureException.class); + + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); + + String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey)); + + String[] chunks = signedJwt.split("\\."); + byte[] signature = Base64.getUrlDecoder().decode(chunks[2]); + + byte[] sigWithBlankR = new byte[signature.length]; + for (int i = 0; i < signature.length; i++) { + if (i < signature.length / 2) { + sigWithBlankR[i] = 0; + } else { + sigWithBlankR[i] = signature[i]; + } + } + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey); + algorithm256.validateSignatureStructure(sigWithBlankR, publicKey); + } + + @Test + public void signatureWithSZeroShouldFail() throws Exception { + exception.expect(SignatureException.class); + + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); + + String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey)); + + String[] chunks = signedJwt.split("\\."); + byte[] signature = Base64.getUrlDecoder().decode(chunks[2]); + + byte[] sigWithBlankS = new byte[signature.length]; + for (int i = 0; i < signature.length; i++) { + if (i < signature.length / 2) { + sigWithBlankS[i] = signature[i]; + } else { + sigWithBlankS[i] = 0; + } + } + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey); + algorithm256.validateSignatureStructure(sigWithBlankS, publicKey); + } + + @Test + public void signatureWithRSValueNotLessThanOrderShouldFail() throws Exception { + exception.expect(SignatureException.class); + + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); + + String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey)); + String jwtWithInvalidSig = signedJwt.substring(0, signedJwt.lastIndexOf('.') + 1) + "_____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ"; + + String[] chunks = jwtWithInvalidSig.split("\\."); + byte[] invalidSignature = Base64.getUrlDecoder().decode(chunks[2]); + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey); + algorithm256.validateSignatureStructure(invalidSignature, publicKey); + } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java index 68fe6e50..78e0063d 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java @@ -11,11 +11,14 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.util.Arrays; import java.util.Base64; import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; @@ -591,6 +594,10 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce .thenThrow(NoSuchAlgorithmException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -609,6 +616,10 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception { .thenThrow(InvalidKeyException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -627,6 +638,10 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception .thenThrow(SignatureException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -935,12 +950,13 @@ public void shouldThrowOnDERSignatureConversionIfSNumberDoesNotHaveExpectedLengt @Test public void shouldThrowOnJOSESignatureConversionIfDoesNotHaveExpectedLength() throws Exception { - ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); byte[] joseSignature = new byte[32 * 2 - 1]; exception.expect(SignatureException.class); exception.expectMessage("Invalid JOSE signature format."); - algorithm256.JOSEToDER(joseSignature); + algorithm256.validateSignatureStructure(joseSignature, publicKey); } @Test From 69dae3eba9235a45e01ab588d5027b73aef918cc Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 3 May 2022 16:16:36 +0200 Subject: [PATCH 102/198] Added banner to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 260f6d6e..0e6a7d48 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). +> :warning: **Important security note:** JVM has a critical vulnerability for ECDSA Algorithms - [CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449). Please review the details of the vulnerability and update your environment. + If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. > This library requires Java 8 or higher. The last version that supported Java 7 was 3.11.0. From 3135ba7893ea60882500fa509c6d98fe48a4f0f8 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 3 May 2022 16:18:01 +0200 Subject: [PATCH 103/198] Removed zero check since it is already verified --- .../main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java index f96fd576..1d2544f6 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -188,11 +188,6 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr throw new SignatureException("Invalid JOSE signature format."); } - // check for 0 r or s here since we have the values - if (BigInteger.ZERO.equals(r) || BigInteger.ZERO.equals(s)) { - throw new SignatureException("R or S value cannot be zero."); - } - BigInteger order = publicKey.getParams().getOrder(); // R and S must be less than N From 208d7b66c281fcd91dbc5d83fe51d676ee5e0d51 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 3 May 2022 18:37:52 +0200 Subject: [PATCH 104/198] Improved order comparison --- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 8 ++++++-- .../jwt/algorithms/ECDSAAlgorithmTest.java | 20 ++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java index 1d2544f6..00b68cad 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -191,8 +191,12 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr BigInteger order = publicKey.getParams().getOrder(); // R and S must be less than N - if (order.compareTo(r) < 1 || order.compareTo(s) < 1) { - throw new SignatureException("The difference between R or S value and order should be greater than one."); + if (order.compareTo(r) < 1) { + throw new SignatureException("The difference between R value and order should be greater than one."); + } + + if (order.compareTo(s) < 1){ + throw new SignatureException("The difference between S value and order should be greater than one."); } } diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java index aee8238c..a02c639b 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -1429,7 +1429,7 @@ public void signatureWithSZeroShouldFail() throws Exception { } @Test - public void signatureWithRSValueNotLessThanOrderShouldFail() throws Exception { + public void signatureWithRValueNotLessThanOrderShouldFail() throws Exception { exception.expect(SignatureException.class); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); @@ -1444,4 +1444,22 @@ public void signatureWithRSValueNotLessThanOrderShouldFail() throws Exception { ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey); algorithm256.validateSignatureStructure(invalidSignature, publicKey); } + + @Test + public void signatureWithSValueNotLessThanOrderShouldFail() throws Exception { + exception.expect(SignatureException.class); + + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); + + String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey)); + String jwtWithInvalidSig = signedJwt.substring(0, signedJwt.lastIndexOf('.') + 1) + "_____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ"; + + String[] chunks = jwtWithInvalidSig.split("\\."); + byte[] invalidSignature = Base64.getUrlDecoder().decode(chunks[2]); + invalidSignature[0] = Byte.MAX_VALUE; + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey); + algorithm256.validateSignatureStructure(invalidSignature, publicKey); + } } \ No newline at end of file From df004b1bc55ed42f9ccb1d2b820bad08879ad273 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 4 May 2022 11:38:43 +0200 Subject: [PATCH 105/198] Updated documentation regarding HMAC Key length --- README.md | 8 ++++---- .../com/auth0/jwt/algorithms/Algorithm.java | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 624a7f68..3e270500 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` ```java String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { - Algorithm algorithm = Algorithm.HMAC256("secret"); + Algorithm algorithm = Algorithm.HMAC256("secret"); //use more secure key JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance @@ -175,7 +175,7 @@ When using RSA or ECDSA algorithms and you just need to **sign** JWTs you can av ```java //HMAC -Algorithm algorithmHS = Algorithm.HMAC256("secret"); +Algorithm algorithmHS = Algorithm.HMAC256("secret"); //use more secure key //RSA RSAPublicKey publicKey = //Get the key instance @@ -185,9 +185,9 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); > Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). -##### HMAC Key Length and Security +##### :key: HMAC Key Length and Security -When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recommendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. +When using a Hash-based Message Authentication Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recommendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. #### Using a KeyProvider: By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index a5247005..27d79909 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -130,7 +130,9 @@ public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException { /** * Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256". * - * @param secret the secret to use in the verify or signing instance. + * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 256 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC256 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -142,6 +144,8 @@ public static Algorithm HMAC256(String secret) throws IllegalArgumentException { * Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256". * * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 256 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC256 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -152,7 +156,9 @@ public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException { /** * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". * - * @param secret the secret to use in the verify or signing instance. + * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 384 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -164,6 +170,8 @@ public static Algorithm HMAC384(String secret) throws IllegalArgumentException { * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". * * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 384 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -174,7 +182,9 @@ public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException { /** * Creates a new Algorithm instance using HmacSHA512. Tokens specify this as "HS512". * - * @param secret the secret to use in the verify or signing instance. + * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 512 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC512 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -186,6 +196,8 @@ public static Algorithm HMAC512(String secret) throws IllegalArgumentException { * Creates a new Algorithm instance using HmacSHA512. Tokens specify this as "HS512". * * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 512 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC512 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ From 645b40a4c9ee9ed62e49d5bebda2c8a6c9d3221a Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Thu, 5 May 2022 11:54:49 +0200 Subject: [PATCH 106/198] Added test cases to differentiate thrown exception --- .../java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java index a02c639b..44d9e6db 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -1370,6 +1370,7 @@ public void emptyECDSA256SignatureShouldFailTokenVerification() throws Exception @Test public void signatureWithAllZerosShouldFail() throws Exception { exception.expect(SignatureException.class); + exception.expectMessage("Invalid Signature: All Zeros."); ECPublicKey pubKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); @@ -1381,6 +1382,7 @@ public void signatureWithAllZerosShouldFail() throws Exception { @Test public void signatureWithRZeroShouldFail() throws Exception { exception.expect(SignatureException.class); + exception.expectMessage("Invalid Signature: All Zeros for R value."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1406,6 +1408,7 @@ public void signatureWithRZeroShouldFail() throws Exception { @Test public void signatureWithSZeroShouldFail() throws Exception { exception.expect(SignatureException.class); + exception.expectMessage("Invalid Signature: All Zeros for S value."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1431,6 +1434,7 @@ public void signatureWithSZeroShouldFail() throws Exception { @Test public void signatureWithRValueNotLessThanOrderShouldFail() throws Exception { exception.expect(SignatureException.class); + exception.expectMessage("The difference between R value and order should be greater than one."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1448,6 +1452,7 @@ public void signatureWithRValueNotLessThanOrderShouldFail() throws Exception { @Test public void signatureWithSValueNotLessThanOrderShouldFail() throws Exception { exception.expect(SignatureException.class); + exception.expectMessage("The difference between S value and order should be greater than one."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); From 9d471d2152354866bfa65a861dac5590601efe79 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Thu, 5 May 2022 14:10:52 +0200 Subject: [PATCH 107/198] Abstracted the error message thrown --- .../java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java | 10 +++++----- .../com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java index 00b68cad..ac34c44e 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -158,7 +158,7 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr } if (isAllZeros(joseSignature)) { - throw new SignatureException("Invalid Signature: All Zeros."); + throw new SignatureException("Invalid signature format."); } // get R @@ -166,7 +166,7 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr System.arraycopy(joseSignature, 0, rBytes, 0, ecNumberSize); BigInteger r = new BigInteger(1, rBytes); if(isAllZeros(rBytes)) { - throw new SignatureException("Invalid Signature: All Zeros for R value."); + throw new SignatureException("Invalid signature format."); } // get S @@ -174,7 +174,7 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr System.arraycopy(joseSignature, ecNumberSize, sBytes, 0, ecNumberSize); BigInteger s = new BigInteger(1, sBytes); if(isAllZeros(sBytes)) { - throw new SignatureException("Invalid Signature: All Zeros for S value."); + throw new SignatureException("Invalid signature format."); } //moved this check from JOSEToDER method @@ -192,11 +192,11 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr // R and S must be less than N if (order.compareTo(r) < 1) { - throw new SignatureException("The difference between R value and order should be greater than one."); + throw new SignatureException("Invalid signature format."); } if (order.compareTo(s) < 1){ - throw new SignatureException("The difference between S value and order should be greater than one."); + throw new SignatureException("Invalid signature format."); } } diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java index 44d9e6db..818924fe 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -1370,7 +1370,7 @@ public void emptyECDSA256SignatureShouldFailTokenVerification() throws Exception @Test public void signatureWithAllZerosShouldFail() throws Exception { exception.expect(SignatureException.class); - exception.expectMessage("Invalid Signature: All Zeros."); + exception.expectMessage("Invalid signature format."); ECPublicKey pubKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); @@ -1382,7 +1382,7 @@ public void signatureWithAllZerosShouldFail() throws Exception { @Test public void signatureWithRZeroShouldFail() throws Exception { exception.expect(SignatureException.class); - exception.expectMessage("Invalid Signature: All Zeros for R value."); + exception.expectMessage("Invalid signature format."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1408,7 +1408,7 @@ public void signatureWithRZeroShouldFail() throws Exception { @Test public void signatureWithSZeroShouldFail() throws Exception { exception.expect(SignatureException.class); - exception.expectMessage("Invalid Signature: All Zeros for S value."); + exception.expectMessage("Invalid signature format."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1434,7 +1434,7 @@ public void signatureWithSZeroShouldFail() throws Exception { @Test public void signatureWithRValueNotLessThanOrderShouldFail() throws Exception { exception.expect(SignatureException.class); - exception.expectMessage("The difference between R value and order should be greater than one."); + exception.expectMessage("Invalid signature format."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1452,7 +1452,7 @@ public void signatureWithRValueNotLessThanOrderShouldFail() throws Exception { @Test public void signatureWithSValueNotLessThanOrderShouldFail() throws Exception { exception.expect(SignatureException.class); - exception.expectMessage("The difference between S value and order should be greater than one."); + exception.expectMessage("Invalid signature format."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); From fd3eac3b721c7481a69d9109cc0666b65544ac5b Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Thu, 5 May 2022 17:17:28 +0200 Subject: [PATCH 108/198] Release 3.19.2 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a022328d..d81f9a18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.19.2](https://github.com/auth0/java-jwt/tree/3.19.2) (2022-05-05) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.1...3.19.2) + +**Security** +- [SDK-3311] Added protection against CVE-2022-21449 [\#579](https://github.com/auth0/java-jwt/pull/579) ([poovamraj](https://github.com/poovamraj)) + ## [3.19.1](https://github.com/auth0/java-jwt/tree/3.19.1) (2022-03-30) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.0...3.19.1) diff --git a/README.md b/README.md index 0e6a7d48..c55fa70c 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.19.1 + 3.19.2 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.19.1' +implementation 'com.auth0:java-jwt:3.19.2' ``` ## Available Algorithms From 4573ba0537c5a34b768be4149ab817075cdbd111 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 6 May 2022 13:02:42 +0200 Subject: [PATCH 109/198] Code formatting for CheckStyle --- config/checkstyle/checkstyle.xml | 2 +- .../com/auth0/jwt/algorithms/ECDSAAlgorithm.java | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index d2615981..b97ccc37 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -206,7 +206,7 @@ value="Catch parameter name ''{0}'' must match pattern ''{1}''."/> - + diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java index 185d95cf..b3046097 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -50,7 +50,8 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { throw new IllegalStateException("The given Public Key is null."); } validateSignatureStructure(signatureBytes, publicKey); - boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), JOSEToDER(signatureBytes)); + boolean valid = crypto.verifySignatureFor( + getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), JOSEToDER(signatureBytes)); if (!valid) { throw new SignatureVerificationException(this); @@ -151,7 +152,7 @@ byte[] DERToJOSE(byte[] derSignature) throws SignatureException { * This method ensures the signature's structure is as expected. * * @param joseSignature is the signature from the JWT - * @param publicKey public key used to verify the JWT + * @param publicKey public key used to verify the JWT * @throws SignatureException if the signature's structure is not as per expectation */ // Visible for testing @@ -168,16 +169,14 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr // get R byte[] rBytes = new byte[ecNumberSize]; System.arraycopy(joseSignature, 0, rBytes, 0, ecNumberSize); - BigInteger r = new BigInteger(1, rBytes); - if(isAllZeros(rBytes)) { + if (isAllZeros(rBytes)) { throw new SignatureException("Invalid signature format."); } // get S byte[] sBytes = new byte[ecNumberSize]; System.arraycopy(joseSignature, ecNumberSize, sBytes, 0, ecNumberSize); - BigInteger s = new BigInteger(1, sBytes); - if(isAllZeros(sBytes)) { + if (isAllZeros(sBytes)) { throw new SignatureException("Invalid signature format."); } @@ -193,13 +192,15 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr } BigInteger order = publicKey.getParams().getOrder(); + BigInteger r = new BigInteger(1, rBytes); + BigInteger s = new BigInteger(1, sBytes); // R and S must be less than N if (order.compareTo(r) < 1) { throw new SignatureException("Invalid signature format."); } - if (order.compareTo(s) < 1){ + if (order.compareTo(s) < 1) { throw new SignatureException("Invalid signature format."); } } From 6f94d823682cf4ddb717d9e336c6f49004078b4b Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 6 May 2022 13:15:37 +0200 Subject: [PATCH 110/198] Removed ES256K tests since it is removed in v4 --- .../java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java index d05fb53d..2e636c71 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -1223,15 +1223,12 @@ public void invalidECDSA256SignatureShouldFailTokenVerification() throws Excepti ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); - ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build(); JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build(); JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build(); - JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build(); verifier256.verify(jwtWithInvalidSig); verifier384.verify(jwtWithInvalidSig); verifier512.verify(jwtWithInvalidSig); - verifier256k.verify(jwtWithInvalidSig); } @Test @@ -1244,15 +1241,12 @@ public void emptyECDSA256SignatureShouldFailTokenVerification() throws Exception ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); - ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build(); JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build(); JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build(); - JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build(); verifier256.verify(jwtWithInvalidSig); verifier384.verify(jwtWithInvalidSig); verifier512.verify(jwtWithInvalidSig); - verifier256k.verify(jwtWithInvalidSig); } @Test From c2e3558feb5cf6bd12f977b692236005053962fd Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 6 May 2022 14:46:45 +0200 Subject: [PATCH 111/198] Added tests to verify null creation in list and map --- .../java/com/auth0/jwt/JWTCreatorTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 4d1c7314..020e5e37 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -16,6 +16,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -919,4 +922,22 @@ public void withPayloadShouldSupportNullValuesEverywhere() { assertThat(headerJson, JsonMatcher.hasEntry("listNestedClaim", listNestedClaim)); assertThat(headerJson, JsonMatcher.hasEntry("objClaim", objClaim)); } + + @Test + public void shouldCreatePayloadWithNullForMap() { + String jwt = JWTCreator.init() + .withClaim("name", (Map) null) + .sign(Algorithm.HMAC256("secret")); + assertThat(jwt, is(notNullValue())); + assertTrue(JWT.decode(jwt).getClaim("name").isNull()); + } + + @Test + public void shouldCreatePayloadWithNullForList() { + String jwt = JWTCreator.init() + .withClaim("name", (List) null) + .sign(Algorithm.HMAC256("secret")); + assertThat(jwt, is(notNullValue())); + assertTrue(JWT.decode(jwt).getClaim("name").isNull()); + } } From 08f2e1de7ad86ad85a182c2d8a01ec04358be5ed Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 6 May 2022 16:01:51 +0200 Subject: [PATCH 112/198] Release 4.0.0-beta.0 --- CHANGELOG.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d81f9a18..c920528c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # Change Log +## [4.0.0-beta.0](https://github.com/auth0/java-jwt/tree/4.0.0-beta.0) (2022-05-06) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0...4.0.0-beta.0) + +**Added** +- [SDK-3159] JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3244] Add Migration Guide [\#576](https://github.com/auth0/java-jwt/pull/576) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3226] Expose claim name and header constants [\#574](https://github.com/auth0/java-jwt/pull/574) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3231] Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) +- Improved README structure [\#571](https://github.com/auth0/java-jwt/pull/571) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3154] Improved Exception Handling [\#568](https://github.com/auth0/java-jwt/pull/568) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3155] Predicate based Claim verification [\#562](https://github.com/auth0/java-jwt/pull/562) ([poovamraj](https://github.com/poovamraj)) +- Add lint checks [\#561](https://github.com/auth0/java-jwt/pull/561) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3186] Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3149] Add Instant support [\#537](https://github.com/auth0/java-jwt/pull/537) ([jimmyjames](https://github.com/jimmyjames)) +- Testing Java LTS versions [\#536](https://github.com/auth0/java-jwt/pull/536) ([poovamraj](https://github.com/poovamraj)) + +**Changed** +- [SDK-3158] Null claim handling [\#564](https://github.com/auth0/java-jwt/pull/564) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3151] Undeprecate Single Key Constructor for Algorithms [\#551](https://github.com/auth0/java-jwt/pull/551) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3151] Update documentation and undeprecate single content sign methods [\#550](https://github.com/auth0/java-jwt/pull/550) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3197] Update test deps [\#539](https://github.com/auth0/java-jwt/pull/539) ([jimmyjames](https://github.com/jimmyjames)) + +**Deprecated** +- [SDK-3192] Deprecate secp256k1 curve for EC Algorithms [\#540](https://github.com/auth0/java-jwt/pull/540) ([poovamraj](https://github.com/poovamraj)) + +**Removed** +- Remove ES256K support [\#556](https://github.com/auth0/java-jwt/pull/556) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3212] Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3160] Remove internal Clock [\#533](https://github.com/auth0/java-jwt/pull/533) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- Improve keyprovider reliability [\#570](https://github.com/auth0/java-jwt/pull/570) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3186] Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) +- Test only change - remove unnecessary throws clause from tests [\#535](https://github.com/auth0/java-jwt/pull/535) ([jimmyjames](https://github.com/jimmyjames)) + +**Security** +- [SDK-3125] Updated documentation regarding HMAC Key length [\#580](https://github.com/auth0/java-jwt/pull/580) ([poovamraj](https://github.com/poovamraj)) + +**Breaking changes** +- [SDK-3231] Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) +- Improve keyprovider reliability [\#570](https://github.com/auth0/java-jwt/pull/570) ([poovamraj](https://github.com/poovamraj)) +- Remove ES256K support [\#556](https://github.com/auth0/java-jwt/pull/556) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3212] Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3171] Fix header claims serialization [\#549](https://github.com/auth0/java-jwt/pull/549) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3150] Serialize dates in collections as seconds since epoch [\#534](https://github.com/auth0/java-jwt/pull/534) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3160] Replace com.auth0.jwt.interfaces.Clock with java.time.Clock [\#532](https://github.com/auth0/java-jwt/pull/532) ([jimmyjames](https://github.com/jimmyjames)) + ## [3.19.2](https://github.com/auth0/java-jwt/tree/3.19.2) (2022-05-05) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.1...3.19.2) From a14643d4a9cca096d3dc2049db6a4e0522d454d9 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 6 May 2022 16:10:37 +0200 Subject: [PATCH 113/198] Updated Changelog for beta --- CHANGELOG.md | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c920528c..123788f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,51 +1,53 @@ # Change Log ## [4.0.0-beta.0](https://github.com/auth0/java-jwt/tree/4.0.0-beta.0) (2022-05-06) -[Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0...4.0.0-beta.0) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0-beta.0) + +💡 Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. **Added** -- [SDK-3159] JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3244] Add Migration Guide [\#576](https://github.com/auth0/java-jwt/pull/576) ([jimmyjames](https://github.com/jimmyjames)) -- [SDK-3226] Expose claim name and header constants [\#574](https://github.com/auth0/java-jwt/pull/574) ([jimmyjames](https://github.com/jimmyjames)) -- [SDK-3231] Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) +- JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) +- Add Migration Guide [\#576](https://github.com/auth0/java-jwt/pull/576) ([jimmyjames](https://github.com/jimmyjames)) +- Expose claim name and header constants [\#574](https://github.com/auth0/java-jwt/pull/574) ([jimmyjames](https://github.com/jimmyjames)) +- Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) - Improved README structure [\#571](https://github.com/auth0/java-jwt/pull/571) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3154] Improved Exception Handling [\#568](https://github.com/auth0/java-jwt/pull/568) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3155] Predicate based Claim verification [\#562](https://github.com/auth0/java-jwt/pull/562) ([poovamraj](https://github.com/poovamraj)) +- Improved Exception Handling [\#568](https://github.com/auth0/java-jwt/pull/568) ([poovamraj](https://github.com/poovamraj)) +- Predicate based Claim verification [\#562](https://github.com/auth0/java-jwt/pull/562) ([poovamraj](https://github.com/poovamraj)) - Add lint checks [\#561](https://github.com/auth0/java-jwt/pull/561) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3186] Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) -- [SDK-3149] Add Instant support [\#537](https://github.com/auth0/java-jwt/pull/537) ([jimmyjames](https://github.com/jimmyjames)) +- Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) +- Add Instant support [\#537](https://github.com/auth0/java-jwt/pull/537) ([jimmyjames](https://github.com/jimmyjames)) - Testing Java LTS versions [\#536](https://github.com/auth0/java-jwt/pull/536) ([poovamraj](https://github.com/poovamraj)) **Changed** -- [SDK-3158] Null claim handling [\#564](https://github.com/auth0/java-jwt/pull/564) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3151] Undeprecate Single Key Constructor for Algorithms [\#551](https://github.com/auth0/java-jwt/pull/551) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3151] Update documentation and undeprecate single content sign methods [\#550](https://github.com/auth0/java-jwt/pull/550) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3197] Update test deps [\#539](https://github.com/auth0/java-jwt/pull/539) ([jimmyjames](https://github.com/jimmyjames)) +- Null claim handling [\#564](https://github.com/auth0/java-jwt/pull/564) ([poovamraj](https://github.com/poovamraj)) +- Undeprecate Single Key Constructor for Algorithms [\#551](https://github.com/auth0/java-jwt/pull/551) ([poovamraj](https://github.com/poovamraj)) +- Update documentation and undeprecate single content sign methods [\#550](https://github.com/auth0/java-jwt/pull/550) ([poovamraj](https://github.com/poovamraj)) +- Update test deps [\#539](https://github.com/auth0/java-jwt/pull/539) ([jimmyjames](https://github.com/jimmyjames)) **Deprecated** -- [SDK-3192] Deprecate secp256k1 curve for EC Algorithms [\#540](https://github.com/auth0/java-jwt/pull/540) ([poovamraj](https://github.com/poovamraj)) +- Deprecate secp256k1 curve for EC Algorithms [\#540](https://github.com/auth0/java-jwt/pull/540) ([poovamraj](https://github.com/poovamraj)) **Removed** - Remove ES256K support [\#556](https://github.com/auth0/java-jwt/pull/556) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3212] Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3160] Remove internal Clock [\#533](https://github.com/auth0/java-jwt/pull/533) ([jimmyjames](https://github.com/jimmyjames)) +- Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) +- Remove internal Clock [\#533](https://github.com/auth0/java-jwt/pull/533) ([jimmyjames](https://github.com/jimmyjames)) **Fixed** - Improve keyprovider reliability [\#570](https://github.com/auth0/java-jwt/pull/570) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3186] Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) +- Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) - Test only change - remove unnecessary throws clause from tests [\#535](https://github.com/auth0/java-jwt/pull/535) ([jimmyjames](https://github.com/jimmyjames)) **Security** -- [SDK-3125] Updated documentation regarding HMAC Key length [\#580](https://github.com/auth0/java-jwt/pull/580) ([poovamraj](https://github.com/poovamraj)) +- Updated documentation regarding HMAC Key length [\#580](https://github.com/auth0/java-jwt/pull/580) ([poovamraj](https://github.com/poovamraj)) **Breaking changes** -- [SDK-3231] Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) +- Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) - Improve keyprovider reliability [\#570](https://github.com/auth0/java-jwt/pull/570) ([poovamraj](https://github.com/poovamraj)) - Remove ES256K support [\#556](https://github.com/auth0/java-jwt/pull/556) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3212] Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3171] Fix header claims serialization [\#549](https://github.com/auth0/java-jwt/pull/549) ([jimmyjames](https://github.com/jimmyjames)) -- [SDK-3150] Serialize dates in collections as seconds since epoch [\#534](https://github.com/auth0/java-jwt/pull/534) ([jimmyjames](https://github.com/jimmyjames)) -- [SDK-3160] Replace com.auth0.jwt.interfaces.Clock with java.time.Clock [\#532](https://github.com/auth0/java-jwt/pull/532) ([jimmyjames](https://github.com/jimmyjames)) +- Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) +- Fix header claims serialization [\#549](https://github.com/auth0/java-jwt/pull/549) ([jimmyjames](https://github.com/jimmyjames)) +- Serialize dates in collections as seconds since epoch [\#534](https://github.com/auth0/java-jwt/pull/534) ([jimmyjames](https://github.com/jimmyjames)) +- Replace com.auth0.jwt.interfaces.Clock with java.time.Clock [\#532](https://github.com/auth0/java-jwt/pull/532) ([jimmyjames](https://github.com/jimmyjames)) ## [3.19.2](https://github.com/auth0/java-jwt/tree/3.19.2) (2022-05-05) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.1...3.19.2) From 90a68cbeba433c1634450f7d4d32e882aec5bd79 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 24 Jun 2022 11:59:21 +0200 Subject: [PATCH 114/198] Release 4.0.0 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 123788f0..6f330308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ # Change Log +## [4.0.0](https://github.com/auth0/java-jwt/tree/4.0.0) (2022-06-24) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0-beta.0...4.0.0) + ## [4.0.0-beta.0](https://github.com/auth0/java-jwt/tree/4.0.0-beta.0) (2022-05-06) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0-beta.0) -💡 Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. +? Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. **Added** - JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) From b786c9dd42c2332c3caaca3b37198e8025c4b912 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 24 Jun 2022 12:16:27 +0200 Subject: [PATCH 115/198] Update release note in CHANGELOG --- CHANGELOG.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f330308..57edfa51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,24 @@ # Change Log ## [4.0.0](https://github.com/auth0/java-jwt/tree/4.0.0) (2022-06-24) -[Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0-beta.0...4.0.0) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0) + +**This is a major release and contains breaking changes!** + +- Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. + +### Main features +- Predicates based claim verification +- Support for Instant API and Lambda functions +- Improved Exceptions API +- Consistent null handling + +See the changelog entries for additional details. ## [4.0.0-beta.0](https://github.com/auth0/java-jwt/tree/4.0.0-beta.0) (2022-05-06) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0-beta.0) -? Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. +- Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. **Added** - JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) From 84dc66063790905561e3bb2addffcd5bc7125697 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 24 Jun 2022 12:19:12 +0200 Subject: [PATCH 116/198] Fix emoji used in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57edfa51..6023d836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ See the changelog entries for additional details. ## [4.0.0-beta.0](https://github.com/auth0/java-jwt/tree/4.0.0-beta.0) (2022-05-06) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0-beta.0) -- Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. +💡 Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. **Added** - JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) From 929b99ed71c086b044e9f38fb6996298b604d58a Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 24 Jun 2022 13:22:48 +0200 Subject: [PATCH 117/198] Update README.md --- README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f649a742..77fb30fc 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ This library is supported for Java 8, 11, and 17. For issues on non-LTS versions ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.19.0' +implementation 'com.auth0:java-jwt:4.0.0' ``` ### Maven @@ -48,16 +48,10 @@ implementation 'com.auth0:java-jwt:3.19.0' com.auth0 java-jwt - 3.19.2 + 4.0.0 ``` -### Gradle - -```gradle -implementation 'com.auth0:java-jwt:3.19.2' -``` - ## Available Algorithms The library implements JWT Verification and Signing using the following algorithms: From 3e6f4b80a125df67a97df6d8eadf675d79907624 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 5 Jul 2022 17:12:01 -0400 Subject: [PATCH 118/198] Create semgrep.yml --- .github/workflows/semgrep.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/semgrep.yml diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 00000000..e0227e37 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,24 @@ +name: Semgrep + +on: + pull_request: {} + + push: + branches: ["master", "main"] + + schedule: + - cron: '30 0 1,15 * *' + +jobs: + semgrep: + name: Scan + runs-on: ubuntu-latest + container: + image: returntocorp/semgrep + if: (github.actor != 'dependabot[bot]') + steps: + - uses: actions/checkout@v3 + + - run: semgrep ci + env: + SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} From 190132ca282c2b6ff2d12b04c1ea07224ad9f271 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Fri, 8 Jul 2022 00:24:46 -0500 Subject: [PATCH 119/198] Update config.yml --- .circleci/config.yml | 48 +++++++++++++++----------------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f47d6efa..c6425bb8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,4 +1,6 @@ version: 2.1 +orbs: + codecov: codecov/codecov@3 commands: checkout-and-build: @@ -8,9 +10,9 @@ commands: # Download and cache dependencies - restore_cache: keys: - - v1-dependencies-{{ checksum "build.gradle" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- + - v1-dependencies-{{ checksum "build.gradle" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- - run: ./gradlew clean build - save_cache: paths: @@ -19,10 +21,7 @@ commands: run-tests: steps: - run: ./gradlew check jacocoTestReport --continue --console=plain - - run: - name: Upload Coverage - when: on_success - command: bash <(curl -s https://codecov.io/bash) -Z -C $CIRCLE_SHA1 + - codecov/upload # TODO re-enable once 4.0.0 is released # run-api-diff: # steps: @@ -43,36 +42,21 @@ jobs: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb -# api-diff: -# docker: -# - image: openjdk:11.0-jdk -# steps: -# - checkout-and-build -# - run-api-diff -# environment: -# GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' -# _JAVA_OPTIONS: "-Xms512m -Xmx1024m" -# TERM: dumb - semgrep: - docker: - - image: returntocorp/semgrep-agent:v1 - environment: - SEMGREP_REPO_NAME: "auth0/java-jwt" - SEMGREP_REPO_URL: "https://github.com/auth0/java-jwt" - steps: - - checkout - - run: - name: Run vulnerabilities tests (Semgrep) - command: | - semgrep-agent --baseline-ref master --publish-token $SEMGREP_TOKEN + # api-diff: + # docker: + # - image: openjdk:11.0-jdk + # steps: + # - checkout-and-build + # - run-api-diff + # environment: + # GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + # _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + # TERM: dumb workflows: build-and-test: jobs: - build - - semgrep: - context: - - semgrep-env # api-diff: # jobs: # - api-diff From 8dc5fbb6cde11084c9c74096d39f869910733bb8 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Mon, 11 Jul 2022 15:29:08 +0200 Subject: [PATCH 120/198] reenable api-diff check for 4.0.0 --- .circleci/config.yml | 43 +++++++++++++++++++++---------------------- lib/build.gradle | 2 +- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c6425bb8..8532ec75 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,15 +22,14 @@ commands: steps: - run: ./gradlew check jacocoTestReport --continue --console=plain - codecov/upload -# TODO re-enable once 4.0.0 is released -# run-api-diff: -# steps: -# # run apiDiff task -# - run: ./gradlew apiDiff -# - store_artifacts: -# path: lib/build/reports/apiDiff/apiDiff.txt -# - store_artifacts: -# path: lib/build/reports/apiDiff/apiDiff.html + run-api-diff: + steps: + # run apiDiff task + - run: ./gradlew apiDiff + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.txt + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.html jobs: build: docker: @@ -42,21 +41,21 @@ jobs: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb - # api-diff: - # docker: - # - image: openjdk:11.0-jdk - # steps: - # - checkout-and-build - # - run-api-diff - # environment: - # GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - # _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - # TERM: dumb + api-diff: + docker: + - image: openjdk:11.0-jdk + steps: + - checkout-and-build + - run-api-diff + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + TERM: dumb workflows: build-and-test: jobs: - build -# api-diff: -# jobs: -# - api-diff + api-diff: + jobs: + - api-diff diff --git a/lib/build.gradle b/lib/build.gradle index b1b24889..01a5adbe 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -17,7 +17,7 @@ oss { repository "java-jwt" organization "auth0" description "Java implementation of JSON Web Token (JWT)" - baselineCompareVersion "3.18.2" + baselineCompareVersion "4.0.0" developers { auth0 { From f30a47b917478010aa91a2093f9db786ef9a7e32 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Mon, 11 Jul 2022 15:44:38 +0200 Subject: [PATCH 121/198] Update config.yml --- .circleci/config.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8532ec75..1675c39f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,16 +41,16 @@ jobs: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb - api-diff: - docker: - - image: openjdk:11.0-jdk - steps: - - checkout-and-build - - run-api-diff - environment: - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - TERM: dumb + api-diff: + docker: + - image: openjdk:11.0-jdk + steps: + - checkout-and-build + - run-api-diff + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + TERM: dumb workflows: build-and-test: From 0bf73a60152ff86014d6687c336851fe78190aba Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 22 Jul 2022 10:59:13 +0200 Subject: [PATCH 122/198] Provide straightforward example for JWKS --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 77fb30fc..822e9db7 100644 --- a/README.md +++ b/README.md @@ -199,10 +199,10 @@ By using a `KeyProvider` you can change in runtime the key used either to verify - `getPrivateKeyId()`: Its called during token signing and it should return the id of the key that identifies the one returned by `getPrivateKey()`. This value is preferred over the one set in the `JWTCreator.Builder#withKeyId(String)` method. If you don't need to set a `kid` value avoid instantiating an Algorithm using a `KeyProvider`. -The following example shows how this would work with `JwkStore`, an imaginary [JWK Set](https://auth0.com/docs/jwks) implementation. For simple key rotation using JWKS, try the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. +The following example shows how this would work with `JwkProvider` from the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. ```java -final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); +final JwkProvider jwkStore = new UrlJwkProvider("https://samples.auth0.com/"); final RSAPrivateKey privateKey = //Get the key instance final String privateKeyId = //Create an Id for the above key @@ -210,7 +210,7 @@ RSAKeyProvider keyProvider = new RSAKeyProvider() { @Override public RSAPublicKey getPublicKeyById(String kid) { //Received 'kid' value might be null if it wasn't defined in the Token's header - RSAPublicKey publicKey = jwkStore.get(kid); + PublicKey publicKey = jwkStore.get(kid).getPublicKey(); return (RSAPublicKey) publicKey; } From 81c8b46b8974bd2160218c2b5116e6d4dfae4a61 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Mon, 25 Jul 2022 11:02:43 +0200 Subject: [PATCH 123/198] Updated variable name for README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 822e9db7..bbc166c1 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ By using a `KeyProvider` you can change in runtime the key used either to verify The following example shows how this would work with `JwkProvider` from the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. ```java -final JwkProvider jwkStore = new UrlJwkProvider("https://samples.auth0.com/"); +final JwkProvider jwkProvider = new UrlJwkProvider("https://samples.auth0.com/"); final RSAPrivateKey privateKey = //Get the key instance final String privateKeyId = //Create an Id for the above key @@ -210,7 +210,7 @@ RSAKeyProvider keyProvider = new RSAKeyProvider() { @Override public RSAPublicKey getPublicKeyById(String kid) { //Received 'kid' value might be null if it wasn't defined in the Token's header - PublicKey publicKey = jwkStore.get(kid).getPublicKey(); + PublicKey publicKey = jwkProvider.get(kid).getPublicKey(); return (RSAPublicKey) publicKey; } From c36ec6ff80c0d62668a5ae726f9198d3ca587a17 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Mon, 22 Aug 2022 10:51:52 +0200 Subject: [PATCH 124/198] Make JWT constants final values --- lib/src/main/java/com/auth0/jwt/HeaderParams.java | 8 ++++---- .../main/java/com/auth0/jwt/RegisteredClaims.java | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/HeaderParams.java b/lib/src/main/java/com/auth0/jwt/HeaderParams.java index 7af3c442..1107f313 100644 --- a/lib/src/main/java/com/auth0/jwt/HeaderParams.java +++ b/lib/src/main/java/com/auth0/jwt/HeaderParams.java @@ -10,20 +10,20 @@ private HeaderParams() {} /** * The algorithm used to sign a JWT. */ - public static String ALGORITHM = "alg"; + public static final String ALGORITHM = "alg"; /** * The content type of the JWT. */ - public static String CONTENT_TYPE = "cty"; + public static final String CONTENT_TYPE = "cty"; /** * The media type of the JWT. */ - public static String TYPE = "typ"; + public static final String TYPE = "typ"; /** * The key ID of a JWT used to specify the key for signature validation. */ - public static String KEY_ID = "kid"; + public static final String KEY_ID = "kid"; } diff --git a/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java index 8c47eb51..c5509716 100644 --- a/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java +++ b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java @@ -13,43 +13,43 @@ private RegisteredClaims() { * The "iss" (issuer) claim identifies the principal that issued the JWT. * Refer RFC 7529 Section 4.1.1 */ - public static String ISSUER = "iss"; + public static final String ISSUER = "iss"; /** * The "sub" (subject) claim identifies the principal that is the subject of the JWT. * Refer RFC 7529 Section 4.1.2 */ - public static String SUBJECT = "sub"; + public static final String SUBJECT = "sub"; /** * The "aud" (audience) claim identifies the recipients that the JWT is intended for. * Refer RFC 7529 Section 4.1.3 */ - public static String AUDIENCE = "aud"; + public static final String AUDIENCE = "aud"; /** * The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be * accepted for processing. * Refer RFC 7529 Section 4.1.4 */ - public static String EXPIRES_AT = "exp"; + public static final String EXPIRES_AT = "exp"; /** * The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. * Refer RFC 7529 Section 4.1.5 */ - public static String NOT_BEFORE = "nbf"; + public static final String NOT_BEFORE = "nbf"; /** * The "iat" (issued at) claim identifies the time at which the JWT was issued. * Refer RFC 7529 Section 4.1.6 */ - public static String ISSUED_AT = "iat"; + public static final String ISSUED_AT = "iat"; /** * The "jti" (JWT ID) claim provides a unique identifier for the JWT. * Refer RFC 7529 Section 4.1.7 */ - public static String JWT_ID = "jti"; + public static final String JWT_ID = "jti"; } From 15202989d733ce695c302d2f2029bd67c919fd99 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Mon, 29 Aug 2022 19:32:23 -0500 Subject: [PATCH 125/198] [SDK-3816] Update docs for verification thread-safety --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 6 +++++- .../java/com/auth0/jwt/interfaces/JWTVerifier.java | 14 +++++++++++++- .../com/auth0/jwt/interfaces/Verification.java | 4 +++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 0bc17fb1..07c86a4c 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -46,7 +46,11 @@ static Verification init(Algorithm algorithm) throws IllegalArgumentException { } /** - * {@link Verification} implementation that accepts all the expected Claim values for verification. + * {@link Verification} implementation that accepts all the expected Claim values for verification, and + * builds a {@link com.auth0.jwt.interfaces.JWTVerifier} used to verify a JWT's signature and expected claims. + * + * Note that this class is not thread-safe. Calling {@link #build()} returns an instance of + * {@link com.auth0.jwt.interfaces.JWTVerifier} which can be reused. */ public static class BaseVerification implements Verification { private final Algorithm algorithm; diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java index b7030a97..2756ddd8 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java @@ -4,7 +4,19 @@ /** - * Used to verify the JWT for its signature and claims. + * Used to verify the JWT for its signature and claims. Implementations must be thread-safe. Instances are created + * using {@link Verification}. + * + *
+ * try {
+ *      JWTVerifier verifier = JWTVerifier.init(Algorithm.RSA256(publicKey, privateKey)
+ *          .withIssuer("auth0")
+ *          .build();
+ *      DecodedJWT jwt = verifier.verify("token");
+ * } catch (JWTVerificationException e) {
+ *      // invalid signature or claims
+ * }
+ * 
*/ public interface JWTVerifier { diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 4a8a0f84..b4adcf5c 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -7,7 +7,9 @@ import java.util.function.BiPredicate; /** - * Constructs and holds the checks required for a JWT to be considered valid. + * Constructs and holds the checks required for a JWT to be considered valid. Note that implementations are + * not thread-safe. Once built by calling {@link #build()}, the resulting + * {@link com.auth0.jwt.interfaces.JWTVerifier} is thread-safe. */ public interface Verification { From f530b268354926792461926a0eb8d810be01583b Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Mon, 29 Aug 2022 20:11:33 -0500 Subject: [PATCH 126/198] Check for null token before splitting --- lib/src/main/java/com/auth0/jwt/TokenUtils.java | 3 +++ lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/lib/src/main/java/com/auth0/jwt/TokenUtils.java b/lib/src/main/java/com/auth0/jwt/TokenUtils.java index 0a76028b..6fa731c5 100644 --- a/lib/src/main/java/com/auth0/jwt/TokenUtils.java +++ b/lib/src/main/java/com/auth0/jwt/TokenUtils.java @@ -12,6 +12,9 @@ abstract class TokenUtils { * @throws JWTDecodeException if the Token doesn't have 3 parts. */ static String[] splitToken(String token) throws JWTDecodeException { + if (token == null) { + throw new JWTDecodeException("The token is null."); + } String[] parts = token.split("\\."); if (parts.length == 2 && token.endsWith(".")) { //Tokens with alg='none' have empty String as Signature. diff --git a/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java b/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java index d32ee859..8649ec90 100644 --- a/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java +++ b/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java @@ -52,4 +52,11 @@ public void shouldThrowOnSplitTokenWithLessThan3Parts() { String token = "two.parts"; TokenUtils.splitToken(token); } + + @Test + public void shouldThrowOnSplitTokenWithNullValue() { + exception.expect(JWTDecodeException.class); + exception.expectMessage("The token is null."); + TokenUtils.splitToken(null); + } } \ No newline at end of file From 39eec490bd9ad07f7260e9fa1fd0cf60369c6036 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 31 Aug 2022 15:34:11 +0200 Subject: [PATCH 127/198] Trigger Build From 122a8af5c09b72a6fd22185970031c1a5b3e6961 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 31 Aug 2022 15:52:11 +0200 Subject: [PATCH 128/198] Disable API diff task temporarily --- .circleci/config.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1675c39f..fb185917 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,14 +22,14 @@ commands: steps: - run: ./gradlew check jacocoTestReport --continue --console=plain - codecov/upload - run-api-diff: - steps: - # run apiDiff task - - run: ./gradlew apiDiff - - store_artifacts: - path: lib/build/reports/apiDiff/apiDiff.txt - - store_artifacts: - path: lib/build/reports/apiDiff/apiDiff.html + # run-api-diff: + # steps: + # # run apiDiff task + # - run: ./gradlew apiDiff + # - store_artifacts: + # path: lib/build/reports/apiDiff/apiDiff.txt + # - store_artifacts: + # path: lib/build/reports/apiDiff/apiDiff.html jobs: build: docker: From 89fbc61318641b88e3b8b400ff4e8a67d04632fa Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 31 Aug 2022 15:56:09 +0200 Subject: [PATCH 129/198] Disable API diff --- .circleci/config.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fb185917..73f58ddb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,21 +41,21 @@ jobs: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb - api-diff: - docker: - - image: openjdk:11.0-jdk - steps: - - checkout-and-build - - run-api-diff - environment: - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - TERM: dumb + # api-diff: + # docker: + # - image: openjdk:11.0-jdk + # steps: + # - checkout-and-build + # - run-api-diff + # environment: + # GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + # _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + # TERM: dumb workflows: build-and-test: jobs: - build - api-diff: - jobs: - - api-diff + # api-diff: + # jobs: + # - api-diff From 884ac926d7f7afb7e42036a65dc739e9bed79e25 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 7 Sep 2022 10:22:13 -0500 Subject: [PATCH 130/198] Update OSS plugin to latest --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index b614415b..a4cda2d7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,7 +3,7 @@ pluginManagement { gradlePluginPortal() } plugins { - id 'com.auth0.gradle.oss-library.java' version '0.16.0' + id 'com.auth0.gradle.oss-library.java' version '0.17.2' } } From f29d70c56768bd7ed8356a44040082087e076cd8 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 8 Sep 2022 21:41:03 -0500 Subject: [PATCH 131/198] Update to gradle 6.9.2 --- gradle/wrapper/gradle-wrapper.properties | 2 +- lib/build.gradle | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4d9ca164..ec991f9a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/lib/build.gradle b/lib/build.gradle index 01a5adbe..eab6a0a5 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -44,7 +44,7 @@ java { compileJava { exclude 'module-info.java' // Required to be compatible with JDK 8+ - options.compilerArgs = ['--release', "8"] + options.release = 8 } javadoc { @@ -99,7 +99,8 @@ task compileModuleInfoJava(type: JavaCompile) { } compileTestJava { - options.compilerArgs = ['--release', "8", "-Xlint:deprecation"] + options.release = 8 + options.compilerArgs = ["-Xlint:deprecation"] } def testJava8 = tasks.register('testJava8', Test) { From adbabeb7c45fc3862a5d5b2c64321d06a33c725d Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Mon, 19 Sep 2022 08:17:15 -0500 Subject: [PATCH 132/198] Add Ship CLI support --- .shiprc | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .shiprc diff --git a/.shiprc b/.shiprc new file mode 100644 index 00000000..7509d078 --- /dev/null +++ b/.shiprc @@ -0,0 +1,6 @@ +{ + "files": { + "README.md": [] + }, + "prefixVersion": false +} \ No newline at end of file From 75a0bc0f9f0411891bb0e791e13dfa91a01e38ac Mon Sep 17 00:00:00 2001 From: frederikprijck Date: Wed, 5 Oct 2022 14:52:37 +0200 Subject: [PATCH 133/198] Add integration with our Shipping orb --- .circleci/config.yml | 12 ++++++++++++ .shiprc | 3 ++- lib/build.gradle | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 73f58ddb..4741868d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,6 @@ version: 2.1 orbs: + ship: auth0/ship@0.7.1 codecov: codecov/codecov@3 commands: @@ -56,6 +57,17 @@ workflows: build-and-test: jobs: - build + - ship/java-publish: + prefix-tag: false + context: + - publish-gh + - publish-sonatype + filters: + branches: + only: + - master + requires: + - build # api-diff: # jobs: # - api-diff diff --git a/.shiprc b/.shiprc index 7509d078..a00fb91e 100644 --- a/.shiprc +++ b/.shiprc @@ -1,6 +1,7 @@ { "files": { - "README.md": [] + "README.md": [], + "lib/build.gradle": [] }, "prefixVersion": false } \ No newline at end of file diff --git a/lib/build.gradle b/lib/build.gradle index eab6a0a5..74cdb5c1 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,3 +1,7 @@ +buildscript { + version = "4.0.0" +} + plugins { id 'java' id 'jacoco' @@ -5,6 +9,13 @@ plugins { id 'checkstyle' } +def signingKey = findProperty('SIGNING_KEY') +def signingKeyPwd = findProperty('SIGNING_PASSWORD') + +signing { + useInMemoryPgpKeys(signingKey, signingKeyPwd) +} + checkstyle { toolVersion '10.0' checkstyleTest.enabled = false //We are disabling lint checks for tests @@ -134,3 +145,11 @@ jar { compileModuleInfoJava.dependsOn compileJava classes.dependsOn compileModuleInfoJava + +// Creates a version.txt file containing the current version of the SDK. +// This file is picked up and parsed by our Ship Orb to determine the version. +task exportVersion() { + doLast { + new File(rootDir, "version.txt").text = "$version" + } +} From d2bf8d35e96ea8233f2a80ca6d7371ad54e5ba18 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 6 Oct 2022 13:47:56 -0500 Subject: [PATCH 134/198] Release 4.1.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ README.md | 4 ++-- lib/build.gradle | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6023d836..04796fd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Change Log +## [4.1.0](https://github.com/auth0/java-jwt/tree/4.1.0) (2022-10-06) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0...4.1.0) + +**⚠️ BREAKING CHANGES** +- Make JWT constants final values [\#604](https://github.com/auth0/java-jwt/pull/604) ([poovamraj](https://github.com/poovamraj)) + +**Added** +- Add integration with our Shipping orb [\#612](https://github.com/auth0/java-jwt/pull/612) ([frederikprijck](https://github.com/frederikprijck)) +- Add Ship CLI support [\#609](https://github.com/auth0/java-jwt/pull/609) ([jimmyjames](https://github.com/jimmyjames)) +- Provide straightforward example for JWKS [\#600](https://github.com/auth0/java-jwt/pull/600) ([poovamraj](https://github.com/poovamraj)) + +**Changed** +- Update to gradle 6.9.2 [\#608](https://github.com/auth0/java-jwt/pull/608) ([jimmyjames](https://github.com/jimmyjames)) +- Update OSS plugin to latest [\#607](https://github.com/auth0/java-jwt/pull/607) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3466] Upgrade Codecov [\#595](https://github.com/auth0/java-jwt/pull/595) ([evansims](https://github.com/evansims)) +- Update README.md [\#590](https://github.com/auth0/java-jwt/pull/590) ([poovamraj](https://github.com/poovamraj)) + +**Fixed** +- Check for null token before splitting [\#606](https://github.com/auth0/java-jwt/pull/606) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3816] Update docs for verification thread-safety [\#605](https://github.com/auth0/java-jwt/pull/605) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.0.0](https://github.com/auth0/java-jwt/tree/4.0.0) (2022-06-24) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0) diff --git a/README.md b/README.md index bbc166c1..a4fa509e 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ This library is supported for Java 8, 11, and 17. For issues on non-LTS versions ### Gradle ```gradle -implementation 'com.auth0:java-jwt:4.0.0' +implementation 'com.auth0:java-jwt:4.1.0' ``` ### Maven @@ -48,7 +48,7 @@ implementation 'com.auth0:java-jwt:4.0.0' com.auth0 java-jwt - 4.0.0 + 4.1.0 ``` diff --git a/lib/build.gradle b/lib/build.gradle index 74cdb5c1..8ec1b630 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,5 +1,5 @@ buildscript { - version = "4.0.0" + version = "4.1.0" } plugins { From 9c708c2ca0a9c7a6ab7b900fcf4292d0654a78ea Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Mon, 10 Oct 2022 21:08:49 +0200 Subject: [PATCH 135/198] Update config.yml --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4741868d..505c6f70 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@0.7.1 + ship: auth0/ship@dev:a88c4d7 codecov: codecov/codecov@3 commands: @@ -66,6 +66,7 @@ workflows: branches: only: - master + - automated-release requires: - build # api-diff: From 7cac89a50bafa65cafebbc919d8b18616b173853 Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Mon, 10 Oct 2022 21:19:03 +0200 Subject: [PATCH 136/198] Update config.yml --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 505c6f70..b431f8d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,6 +59,7 @@ workflows: - build - ship/java-publish: prefix-tag: false + package-name: java-jwt context: - publish-gh - publish-sonatype From 245c019bd20bb479b68f220d576e5ad78cae9779 Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Mon, 10 Oct 2022 21:23:39 +0200 Subject: [PATCH 137/198] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b431f8d7..10a12231 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@dev:a88c4d7 + ship: auth0/ship@dev:9412fdc codecov: codecov/codecov@3 commands: From bd9c8133025c9da93ce7d5f8885fea43b75c2421 Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Mon, 10 Oct 2022 21:35:27 +0200 Subject: [PATCH 138/198] Update config.yml --- .circleci/config.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 10a12231..6a313263 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@dev:9412fdc + ship: auth0/ship@dev:d4bf2df codecov: codecov/codecov@3 commands: @@ -59,7 +59,6 @@ workflows: - build - ship/java-publish: prefix-tag: false - package-name: java-jwt context: - publish-gh - publish-sonatype From 6a666b0c822d8a453405e67f702c0b326d2d8a5c Mon Sep 17 00:00:00 2001 From: frederikprijck Date: Tue, 11 Oct 2022 12:05:04 +0200 Subject: [PATCH 139/198] update build.gradle file --- lib/build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 8ec1b630..d7951ff3 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -9,8 +9,8 @@ plugins { id 'checkstyle' } -def signingKey = findProperty('SIGNING_KEY') -def signingKeyPwd = findProperty('SIGNING_PASSWORD') +def signingKey = findProperty('signingKey') +def signingKeyPwd = findProperty('signingPassword') signing { useInMemoryPgpKeys(signingKey, signingKeyPwd) @@ -29,6 +29,7 @@ oss { organization "auth0" description "Java implementation of JSON Web Token (JWT)" baselineCompareVersion "4.0.0" + skipAssertSigningConfiguration true developers { auth0 { @@ -66,6 +67,7 @@ javadoc { dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' + testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' testImplementation 'net.jodah:concurrentunit:0.4.6' From 4fbe13f8da785c4826cf5b8fd4c3fec708cdda89 Mon Sep 17 00:00:00 2001 From: frederikprijck Date: Tue, 11 Oct 2022 12:18:20 +0200 Subject: [PATCH 140/198] revert changes --- .circleci/config.yml | 1 - lib/build.gradle | 1 - 2 files changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6a313263..23f09bb2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,7 +66,6 @@ workflows: branches: only: - master - - automated-release requires: - build # api-diff: diff --git a/lib/build.gradle b/lib/build.gradle index d7951ff3..3649399c 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -67,7 +67,6 @@ javadoc { dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' - testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' testImplementation 'net.jodah:concurrentunit:0.4.6' From a2ecb759b2b0200f5a61fe9227b5a2ddf804eee7 Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Tue, 11 Oct 2022 15:16:41 +0200 Subject: [PATCH 141/198] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 23f09bb2..e6b1dcd9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@dev:d4bf2df + ship: auth0/ship@0.7.2 codecov: codecov/codecov@3 commands: From 1e9072d5b0f762f8fe83c6674cd9a3a77bc83255 Mon Sep 17 00:00:00 2001 From: noetro Date: Sat, 15 Oct 2022 03:34:26 +0200 Subject: [PATCH 142/198] Optimise TokenUtils parsing (#611) * Optimise parsing of token for well-defined JWT format * Update error message in test to match new code * Fixing checkstyle issues * Added missing test case for no parts * Return new JWTDecodeException Return a new JWTDecodeException from private utility method `wrongNumberOfParts`, instead of throwing, since we throw from `splitToken()`. Co-authored-by: Jim Anderson Co-authored-by: Jim Anderson --- .../main/java/com/auth0/jwt/TokenUtils.java | 32 ++++++++++++---- .../java/com/auth0/jwt/JWTDecoderTest.java | 2 +- .../java/com/auth0/jwt/TokenUtilsTest.java | 38 +++++++++++++++++-- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/TokenUtils.java b/lib/src/main/java/com/auth0/jwt/TokenUtils.java index 6fa731c5..e62b9832 100644 --- a/lib/src/main/java/com/auth0/jwt/TokenUtils.java +++ b/lib/src/main/java/com/auth0/jwt/TokenUtils.java @@ -15,15 +15,33 @@ static String[] splitToken(String token) throws JWTDecodeException { if (token == null) { throw new JWTDecodeException("The token is null."); } - String[] parts = token.split("\\."); - if (parts.length == 2 && token.endsWith(".")) { - //Tokens with alg='none' have empty String as Signature. - parts = new String[]{parts[0], parts[1], ""}; + + char delimiter = '.'; + + int firstPeriodIndex = token.indexOf(delimiter); + if (firstPeriodIndex == -1) { + throw wrongNumberOfParts(0); } - if (parts.length != 3) { - throw new JWTDecodeException( - String.format("The token was expected to have 3 parts, but got %s.", parts.length)); + + int secondPeriodIndex = token.indexOf(delimiter, firstPeriodIndex + 1); + if (secondPeriodIndex == -1) { + throw wrongNumberOfParts(2); + } + + // too many ? + if (token.indexOf(delimiter, secondPeriodIndex + 1) != -1) { + throw wrongNumberOfParts("> 3"); } + + String[] parts = new String[3]; + parts[0] = token.substring(0, firstPeriodIndex); + parts[1] = token.substring(firstPeriodIndex + 1, secondPeriodIndex); + parts[2] = token.substring(secondPeriodIndex + 1); + return parts; } + + private static JWTDecodeException wrongNumberOfParts(Object partCount) { + return new JWTDecodeException(String.format("The token was expected to have 3 parts, but got %s.", partCount)); + } } diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index 82dc895c..cc427d60 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -50,7 +50,7 @@ public void shouldThrowIfLessThan3Parts() { @Test public void shouldThrowIfMoreThan3Parts() { exception.expect(JWTDecodeException.class); - exception.expectMessage("The token was expected to have 3 parts, but got 4."); + exception.expectMessage("The token was expected to have 3 parts, but got > 3."); JWT.decode("this.has.four.parts"); } diff --git a/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java b/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java index 8649ec90..01806ddc 100644 --- a/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java +++ b/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java @@ -13,6 +13,30 @@ public class TokenUtilsTest { @Rule public ExpectedException exception = ExpectedException.none(); + @Test + public void toleratesEmptyFirstPart() { + String token = ".eyJpc3MiOiJhdXRoMCJ9.W1mx_Y0hbAMbPmfW9whT605AAcxB7REFuJiDAHk2Sdc"; + String[] parts = TokenUtils.splitToken(token); + + assertThat(parts, is(notNullValue())); + assertThat(parts, is(arrayWithSize(3))); + assertThat(parts[0], is("")); + assertThat(parts[1], is("eyJpc3MiOiJhdXRoMCJ9")); + assertThat(parts[2], is("W1mx_Y0hbAMbPmfW9whT605AAcxB7REFuJiDAHk2Sdc")); + } + + @Test + public void toleratesEmptySecondPart() { + String token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0..W1mx_Y0hbAMbPmfW9whT605AAcxB7REFuJiDAHk2Sdc"; + String[] parts = TokenUtils.splitToken(token); + + assertThat(parts, is(notNullValue())); + assertThat(parts, is(arrayWithSize(3))); + assertThat(parts[0], is("eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0")); + assertThat(parts[1], is("")); + assertThat(parts[2], is("W1mx_Y0hbAMbPmfW9whT605AAcxB7REFuJiDAHk2Sdc")); + } + @Test public void shouldSplitToken() { String token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJhdXRoMCJ9.W1mx_Y0hbAMbPmfW9whT605AAcxB7REFuJiDAHk2Sdc"; @@ -34,19 +58,27 @@ public void shouldSplitTokenWithEmptySignature() { assertThat(parts, is(arrayWithSize(3))); assertThat(parts[0], is("eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0")); assertThat(parts[1], is("eyJpc3MiOiJhdXRoMCJ9")); - assertThat(parts[2], is(isEmptyString())); + assertThat(parts[2], is(emptyString())); } @Test public void shouldThrowOnSplitTokenWithMoreThan3Parts() { exception.expect(JWTDecodeException.class); - exception.expectMessage("The token was expected to have 3 parts, but got 4."); + exception.expectMessage("The token was expected to have 3 parts, but got > 3."); String token = "this.has.four.parts"; TokenUtils.splitToken(token); } @Test - public void shouldThrowOnSplitTokenWithLessThan3Parts() { + public void shouldThrowOnSplitTokenWithNoParts() { + exception.expect(JWTDecodeException.class); + exception.expectMessage("The token was expected to have 3 parts, but got 0."); + String token = "notajwt"; + TokenUtils.splitToken(token); + } + + @Test + public void shouldThrowOnSplitTokenWith2Parts() { exception.expect(JWTDecodeException.class); exception.expectMessage("The token was expected to have 3 parts, but got 2."); String token = "two.parts"; From 9840e74b7ef87ce9678d2ee71308499fb1b6f757 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 14 Oct 2022 20:37:24 -0500 Subject: [PATCH 143/198] Update Claim#asString documentation (#615) --- lib/src/main/java/com/auth0/jwt/interfaces/Claim.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index 7c4da4f7..ca5244d6 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -62,9 +62,10 @@ public interface Claim { /** * Get this Claim as a String. - * If the value isn't of type String or it can't be converted to a String, {@code null} will be returned. + * If the value isn't of type String, {@code null} will be returned. For a String representation of non-textual + * claim types, clients can call {@code toString()}. * - * @return the value as a String or null. + * @return the value as a String or null if the underlying value is not a string. */ String asString(); From 1875ed74b136422952080b5f2ac7656e67e51f27 Mon Sep 17 00:00:00 2001 From: "sre-57-opslevel[bot]" <113727212+sre-57-opslevel[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 13:07:35 +0000 Subject: [PATCH 144/198] Upload OpsLevel YAML --- opslevel.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 opslevel.yml diff --git a/opslevel.yml b/opslevel.yml new file mode 100644 index 00000000..009a5ec0 --- /dev/null +++ b/opslevel.yml @@ -0,0 +1,6 @@ +--- +version: 1 +repository: + owner: dx_sdks + tier: + tags: From e8808abb910bb2d3ea8da816e626a7df9fc543e9 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 19 Oct 2022 10:25:34 -0500 Subject: [PATCH 145/198] Update .shiprc to only update lib version in build.gradle (#625) --- .shiprc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.shiprc b/.shiprc index a00fb91e..2bd0fdb3 100644 --- a/.shiprc +++ b/.shiprc @@ -1,7 +1,7 @@ { "files": { "README.md": [], - "lib/build.gradle": [] + "lib/build.gradle": ["version[[:blank:]]*=[[:blank:]]*{MAJOR}.{MINOR}.{PATCH}"] }, "prefixVersion": false } \ No newline at end of file From 49c5def67ab2d37aa98f210bec2ca5ea39889d8c Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 19 Oct 2022 10:46:57 -0500 Subject: [PATCH 146/198] Re-enable japicmp API diff checking (#619) --- .circleci/config.yml | 42 +++++++++++++++++++++--------------------- lib/build.gradle | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e6b1dcd9..32e9d4de 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,14 +23,14 @@ commands: steps: - run: ./gradlew check jacocoTestReport --continue --console=plain - codecov/upload - # run-api-diff: - # steps: - # # run apiDiff task - # - run: ./gradlew apiDiff - # - store_artifacts: - # path: lib/build/reports/apiDiff/apiDiff.txt - # - store_artifacts: - # path: lib/build/reports/apiDiff/apiDiff.html + run-api-diff: + steps: + # run apiDiff task + - run: ./gradlew apiDiff + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.txt + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.html jobs: build: docker: @@ -42,16 +42,16 @@ jobs: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb - # api-diff: - # docker: - # - image: openjdk:11.0-jdk - # steps: - # - checkout-and-build - # - run-api-diff - # environment: - # GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - # _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - # TERM: dumb + api-diff: + docker: + - image: openjdk:11.0-jdk + steps: + - checkout-and-build + - run-api-diff + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + TERM: dumb workflows: build-and-test: @@ -68,6 +68,6 @@ workflows: - master requires: - build - # api-diff: - # jobs: - # - api-diff + api-diff: + jobs: + - api-diff diff --git a/lib/build.gradle b/lib/build.gradle index 3649399c..a0d7a7da 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -28,7 +28,7 @@ oss { repository "java-jwt" organization "auth0" description "Java implementation of JSON Web Token (JWT)" - baselineCompareVersion "4.0.0" + baselineCompareVersion "4.1.0" skipAssertSigningConfiguration true developers { From 3526fcc80ec15038de1a13a2c84d55d2453c835a Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 19 Oct 2022 21:53:07 -0500 Subject: [PATCH 147/198] Release 4.2.0 (#626) --- CHANGELOG.md | 12 ++++++++++++ README.md | 4 ++-- lib/build.gradle | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04796fd7..c6c2d511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## [4.2.0](https://github.com/auth0/java-jwt/tree/4.2.0) (2022-10-19) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.1.0...4.2.0) + +**Changed** +- Re-enable japicmp API diff checking [\#619](https://github.com/auth0/java-jwt/pull/619) ([jimmyjames](https://github.com/jimmyjames)) +- Update .shiprc to only update lib version in build.gradle [\#625](https://github.com/auth0/java-jwt/pull/625) ([jimmyjames](https://github.com/jimmyjames)) +- Optimise TokenUtils parsing [\#611](https://github.com/auth0/java-jwt/pull/611) ([noetro](https://github.com/noetro)) +- Update Circle Ship Orb configuration [\#616](https://github.com/auth0/java-jwt/pull/616) ([frederikprijck](https://github.com/frederikprijck)) + +**Fixed** +- Update Claim#asString documentation [\#615](https://github.com/auth0/java-jwt/pull/615) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.1.0](https://github.com/auth0/java-jwt/tree/4.1.0) (2022-10-06) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0...4.1.0) diff --git a/README.md b/README.md index a4fa509e..89b67ad8 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ This library is supported for Java 8, 11, and 17. For issues on non-LTS versions ### Gradle ```gradle -implementation 'com.auth0:java-jwt:4.1.0' +implementation 'com.auth0:java-jwt:4.2.0' ``` ### Maven @@ -48,7 +48,7 @@ implementation 'com.auth0:java-jwt:4.1.0' com.auth0 java-jwt - 4.1.0 + 4.2.0 ``` diff --git a/lib/build.gradle b/lib/build.gradle index a0d7a7da..05807472 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,5 +1,5 @@ buildscript { - version = "4.1.0" + version = "4.2.0" } plugins { From 32935767d3414d1e91feec41ee69159b3d2524eb Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 20 Oct 2022 17:08:35 -0500 Subject: [PATCH 148/198] Fix .shiprc build.gradle version substitution pattern (#627) --- .shiprc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.shiprc b/.shiprc index 2bd0fdb3..fe59345e 100644 --- a/.shiprc +++ b/.shiprc @@ -1,7 +1,7 @@ { "files": { "README.md": [], - "lib/build.gradle": ["version[[:blank:]]*=[[:blank:]]*{MAJOR}.{MINOR}.{PATCH}"] + "lib/build.gradle": ["version = \"{MAJOR}.{MINOR}.{PATCH}\""] }, "prefixVersion": false } \ No newline at end of file From 3d3980de9c0a84b6265de9263647cdbfda7c8a5b Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Sat, 22 Oct 2022 21:02:48 -0500 Subject: [PATCH 149/198] Update ship orb (#629) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 32e9d4de..9cf198c8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@0.7.2 + ship: auth0/ship@0.7.3 codecov: codecov/codecov@3 commands: From c8d0ba995543c9cee50abe9f4aa4f9873b29489f Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Mon, 24 Oct 2022 09:14:06 -0500 Subject: [PATCH 150/198] Bump `com.fasterxml.jackson.core:jackson-databind` to 2.13.4.2 (#630) Resolves https://www.cve.org/CVERecord?id=CVE-2022-42003 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index 05807472..9cc4349c 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -65,7 +65,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4.2' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' From 3cff66f1a23b51ab85ae4d7c5c1837b90677c75e Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Mon, 24 Oct 2022 16:52:15 -0500 Subject: [PATCH 151/198] Use latest ship orb (#634) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9cf198c8..6c87574b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@0.7.3 + ship: auth0/ship@0 codecov: codecov/codecov@3 commands: From 4c2533dbd0bc1aa714ac7d0e291652cdaecdd265 Mon Sep 17 00:00:00 2001 From: Nikolay Edigaryev Date: Tue, 25 Oct 2022 06:08:09 +0400 Subject: [PATCH 152/198] Remove emoji to fix the broken documentation link (#632) Co-authored-by: Jim Anderson --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 89b67ad8..f96e806f 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); > Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). -##### :key: HMAC Key Length and Security +##### HMAC Key Length and Security When using a Hash-based Message Authentication Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recommendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. From 1e38286a52936d7ccdae3f457369389ad7474b7a Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Tue, 25 Oct 2022 07:26:20 -0500 Subject: [PATCH 153/198] Release 4.2.1 (#636) --- CHANGELOG.md | 7 +++++++ README.md | 4 ++-- lib/build.gradle | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c2d511..ed3706dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [4.2.1](https://github.com/auth0/java-jwt/tree/4.2.1) (2022-10-24) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.0...4.2.1) + +**Security** +- Use latest ship orb [\#634](https://github.com/auth0/java-jwt/pull/634) ([jimmyjames](https://github.com/jimmyjames)) +- Bump `com.fasterxml.jackson.core:jackson-databind` to 2.13.4.2 [\#630](https://github.com/auth0/java-jwt/pull/630) ([evansims](https://github.com/evansims)) + ## [4.2.0](https://github.com/auth0/java-jwt/tree/4.2.0) (2022-10-19) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.1.0...4.2.0) diff --git a/README.md b/README.md index f96e806f..bfa4345d 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ This library is supported for Java 8, 11, and 17. For issues on non-LTS versions ### Gradle ```gradle -implementation 'com.auth0:java-jwt:4.2.0' +implementation 'com.auth0:java-jwt:4.2.1' ``` ### Maven @@ -48,7 +48,7 @@ implementation 'com.auth0:java-jwt:4.2.0' com.auth0 java-jwt - 4.2.0 + 4.2.1 ``` diff --git a/lib/build.gradle b/lib/build.gradle index 9cc4349c..d6c9b061 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,5 +1,5 @@ buildscript { - version = "4.2.0" + version = "4.2.1" } plugins { From d0ebc3f50581f3341a9b214f32612926b8b1a201 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 26 Oct 2022 03:38:39 -0500 Subject: [PATCH 154/198] [SDK-3694] README redesign (#617) --- EXAMPLES.md | 130 +++++++++++++ README.md | 523 ++++++++-------------------------------------------- 2 files changed, 206 insertions(+), 447 deletions(-) create mode 100644 EXAMPLES.md diff --git a/EXAMPLES.md b/EXAMPLES.md new file mode 100644 index 00000000..8a849303 --- /dev/null +++ b/EXAMPLES.md @@ -0,0 +1,130 @@ +# Examples using java-jwt + +* [Inspecting a DecodedJWT](#inspecting-a-decodedjwt) +* [DateTime Claim Validation](#datetime-claim-validation) +* [Using custom claims](#using-custom-claims) +* [Using a KeyProvider](#using-a-keyprovider) + +## Inspecting a DecodedJWT + +The successful verification of a JWT returns a `DecodedJWT`, from which you can obtain its contents. + +```java +DecodedJWT jwt = JWT.require(algorithm) + .build() + .verify("a.b.c"); + +// standard claims can be retrieved through first-class methods +String subject = jwt.getSubject(); +String aud = jwt.getAudience(); +// ... + +// custom claims can also be obtained +String customStringClaim = jwt.getClaim("custom-string-claim").asString(); +``` + +When retrieving custom claims, a [Claim](https://javadoc.io/doc/com.auth0/java-jwt/latest/com/auth0/jwt/interfaces/Claim.html) is returned, which can then be used to obtain the value depending on the value's underlying type. + +## DateTime Claim Validation + +A JWT token may include DateNumber fields that can be used to validate that: + +* The token was issued in a past date `"iat" < NOW` +* The token hasn't expired yet `"exp" > NOW` +* The token can already be used. `"nbf" < NOW` + +When verifying a JWT, the standard DateTime claims are validated by default. A `JWTVerificationException` is thrown if any of the claim values are invalid. + +To specify a **leeway** in which the JWT should still be considered valid, use the `acceptLeeway()` method in the `JWTVerifier` builder and pass a positive seconds value. This applies to every item listed above. + +```java +JWTVerifier verifier = JWT.require(algorithm) + .acceptLeeway(1) // 1 sec for nbf, iat and exp + .build(); +``` + +You can also specify a custom value for a given DateTime claim and override the default one for only that claim. + +```java +JWTVerifier verifier = JWT.require(algorithm) + .acceptLeeway(1) //1 sec for nbf and iat + .acceptExpiresAt(5) //5 secs for exp + .build(); +``` + +If you need to test this behavior in your application, cast the `Verification` instance to a `BaseVerification` to gain visibility of the `verification.build()` method that accepts a `java.time.Clock`. e.g.: + +```java +BaseVerification verification = (BaseVerification) JWT.require(algorithm) + .acceptLeeway(1) + .acceptExpiresAt(5); +private final Clock mockNow = Clock.fixed(Instant.ofEpochSecond(1477592), ZoneId.of("UTC")); +JWTVerifier verifier = verification.build(clock); +``` + +## Using custom claims + +### JWT creation +A JWT can be built with custom payload and header claims, by using the `withHeader` and `withClaim` methods. + +```java +String jwt = JWT.create() + .withHeader(headerMap) + .withClaim("string-claim", "string-value") + .withClaim("number-claim", 42) + .withClaim("bool-claim", true) + .withClaim("datetime-claim", Instant.now()) + .sign(algorithm); +``` + +See the [JavaDoc](https://javadoc.io/doc/com.auth0/java-jwt/latest/com/auth0/jwt/JWTCreator.Builder.html) for all available custom claim methods. + +### JWT verification + +You can also verify a JWT's custom claims: + +```java +JWTVerifier verifier = JWT.require(algorithm) + .withClaim("number-claim", 123) + .withClaimPresence("some-claim-that-just-needs-to-be-present") + .withClaim("predicate-claim", (claim, decodedJWT) -> "custom value".equals(claim.asString())) + .build(); +DecodedJWT jwt = verifier.verify("my.jwt.token"); +``` + +See the [JavaDoc](https://javadoc.io/doc/com.auth0/java-jwt/latest/com/auth0/jwt/JWTVerifier.BaseVerification.html) for all available custom claim verification methods. + +## Using a KeyProvider + +A `KeyProvider` can be used to obtain the keys needed for signing and verifying a JWT. How these keys are constructed are beyond the scope of this library, but the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library provides the ability to obtain the public key from a JWK. +The example below demonstrates this for the RSA algorithm (`ECDSAKeyProvider` can be used for ECDSA). + +```java +JwkProvider provider = new JwkProviderBuilder("https://samples.auth0.com/") + .cached(10, 24, TimeUnit.HOURS) + .rateLimited(10, 1, TimeUnit.MINUTES) + .build(); +final RSAPrivateKey privateKey = // private key +final String privateKeyId = // private key ID + +RSAKeyProvider keyProvider = new RSAKeyProvider() { + @Override + public RSAPublicKey getPublicKeyById(String kid) { + return (RSAPublicKey) jwkProvider.get(kid).getPublicKey(); + } + + @Override + public RSAPrivateKey getPrivateKey() { + // return the private key used + return rsaPrivateKey; + } + + @Override + public String getPrivateKeyId() { + return rsaPrivateKeyId; + } +}; + +Algorithm algorithm = Algorithm.RSA256(keyProvider); +//Use the Algorithm to create and verify JWTs. +``` \ No newline at end of file diff --git a/README.md b/README.md index bfa4345d..7786cbaf 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,26 @@ - - -# Java JWT +![A Java implementation of JSON Web Token (JWT) - RFC 7519.](https://cdn.auth0.com/website/sdks/banners/java-jwt-banner.png) [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) -[![License](https://img.shields.io/:license-mit-blue.svg?style=flat)](https://doge.mit-license.org) -[![Javadoc](https://javadoc.io/badge2/com.auth0/java-jwt/javadoc.svg)](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) - -A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). +[![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](https://doge.mit-license.org/) +[![Maven Central](https://img.shields.io/maven-central/v/com.auth0/java-jwt.svg?style=flat-square)](https://mvnrepository.com/artifact/com.auth0/java-jwt) +[![javadoc](https://javadoc.io/badge2/com.auth0/auth0/javadoc.svg)](https://javadoc.io/doc/com.auth0/java-jwt) -> :warning: **Important security note:** JVM has a critical vulnerability for ECDSA Algorithms - [CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449). Please review the details of the vulnerability and update your environment. - -If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. +:books: [Documentation](#documentation) - :rocket: [Getting Started](#getting-started) - :computer: [API Reference](#api-reference) :speech_balloon: [Feedback](#feedback) -> You are viewing the documentation for the v4 beta release. For the latest stable release, please see the [version 3.x documentation](https://github.com/auth0/java-jwt). +## Documentation +- [Examples](./EXAMPLES.md) - code samples for common java-jwt scenarios. +- [Docs site](https://www.auth0.com/docs) - explore our docs site and learn more about Auth0. -## Table of Contents -- [**Requirements**](#requirements) -- [**Installation**](#installation) -- [**Available Algorithms**](#available-algorithms) -- [**Quickstart**](#quickstart) - + [**Create and Sign a Token**](#create-and-sign-a-token) - + [**Verify a Token**](#verify-a-token) -- [**Usage**](#usage) - + [**Pick the algorithm**](#pick-the-algorithm) - + [**Time Validation**](#time-validation) - + [**Header Claims**](#header-claims) - + [**Payload Claims**](#payload-claims) - + [**Claim Class**](#claim-class) -- [**Javadoc**](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) +## Getting Started -## Requirements +### Requirements -This library is supported for Java 8, 11, and 17. For issues on non-LTS versions above 8, consideration will be given on a case-by-case basis. +This library is supported for Java LTS versions 8, 11, and 17. For issues on non-LTS versions above 8, consideration will be given on a case-by-case basis. -## Installation +> `java-jwt` is intended for server-side JVM applications. Android applications should use [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android). -### Gradle - -```gradle -implementation 'com.auth0:java-jwt:4.2.1' -``` - -### Maven - -```xml - - com.auth0 - java-jwt - 4.2.1 - -``` - -## Available Algorithms - -The library implements JWT Verification and Signing using the following algorithms: +`java-jwt` supports the following algorithms for both signing and verification: | JWS | Algorithm | Description | | :-------------: | :-------------: | :----- | @@ -70,434 +36,97 @@ The library implements JWT Verification and Signing using the following algorith > Note - Support for ECDSA with curve secp256k1 and SHA-256 (ES256K) has been dropped since it has been [disabled in Java 15](https://www.oracle.com/java/technologies/javase/15-relnote-issues.html#JDK-8237219) -## Quickstart - -### Create and Sign a Token - -You'll first need to create a `JWTCreator` instance by calling `JWT.create()`. Use the builder to define the custom Claims your token needs to have. Finally to get the String token call `sign()` and pass the `Algorithm` instance. - -* Example using `HS256` - - ```java - try { - Algorithm algorithm = Algorithm.HMAC256("secret"); - String token = JWT.create() - .withIssuer("auth0") - .sign(algorithm); - } catch (JWTCreationException exception){ - //Invalid Signing configuration / Couldn't convert Claims. - } - ``` - -* Example using `RS256` - - ```java - RSAPublicKey publicKey = //Get the key instance - RSAPrivateKey privateKey = //Get the key instance - try { - Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); - String token = JWT.create() - .withIssuer("auth0") - .sign(algorithm); - } catch (JWTCreationException exception){ - //Invalid Signing configuration / Couldn't convert Claims. - } - ``` - -If a Claim couldn't be converted to JSON or the Key used in the signing process was invalid a `JWTCreationException` will raise. - -### Verify a Token - -You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` and passing the `Algorithm` instance. If you require the token to have specific Claim values, use the builder to define them. The instance returned by the method `build()` is reusable, so you can define it once and use it to verify different tokens. Finally call `verifier.verify()` passing the token. - -* Example using `HS256` - - ```java - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - try { - Algorithm algorithm = Algorithm.HMAC256("secret"); //use more secure key - JWTVerifier verifier = JWT.require(algorithm) - .withIssuer("auth0") - .build(); //Reusable verifier instance - DecodedJWT jwt = verifier.verify(token); - } catch (JWTVerificationException exception){ - //Invalid signature/claims - } - ``` - -* Example using `RS256` - - ```java - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - RSAPublicKey publicKey = //Get the key instance - RSAPrivateKey privateKey = //Get the key instance - try { - Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); - JWTVerifier verifier = JWT.require(algorithm) - .withIssuer("auth0") - .build(); //Reusable verifier instance - DecodedJWT jwt = verifier.verify(token); - } catch (JWTVerificationException exception){ - //Invalid signature/claims - } - ``` - -If the token has an invalid signature or the Claim requirement is not met, a `JWTVerificationException` will raise. - -
-Need to peek into a JWT without verifying it? (Click to expand) - -### Decode a Token - -> __Warning:__ This will __not__ verify whether the signature is valid. You should __not__ use this for untrusted messages. You most likely want to use `JWTVerifier` as documented above instead. - -```java -String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; -try { - DecodedJWT jwt = JWT.decode(token); -} catch (JWTDecodeException exception){ - //Invalid token -} -``` - -If the token has an invalid syntax or the header or payload are not JSONs, a `JWTDecodeException` will raise. - -
- -## Usage - -### Pick the Algorithm - -The Algorithm defines how a token is signed and verified. It can be instantiated with the raw value of the secret in the case of HMAC algorithms, or the key pairs or `KeyProvider` in the case of RSA and ECDSA algorithms. Once created, the instance is reusable for token signing and verification operations. - -When using RSA or ECDSA algorithms and you just need to **sign** JWTs you can avoid specifying a Public Key by passing a `null` value. The same can be done with the Private Key when you just need to **verify** JWTs. - - -#### Using static secrets or keys: - -```java -//HMAC -Algorithm algorithmHS = Algorithm.HMAC256("secret"); //use more secure key - -//RSA -RSAPublicKey publicKey = //Get the key instance -RSAPrivateKey privateKey = //Get the key instance -Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); -``` - -> Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). - -##### HMAC Key Length and Security - -When using a Hash-based Message Authentication Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recommendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. - -#### Using a KeyProvider: -By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: - -- `getPublicKeyById(String kid)`: Its called during token signature verification and it should return the key used to verify the token. If key rotation is being used, e.g. [JWK](https://tools.ietf.org/html/rfc7517) it can fetch the correct rotation key using the id. (Or just return the same key all the time). -- `getPrivateKey()`: Its called during token signing and it should return the key that will be used to sign the JWT. -- `getPrivateKeyId()`: Its called during token signing and it should return the id of the key that identifies the one returned by `getPrivateKey()`. This value is preferred over the one set in the `JWTCreator.Builder#withKeyId(String)` method. If you don't need to set a `kid` value avoid instantiating an Algorithm using a `KeyProvider`. - - -The following example shows how this would work with `JwkProvider` from the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. - -```java -final JwkProvider jwkProvider = new UrlJwkProvider("https://samples.auth0.com/"); -final RSAPrivateKey privateKey = //Get the key instance -final String privateKeyId = //Create an Id for the above key - -RSAKeyProvider keyProvider = new RSAKeyProvider() { - @Override - public RSAPublicKey getPublicKeyById(String kid) { - //Received 'kid' value might be null if it wasn't defined in the Token's header - PublicKey publicKey = jwkProvider.get(kid).getPublicKey(); - return (RSAPublicKey) publicKey; - } - - @Override - public RSAPrivateKey getPrivateKey() { - return privateKey; - } - - @Override - public String getPrivateKeyId() { - return privateKeyId; - } -}; - -Algorithm algorithm = Algorithm.RSA256(keyProvider); -//Use the Algorithm to create and verify JWTs. -``` - - -### Time Validation - -The JWT token may include DateNumber fields that can be used to validate that: -* The token was issued in a past date `"iat" < TODAY` -* The token hasn't expired yet `"exp" > TODAY` and -* The token can already be used. `"nbf" < TODAY` - -When verifying a token the time validation occurs automatically, resulting in a `JWTVerificationException` being throw when the values are invalid. If any of the previous fields are missing they won't be considered in this validation. - -To specify a **leeway window** in which the Token should still be considered valid, use the `acceptLeeway()` method in the `JWTVerifier` builder and pass a positive seconds value. This applies to every item listed above. - -```java -JWTVerifier verifier = JWT.require(algorithm) - .acceptLeeway(1) // 1 sec for nbf, iat and exp - .build(); -``` - -You can also specify a custom value for a given Date claim and override the default one for only that claim. - -```java -JWTVerifier verifier = JWT.require(algorithm) - .acceptLeeway(1) //1 sec for nbf and iat - .acceptExpiresAt(5) //5 secs for exp - .build(); -``` - -If you need to test this behavior in your lib/app cast the `Verification` instance to a `BaseVerification` to gain visibility of the `verification.build()` method that accepts a `java.time.Clock`. e.g.: - -```java -BaseVerification verification = (BaseVerification) JWT.require(algorithm) - .acceptLeeway(1) - .acceptExpiresAt(5); -private final Clock mockNow = Clock.fixed(Instant.ofEpochSecond(1477592), ZoneId.of("UTC")); -JWTVerifier verifier = verification.build(clock); -``` - -### Header Claims - -#### Algorithm ("alg") - -Returns the Algorithm value or null if it's not defined in the Header. - -```java -String algorithm = jwt.getAlgorithm(); -``` - -#### Type ("typ") - -Returns the Type value or null if it's not defined in the Header. - -```java -String type = jwt.getType(); -``` - -#### Content Type ("cty") - -Returns the Content Type value or null if it's not defined in the Header. - -```java -String contentType = jwt.getContentType(); -``` - -#### Key Id ("kid") - -Returns the Key Id value or null if it's not defined in the Header. - -```java -String keyId = jwt.getKeyId(); -``` - -#### Private Claims - -Additional Claims defined in the token's Header can be obtained by calling `getHeaderClaim()` and passing the Claim name. A Claim will always be returned, even if it can't be found. You can check if a Claim's value is null by calling `claim.isNull()`. - -```java -Claim claim = jwt.getHeaderClaim("owner"); -``` - -When creating a Token with the `JWT.create()` you can specify header Claims by calling `withHeader()` and passing both the map of claims. - -```java -Map headerClaims = new HashMap(); -headerClaims.put("owner", "auth0"); -String token = JWT.create() - .withHeader(headerClaims) - .sign(algorithm); -``` - -> The `alg` and `typ` values will always be included in the Header after the signing process. - - -### Payload Claims - -#### Issuer ("iss") - -Returns the Issuer value or null if it's not defined in the Payload. - -```java -String issuer = jwt.getIssuer(); -``` - -#### Subject ("sub") - -Returns the Subject value or null if it's not defined in the Payload. - -```java -String subject = jwt.getSubject(); -``` - -#### Audience ("aud") - -Returns the Audience value or null if it's not defined in the Payload. - -```java -List audience = jwt.getAudience(); -``` - -#### Expiration Time ("exp") - -Returns the Expiration Time value or null if it's not defined in the Payload. - -```java -Date expiresAt = jwt.getExpiresAt(); -``` - -If you prefer to work with `java.time.Instant` instead of `java.util.Date`: - -```java -Instant expiresAt = jwt.getExpiresAtAsInstant(); -``` - -#### Not Before ("nbf") - -Returns the Not Before value or null if it's not defined in the Payload. - -```java -Date notBefore = jwt.getNotBefore(); -``` - -If you prefer to work with `java.time.Instant` instead of `java.util.Date`: - -```java -Instant notBefore = jwt.getNotBeforeAsInstant(); -``` - -#### Issued At ("iat") - -Returns the Issued At value or null if it's not defined in the Payload. - -```java -Date issuedAt = jwt.getIssuedAt(); -``` - -If you prefer to work with `java.time.Instant` instead of `java.util.Date`: - -```java -Instant issuedAt = jwt.getIssuedAtAsInstant(); -``` - -#### JWT ID ("jti") +> :warning: **Important security note:** JVM has a critical vulnerability for ECDSA Algorithms - [CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449). Please review the details of the vulnerability and update your environment. +### Installation -Returns the JWT ID value or null if it's not defined in the Payload. +Add the dependency via Maven: -```java -String id = jwt.getId(); +```xml + + com.auth0 + java-jwt + 4.2.1 + ``` -#### Private Claims +or Gradle: -Additional Claims defined in the token's Payload can be obtained by calling `getClaims()` or `getClaim()` and passing the Claim name. A Claim will always be returned, even if it can't be found. You can check if a Claim's value is null by calling `claim.isNull()`. - -```java -Map claims = jwt.getClaims(); //Key is the Claim name -Claim claim = claims.get("isAdmin"); +```gradle +implementation 'com.auth0:jva-jwt:4.2.1' ``` -or +### Create a JWT -```java -Claim claim = jwt.getClaim("isAdmin"); -``` +Use `JWT.create()`, configure the claims, and then call `sign(algorithm)` to sign the JWT. -When creating a Token with the `JWT.create()` you can specify a custom Claim by calling `withClaim()` and passing both the name and the value. +The example below demonstrates this using the `RS256` signing algorithm: ```java -String token = JWT.create() - .withClaim("name", 123) - .withArrayClaim("array", new Integer[]{1, 2, 3}) - .withNullClaim("claim_name") +try { + Algorithm algorithm = Algorithm.RSA256(rsaPublicKey, rsaPrivateKey); + String token = JWT.create() + .withIssuer("auth0") .sign(algorithm); +} catch (JWTCreationException exception){ + // Invalid Signing configuration / Couldn't convert Claims. +} ``` -You can also create a JWT by calling `withPayload()` and passing a map of claim names to values: +### Verify a JWT -```java -Map payloadClaims = new HashMap<>(); -payloadClaims.put("@context", "https://auth0.com/"); -String token = JWT.create() - .withPayload(payloadClaims) - .sign(algorithm); -``` +Create a `JWTVerifier` passing the `Algorithm`, and specify any required claim values. -You can also verify custom Claims on the `JWT.require()` by calling `withClaim()` and passing both the name and the required value. +The following example uses `RS256` to verify the JWT. ```java -JWTVerifier verifier = JWT.require(algorithm) - .withClaim("name", 123) - .withArrayClaim("array", 1, 2, 3) - .withNullClaim("null_value") //checks if the claim name provided has null value - .withClaimPresence("claim_presence") //checks if the claim name provided is in the payload - .withClaim("predicate", (claim, decodedJWT) -> "custom_check".equals(claim.asString())) //can be used to run custom verification - .build(); -DecodedJWT jwt = verifier.verify("my.jwt.token"); +String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; +DecodedJWT decodedJWT; +try { + Algorithm algorithm = Algorithm.RSA256(rsaPublicKey, rsaPrivateKey); + JWTVerifier verifier = JWT.require(algorithm) + // specify an specific claim validations + .withIssuer("auth0") + // reusable verifier instance + .build(); + + decodedJWT jwt = verifier.verify(token); +} catch (JWTVerificationException exception){ + // Invalid signature/claims +} ``` -> Currently supported classes for custom JWT Claim creation and verification are: Boolean, Integer, Double, String, Date and Arrays of type String and Integer. - - -### Claim Class -The Claim class is a wrapper for the Claim values. It allows you to get the Claim as different class types. The available helpers are: - -#### Primitives -* **asBoolean()**: Returns the Boolean value or null if it can't be converted. -* **asInt()**: Returns the Integer value or null if it can't be converted. -* **asDouble()**: Returns the Double value or null if it can't be converted. -* **asLong()**: Returns the Long value or null if it can't be converted. -* **asString()**: Returns the String value or null if it can't be converted. -* **asInstant()**: Returns the Instant value or null if it can't be converted. -* **asDate()**: Returns the Date value or null if it can't be converted. - -> For `asInstant()` and `asDate()` the value must be a NumericDate (Unix Epoch/Timestamp). Note that the [JWT Standard](https://tools.ietf.org/html/rfc7519#section-2) specified that all the *NumericDate* values must be in seconds. - -#### Custom Classes and Collections -To obtain a Claim as a Collection you'll need to provide the **Class Type** of the contents to convert from. - -* **as(class)**: Returns the value parsed as **Class Type**. For collections you should use the `asArray` and `asList` methods. -* **asMap()**: Returns the value parsed as **Map**. -* **asArray(class)**: Returns the value parsed as an Array of elements of type **Class Type**, or null if the value isn't a JSON Array. -* **asList(class)**: Returns the value parsed as a List of elements of type **Class Type**, or null if the value isn't a JSON Array. - -If the values can't be converted to the given **Class Type** a `JWTDecodeException` will raise. - - +If the token has an invalid signature or the Claim requirement is not met, a `JWTVerificationException` will be thrown. -## What is Auth0? +See the [examples](./EXAMPLES.md) and [JavaDocs](https://javadoc.io/doc/com.auth0/java-jwt/latest) for additional documentation. -Auth0 helps you to: +## API Reference -* Add authentication with [multiple authentication sources](https://auth0.com/docs/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, among others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**. -* Add authentication through more traditional **[username/password databases](https://auth0.com/docs/connections/database)**. -* Add support for **[linking different user accounts](https://auth0.com/docs/users/user-account-linking)** with the same user. -* Support for generating signed [Json Web Tokens](https://auth0.com/docs/tokens/json-web-tokens) to call your APIs and **flow the user identity** securely. -* Analytics of how, when and where users are logging in. -* Pull data from other sources and add it to the user profile, through [JavaScript rules](https://auth0.com/docs/rules). +- [java-jwt JavaDocs](https://javadoc.io/doc/com.auth0/java-jwt/latest) -## Create a free account in Auth0 +## Feedback -1. Go to [Auth0](https://auth0.com) and click Sign Up. -2. Use Google, GitHub or Microsoft Account to login. +### Contributing -## Issue Reporting +We appreciate feedback and contribution to this repo! Before you get started, please see the following: -If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. +- [Auth0's general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) +- [Auth0's code of conduct guidelines]((https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)) -## Author +### Raise an issue +To provide feedback or report a bug, [please raise an issue on our issue tracker](https://github.com/auth0/java-jwt/issues). -[Auth0](https://auth0.com/) +### Vulnerability Reporting +Please do not report security vulnerabilities on the public Github issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. -## License +--- -This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info. +

+ + + + Auth0 Logo + +

+

Auth0 is an easy to implement, adaptable authentication and authorization platform. To learn more checkout Why Auth0?

+

+This project is licensed under the MIT license. See the LICENSE file for more info.

From 56e663c3b5830e0329160f2165043aff89f8d3bb Mon Sep 17 00:00:00 2001 From: Jeff Allen <103958546+trestletech-dd@users.noreply.github.com> Date: Wed, 26 Oct 2022 16:03:25 -0500 Subject: [PATCH 155/198] Fix the spelling of the dependency name in README (#638) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7786cbaf..8e634ee6 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add the dependency via Maven: or Gradle: ```gradle -implementation 'com.auth0:jva-jwt:4.2.1' +implementation 'com.auth0:java-jwt:4.2.1' ``` ### Create a JWT From 931cd1243c4653744c0f1cc59f182566139cd2c5 Mon Sep 17 00:00:00 2001 From: Wilson Chuks Date: Wed, 23 Nov 2022 07:17:22 +0900 Subject: [PATCH 156/198] Fixed a typographical error on the readme file (#641) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e634ee6..903e37ba 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ try { // reusable verifier instance .build(); - decodedJWT jwt = verifier.verify(token); + decodedJWT = verifier.verify(token); } catch (JWTVerificationException exception){ // Invalid signature/claims } From 14618b5af545e382ce734ef3cb54ba12398460dc Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 11 Jan 2023 14:32:18 +0530 Subject: [PATCH 157/198] Temporarily disable auto release --- .circleci/config.yml | 12 ------------ lib/build.gradle | 11 ----------- 2 files changed, 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6c87574b..1675c39f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,5 @@ version: 2.1 orbs: - ship: auth0/ship@0 codecov: codecov/codecov@3 commands: @@ -57,17 +56,6 @@ workflows: build-and-test: jobs: - build - - ship/java-publish: - prefix-tag: false - context: - - publish-gh - - publish-sonatype - filters: - branches: - only: - - master - requires: - - build api-diff: jobs: - api-diff diff --git a/lib/build.gradle b/lib/build.gradle index d6c9b061..6b2fdfe3 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,6 +1,3 @@ -buildscript { - version = "4.2.1" -} plugins { id 'java' @@ -9,13 +6,6 @@ plugins { id 'checkstyle' } -def signingKey = findProperty('signingKey') -def signingKeyPwd = findProperty('signingPassword') - -signing { - useInMemoryPgpKeys(signingKey, signingKeyPwd) -} - checkstyle { toolVersion '10.0' checkstyleTest.enabled = false //We are disabling lint checks for tests @@ -29,7 +19,6 @@ oss { organization "auth0" description "Java implementation of JSON Web Token (JWT)" baselineCompareVersion "4.1.0" - skipAssertSigningConfiguration true developers { auth0 { From b4fae643ad27ed5efb28a12da7ac7d52f9917706 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 11 Jan 2023 17:50:24 +0100 Subject: [PATCH 158/198] Release 4.2.2 (#650) --- CHANGELOG.md | 6 ++++++ README.md | 9 +++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed3706dc..eaf3feec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [4.2.2](https://github.com/auth0/java-jwt/tree/4.2.2) (2023-01-11) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.1...4.2.2) + +This patch release does not contain any functional changes, but is being released using an updated signing key for verification as part of our commitment to best security practices. +Please review [the README note for additional details.](https://github.com/auth0/java-jwt/blob/master/README.md) + ## [4.2.1](https://github.com/auth0/java-jwt/tree/4.2.1) (2022-10-24) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.0...4.2.1) diff --git a/README.md b/README.md index 903e37ba..8583ed7a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +> **Note** +> As part of our ongoing commitment to best security practices, we have rotated the signing keys used to sign previous releases of this SDK. As a result, new patch builds have been released using the new signing key. Please upgrade at your earliest convenience. +> +> While this change won't affect most developers, if you have implemented a dependency signature validation step in your build process, you may notice a warning that past releases can't be verified. This is expected, and a result of the key rotation process. Updating to the latest version will resolve this for you. + ![A Java implementation of JSON Web Token (JWT) - RFC 7519.](https://cdn.auth0.com/website/sdks/banners/java-jwt-banner.png) [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) @@ -45,14 +50,14 @@ Add the dependency via Maven: com.auth0 java-jwt - 4.2.1 + 4.2.2 ``` or Gradle: ```gradle -implementation 'com.auth0:java-jwt:4.2.1' +implementation 'com.auth0:java-jwt:4.2.2' ``` ### Create a JWT From 6e80ea1e5a8624f34f133c3be71078ee9ab83906 Mon Sep 17 00:00:00 2001 From: CodeDead Date: Thu, 26 Jan 2023 22:44:56 +0100 Subject: [PATCH 159/198] Feature/cleanup (#642) * GIT compliance * Simpler HashMap declaration * Replaced usage of deprecated JsonMapper methods, removed unneeded null check * Removed unused import * Removed unused imports Co-authored-by: Jim Anderson --- EXAMPLES.md | 2 +- LICENSE | 2 +- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 11 +++++++---- .../java/com/auth0/jwt/algorithms/RSAAlgorithm.java | 1 - lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java | 2 +- .../test/java/com/auth0/jwt/ConcurrentVerifyTest.java | 3 --- lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java | 2 -- .../com/auth0/jwt/algorithms/HMACAlgorithmTest.java | 1 - settings.gradle | 2 +- 9 files changed, 11 insertions(+), 15 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 8a849303..995e4c1d 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -127,4 +127,4 @@ RSAKeyProvider keyProvider = new RSAKeyProvider() { Algorithm algorithm = Algorithm.RSA256(keyProvider); //Use the Algorithm to create and verify JWTs. -``` \ No newline at end of file +``` diff --git a/LICENSE b/LICENSE index 4a7a13ad..bcd1854c 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index a99f0fa0..7ed83940 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import java.nio.charset.StandardCharsets; @@ -31,12 +32,14 @@ public final class JWTCreator { private static final SimpleModule module; static { - mapper = new ObjectMapper(); module = new SimpleModule(); module.addSerializer(PayloadClaimsHolder.class, new PayloadSerializer()); module.addSerializer(HeaderClaimsHolder.class, new HeaderSerializer()); - mapper.registerModule(module); - mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); + + mapper = JsonMapper.builder() + .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) + .build() + .registerModule(module); } private JWTCreator(Algorithm algorithm, Map headerClaims, Map payloadClaims) @@ -489,7 +492,7 @@ private static boolean validateClaim(Map map) { return false; } - if (entry.getKey() == null || !(entry.getKey() instanceof String)) { + if (!(entry.getKey() instanceof String)) { return false; } } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index 0c7a5b57..ca892e60 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -5,7 +5,6 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.RSAKeyProvider; -import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; diff --git a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java index 3746dcd2..8031d8c0 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java +++ b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java @@ -37,7 +37,7 @@ class BasicHeader implements Header, Serializable { this.type = type; this.contentType = contentType; this.keyId = keyId; - this.tree = Collections.unmodifiableMap(tree == null ? new HashMap() : tree); + this.tree = Collections.unmodifiableMap(tree == null ? new HashMap<>() : tree); this.objectReader = objectReader; } diff --git a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java index a06df7b7..32ede1de 100644 --- a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java +++ b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java @@ -10,14 +10,11 @@ import org.junit.rules.ExpectedException; import java.security.interfaces.ECKey; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAKey; import java.util.Collections; import java.util.List; import java.util.concurrent.*; -import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; //@Ignore("Skipping concurrency tests") diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 020e5e37..8fc83b44 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -16,9 +16,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java index 4a0269cc..9b6ac0c0 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java @@ -13,7 +13,6 @@ import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import static com.auth0.jwt.algorithms.CryptoTestHelper.asJWT; import static com.auth0.jwt.algorithms.CryptoTestHelper.assertSignaturePresent; diff --git a/settings.gradle b/settings.gradle index a4cda2d7..8d5f112c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,4 +8,4 @@ pluginManagement { } include ':java-jwt' -project(':java-jwt').projectDir = new File(rootProject.projectDir, '/lib') \ No newline at end of file +project(':java-jwt').projectDir = new File(rootProject.projectDir, '/lib') From 12ae664a60d5e12d824c0896d5a82ef8522a8b69 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 26 Jan 2023 17:04:32 -0600 Subject: [PATCH 160/198] Fix for `exp` claim considered valid if equal to now (#652) exp claim cannot be equal to now --- .../main/java/com/auth0/jwt/JWTVerifier.java | 6 ++-- lib/src/test/java/com/auth0/jwt/JWTTest.java | 5 ++-- .../java/com/auth0/jwt/JWTVerifierTest.java | 30 +++++++++++++++++-- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 07c86a4c..6cec2026 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -346,7 +346,7 @@ private boolean assertValidInstantClaim(String claimName, Claim claim, long leew throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal), claimVal); } } else { - isValid = assertInstantIsPast(claimVal, leeway, now); + isValid = assertInstantIsLessThanOrEqualToNow(claimVal, leeway, now); if (!isValid) { throw new IncorrectClaimException( String.format("The Token can't be used before %s.", claimVal), claimName, claim); @@ -356,10 +356,10 @@ private boolean assertValidInstantClaim(String claimName, Claim claim, long leew } private boolean assertInstantIsFuture(Instant claimVal, long leeway, Instant now) { - return !(claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)); + return claimVal == null || now.minus(Duration.ofSeconds(leeway)).isBefore(claimVal); } - private boolean assertInstantIsPast(Instant claimVal, long leeway, Instant now) { + private boolean assertInstantIsLessThanOrEqualToNow(Instant claimVal, long leeway, Instant now) { return !(claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index b9f56a2e..087f1e9e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -12,6 +12,7 @@ import java.security.interfaces.ECKey; import java.security.interfaces.RSAKey; import java.time.Clock; +import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.util.Base64; @@ -270,12 +271,12 @@ public void shouldGetStringAudience() { @Test public void shouldGetExpirationTime() { long seconds = 1477592L; - Clock clock = Clock.fixed(Instant.ofEpochSecond(seconds), ZoneId.of("UTC")); + Clock mockNow = Clock.fixed(Instant.ofEpochSecond(seconds - 1), ZoneId.of("UTC")); String token = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0Nzc1OTJ9.x_ZjkPkKYUV5tdvc0l8go6D_z2kez1MQcOxokXrDc3k"; JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWT.require(Algorithm.HMAC256("secret")); DecodedJWT jwt = verification - .build(clock) + .build(mockNow) .verify(token); assertThat(jwt, is(notNullValue())); diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 6d8ba201..5a784b87 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -657,6 +657,7 @@ public void shouldThrowOnNegativeCustomLeeway() { } // Expires At + @Test public void shouldValidateExpiresAtWithLeeway() { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; @@ -674,12 +675,26 @@ public void shouldValidateExpiresAtIfPresent() { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); DecodedJWT jwt = verification - .build(mockNow) + .build(mockOneSecondEarlier) .verify(token); assertThat(jwt, is(notNullValue())); } + @Test + public void shouldThrowWhenExpiresAtIsNow() { + // exp must be > now + TokenExpiredException e = assertThrows(null, TokenExpiredException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification + .build(mockNow) + .verify(token); + }); + assertThat(e.getMessage(), is("The Token has expired on 1970-01-18T02:26:32Z.")); + assertThat(e.getExpiredOn(), is(Instant.ofEpochSecond(1477592L))); + } + @Test public void shouldThrowOnInvalidExpiresAtIfPresent() { TokenExpiredException e = assertThrows(null, TokenExpiredException.class, () -> { @@ -731,7 +746,18 @@ public void shouldThrowOnInvalidNotBeforeIfPresent() { @Test public void shouldValidateNotBeforeIfPresent() { - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE0Nzc1OTN9.f4zVV0TbbTG5xxDjSoGZ320JIMchGoQCWrnT5MyQdT0"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification + .build(mockOneSecondLater) + .verify(token); + + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldAcceptNotBeforeEqualToNow() { + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE0Nzc1OTJ9.71XBtRmkAa4iKnyhbS4NPW-Xr26eAVAdHZgmupS7a5o"; JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); DecodedJWT jwt = verification .build(mockNow) From 902431804ee68a0e4727f41397cd56608918ba37 Mon Sep 17 00:00:00 2001 From: noetro Date: Tue, 31 Jan 2023 03:09:45 +0100 Subject: [PATCH 161/198] Improve JWT parse / decode performance (#620) * Optimise parsing of token for well-defined JWT format * Update error message in test to match new code * Fixing checkstyle issues * Added missing test case for no parts * Return new JWTDecodeException Return a new JWTDecodeException from private utility method `wrongNumberOfParts`, instead of throwing, since we throw from `splitToken()`. * Add JMH support to build script * Add benchmark for decoder and cleanup build file * Optimise JWT deserialisation by re-using threadsafe Jackson objects * Disable lint checks on JMH source set that is for testing * Remove extra line break --------- Co-authored-by: Jim Anderson Co-authored-by: Jim Anderson --- lib/build.gradle | 46 ++++++++++++++++++- .../jwt/benchmark/JWTDecoderBenchmark.java | 20 ++++++++ .../java/com/auth0/jwt/impl/BasicHeader.java | 13 +++--- .../auth0/jwt/impl/HeaderDeserializer.java | 20 +++----- .../java/com/auth0/jwt/impl/JWTParser.java | 22 +++++++-- .../com/auth0/jwt/impl/JsonNodeClaim.java | 36 ++++++++------- .../auth0/jwt/impl/PayloadDeserializer.java | 22 ++++----- .../java/com/auth0/jwt/impl/PayloadImpl.java | 20 ++++---- .../jwt/impl/HeaderDeserializerTest.java | 10 ++-- .../com/auth0/jwt/impl/JWTParserTest.java | 2 +- .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 25 ++++++---- .../jwt/impl/PayloadDeserializerTest.java | 19 ++++---- .../com/auth0/jwt/impl/PayloadImplTest.java | 29 ++++++------ 13 files changed, 181 insertions(+), 103 deletions(-) create mode 100644 lib/src/jmh/java/com/auth0/jwt/benchmark/JWTDecoderBenchmark.java diff --git a/lib/build.gradle b/lib/build.gradle index 6b2fdfe3..6190a239 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -6,10 +6,28 @@ plugins { id 'checkstyle' } +sourceSets { + jmh { + + } +} + +configurations { + jmhImplementation { + extendsFrom implementation + } +} + checkstyle { toolVersion '10.0' - checkstyleTest.enabled = false //We are disabling lint checks for tests } +//We are disabling lint checks for tests +tasks.named("checkstyleTest").configure({ + enabled = false +}) +tasks.named("checkstyleJmh").configure({ + enabled = false +}) logger.lifecycle("Using version ${version} for ${group}.${name}") @@ -61,6 +79,10 @@ dependencies { testImplementation 'net.jodah:concurrentunit:0.4.6' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation 'org.mockito:mockito-core:4.4.0' + + jmhImplementation sourceSets.main.output + jmhImplementation 'org.openjdk.jmh:jmh-core:1.35' + jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.35' } jacoco { @@ -143,3 +165,25 @@ task exportVersion() { new File(rootDir, "version.txt").text = "$version" } } + +// you can pass any arguments JMH accepts via Gradle args. +// Example: ./gradlew runJMH --args="-lrf" +tasks.register('runJMH', JavaExec) { + description 'Run JMH benchmarks.' + group 'verification' + + main 'org.openjdk.jmh.Main' + classpath sourceSets.jmh.runtimeClasspath + + args project.hasProperty("args") ? project.property("args").split() : "" +} +tasks.register('jmhHelp', JavaExec) { + description 'Prints the available command line options for JMH.' + group 'help' + + main 'org.openjdk.jmh.Main' + classpath sourceSets.jmh.runtimeClasspath + + args '-h' +} + diff --git a/lib/src/jmh/java/com/auth0/jwt/benchmark/JWTDecoderBenchmark.java b/lib/src/jmh/java/com/auth0/jwt/benchmark/JWTDecoderBenchmark.java new file mode 100644 index 00000000..81d3737a --- /dev/null +++ b/lib/src/jmh/java/com/auth0/jwt/benchmark/JWTDecoderBenchmark.java @@ -0,0 +1,20 @@ +package com.auth0.jwt.benchmark; + +import com.auth0.jwt.JWT; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.infra.Blackhole; + +/** + * This class is a JMH benchmark for decoding JWTs. + */ +public class JWTDecoderBenchmark { + private static final String TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void throughputDecodeTime(Blackhole blackhole) { + blackhole.consume(JWT.decode(TOKEN)); + } +} diff --git a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java index 8031d8c0..5a881ab5 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java +++ b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java @@ -2,12 +2,11 @@ import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Header; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import java.io.Serializable; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import static com.auth0.jwt.impl.JsonNodeClaim.extractClaim; @@ -23,7 +22,7 @@ class BasicHeader implements Header, Serializable { private final String contentType; private final String keyId; private final Map tree; - private final ObjectReader objectReader; + private final ObjectCodec objectCodec; BasicHeader( String algorithm, @@ -31,14 +30,14 @@ class BasicHeader implements Header, Serializable { String contentType, String keyId, Map tree, - ObjectReader objectReader + ObjectCodec objectCodec ) { this.algorithm = algorithm; this.type = type; this.contentType = contentType; this.keyId = keyId; - this.tree = Collections.unmodifiableMap(tree == null ? new HashMap<>() : tree); - this.objectReader = objectReader; + this.tree = tree == null ? Collections.emptyMap() : Collections.unmodifiableMap(tree); + this.objectCodec = objectCodec; } Map getTree() { @@ -67,6 +66,6 @@ public String getKeyId() { @Override public Claim getHeaderClaim(String name) { - return extractClaim(name, tree, objectReader); + return extractClaim(name, tree, objectCodec); } } diff --git a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java index 9293fd4d..ad6e4ce0 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java @@ -2,11 +2,11 @@ import com.auth0.jwt.HeaderParams; import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.interfaces.Header; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; @@ -19,22 +19,14 @@ * * @see JWTParser */ -class HeaderDeserializer extends StdDeserializer { +class HeaderDeserializer extends StdDeserializer
{ - private final ObjectReader objectReader; - - HeaderDeserializer(ObjectReader objectReader) { - this(null, objectReader); - } - - private HeaderDeserializer(Class vc, ObjectReader objectReader) { - super(vc); - - this.objectReader = objectReader; + HeaderDeserializer() { + super(Header.class); } @Override - public BasicHeader deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public Header deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { Map tree = p.getCodec().readValue(p, new TypeReference>() { }); if (tree == null) { @@ -45,7 +37,7 @@ public BasicHeader deserialize(JsonParser p, DeserializationContext ctxt) throws String type = getString(tree, HeaderParams.TYPE); String contentType = getString(tree, HeaderParams.CONTENT_TYPE); String keyId = getString(tree, HeaderParams.KEY_ID); - return new BasicHeader(algorithm, type, contentType, keyId, tree, objectReader); + return new BasicHeader(algorithm, type, contentType, keyId, tree, p.getCodec()); } String getString(Map tree, String claimName) { diff --git a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java index fe1600bd..022520f5 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java @@ -16,15 +16,21 @@ * {@link HeaderSerializer} and {@link PayloadSerializer}. */ public class JWTParser implements JWTPartsParser { + private static final ObjectMapper DEFAULT_OBJECT_MAPPER = createDefaultObjectMapper(); + private static final ObjectReader DEFAULT_PAYLOAD_READER = DEFAULT_OBJECT_MAPPER.readerFor(Payload.class); + private static final ObjectReader DEFAULT_HEADER_READER = DEFAULT_OBJECT_MAPPER.readerFor(Header.class); + private final ObjectReader payloadReader; private final ObjectReader headerReader; public JWTParser() { - this(getDefaultObjectMapper()); + this.payloadReader = DEFAULT_PAYLOAD_READER; + this.headerReader = DEFAULT_HEADER_READER; } JWTParser(ObjectMapper mapper) { addDeserializers(mapper); + this.payloadReader = mapper.readerFor(Payload.class); this.headerReader = mapper.readerFor(Header.class); } @@ -55,18 +61,24 @@ public Header parseHeader(String json) throws JWTDecodeException { } } - private void addDeserializers(ObjectMapper mapper) { + static void addDeserializers(ObjectMapper mapper) { SimpleModule module = new SimpleModule(); - ObjectReader reader = mapper.reader(); - module.addDeserializer(Payload.class, new PayloadDeserializer(reader)); - module.addDeserializer(Header.class, new HeaderDeserializer(reader)); + module.addDeserializer(Payload.class, new PayloadDeserializer()); + module.addDeserializer(Header.class, new HeaderDeserializer()); mapper.registerModule(module); } static ObjectMapper getDefaultObjectMapper() { + return DEFAULT_OBJECT_MAPPER; + } + + private static ObjectMapper createDefaultObjectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + + addDeserializers(mapper); + return mapper; } diff --git a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java index 456d1515..0a7e22f3 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -4,9 +4,9 @@ import com.auth0.jwt.interfaces.Claim; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import java.io.IOException; import java.lang.reflect.Array; @@ -21,12 +21,12 @@ */ class JsonNodeClaim implements Claim { - private final ObjectReader objectReader; + private final ObjectCodec codec; private final JsonNode data; - private JsonNodeClaim(JsonNode node, ObjectReader objectReader) { + private JsonNodeClaim(JsonNode node, ObjectCodec codec) { this.data = node; - this.objectReader = objectReader; + this.codec = codec; } @Override @@ -82,7 +82,7 @@ public T[] asArray(Class clazz) throws JWTDecodeException { T[] arr = (T[]) Array.newInstance(clazz, data.size()); for (int i = 0; i < data.size(); i++) { try { - arr[i] = objectReader.treeToValue(data.get(i), clazz); + arr[i] = codec.treeToValue(data.get(i), clazz); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to " + clazz.getSimpleName(), e); } @@ -99,7 +99,7 @@ public List asList(Class clazz) throws JWTDecodeException { List list = new ArrayList<>(); for (int i = 0; i < data.size(); i++) { try { - list.add(objectReader.treeToValue(data.get(i), clazz)); + list.add(codec.treeToValue(data.get(i), clazz)); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to " + clazz.getSimpleName(), e); } @@ -113,11 +113,11 @@ public Map asMap() throws JWTDecodeException { return null; } - try { - TypeReference> mapType = new TypeReference>() { - }; - JsonParser thisParser = objectReader.treeAsTokens(data); - return thisParser.readValueAs(mapType); + TypeReference> mapType = new TypeReference>() { + }; + + try (JsonParser parser = codec.treeAsTokens(data)) { + return parser.readValueAs(mapType); } catch (IOException e) { throw new JWTDecodeException("Couldn't map the Claim value to Map", e); } @@ -129,8 +129,8 @@ public T as(Class clazz) throws JWTDecodeException { if (isMissing() || isNull()) { return null; } - return objectReader.treeAsTokens(data).readValueAs(clazz); - } catch (IOException e) { + return codec.treeToValue(data, clazz); + } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim value to " + clazz.getSimpleName(), e); } } @@ -160,21 +160,23 @@ public String toString() { * * @param claimName the Claim to search for. * @param tree the JsonNode tree to search the Claim in. + * @param objectCodec the object codec in use for deserialization * @return a valid non-null Claim. */ - static Claim extractClaim(String claimName, Map tree, ObjectReader objectReader) { + static Claim extractClaim(String claimName, Map tree, ObjectCodec objectCodec) { JsonNode node = tree.get(claimName); - return claimFromNode(node, objectReader); + return claimFromNode(node, objectCodec); } /** * Helper method to create a Claim representation from the given JsonNode. * * @param node the JsonNode to convert into a Claim. + * @param objectCodec the object codec in use for deserialization * @return a valid Claim instance. If the node is null or missing, a NullClaim will be returned. */ - static Claim claimFromNode(JsonNode node, ObjectReader objectReader) { - return new JsonNodeClaim(node, objectReader); + static Claim claimFromNode(JsonNode node, ObjectCodec objectCodec) { + return new JsonNodeClaim(node, objectCodec); } } \ No newline at end of file diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 37e70f7a..65fba3ac 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -5,6 +5,7 @@ import com.auth0.jwt.interfaces.Payload; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; @@ -24,16 +25,8 @@ */ class PayloadDeserializer extends StdDeserializer { - private final ObjectReader objectReader; - - PayloadDeserializer(ObjectReader reader) { - this(null, reader); - } - - private PayloadDeserializer(Class vc, ObjectReader reader) { - super(vc); - - this.objectReader = reader; + PayloadDeserializer() { + super(Payload.class); } @Override @@ -46,16 +39,17 @@ public Payload deserialize(JsonParser p, DeserializationContext ctxt) throws IOE String issuer = getString(tree, RegisteredClaims.ISSUER); String subject = getString(tree, RegisteredClaims.SUBJECT); - List audience = getStringOrArray(tree, RegisteredClaims.AUDIENCE); + List audience = getStringOrArray(p.getCodec(), tree, RegisteredClaims.AUDIENCE); Instant expiresAt = getInstantFromSeconds(tree, RegisteredClaims.EXPIRES_AT); Instant notBefore = getInstantFromSeconds(tree, RegisteredClaims.NOT_BEFORE); Instant issuedAt = getInstantFromSeconds(tree, RegisteredClaims.ISSUED_AT); String jwtId = getString(tree, RegisteredClaims.JWT_ID); - return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, objectReader); + return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, p.getCodec()); } - List getStringOrArray(Map tree, String claimName) throws JWTDecodeException { + List getStringOrArray(ObjectCodec codec, Map tree, String claimName) + throws JWTDecodeException { JsonNode node = tree.get(claimName); if (node == null || node.isNull() || !(node.isArray() || node.isTextual())) { return null; @@ -67,7 +61,7 @@ List getStringOrArray(Map tree, String claimName) thro List list = new ArrayList<>(node.size()); for (int i = 0; i < node.size(); i++) { try { - list.add(objectReader.treeToValue(node.get(i), String.class)); + list.add(codec.treeToValue(node.get(i), String.class)); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to String", e); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java index 75e79474..bfd9b0ea 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java @@ -2,12 +2,16 @@ import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Payload; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import java.io.Serializable; import java.time.Instant; -import java.util.*; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static com.auth0.jwt.impl.JsonNodeClaim.extractClaim; @@ -30,7 +34,7 @@ class PayloadImpl implements Payload, Serializable { private final Instant issuedAt; private final String jwtId; private final Map tree; - private final ObjectReader objectReader; + private final ObjectCodec objectCodec; PayloadImpl( String issuer, @@ -41,7 +45,7 @@ class PayloadImpl implements Payload, Serializable { Instant issuedAt, String jwtId, Map tree, - ObjectReader objectReader + ObjectCodec objectCodec ) { this.issuer = issuer; this.subject = subject; @@ -50,8 +54,8 @@ class PayloadImpl implements Payload, Serializable { this.notBefore = notBefore; this.issuedAt = issuedAt; this.jwtId = jwtId; - this.tree = tree != null ? Collections.unmodifiableMap(tree) : Collections.emptyMap(); - this.objectReader = objectReader; + this.tree = tree != null ? Collections.unmodifiableMap(tree) : Collections.emptyMap(); + this.objectCodec = objectCodec; } Map getTree() { @@ -111,14 +115,14 @@ public String getId() { @Override public Claim getClaim(String name) { - return extractClaim(name, tree, objectReader); + return extractClaim(name, tree, objectCodec); } @Override public Map getClaims() { Map claims = new HashMap<>(tree.size() * 2); for (String name : tree.keySet()) { - claims.put(name, extractClaim(name, tree, objectReader)); + claims.put(name, extractClaim(name, tree, objectCodec)); } return Collections.unmodifiableMap(claims); } diff --git a/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java index 328f4ab4..02d782a7 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.TextNode; import org.junit.Before; @@ -22,8 +21,10 @@ import java.util.HashMap; import java.util.Map; -import static org.hamcrest.Matchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -34,11 +35,10 @@ public class HeaderDeserializerTest { @Rule public ExpectedException exception = ExpectedException.none(); private HeaderDeserializer deserializer; - private ObjectReader objectReader = new ObjectMapper().reader(); @Before public void setUp() { - deserializer = new HeaderDeserializer(objectReader); + deserializer = new HeaderDeserializer(); } @Test @@ -46,7 +46,7 @@ public void shouldThrowOnNullTree() throws Exception { exception.expect(JWTDecodeException.class); exception.expectMessage("Parsing the Header's JSON resulted on a Null map"); - JsonDeserializer deserializer = new HeaderDeserializer(objectReader); + JsonDeserializer deserializer = new HeaderDeserializer(); JsonParser parser = mock(JsonParser.class); ObjectCodec codec = mock(ObjectCodec.class); DeserializationContext context = mock(DeserializationContext.class); diff --git a/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java b/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java index a40a23d4..da62131a 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java @@ -42,7 +42,7 @@ public void shouldGetDefaultObjectMapper() { @Test public void shouldAddDeserializers() { ObjectMapper mapper = mock(ObjectMapper.class); - new JWTParser(mapper); + JWTParser.addDeserializers(mapper); verify(mapper).registerModule(any(Module.class)); } diff --git a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java index 18f59f04..a0364953 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.MissingNode; import com.fasterxml.jackson.databind.node.NullNode; @@ -21,20 +20,31 @@ import java.io.IOException; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; public class JsonNodeClaimTest { private ObjectMapper mapper; - private ObjectReader objectReader; @Rule public ExpectedException exception = ExpectedException.none(); @@ -42,7 +52,6 @@ public class JsonNodeClaimTest { @Before public void setUp() { mapper = getDefaultObjectMapper(); - objectReader = mapper.reader(); } @Test @@ -55,7 +64,7 @@ public void shouldGetBooleanValue() { } private Claim claimFromNode(JsonNode value) { - return JsonNodeClaim.claimFromNode(value, objectReader); + return JsonNodeClaim.claimFromNode(value, mapper); } @Test @@ -282,7 +291,7 @@ public void shouldThrowIfAnExtraordinaryExceptionHappensWhenParsingAsGenericMap( JsonNode value = mock(ObjectNode.class); when(value.getNodeType()).thenReturn(JsonNodeType.OBJECT); - ObjectReader mockedMapper = mock(ObjectReader.class); + ObjectMapper mockedMapper = mock(ObjectMapper.class); JsonNodeClaim claim = (JsonNodeClaim) JsonNodeClaim.claimFromNode(value, mockedMapper); JsonNodeClaim spiedClaim = spy(claim); diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java index 2d80bed9..86c4f10f 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java @@ -35,9 +35,12 @@ public class PayloadDeserializerTest { public ExpectedException exception = ExpectedException.none(); private PayloadDeserializer deserializer; + private ObjectMapper objectMapper; + @Before public void setUp() { - deserializer = new PayloadDeserializer(new ObjectMapper().reader()); + objectMapper = new ObjectMapper(); + deserializer = new PayloadDeserializer(); } @Test @@ -68,7 +71,7 @@ public void shouldThrowWhenParsingArrayWithObjectValue() throws Exception { ArrayNode arrNode = new ArrayNode(JsonNodeFactory.instance, subNodes); tree.put("key", arrNode); - deserializer.getStringOrArray(tree, "key"); + deserializer.getStringOrArray(objectMapper, tree, "key"); } @Test @@ -123,7 +126,7 @@ public void shouldGetStringArrayWhenParsingArrayNode() { ArrayNode arrNode = new ArrayNode(JsonNodeFactory.instance, subNodes); tree.put("key", arrNode); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(notNullValue())); assertThat(values, is(IsCollectionWithSize.hasSize(2))); assertThat(values, is(IsIterableContaining.hasItems("one", "two"))); @@ -135,7 +138,7 @@ public void shouldGetStringArrayWhenParsingTextNode() { TextNode textNode = new TextNode("something"); tree.put("key", textNode); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(notNullValue())); assertThat(values, is(IsCollectionWithSize.hasSize(1))); assertThat(values, is(IsIterableContaining.hasItems("something"))); @@ -147,7 +150,7 @@ public void shouldGetEmptyStringArrayWhenParsingEmptyTextNode() { TextNode textNode = new TextNode(""); tree.put("key", textNode); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(notNullValue())); assertThat(values, is(IsEmptyCollection.empty())); } @@ -158,7 +161,7 @@ public void shouldGetNullArrayWhenParsingNullNode() { NullNode node = NullNode.getInstance(); tree.put("key", node); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(nullValue())); } @@ -167,7 +170,7 @@ public void shouldGetNullArrayWhenParsingNullNodeValue() { Map tree = new HashMap<>(); tree.put("key", null); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(nullValue())); } @@ -177,7 +180,7 @@ public void shouldGetNullArrayWhenParsingNonArrayOrTextNode() { IntNode node = new IntNode(456789); tree.put("key", node); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(nullValue())); } diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java index da0c880e..5ad7ac68 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java @@ -29,29 +29,28 @@ public class PayloadImplTest { private final Instant notBefore = Instant.now(); private final Instant issuedAt = Instant.now(); - private ObjectReader objectReader; + private ObjectMapper objectMapper; @Before public void setUp() { - ObjectMapper mapper = getDefaultObjectMapper(); - objectReader = mapper.reader(); + objectMapper = getDefaultObjectMapper(); Map tree = new HashMap<>(); tree.put("extraClaim", new TextNode("extraValue")); - payload = new PayloadImpl("issuer", "subject", Collections.singletonList("audience"), expiresAt, notBefore, issuedAt, "jwtId", tree, objectReader); + payload = new PayloadImpl("issuer", "subject", Collections.singletonList("audience"), expiresAt, notBefore, issuedAt, "jwtId", tree, objectMapper); } @Test public void shouldHaveUnmodifiableTree() { exception.expect(UnsupportedOperationException.class); - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, new HashMap<>(), objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, new HashMap<>(), objectMapper); payload.getTree().put("something", null); } @Test public void shouldHaveUnmodifiableAudience() { exception.expect(UnsupportedOperationException.class); - PayloadImpl payload = new PayloadImpl(null, null, new ArrayList<>(), null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, new ArrayList<>(), null, null, null, null, null, objectMapper); payload.getAudience().add("something"); } @@ -63,7 +62,7 @@ public void shouldGetIssuer() { @Test public void shouldGetNullIssuerIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getIssuer(), is(nullValue())); } @@ -76,7 +75,7 @@ public void shouldGetSubject() { @Test public void shouldGetNullSubjectIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getSubject(), is(nullValue())); } @@ -91,7 +90,7 @@ public void shouldGetAudience() { @Test public void shouldGetNullAudienceIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getAudience(), is(nullValue())); } @@ -105,7 +104,7 @@ public void shouldGetExpiresAt() { @Test public void shouldGetNullExpiresAtIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getExpiresAt(), is(nullValue())); assertThat(payload.getExpiresAtAsInstant(), is(nullValue())); @@ -120,7 +119,7 @@ public void shouldGetNotBefore() { @Test public void shouldGetNullNotBeforeIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getNotBefore(), is(nullValue())); assertThat(payload.getNotBeforeAsInstant(), is(nullValue())); @@ -135,7 +134,7 @@ public void shouldGetIssuedAt() { @Test public void shouldGetNullIssuedAtIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getIssuedAt(), is(nullValue())); assertThat(payload.getIssuedAtAsInstant(), is(nullValue())); @@ -149,7 +148,7 @@ public void shouldGetJWTId() { @Test public void shouldGetNullJWTIdIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getId(), is(nullValue())); } @@ -163,7 +162,7 @@ public void shouldGetExtraClaim() { @Test public void shouldGetNotNullExtraClaimIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getClaim("missing"), is(notNullValue())); assertThat(payload.getClaim("missing").isMissing(), is(true)); @@ -175,7 +174,7 @@ public void shouldGetClaims() { Map tree = new HashMap<>(); tree.put("extraClaim", new TextNode("extraValue")); tree.put("sub", new TextNode("auth0")); - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, tree, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, tree, objectMapper); assertThat(payload, is(notNullValue())); Map claims = payload.getClaims(); assertThat(claims, is(notNullValue())); From b610b6635ba1ed948a06fea777eeeecca2364303 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Sun, 12 Feb 2023 21:53:23 -0600 Subject: [PATCH 162/198] Release 4.3.0 (#655) --- CHANGELOG.md | 10 ++++++++++ README.md | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaf3feec..61ebad87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## [4.3.0](https://github.com/auth0/java-jwt/tree/4.3.0) (2023-02-10) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.2...4.3.0) + +**Changed** +- Improve JWT parse/decode performance [\#620](https://github.com/auth0/java-jwt/pull/620) ([noetro](https://github.com/noetro)) + +**Fixed** +- Fix for exp claim considered valid if equal to now [\#652](https://github.com/auth0/java-jwt/pull/652) ([jimmyjames](https://github.com/jimmyjames)) +- Code cleanup [\#642](https://github.com/auth0/java-jwt/pull/642) ([CodeDead](https://github.com/CodeDead)) + ## [4.2.2](https://github.com/auth0/java-jwt/tree/4.2.2) (2023-01-11) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.1...4.2.2) diff --git a/README.md b/README.md index 8583ed7a..9391f87d 100644 --- a/README.md +++ b/README.md @@ -50,14 +50,14 @@ Add the dependency via Maven: com.auth0 java-jwt - 4.2.2 + 4.3.0 ``` or Gradle: ```gradle -implementation 'com.auth0:java-jwt:4.2.2' +implementation 'com.auth0:java-jwt:4.3.0' ``` ### Create a JWT From a18955bb99bf6994e424971db3f621bc0cc64a86 Mon Sep 17 00:00:00 2001 From: Andreas Rigas <48296471+andrewrigas@users.noreply.github.com> Date: Mon, 27 Mar 2023 15:27:30 +0100 Subject: [PATCH 163/198] Add support for passing json values for header and payload (#643) --- lib/build.gradle | 2 +- .../main/java/com/auth0/jwt/JWTCreator.java | 48 ++++++++++++ .../java/com/auth0/jwt/JWTCreatorTest.java | 78 ++++++++++++++++++- 3 files changed, 124 insertions(+), 4 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 6190a239..d147f2d0 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -72,7 +72,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 7ed83940..f554cbc3 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -98,6 +98,27 @@ public Builder withHeader(Map headerClaims) { return this; } + /** + * Add specific Claims to set as the Header. + * If provided json is null then nothing is changed + * + * @param headerClaimsJson the values to use as Claims in the token's Header. + * @return this same Builder instance. + * @throws IllegalArgumentException if json value has invalid structure + */ + public Builder withHeader(String headerClaimsJson) throws IllegalArgumentException { + if (headerClaimsJson == null) { + return this; + } + + try { + Map headerClaims = mapper.readValue(headerClaimsJson, HashMap.class); + return withHeader(headerClaims); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Invalid header JSON", e); + } + } + /** * Add a specific Key Id ("kid") claim to the Header. * If the {@link Algorithm} used to sign this token was instantiated with a KeyProvider, @@ -467,6 +488,33 @@ public Builder withPayload(Map payloadClaims) throws IllegalArgumentE return this; } + /** + * Add specific Claims to set as the Payload. If the provided json is null then + * nothing is changed. + * + *

+ * If any of the claims are invalid, none will be added. + *

+ * + * @param payloadClaimsJson the values to use as Claims in the token's payload. + * @return this same Builder instance. + * @throws IllegalArgumentException if any of the claim keys or null, + * or if the values are not of a supported type, + * or if json value has invalid structure. + */ + public Builder withPayload(String payloadClaimsJson) throws IllegalArgumentException { + if (payloadClaimsJson == null) { + return this; + } + + try { + Map payloadClaims = mapper.readValue(payloadClaimsJson, HashMap.class); + return withPayload(payloadClaims); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Invalid payload JSON", e); + } + } + private boolean validatePayload(Map payload) { for (Map.Entry entry : payload.entrySet()) { String key = entry.getKey(); diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 8fc83b44..e4ab8cb0 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -3,6 +3,7 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Rule; import org.junit.Test; @@ -82,13 +83,48 @@ public void shouldAddHeaderClaim() { @Test public void shouldReturnBuilderIfNullMapIsProvided() { + Map nullMap = null; + String nullString = null; String signed = JWTCreator.init() - .withHeader(null) + .withHeader(nullMap) + .withHeader(nullString) .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); } + @Test + public void shouldSupportJsonValueHeaderWithNestedDataStructure() { + String stringClaim = "someClaim"; + Integer intClaim = 1; + List nestedListClaims = Arrays.asList("1", "2"); + String claimsJson = "{\"stringClaim\": \"someClaim\", \"intClaim\": 1, \"nestedClaim\": { \"listClaim\": [ \"1\", \"2\" ]}}"; + + String jwt = JWTCreator.init() + .withHeader(claimsJson) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); + + assertThat(headerJson, JsonMatcher.hasEntry("stringClaim", stringClaim)); + assertThat(headerJson, JsonMatcher.hasEntry("intClaim", intClaim)); + assertThat(headerJson, JsonMatcher.hasEntry("listClaim", nestedListClaims)); + } + + @Test + public void shouldFailWithIllegalArgumentExceptionForInvalidJsonForHeaderClaims() { + String invalidJson = "{ invalidJson }"; + + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Invalid header JSON"); + + JWTCreator.init() + .withHeader(invalidJson) + .sign(Algorithm.HMAC256("secret")); + } + @Test public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() { Map header = new HashMap<>(); @@ -105,6 +141,7 @@ public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() { assertThat(headerJson, JsonMatcher.hasEntry(HeaderParams.KEY_ID, "xyz")); } + @Test public void shouldOverwriteExistingHeadersWhenSettingSameHeaderKey() { Map header = new HashMap<>(); @@ -715,8 +752,11 @@ public void withPayloadShouldAddBasicClaim() { @Test public void withPayloadShouldCreateJwtWithEmptyBodyIfPayloadNull() { + Map nullMap = null; + String nullString = null; String jwt = JWTCreator.init() - .withPayload(null) + .withPayload(nullMap) + .withPayload(nullString) .sign(Algorithm.HMAC256("secret")); assertThat(jwt, is(notNullValue())); @@ -921,10 +961,42 @@ public void withPayloadShouldSupportNullValuesEverywhere() { assertThat(headerJson, JsonMatcher.hasEntry("objClaim", objClaim)); } + @Test + public void withPayloadShouldSupportJsonValueWithNestedDataStructure() { + String stringClaim = "someClaim"; + Integer intClaim = 1; + List nestedListClaims = Arrays.asList("1", "2"); + String claimsJson = "{\"stringClaim\": \"someClaim\", \"intClaim\": 1, \"nestedClaim\": { \"listClaim\": [ \"1\", \"2\" ]}}"; + + String jwt = JWTCreator.init() + .withPayload(claimsJson) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + + assertThat(payloadJson, JsonMatcher.hasEntry("stringClaim", stringClaim)); + assertThat(payloadJson, JsonMatcher.hasEntry("intClaim", intClaim)); + assertThat(payloadJson, JsonMatcher.hasEntry("listClaim", nestedListClaims)); + } + + @Test + public void shouldFailWithIllegalArgumentExceptionForInvalidJsonForPayloadClaims() { + String invalidJson = "{ invalidJson }"; + + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Invalid payload JSON"); + + JWTCreator.init() + .withPayload(invalidJson) + .sign(Algorithm.HMAC256("secret")); + } + @Test public void shouldCreatePayloadWithNullForMap() { String jwt = JWTCreator.init() - .withClaim("name", (Map) null) + .withClaim("name", (Map) null) .sign(Algorithm.HMAC256("secret")); assertThat(jwt, is(notNullValue())); assertTrue(JWT.decode(jwt).getClaim("name").isNull()); From e85a00a00543b2b17d0460e9c1c11c2aa31fbcbc Mon Sep 17 00:00:00 2001 From: Robin Karlsson Date: Mon, 27 Mar 2023 21:57:04 +0200 Subject: [PATCH 164/198] Preserve insertion order for claims (#656) Co-authored-by: Jim Anderson --- .../main/java/com/auth0/jwt/JWTCreator.java | 8 +-- .../java/com/auth0/jwt/JWTCreatorTest.java | 49 ++++++++++++++++++- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index f554cbc3..0b0d21e4 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -71,8 +71,8 @@ public static class Builder { private final Map headerClaims; Builder() { - this.payloadClaims = new HashMap<>(); - this.headerClaims = new HashMap<>(); + this.payloadClaims = new LinkedHashMap<>(); + this.headerClaims = new LinkedHashMap<>(); } /** @@ -112,7 +112,7 @@ public Builder withHeader(String headerClaimsJson) throws IllegalArgumentExcepti } try { - Map headerClaims = mapper.readValue(headerClaimsJson, HashMap.class); + Map headerClaims = mapper.readValue(headerClaimsJson, LinkedHashMap.class); return withHeader(headerClaims); } catch (JsonProcessingException e) { throw new IllegalArgumentException("Invalid header JSON", e); @@ -508,7 +508,7 @@ public Builder withPayload(String payloadClaimsJson) throws IllegalArgumentExcep } try { - Map payloadClaims = mapper.readValue(payloadClaimsJson, HashMap.class); + Map payloadClaims = mapper.readValue(payloadClaimsJson, LinkedHashMap.class); return withPayload(payloadClaims); } catch (JsonProcessingException e) { throw new IllegalArgumentException("Invalid payload JSON", e); diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index e4ab8cb0..53cd267b 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -3,8 +3,8 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -1010,4 +1010,51 @@ public void shouldCreatePayloadWithNullForList() { assertThat(jwt, is(notNullValue())); assertTrue(JWT.decode(jwt).getClaim("name").isNull()); } + + @Test + public void shouldPreserveInsertionOrder() throws Exception { + String taxonomyJson = "{\"class\": \"mammalia\", \"order\": \"carnivora\", \"family\": \"canidae\", \"genus\": \"vulpes\"}"; + List taxonomyClaims = Arrays.asList("class", "order", "family", "genus"); + List headerInsertionOrder = new ArrayList<>(taxonomyClaims); + Map header = new LinkedHashMap<>(); + for (int i = 0; i < 10; i++) { + String key = "h" + i; + header.put(key, "v" + 1); + headerInsertionOrder.add(key); + } + + List payloadInsertionOrder = new ArrayList<>(taxonomyClaims); + JWTCreator.Builder builder = JWTCreator.init() + .withHeader(taxonomyJson) + .withHeader(header) + .withPayload(taxonomyJson); + for (int i = 0; i < 10; i++) { + String name = "c" + i; + builder = builder.withClaim(name, "v" + i); + payloadInsertionOrder.add(name); + } + String signed = builder.sign(Algorithm.HMAC256("secret")); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + Base64.Decoder urlDecoder = Base64.getUrlDecoder(); + String headerJson = new String(urlDecoder.decode(parts[0]), StandardCharsets.UTF_8); + String payloadJson = new String(urlDecoder.decode(parts[1]), StandardCharsets.UTF_8); + + ObjectMapper objectMapper = new ObjectMapper(); + + List headerFields = new ArrayList<>(); + objectMapper.readValue(headerJson, ObjectNode.class) + .fieldNames().forEachRemaining(headerFields::add); + headerFields.retainAll(headerInsertionOrder); + assertThat("Header insertion order should be preserved", + headerFields, is(equalTo(headerInsertionOrder))); + + List payloadFields = new ArrayList<>(); + objectMapper.readValue(payloadJson, ObjectNode.class) + .fieldNames().forEachRemaining(payloadFields::add); + payloadFields.retainAll(payloadInsertionOrder); + assertThat("Claim insertion order should be preserved", + payloadFields, is(equalTo(payloadInsertionOrder))); + } } From 652bf7dac4c10d1213609a2db6fb50815920ac09 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 30 Mar 2023 22:12:01 -0500 Subject: [PATCH 165/198] update jackson to 2.14.2 (#657) --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index d147f2d0..c4e11764 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -72,7 +72,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' From 82548a6e7b69ad0c6974519c9ebb313d15237cad Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 31 Mar 2023 13:31:38 -0500 Subject: [PATCH 166/198] Release 4.4.0 (#658) --- CHANGELOG.md | 8 ++++++++ README.md | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61ebad87..1b1e75bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## [4.4.0](https://github.com/auth0/java-jwt/tree/4.4.0) (2023-03-31) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.3.0...4.4.0) + +**Changed** +- Add support for passing json values for header and payload [\#643](https://github.com/auth0/java-jwt/pull/643) ([andrewrigas](https://github.com/andrewrigas)) +- Preserve insertion order for claims [\#656](https://github.com/auth0/java-jwt/pull/656) ([snago](https://github.com/snago)) +- Update Jackson to 2.14.2 [\#657](https://github.com/auth0/java-jwt/pull/657) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.3.0](https://github.com/auth0/java-jwt/tree/4.3.0) (2023-02-10) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.2...4.3.0) diff --git a/README.md b/README.md index 9391f87d..9f425898 100644 --- a/README.md +++ b/README.md @@ -50,14 +50,14 @@ Add the dependency via Maven: com.auth0 java-jwt - 4.3.0 + 4.4.0 ``` or Gradle: ```gradle -implementation 'com.auth0:java-jwt:4.3.0' +implementation 'com.auth0:java-jwt:4.4.0' ``` ### Create a JWT From 923e9c46db888c500ff44032fa900b871fae30ed Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 25 Apr 2023 23:02:41 -0300 Subject: [PATCH 167/198] Replace issue templates with issue forms [SDK-4167] (#661) --- .github/ISSUE_TEMPLATE/Bug Report.yml | 67 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/Feature Request.yml | 53 +++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 7 +-- .github/ISSUE_TEMPLATE/feature_request.md | 39 ------------- .github/ISSUE_TEMPLATE/report-a-bug.md | 55 ------------------ 5 files changed, 122 insertions(+), 99 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/Bug Report.yml create mode 100644 .github/ISSUE_TEMPLATE/Feature Request.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/report-a-bug.md diff --git a/.github/ISSUE_TEMPLATE/Bug Report.yml b/.github/ISSUE_TEMPLATE/Bug Report.yml new file mode 100644 index 00000000..d5d861e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug Report.yml @@ -0,0 +1,67 @@ +name: 🐞 Report a bug +description: Have you found a bug or issue? Create a bug report for this library +labels: ["bug"] + +body: + - type: markdown + attributes: + value: | + **Please do not report security vulnerabilities here**. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues. + + - type: checkboxes + id: checklist + attributes: + label: Checklist + options: + - label: I have looked into the [Readme](https://github.com/auth0/java-jwt#readme) and [Examples](https://github.com/auth0/java-jwt/blob/master/EXAMPLES.md), and have not found a suitable solution or answer. + required: true + - label: I have looked into the [API documentation](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) and have not found a suitable solution or answer. + required: true + - label: I have searched the [issues](https://github.com/auth0/java-jwt/issues) and have not found a suitable solution or answer. + required: true + - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. + required: true + - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). + required: true + + - type: textarea + id: description + attributes: + label: Description + description: Provide a clear and concise description of the issue, including what you expected to happen. + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Reproduction + description: Detail the steps taken to reproduce this error, and whether this issue can be reproduced consistently or if it is intermittent. + placeholder: | + 1. Step 1... + 2. Step 2... + 3. ... + validations: + required: true + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Other libraries that might be involved, or any other relevant information you think would be useful. + validations: + required: false + + - type: input + id: environment-version + attributes: + label: java-jwt version + validations: + required: true + + - type: input + id: environment-java-version + attributes: + label: Java version + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/Feature Request.yml b/.github/ISSUE_TEMPLATE/Feature Request.yml new file mode 100644 index 00000000..38fee433 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature Request.yml @@ -0,0 +1,53 @@ +name: 🧩 Feature request +description: Suggest an idea or a feature for this library +labels: ["feature request"] + +body: + - type: checkboxes + id: checklist + attributes: + label: Checklist + options: + - label: I have looked into the [Readme](https://github.com/auth0/java-jwt#readme) and [Examples](https://github.com/auth0/java-jwt/blob/master/EXAMPLES.md), and have not found a suitable solution or answer. + required: true + - label: I have looked into the [API documentation](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) and have not found a suitable solution or answer. + required: true + - label: I have searched the [issues](https://github.com/auth0/java-jwt/issues) and have not found a suitable solution or answer. + required: true + - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. + required: true + - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). + required: true + + - type: textarea + id: description + attributes: + label: Describe the problem you'd like to have solved + description: A clear and concise description of what the problem is. + placeholder: I'm always frustrated when... + validations: + required: true + + - type: textarea + id: ideal-solution + attributes: + label: Describe the ideal solution + description: A clear and concise description of what you want to happen. + validations: + required: true + + - type: textarea + id: alternatives-and-workarounds + attributes: + label: Alternatives and current workarounds + description: A clear and concise description of any alternatives you've considered or any workarounds that are currently in place. + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3cd7aa53..f58e0249 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,5 @@ blank_issues_enabled: false contact_links: - name: Auth0 Community - url: https://community.auth0.com/c/sdks/5 - about: Discuss this SDK in the Auth0 Community forums - - name: Library Documentation - url: https://github.com/auth0/java-jwt/blob/master/README.md - about: Read the library documentation + url: https://community.auth0.com + about: Discuss this library in the Auth0 Community forums diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 68352ba2..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Feature request -about: Suggest an idea or a feature for this project -title: '' -labels: feature request -assignees: '' ---- - - - -### Describe the problem you'd like to have solved - - - -### Describe the ideal solution - - - -## Alternatives and current work-arounds - - - -### Additional information, if any - - \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/report-a-bug.md b/.github/ISSUE_TEMPLATE/report-a-bug.md deleted file mode 100644 index e5cf8c46..00000000 --- a/.github/ISSUE_TEMPLATE/report-a-bug.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: Report a bug -about: Have you found a bug or issue? Create a bug report for this SDK -title: '' -labels: bug report -assignees: '' ---- - - - -### Describe the problem - - - -### What was the expected behavior? - - - -### Reproduction - - -- Step 1.. -- Step 2.. -- ... - -### Environment - - - -- **Version of this library used:** -- **Version of Java used:** -- **Other modules/plugins/libraries that might be involved:** -- **Any other relevant information you think would be useful:** From d8fe9a2654a69ff9c39c1fa711b8abb622a4f7d6 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 8 Jun 2023 21:15:57 -0500 Subject: [PATCH 168/198] Empty string audience claim should be deserialized as empty string (#663) --- lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java | 2 +- .../test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 65fba3ac..b1d32a12 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -54,7 +54,7 @@ List getStringOrArray(ObjectCodec codec, Map tree, Str if (node == null || node.isNull() || !(node.isArray() || node.isTextual())) { return null; } - if (node.isTextual() && !node.asText().isEmpty()) { + if (node.isTextual()) { return Collections.singletonList(node.asText()); } diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java index 86c4f10f..c3e04013 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java @@ -145,14 +145,14 @@ public void shouldGetStringArrayWhenParsingTextNode() { } @Test - public void shouldGetEmptyStringArrayWhenParsingEmptyTextNode() { + public void shouldGetEmptyStringInArrayWhenParsingEmptyTextNode() { Map tree = new HashMap<>(); TextNode textNode = new TextNode(""); tree.put("key", textNode); List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(notNullValue())); - assertThat(values, is(IsEmptyCollection.empty())); + assertThat(values, is(IsIterableContaining.hasItem(""))); } @Test From 6ef84cf5a11dfdd45f6f00eb067f27123aa68bf7 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Mon, 17 Jul 2023 12:46:01 -0500 Subject: [PATCH 169/198] chore(security): Update and pin Graddle workflow actions --- .github/workflows/gradle-wrapper-validation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index a015578a..20c61e51 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -6,5 +6,5 @@ jobs: name: "validation/gradlew" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: gradle/wrapper-validation-action@v1 + - uses: actions/checkout@v3 + - uses: gradle/wrapper-validation-action@8d49e559aae34d3e0eb16cde532684bc9702762b # pin@v1 From 8c9bbf8d838e6fed8ab589106a47eeb0b381ef28 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Mon, 17 Jul 2023 12:46:59 -0500 Subject: [PATCH 170/198] Update gradle-wrapper-validation.yml --- .github/workflows/gradle-wrapper-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 20c61e51..ce302cb4 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -7,4 +7,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: gradle/wrapper-validation-action@8d49e559aae34d3e0eb16cde532684bc9702762b # pin@v1 + - uses: gradle/wrapper-validation-action@8d49e559aae34d3e0eb16cde532684bc9702762b # pin@v1.0.6 From ef1825b63c81cad6aa5b1e1b7c943ad639bd3140 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 17 Aug 2023 07:09:19 -0500 Subject: [PATCH 171/198] [SDK-4443] Use GitHub Actions for CI (#668) --- .github/workflows/build-and-test.yml | 27 +++++++++++++++++++++++++++ .github/workflows/dependabot.yml | 14 ++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 .github/workflows/build-and-test.yml create mode 100644 .github/workflows/dependabot.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 00000000..f86ed60e --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,27 @@ +name: auth0/java-jwt/build-and-test + +on: + pull_request: + merge_group: + push: + branches: ["master", "main", "v1"] + +jobs: + gradle: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + - uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c + with: + arguments: assemble apiDiff check jacocoTestReport --continue --console=plain + - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d + with: + flags: unittests + - uses: actions/upload-artifact@v3 + with: + name: Reports + path: lib/build/reports diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 00000000..f2839f50 --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + + - package-ecosystem: "gradle" + directory: "lib" + schedule: + interval: "daily" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] \ No newline at end of file From 9f7eaa49384ea5f66e985ff97ef2a0520e391570 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 17 Aug 2023 23:16:04 -0500 Subject: [PATCH 172/198] Remove CircleCI (#670) --- .circleci/config.yml | 61 -------------------------------------------- README.md | 2 +- 2 files changed, 1 insertion(+), 62 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 1675c39f..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,61 +0,0 @@ -version: 2.1 -orbs: - codecov: codecov/codecov@3 - -commands: - checkout-and-build: - steps: - - checkout - - run: chmod +x gradlew - # Download and cache dependencies - - restore_cache: - keys: - - v1-dependencies-{{ checksum "build.gradle" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - - run: ./gradlew clean build - - save_cache: - paths: - - ~/.m2 - key: v1-dependencies-{{ checksum "build.gradle" }} - run-tests: - steps: - - run: ./gradlew check jacocoTestReport --continue --console=plain - - codecov/upload - run-api-diff: - steps: - # run apiDiff task - - run: ./gradlew apiDiff - - store_artifacts: - path: lib/build/reports/apiDiff/apiDiff.txt - - store_artifacts: - path: lib/build/reports/apiDiff/apiDiff.html -jobs: - build: - docker: - - image: openjdk:11.0-jdk - steps: - - checkout-and-build - - run-tests - environment: - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - TERM: dumb - api-diff: - docker: - - image: openjdk:11.0-jdk - steps: - - checkout-and-build - - run-api-diff - environment: - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - TERM: dumb - -workflows: - build-and-test: - jobs: - - build - api-diff: - jobs: - - api-diff diff --git a/README.md b/README.md index 9f425898..4663cbdf 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ![A Java implementation of JSON Web Token (JWT) - RFC 7519.](https://cdn.auth0.com/website/sdks/banners/java-jwt-banner.png) -[![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) +![Build Status](https://img.shields.io/github/checks-status/auth0/java-jwt/master) [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](https://doge.mit-license.org/) [![Maven Central](https://img.shields.io/maven-central/v/com.auth0/java-jwt.svg?style=flat-square)](https://mvnrepository.com/artifact/com.auth0/java-jwt) From a6fa0b4d338386e7f0650b714444633420af0515 Mon Sep 17 00:00:00 2001 From: Wood Hwang Date: Wed, 13 Sep 2023 09:24:47 +0900 Subject: [PATCH 173/198] Fix typo on a comment in JWTCreator.java (#672) --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 0b0d21e4..bfcb9147 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -582,7 +582,7 @@ private static boolean isBasicType(Object value) { } /** - * Creates a new JWT and signs is with the given algorithm. + * Creates a new JWT and signs it with the given algorithm. * * @param algorithm used to sign the JWT * @return a new JWT token From bad6035bb87007d6744f4089857e22f8f085e1e5 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Mon, 20 Nov 2023 10:15:43 -0600 Subject: [PATCH 174/198] Remove dead README links (#676) --- lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index 27d79909..248af7c5 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -132,7 +132,6 @@ public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 256 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC256 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -145,7 +144,6 @@ public static Algorithm HMAC256(String secret) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 256 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC256 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -158,7 +156,6 @@ public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 384 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -171,7 +168,6 @@ public static Algorithm HMAC384(String secret) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 384 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -184,7 +180,6 @@ public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 512 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC512 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -197,7 +192,6 @@ public static Algorithm HMAC512(String secret) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 512 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC512 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ From d5c05d73a245b5de84830ea67565556232598fb1 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 1 Dec 2023 06:48:30 -0600 Subject: [PATCH 175/198] empty expected audience array should throw InvalidClaimException (#679) --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 15 +++++++++++---- .../test/java/com/auth0/jwt/JWTVerifierTest.java | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 6cec2026..bf180300 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -364,12 +364,19 @@ private boolean assertInstantIsLessThanOrEqualToNow(Instant claimVal, long leewa } private boolean assertValidAudienceClaim( - List audience, - List values, + List actualAudience, + List expectedAudience, boolean shouldContainAll ) { - return !(audience == null || (shouldContainAll && !audience.containsAll(values)) - || (!shouldContainAll && Collections.disjoint(audience, values))); + if (actualAudience == null || expectedAudience == null) { + return false; + } + + if (shouldContainAll) { + return actualAudience.containsAll(expectedAudience); + } else { + return !Collections.disjoint(actualAudience, expectedAudience); + } } private void assertPositive(long leeway) { diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 5a784b87..732d6365 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -310,6 +310,21 @@ public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() { assertThat(e.getClaimValue().asArray(String.class), is(new String[] {null})); } + @Test + public void shouldThrowWhenExpectedEmptyList() { + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + // Token 'aud': 'wide audience' + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ3aWRlIGF1ZGllbmNlIn0.c9anq03XepcuEKWEVsPk9cck0sIIfrT6hHbBsCar49o"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience(new String[0]) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE)); + assertThat(e.getClaimValue().asString(), is("wide audience")); + } + @Test public void shouldNotReplaceWhenMultipleChecksAreAdded() { JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) From d97f4e6df09d5437aede60776fa69e0ad49824af Mon Sep 17 00:00:00 2001 From: Kasper Karlsson Date: Tue, 6 Feb 2024 05:27:38 +0100 Subject: [PATCH 176/198] Fix typo in example code (#682) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4663cbdf..285f56ec 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ DecodedJWT decodedJWT; try { Algorithm algorithm = Algorithm.RSA256(rsaPublicKey, rsaPrivateKey); JWTVerifier verifier = JWT.require(algorithm) - // specify an specific claim validations + // specify any specific claim validations .withIssuer("auth0") // reusable verifier instance .build(); From 3aa997afc11d109a8565d3101711bfb0ce110bf6 Mon Sep 17 00:00:00 2001 From: Steven Wong Date: Thu, 25 Jul 2024 22:25:13 +0800 Subject: [PATCH 177/198] Update codeowner file with new GitHub team name --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 60f116c0..7958e8bd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @auth0/dx-sdks-engineer +* @auth0/project-dx-sdks-engineer-codeowner From 0c76c94784e1e00010e9848b09eff2c7df1ae4d6 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 19 Dec 2023 15:07:35 +0100 Subject: [PATCH 178/198] Automate release workflow --- .github/actions/get-prerelease/action.yml | 30 +++++++ .github/actions/get-release-notes/action.yml | 42 ++++++++++ .github/actions/get-version/action.yml | 21 +++++ .github/actions/maven-publish/action.yml | 44 ++++++++++ .github/actions/release-create/action.yml | 47 +++++++++++ .github/actions/tag-exists/action.yml | 36 ++++++++ .github/workflows/java-release.yml | 88 ++++++++++++++++++++ .github/workflows/release.yml | 27 ++++++ lib/build.gradle | 16 ++-- 9 files changed, 343 insertions(+), 8 deletions(-) create mode 100644 .github/actions/get-prerelease/action.yml create mode 100644 .github/actions/get-release-notes/action.yml create mode 100644 .github/actions/get-version/action.yml create mode 100644 .github/actions/maven-publish/action.yml create mode 100644 .github/actions/release-create/action.yml create mode 100644 .github/actions/tag-exists/action.yml create mode 100644 .github/workflows/java-release.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/actions/get-prerelease/action.yml b/.github/actions/get-prerelease/action.yml new file mode 100644 index 00000000..ce7acdc3 --- /dev/null +++ b/.github/actions/get-prerelease/action.yml @@ -0,0 +1,30 @@ +name: Return a boolean indicating if the version contains prerelease identifiers + +# +# Returns a simple true/false boolean indicating whether the version indicates it's a prerelease or not. +# +# TODO: Remove once the common repo is public. +# + +inputs: + version: + required: true + +outputs: + prerelease: + value: ${{ steps.get_prerelease.outputs.PRERELEASE }} + +runs: + using: composite + + steps: + - id: get_prerelease + shell: bash + run: | + if [[ "${VERSION}" == *"beta"* || "${VERSION}" == *"alpha"* ]]; then + echo "PRERELEASE=true" >> $GITHUB_OUTPUT + else + echo "PRERELEASE=false" >> $GITHUB_OUTPUT + fi + env: + VERSION: ${{ inputs.version }} diff --git a/.github/actions/get-release-notes/action.yml b/.github/actions/get-release-notes/action.yml new file mode 100644 index 00000000..287d2066 --- /dev/null +++ b/.github/actions/get-release-notes/action.yml @@ -0,0 +1,42 @@ +name: Return the release notes extracted from the body of the PR associated with the release. + +# +# Returns the release notes from the content of a pull request linked to a release branch. It expects the branch name to be in the format release/vX.Y.Z, release/X.Y.Z, release/vX.Y.Z-beta.N. etc. +# +# TODO: Remove once the common repo is public. +# +inputs: + version: + required: true + repo_name: + required: false + repo_owner: + required: true + token: + required: true + +outputs: + release-notes: + value: ${{ steps.get_release_notes.outputs.RELEASE_NOTES }} + +runs: + using: composite + + steps: + - uses: actions/github-script@v7 + id: get_release_notes + with: + result-encoding: string + script: | + const { data: pulls } = await github.rest.pulls.list({ + owner: process.env.REPO_OWNER, + repo: process.env.REPO_NAME, + state: 'all', + head: `${process.env.REPO_OWNER}:release/${process.env.VERSION}`, + }); + core.setOutput('RELEASE_NOTES', pulls[0].body); + env: + GITHUB_TOKEN: ${{ inputs.token }} + REPO_OWNER: ${{ inputs.repo_owner }} + REPO_NAME: ${{ inputs.repo_name }} + VERSION: ${{ inputs.version }} diff --git a/.github/actions/get-version/action.yml b/.github/actions/get-version/action.yml new file mode 100644 index 00000000..9440ec92 --- /dev/null +++ b/.github/actions/get-version/action.yml @@ -0,0 +1,21 @@ +name: Return the version extracted from the branch name + +# +# Returns the version from the .version file. +# +# TODO: Remove once the common repo is public. +# + +outputs: + version: + value: ${{ steps.get_version.outputs.VERSION }} + +runs: + using: composite + + steps: + - id: get_version + shell: bash + run: | + VERSION=$(head -1 .version) + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT diff --git a/.github/actions/maven-publish/action.yml b/.github/actions/maven-publish/action.yml new file mode 100644 index 00000000..ee477061 --- /dev/null +++ b/.github/actions/maven-publish/action.yml @@ -0,0 +1,44 @@ +name: Publish release to Java + +inputs: + ossr-username: + required: true + ossr-password: + required: true + signing-key: + required: true + signing-password: + required: true + java-version: + required: true + is-android: + required: true + version: + required: true + +runs: + using: composite + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Java + shell: bash + run: | + curl -s "https://get.sdkman.io" | bash + source "/home/runner/.sdkman/bin/sdkman-init.sh" + sdk list java + sdk install java ${{ inputs.java-version }} && sdk default java ${{ inputs.java-version }} + + - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # pin@1.1.0 + + - name: Publish Java + shell: bash + if: inputs.is-android == 'false' + run: ./gradlew clean assemble sign publishMavenJavaPublicationToMavenRepository -PisSnapshot=false -Pversion="${{ inputs.version }}" -PossrhUsername="${{ inputs.ossr-username }}" -PossrhPassword="${{ inputs.ossr-password }}" -PsigningKey="${{ inputs.signing-key }}" -PsigningPassword="${{ inputs.signing-password }}" + + - name: Publish Android + shell: bash + if: inputs.is-android == 'true' + run: ./gradlew clean assemble sign publishAndroidLibraryPublicationToMavenRepository -PisSnapshot=false -Pversion="${{ inputs.version }}" -PossrhUsername="${{ inputs.ossr-username }}" -PossrhPassword="${{ inputs.ossr-password }}" -PsigningKey="${{ inputs.signing-key }}" -PsigningPassword="${{ inputs.signing-password }}" diff --git a/.github/actions/release-create/action.yml b/.github/actions/release-create/action.yml new file mode 100644 index 00000000..6a2bf804 --- /dev/null +++ b/.github/actions/release-create/action.yml @@ -0,0 +1,47 @@ +name: Create a GitHub release + +# +# Creates a GitHub release with the given version. +# +# TODO: Remove once the common repo is public. +# + +inputs: + token: + required: true + files: + required: false + name: + required: true + body: + required: true + tag: + required: true + commit: + required: true + draft: + default: false + required: false + prerelease: + default: false + required: false + fail_on_unmatched_files: + default: true + required: false + +runs: + using: composite + + steps: + - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + with: + body: ${{ inputs.body }} + name: ${{ inputs.name }} + tag_name: ${{ inputs.tag }} + target_commitish: ${{ inputs.commit }} + draft: ${{ inputs.draft }} + prerelease: ${{ inputs.prerelease }} + fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }} + files: ${{ inputs.files }} + env: + GITHUB_TOKEN: ${{ inputs.token }} diff --git a/.github/actions/tag-exists/action.yml b/.github/actions/tag-exists/action.yml new file mode 100644 index 00000000..b5fbdb73 --- /dev/null +++ b/.github/actions/tag-exists/action.yml @@ -0,0 +1,36 @@ +name: Return a boolean indicating if a tag already exists for the repository + +# +# Returns a simple true/false boolean indicating whether the tag exists or not. +# +# TODO: Remove once the common repo is public. +# + +inputs: + token: + required: true + tag: + required: true + +outputs: + exists: + description: 'Whether the tag exists or not' + value: ${{ steps.tag-exists.outputs.EXISTS }} + +runs: + using: composite + + steps: + - id: tag-exists + shell: bash + run: | + GET_API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}" + http_status_code=$(curl -LI $GET_API_URL -o /dev/null -w '%{http_code}\n' -s -H "Authorization: token ${GITHUB_TOKEN}") + if [ "$http_status_code" -ne "404" ] ; then + echo "EXISTS=true" >> $GITHUB_OUTPUT + else + echo "EXISTS=false" >> $GITHUB_OUTPUT + fi + env: + TAG_NAME: ${{ inputs.tag }} + GITHUB_TOKEN: ${{ inputs.token }} diff --git a/.github/workflows/java-release.yml b/.github/workflows/java-release.yml new file mode 100644 index 00000000..3f81eb14 --- /dev/null +++ b/.github/workflows/java-release.yml @@ -0,0 +1,88 @@ +name: Create Java and GitHub Release + +on: + workflow_call: + inputs: + java-version: + required: true + type: string + is-android: + required: true + type: string + secrets: + ossr-username: + required: true + ossr-password: + required: true + signing-key: + required: true + signing-password: + required: true + github-token: + required: true + +### TODO: Replace instances of './.github/actions/' w/ `auth0/dx-sdk-actions/` and append `@latest` after the common `dx-sdk-actions` repo is made public. +### TODO: Also remove `get-prerelease`, `get-version`, `release-create`, `tag-create` and `tag-exists` actions from this repo's .github/actions folder once the repo is public. + +jobs: + release: + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) + runs-on: ubuntu-latest + environment: release + + steps: + # Checkout the code + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Get the version from the branch name + - id: get_version + uses: ./.github/actions/get-version + + # Get the prerelease flag from the branch name + - id: get_prerelease + uses: ./.github/actions/get-prerelease + with: + version: ${{ steps.get_version.outputs.version }} + + # Get the release notes + - id: get_release_notes + uses: ./.github/actions/get-release-notes + with: + token: ${{ secrets.github-token }} + version: ${{ steps.get_version.outputs.version }} + repo_owner: ${{ github.repository_owner }} + repo_name: ${{ github.event.repository.name }} + + # Check if the tag already exists + - id: tag_exists + uses: ./.github/actions/tag-exists + with: + tag: ${{ steps.get_version.outputs.version }} + token: ${{ secrets.github-token }} + + # If the tag already exists, exit with an error + - if: steps.tag_exists.outputs.exists == 'true' + run: exit 1 + + # Publish the release to Maven + - uses: ./.github/actions/maven-publish + with: + java-version: ${{ inputs.java-version }} + is-android: ${{ inputs.is-android }} + version: ${{ steps.get_version.outputs.version }} + ossr-username: ${{ secrets.ossr-username }} + ossr-password: ${{ secrets.ossr-password }} + signing-key: ${{ secrets.signing-key }} + signing-password: ${{ secrets.signing-password }} + + # Create a release for the tag + - uses: ./.github/actions/release-create + with: + token: ${{ secrets.github-token }} + name: ${{ steps.get_version.outputs.version }} + body: ${{ steps.get_release_notes.outputs.release-notes }} + tag: ${{ steps.get_version.outputs.version }} + commit: ${{ github.sha }} + prerelease: ${{ steps.get_prerelease.outputs.prerelease }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..63482cca --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,27 @@ +name: Create GitHub Release + +on: + pull_request: + types: + - closed + workflow_dispatch: + +permissions: + contents: write + +### TODO: Replace instances of './.github/workflows/' w/ `auth0/dx-sdk-actions/workflows/` and append `@latest` after the common `dx-sdk-actions` repo is made public. +### TODO: Also remove `get-prerelease`, `get-release-notes`, `get-version`, `maven-publish`, `release-create`, and `tag-exists` actions from this repo's .github/actions folder once the repo is public. +### TODO: Also remove `java-release` workflow from this repo's .github/workflows folder once the repo is public. + +jobs: + release: + uses: ./.github/workflows/java-release.yml + with: + java-version: 8.0.382-tem + is-android: false + secrets: + ossr-username: ${{ secrets.OSSR_USERNAME }} + ossr-password: ${{ secrets.OSSR_PASSWORD }} + signing-key: ${{ secrets.SIGNING_KEY }} + signing-password: ${{ secrets.SIGNING_PASSWORD }} + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/lib/build.gradle b/lib/build.gradle index c4e11764..aa134c32 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -31,12 +31,16 @@ tasks.named("checkstyleJmh").configure({ logger.lifecycle("Using version ${version} for ${group}.${name}") +def signingKey = findProperty('signingKey') +def signingKeyPwd = findProperty('signingPassword') + oss { name "java jwt" repository "java-jwt" organization "auth0" description "Java implementation of JSON Web Token (JWT)" baselineCompareVersion "4.1.0" + skipAssertSigningConfiguration true developers { auth0 { @@ -54,6 +58,10 @@ oss { } } +signing { + useInMemoryPgpKeys(signingKey, signingKeyPwd) +} + java { toolchain { languageVersion = JavaLanguageVersion.of(11) @@ -158,14 +166,6 @@ jar { compileModuleInfoJava.dependsOn compileJava classes.dependsOn compileModuleInfoJava -// Creates a version.txt file containing the current version of the SDK. -// This file is picked up and parsed by our Ship Orb to determine the version. -task exportVersion() { - doLast { - new File(rootDir, "version.txt").text = "$version" - } -} - // you can pass any arguments JMH accepts via Gradle args. // Example: ./gradlew runJMH --args="-lrf" tasks.register('runJMH', JavaExec) { From 05fef18c2b3cdfd5ece2cb51ebcbff5a6ebce127 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 19 Dec 2023 15:10:35 +0100 Subject: [PATCH 179/198] Add .version and modify .shiprc --- .shiprc | 1 + .version | 1 + 2 files changed, 2 insertions(+) create mode 100644 .version diff --git a/.shiprc b/.shiprc index fe59345e..1b83cc62 100644 --- a/.shiprc +++ b/.shiprc @@ -1,6 +1,7 @@ { "files": { "README.md": [], + ".version": [], "lib/build.gradle": ["version = \"{MAJOR}.{MINOR}.{PATCH}\""] }, "prefixVersion": false diff --git a/.version b/.version new file mode 100644 index 00000000..64b5ae39 --- /dev/null +++ b/.version @@ -0,0 +1 @@ +4.4.0 \ No newline at end of file From 9cd2b043280e3f66778d75198aa34bf821696c3c Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Wed, 20 Dec 2023 14:26:46 +0100 Subject: [PATCH 180/198] Update .github/workflows/release.yml Co-authored-by: Jim Anderson --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63482cca..7a98f05e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: release: uses: ./.github/workflows/java-release.yml with: - java-version: 8.0.382-tem + java-version: 11.0.21-tem is-android: false secrets: ossr-username: ${{ secrets.OSSR_USERNAME }} From 876d7456d362e031fcb1a56609e2c850bede426f Mon Sep 17 00:00:00 2001 From: Tanya Sinha Date: Thu, 14 Nov 2024 16:05:59 +0530 Subject: [PATCH 181/198] Add reversing lab scanner (#695) --- .github/actions/rl-scanner/action.yml | 66 ++++++++++++++++++++++++ .github/workflows/release.yml | 14 +++++ .github/workflows/rl-secure.yml | 73 +++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 .github/actions/rl-scanner/action.yml create mode 100644 .github/workflows/rl-secure.yml diff --git a/.github/actions/rl-scanner/action.yml b/.github/actions/rl-scanner/action.yml new file mode 100644 index 00000000..fbf81217 --- /dev/null +++ b/.github/actions/rl-scanner/action.yml @@ -0,0 +1,66 @@ +name: 'Reversing Labs Scanner' +description: 'Runs the Reversing Labs scanner on a specified artifact.' +inputs: + artifact-path: + description: 'Path to the artifact to be scanned.' + required: true + version: + description: 'Version of the artifact.' + required: true + +runs: + using: 'composite' + steps: + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Python dependencies + shell: bash + run: | + pip install boto3 requests + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ env.PRODSEC_TOOLS_ARN }} + aws-region: us-east-1 + mask-aws-account-id: true + + - name: Install RL Wrapper + shell: bash + run: | + pip install rl-wrapper>=1.0.0 --index-url "https://${{ env.PRODSEC_TOOLS_USER }}:${{ env.PRODSEC_TOOLS_TOKEN }}@a0us.jfrog.io/artifactory/api/pypi/python-local/simple" + - name: Run RL Scanner + shell: bash + env: + RLSECURE_LICENSE: ${{ env.RLSECURE_LICENSE }} + RLSECURE_SITE_KEY: ${{ env.RLSECURE_SITE_KEY }} + SIGNAL_HANDLER_TOKEN: ${{ env.SIGNAL_HANDLER_TOKEN }} + PYTHONUNBUFFERED: 1 + run: | + if [ ! -f "${{ inputs.artifact-path }}" ]; then + echo "Artifact not found: ${{ inputs.artifact-path }}" + exit 1 + fi + rl-wrapper \ + --artifact "${{ inputs.artifact-path }}" \ + --name "${{ github.event.repository.name }}" \ + --version "${{ inputs.version }}" \ + --repository "${{ github.repository }}" \ + --commit "${{ github.sha }}" \ + --build-env "github_actions" \ + --suppress_output + # Check the outcome of the scanner + if [ $? -ne 0 ]; then + echo "RL Scanner failed." + echo "scan-status=failed" >> $GITHUB_ENV + exit 1 + else + echo "RL Scanner passed." + echo "scan-status=success" >> $GITHUB_ENV + fi +outputs: + scan-status: + description: 'The outcome of the scan process.' + value: ${{ env.scan-status }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a98f05e..49e48059 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,14 +8,28 @@ on: permissions: contents: write + id-token: write # This is required for requesting the JWT ### TODO: Replace instances of './.github/workflows/' w/ `auth0/dx-sdk-actions/workflows/` and append `@latest` after the common `dx-sdk-actions` repo is made public. ### TODO: Also remove `get-prerelease`, `get-release-notes`, `get-version`, `maven-publish`, `release-create`, and `tag-exists` actions from this repo's .github/actions folder once the repo is public. ### TODO: Also remove `java-release` workflow from this repo's .github/workflows folder once the repo is public. jobs: + rl-scanner: + uses: ./.github/workflows/rl-secure.yml + with: + java-version: 11 + artifact-name: 'java-jwt.tgz' + secrets: + RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} + RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }} + SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }} + PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }} + PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }} + PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }} release: uses: ./.github/workflows/java-release.yml + needs: rl-scanner with: java-version: 11.0.21-tem is-android: false diff --git a/.github/workflows/rl-secure.yml b/.github/workflows/rl-secure.yml new file mode 100644 index 00000000..ef329594 --- /dev/null +++ b/.github/workflows/rl-secure.yml @@ -0,0 +1,73 @@ +name: RL-Secure Workflow + +on: + workflow_call: + inputs: + java-version: + required: true + type: string + artifact-name: + required: true + type: string + secrets: + RLSECURE_LICENSE: + required: true + RLSECURE_SITE_KEY: + required: true + SIGNAL_HANDLER_TOKEN: + required: true + PRODSEC_TOOLS_USER: + required: true + PRODSEC_TOOLS_TOKEN: + required: true + PRODSEC_TOOLS_ARN: + required: true + +jobs: + checkout-build-scan-only: + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) + runs-on: ubuntu-latest + outputs: + scan-status: ${{ steps.rl-scan-conclusion.outcome }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: ${{ inputs.java-version }} + + - name: Build with Gradle + uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c + with: + arguments: assemble apiDiff check jacocoTestReport --continue --console=plain + + - name: Get Artifact Version + id: get_version + uses: ./.github/actions/get-version + + - name: Create tgz build artifact + run: | + tar -czvf ${{ inputs.artifact-name }} * + + - name: Run RL Scanner + id: rl-scan-conclusion + uses: ./.github/actions/rl-scanner + with: + artifact-path: "$(pwd)/${{ inputs.artifact-name }}" + version: "${{ steps.get_version.outputs.version }}" + env: + RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} + RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }} + SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }} + PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }} + PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }} + PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }} + + - name: Output scan result + run: echo "scan-status=${{ steps.rl-scan-conclusion.outcome }}" >> $GITHUB_ENV \ No newline at end of file From 41d93afce36dd07431f45f39401f24840835f54e Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 15 Nov 2024 21:12:00 +0530 Subject: [PATCH 182/198] added snyk workflow --- .github/workflows/snyk.yml | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/snyk.yml diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml new file mode 100644 index 00000000..9394832d --- /dev/null +++ b/.github/workflows/snyk.yml @@ -0,0 +1,47 @@ +name: Snyk + +on: + merge_group: + workflow_dispatch: + pull_request_target: + types: + - opened + - synchronize + push: + branches: + - master + schedule: + - cron: '30 0 1,15 * *' + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +jobs: + authorize: + name: Authorize + environment: ${{ github.actor != 'dependabot[bot]' && github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && 'external' || 'internal' }} + runs-on: ubuntu-latest + steps: + - run: true + + check: + needs: authorize + + name: Check for Vulnerabilities + runs-on: ubuntu-latest + + steps: + - if: github.actor == 'dependabot[bot]' || github.event_name == 'merge_group' + run: exit 0 # Skip unnecessary test runs for dependabot and merge queues. Artifically flag as successful, as this is a required check for branch protection. + + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + + - uses: snyk/actions/gradle-jdk11@b98d498629f1c368650224d6d212bf7dfa89e4bf # pin@0.4.0 + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} \ No newline at end of file From b915cb6ebe089ac68ae70221792576d322c9a924 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 15 Nov 2024 21:16:11 +0530 Subject: [PATCH 183/198] removed pull_request_target --- .github/workflows/snyk.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index 9394832d..457b6afa 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -3,7 +3,7 @@ name: Snyk on: merge_group: workflow_dispatch: - pull_request_target: + pull_request: types: - opened - synchronize @@ -21,16 +21,8 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: - authorize: - name: Authorize - environment: ${{ github.actor != 'dependabot[bot]' && github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && 'external' || 'internal' }} - runs-on: ubuntu-latest - steps: - - run: true check: - needs: authorize - name: Check for Vulnerabilities runs-on: ubuntu-latest From de23d8f21f49e4d7a5b5572ac1416d13f2ddc5e8 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 3 Jan 2025 18:21:41 +0530 Subject: [PATCH 184/198] add java 21 in CI --- .github/workflows/build-and-test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f86ed60e..32886d68 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -9,12 +9,17 @@ on: jobs: gradle: runs-on: ubuntu-latest + + strategy: + matrix: + java-version: [11, 17, 21] + steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: distribution: temurin - java-version: 11 + java-version: ${{ matrix.java-version }} - uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c with: arguments: assemble apiDiff check jacocoTestReport --continue --console=plain From 9e04f118db4f3356e673aa981be95a56f8ddf84d Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 3 Jan 2025 18:25:53 +0530 Subject: [PATCH 185/198] added java 21 --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 32886d68..5ca24522 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - java-version: [11, 17, 21] + java-version: [11, 21] steps: - uses: actions/checkout@v3 From cc705395f5099d09a8a33a0ea35c87915e38aa66 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 17 Jan 2025 10:27:38 +0530 Subject: [PATCH 186/198] added java 21 tests --- .github/workflows/build-and-test.yml | 7 +------ lib/build.gradle | 11 +++++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5ca24522..f86ed60e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -9,17 +9,12 @@ on: jobs: gradle: runs-on: ubuntu-latest - - strategy: - matrix: - java-version: [11, 21] - steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: distribution: temurin - java-version: ${{ matrix.java-version }} + java-version: 11 - uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c with: arguments: assemble apiDiff check jacocoTestReport --continue --console=plain diff --git a/lib/build.gradle b/lib/build.gradle index aa134c32..77de85a7 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -154,9 +154,20 @@ def testJava17 = tasks.register('testJava17', Test) { shouldRunAfter(tasks.named('test')) } +def testJava21 = tasks.register('testJava21', Test) { + description = 'Runs unit tests on Java 21.' + group = 'verification' + + javaLauncher.set(javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(21) + }) + shouldRunAfter(tasks.named('test')) +} + tasks.named('check') { dependsOn(testJava8) dependsOn(testJava17) + dependsOn(testJava21) } jar { From d20aec6d58cd37ab518b987cd811bb6480a2ff97 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 17 Jan 2025 10:35:41 +0530 Subject: [PATCH 187/198] updated jacoco toll version --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index 77de85a7..bff7bb9c 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -94,7 +94,7 @@ dependencies { } jacoco { - toolVersion = "0.8.7" + toolVersion = "0.8.10" } jacocoTestReport { From 8278f62be812496210c5fa5a11192741d2d6c8c7 Mon Sep 17 00:00:00 2001 From: Carlos Galan Cladera Date: Wed, 11 Dec 2024 14:53:41 +0100 Subject: [PATCH 188/198] fix: upgrade jackson-core to 2.15 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index aa134c32..c2c77533 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -80,7 +80,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.0' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' From 3840a1eaf07e2de55c7676b90d6b398526355ff1 Mon Sep 17 00:00:00 2001 From: Carlos Galan Cladera Date: Wed, 11 Dec 2024 15:11:01 +0100 Subject: [PATCH 189/198] fix: upgrade jackson-core to 2.15.4 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index c2c77533..16a55af1 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -80,7 +80,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.0' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.4' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' From a52bec0224ea087116c807d8f895fa98d1596013 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 22 Jan 2025 14:02:26 +0530 Subject: [PATCH 190/198] added maven-publish changes --- .github/actions/maven-publish/action.yml | 25 ++--- .github/workflows/java-release.yml | 9 +- .github/workflows/release.yml | 3 +- gradle.properties | 22 +++++ gradle/maven-publish.gradle | 113 +++++++++++++++++++++++ gradle/versioning.gradle | 17 ++++ lib/build.gradle | 89 +++++++++++++----- settings.gradle | 3 - 8 files changed, 231 insertions(+), 50 deletions(-) create mode 100644 gradle/maven-publish.gradle create mode 100644 gradle/versioning.gradle diff --git a/.github/actions/maven-publish/action.yml b/.github/actions/maven-publish/action.yml index ee477061..0d280cbe 100644 --- a/.github/actions/maven-publish/action.yml +++ b/.github/actions/maven-publish/action.yml @@ -1,20 +1,16 @@ name: Publish release to Java inputs: + java-version: + required: true ossr-username: required: true - ossr-password: + ossr-token: required: true signing-key: required: true signing-password: required: true - java-version: - required: true - is-android: - required: true - version: - required: true runs: using: composite @@ -33,12 +29,11 @@ runs: - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # pin@1.1.0 - - name: Publish Java - shell: bash - if: inputs.is-android == 'false' - run: ./gradlew clean assemble sign publishMavenJavaPublicationToMavenRepository -PisSnapshot=false -Pversion="${{ inputs.version }}" -PossrhUsername="${{ inputs.ossr-username }}" -PossrhPassword="${{ inputs.ossr-password }}" -PsigningKey="${{ inputs.signing-key }}" -PsigningPassword="${{ inputs.signing-password }}" - - - name: Publish Android + - name: Publish Android/Java Packages to Maven shell: bash - if: inputs.is-android == 'true' - run: ./gradlew clean assemble sign publishAndroidLibraryPublicationToMavenRepository -PisSnapshot=false -Pversion="${{ inputs.version }}" -PossrhUsername="${{ inputs.ossr-username }}" -PossrhPassword="${{ inputs.ossr-password }}" -PsigningKey="${{ inputs.signing-key }}" -PsigningPassword="${{ inputs.signing-password }}" + run: ./gradlew publish -PisSnapshot=false --stacktrace + env: + MAVEN_USERNAME: ${{ inputs.ossr-username }} + MAVEN_PASSWORD: ${{ inputs.ossr-token }} + SIGNING_KEY: ${{ inputs.signing-key}} + SIGNING_PASSWORD: ${{ inputs.signing-password}} \ No newline at end of file diff --git a/.github/workflows/java-release.yml b/.github/workflows/java-release.yml index 3f81eb14..ddce3e59 100644 --- a/.github/workflows/java-release.yml +++ b/.github/workflows/java-release.yml @@ -6,13 +6,10 @@ on: java-version: required: true type: string - is-android: - required: true - type: string secrets: ossr-username: required: true - ossr-password: + ossr-token: required: true signing-key: required: true @@ -70,10 +67,8 @@ jobs: - uses: ./.github/actions/maven-publish with: java-version: ${{ inputs.java-version }} - is-android: ${{ inputs.is-android }} - version: ${{ steps.get_version.outputs.version }} ossr-username: ${{ secrets.ossr-username }} - ossr-password: ${{ secrets.ossr-password }} + ossr-token: ${{ secrets.ossr-token }} signing-key: ${{ secrets.signing-key }} signing-password: ${{ secrets.signing-password }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49e48059..2b00e426 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,10 +32,9 @@ jobs: needs: rl-scanner with: java-version: 11.0.21-tem - is-android: false secrets: ossr-username: ${{ secrets.OSSR_USERNAME }} - ossr-password: ${{ secrets.OSSR_PASSWORD }} + ossr-token: ${{ secrets.OSSR_TOKEN }} signing-key: ${{ secrets.SIGNING_KEY }} signing-password: ${{ secrets.SIGNING_PASSWORD }} github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/gradle.properties b/gradle.properties index aac7c9b4..b4d8583f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,3 +15,25 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + +GROUP=com.auth0 +POM_ARTIFACT_ID=java-jwt +VERSION_NAME=4.4.0 + +POM_NAME=java jwt +POM_DESCRIPTION=Java client library for the Auth0 platform +POM_PACKAGING=jar + +POM_URL=https://github.com/auth0/java-jwt +POM_SCM_URL=https://github.com/auth0/java-jwt + +POM_SCM_CONNECTION=scm:git:https://github.com/auth0/java-jwt.git +POM_SCM_DEV_CONNECTION=scm:git:https://github.com/auth0/java-jwt.git + +POM_LICENCE_NAME=The MIT License (MIT) +POM_LICENCE_URL=https://raw.githubusercontent.com/auth0/java-jwt/master/LICENSE +POM_LICENCE_DIST=repo + +POM_DEVELOPER_ID=auth0 +POM_DEVELOPER_NAME=Auth0 +POM_DEVELOPER_EMAIL=oss@auth0.com \ No newline at end of file diff --git a/gradle/maven-publish.gradle b/gradle/maven-publish.gradle new file mode 100644 index 00000000..206a581b --- /dev/null +++ b/gradle/maven-publish.gradle @@ -0,0 +1,113 @@ +apply plugin: 'maven-publish' +apply plugin: 'signing' + +task('sourcesJar', type: Jar, dependsOn: classes) { + archiveClassifier = 'sources' + from sourceSets.main.allSource +} + +task('javadocJar', type: Jar, dependsOn: javadoc) { + archiveClassifier = 'javadoc' + from javadoc.getDestinationDir() +} +tasks.withType(Javadoc).configureEach { + javadocTool = javaToolchains.javadocToolFor { + // Use latest JDK for javadoc generation + languageVersion = JavaLanguageVersion.of(17) + } +} + +javadoc { + // Specify the Java version that the project will use + options.addStringOption('-release', "8") +} +artifacts { + archives sourcesJar, javadocJar +} + + +final releaseRepositoryUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" +final snapshotRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/" + +publishing { + publications { + mavenJava(MavenPublication) { + + groupId = GROUP + artifactId = POM_ARTIFACT_ID + version = getVersionName() + + artifact("$buildDir/libs/${project.name}-${version}.jar") + artifact sourcesJar + artifact javadocJar + + pom { + name = POM_NAME + packaging = POM_PACKAGING + description = POM_DESCRIPTION + url = POM_URL + + licenses { + license { + name = POM_LICENCE_NAME + url = POM_LICENCE_URL + distribution = POM_LICENCE_DIST + } + } + + developers { + developer { + id = POM_DEVELOPER_ID + name = POM_DEVELOPER_NAME + email = POM_DEVELOPER_EMAIL + } + } + + scm { + url = POM_SCM_URL + connection = POM_SCM_CONNECTION + developerConnection = POM_SCM_DEV_CONNECTION + } + + pom.withXml { + def dependenciesNode = asNode().appendNode('dependencies') + + project.configurations.implementation.allDependencies.each { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + } + } + } + } + } + repositories { + maven { + name = "sonatype" + url = version.endsWith('SNAPSHOT') ? snapshotRepositoryUrl : releaseRepositoryUrl + credentials { + username = System.getenv("MAVEN_USERNAME") + password = System.getenv("MAVEN_PASSWORD") + } + } + } +} + +signing { + def signingKey = System.getenv("SIGNING_KEY") + def signingPassword = System.getenv("SIGNING_PASSWORD") + useInMemoryPgpKeys(signingKey, signingPassword) + + sign publishing.publications.mavenJava +} + +javadoc { + if(JavaVersion.current().isJava9Compatible()) { + options.addBooleanOption('html5', true) + } +} + +tasks.named('publish').configure { + dependsOn tasks.named('assemble') +} \ No newline at end of file diff --git a/gradle/versioning.gradle b/gradle/versioning.gradle new file mode 100644 index 00000000..3441ae11 --- /dev/null +++ b/gradle/versioning.gradle @@ -0,0 +1,17 @@ +def getVersionFromFile() { + def versionFile = rootProject.file('.version') + return versionFile.text.readLines().first().trim() +} + +def isSnapshot() { + return hasProperty('isSnapshot') ? isSnapshot.toBoolean() : true +} + +def getVersionName() { + return isSnapshot() ? project.version+"-SNAPSHOT" : project.version +} + +ext { + getVersionName = this.&getVersionName + getVersionFromFile = this.&getVersionFromFile +} \ No newline at end of file diff --git a/lib/build.gradle b/lib/build.gradle index 148bce72..167f234a 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,9 +1,19 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + // https://github.com/melix/japicmp-gradle-plugin/issues/36 + classpath 'com.google.guava:guava:31.1-jre' + } +} plugins { id 'java' id 'jacoco' - id 'com.auth0.gradle.oss-library.java' id 'checkstyle' + id 'me.champeau.gradle.japicmp' version '0.2.9' } sourceSets { @@ -29,37 +39,69 @@ tasks.named("checkstyleJmh").configure({ enabled = false }) -logger.lifecycle("Using version ${version} for ${group}.${name}") +apply from: rootProject.file('gradle/versioning.gradle') + +version = getVersionFromFile() +group = GROUP +logger.lifecycle("Using version ${version} for ${name} group $group") -def signingKey = findProperty('signingKey') -def signingKeyPwd = findProperty('signingPassword') +import me.champeau.gradle.japicmp.JapicmpTask -oss { - name "java jwt" - repository "java-jwt" - organization "auth0" - description "Java implementation of JSON Web Token (JWT)" - baselineCompareVersion "4.1.0" - skipAssertSigningConfiguration true +project.afterEvaluate { - developers { - auth0 { - displayName = "Auth0" - email = "oss@auth0.com" + def versions = project.ext.testInJavaVersions + for (pluginJavaTestVersion in versions) { + def taskName = "testInJava-${pluginJavaTestVersion}" + tasks.register(taskName, Test) { + def versionToUse = taskName.split("-").getAt(1) as Integer + description = "Runs unit tests on Java version ${versionToUse}." + project.logger.quiet("Test will be running in ${versionToUse}") + group = 'verification' + javaLauncher.set(javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(versionToUse) + }) + shouldRunAfter(tasks.named('test')) } - lbalmaceda { - displayName = "Luciano Balmaceda" - email = "luciano.balmaceda@auth0.com" + tasks.named('check') { + dependsOn(taskName) } - hzalaz { - displayName = "Hernan Zalazar" - email = "hernan@auth0.com" + } + + project.configure(project) { + def baselineVersion = project.ext.baselineCompareVersion + task('apiDiff', type: JapicmpTask, dependsOn: 'jar') { + oldClasspath = files(getBaselineJar(project, baselineVersion)) + newClasspath = files(jar.archiveFile) + onlyModified = true + failOnModification = true + ignoreMissingClasses = true + htmlOutputFile = file("$buildDir/reports/apiDiff/apiDiff.html") + txtOutputFile = file("$buildDir/reports/apiDiff/apiDiff.txt") + doLast { + project.logger.quiet("Comparing against baseline version ${baselineVersion}") + } + } + } +} + +private static File getBaselineJar(Project project, String baselineVersion) { + // Use detached configuration: https://github.com/square/okhttp/blob/master/build.gradle#L270 + def group = project.group + try { + def baseline = "${project.group}:${project.name}:$baselineVersion" + project.group = 'virtual_group_for_japicmp' + def dependency = project.dependencies.create(baseline + "@jar") + return project.configurations.detachedConfiguration(dependency).files.find { + it.name == "${project.name}-${baselineVersion}.jar" } + } finally { + project.group = group } } -signing { - useInMemoryPgpKeys(signingKey, signingKeyPwd) +ext { + baselineCompareVersion = '4.1.0' + testInJavaVersions = [8, 11, 17, 21] } java { @@ -198,3 +240,4 @@ tasks.register('jmhHelp', JavaExec) { args '-h' } +apply from: rootProject.file('gradle/maven-publish.gradle') diff --git a/settings.gradle b/settings.gradle index 8d5f112c..d3c4c85b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,9 +2,6 @@ pluginManagement { repositories { gradlePluginPortal() } - plugins { - id 'com.auth0.gradle.oss-library.java' version '0.17.2' - } } include ':java-jwt' From 2643ca2a464dbb159377b67ae32c9e0b7c276b55 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 22 Jan 2025 15:55:30 +0530 Subject: [PATCH 191/198] removed version from gralde properties --- gradle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index b4d8583f..74a5a049 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,6 @@ org.gradle.jvmargs=-Xmx1536m GROUP=com.auth0 POM_ARTIFACT_ID=java-jwt -VERSION_NAME=4.4.0 POM_NAME=java jwt POM_DESCRIPTION=Java client library for the Auth0 platform From 87710ed5aca11004ee7bc104fb60bb7c5994b0a5 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 22 Jan 2025 19:12:50 +0530 Subject: [PATCH 192/198] updated version --- gradle/maven-publish.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/maven-publish.gradle b/gradle/maven-publish.gradle index 206a581b..a9ad38d3 100644 --- a/gradle/maven-publish.gradle +++ b/gradle/maven-publish.gradle @@ -19,7 +19,7 @@ tasks.withType(Javadoc).configureEach { javadoc { // Specify the Java version that the project will use - options.addStringOption('-release', "8") + options.addStringOption('-release', "11") } artifacts { archives sourcesJar, javadocJar From 65181f8838bcd8cebb8585c8024baba9680d77d9 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 22 Jan 2025 19:47:05 +0530 Subject: [PATCH 193/198] Release 4.5.0 --- .version | 2 +- CHANGELOG.md | 14 ++++++++++++++ README.md | 4 ++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.version b/.version index 64b5ae39..ae153944 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -4.4.0 \ No newline at end of file +4.5.0 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b1e75bd..6bb634fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## [4.5.0](https://github.com/auth0/java-jwt/tree/4.5.0) (2025-01-22) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.4.0...4.5.0) + +**Added** +- Fix jackson vuln [\#705](https://github.com/auth0/java-jwt/pull/705) ([tanya732](https://github.com/tanya732)) +- Fix typo in example code [\#682](https://github.com/auth0/java-jwt/pull/682) ([kasperkarlsson](https://github.com/kasperkarlsson)) +- Remove dead README links [\#676](https://github.com/auth0/java-jwt/pull/676) ([jimmyjames](https://github.com/jimmyjames)) +- Fix typo on a comment in JWTCreator.java [\#672](https://github.com/auth0/java-jwt/pull/672) ([sgc109](https://github.com/sgc109)) +- Remove CircleCI [\#670](https://github.com/auth0/java-jwt/pull/670) ([jimmyjames](https://github.com/jimmyjames)) +- Empty string audience claim should be deserialized as empty string [\#663](https://github.com/auth0/java-jwt/pull/663) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- empty expected audience array should throw InvalidClaimException [\#679](https://github.com/auth0/java-jwt/pull/679) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.4.0](https://github.com/auth0/java-jwt/tree/4.4.0) (2023-03-31) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.3.0...4.4.0) diff --git a/README.md b/README.md index 285f56ec..9d0ae41c 100644 --- a/README.md +++ b/README.md @@ -50,14 +50,14 @@ Add the dependency via Maven: com.auth0 java-jwt - 4.4.0 + 4.5.0 ``` or Gradle: ```gradle -implementation 'com.auth0:java-jwt:4.4.0' +implementation 'com.auth0:java-jwt:4.5.0' ``` ### Create a JWT From 6e76728a92249fd8595e79ee8fdd742a063aa61c Mon Sep 17 00:00:00 2001 From: tanya732 Date: Mon, 27 Jan 2025 12:24:01 +0530 Subject: [PATCH 194/198] upgraded plugin --- lib/build.gradle | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 167f234a..83093fc1 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -13,7 +13,7 @@ plugins { id 'java' id 'jacoco' id 'checkstyle' - id 'me.champeau.gradle.japicmp' version '0.2.9' + id 'me.champeau.gradle.japicmp' version '0.4.1' } sourceSets { @@ -70,8 +70,8 @@ project.afterEvaluate { project.configure(project) { def baselineVersion = project.ext.baselineCompareVersion task('apiDiff', type: JapicmpTask, dependsOn: 'jar') { - oldClasspath = files(getBaselineJar(project, baselineVersion)) - newClasspath = files(jar.archiveFile) + oldClasspath.from(files(getBaselineJar(project, baselineVersion))) + newClasspath.from(files(jar.archiveFile)) onlyModified = true failOnModification = true ignoreMissingClasses = true @@ -122,6 +122,7 @@ javadoc { } dependencies { + implementation 'com.fasterxml.jackson.core:jackson-core:2.15.4' implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.4' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' From dcbc560d44903fb47012b291beebad60fe37da73 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Tue, 28 Jan 2025 10:58:19 +0530 Subject: [PATCH 195/198] added JAVA_HOME path --- .github/actions/maven-publish/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/maven-publish/action.yml b/.github/actions/maven-publish/action.yml index 0d280cbe..88fdaa1a 100644 --- a/.github/actions/maven-publish/action.yml +++ b/.github/actions/maven-publish/action.yml @@ -26,6 +26,8 @@ runs: source "/home/runner/.sdkman/bin/sdkman-init.sh" sdk list java sdk install java ${{ inputs.java-version }} && sdk default java ${{ inputs.java-version }} + export JAVA_HOME=${SDKMAN_DIR}/candidates/java/current + echo "JAVA_HOME is set to $JAVA_HOME" - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # pin@1.1.0 From ca4f842509e7523b9a6e0f94b1f9b02e8509be93 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Tue, 28 Jan 2025 11:16:45 +0530 Subject: [PATCH 196/198] Release 4.5.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bb634fb..577ccf1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## [4.5.0](https://github.com/auth0/java-jwt/tree/4.5.0) (2025-01-28) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.4.0...4.5.0) + +**Added** +- Upgraded Plugin [\#711](https://github.com/auth0/java-jwt/pull/711) ([tanya732](https://github.com/tanya732)) +- Fix jackson vuln [\#705](https://github.com/auth0/java-jwt/pull/705) ([tanya732](https://github.com/tanya732)) +- Fix typo in example code [\#682](https://github.com/auth0/java-jwt/pull/682) ([kasperkarlsson](https://github.com/kasperkarlsson)) +- Remove dead README links [\#676](https://github.com/auth0/java-jwt/pull/676) ([jimmyjames](https://github.com/jimmyjames)) +- Fix typo on a comment in JWTCreator.java [\#672](https://github.com/auth0/java-jwt/pull/672) ([sgc109](https://github.com/sgc109)) +- Remove CircleCI [\#670](https://github.com/auth0/java-jwt/pull/670) ([jimmyjames](https://github.com/jimmyjames)) +- Empty string audience claim should be deserialized as empty string [\#663](https://github.com/auth0/java-jwt/pull/663) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- empty expected audience array should throw InvalidClaimException [\#679](https://github.com/auth0/java-jwt/pull/679) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.5.0](https://github.com/auth0/java-jwt/tree/4.5.0) (2025-01-22) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.4.0...4.5.0) From 05bc0027837cf85be597efbea51ce7b2104136d9 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 29 Jan 2025 03:09:12 +0530 Subject: [PATCH 197/198] added JAVA_HOME --- .github/actions/maven-publish/action.yml | 3 +++ .github/workflows/java-release.yml | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/.github/actions/maven-publish/action.yml b/.github/actions/maven-publish/action.yml index 88fdaa1a..01e3a621 100644 --- a/.github/actions/maven-publish/action.yml +++ b/.github/actions/maven-publish/action.yml @@ -30,11 +30,14 @@ runs: echo "JAVA_HOME is set to $JAVA_HOME" - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # pin@1.1.0 + env: + JAVA_HOME: ${{ env.JAVA_HOME }} - name: Publish Android/Java Packages to Maven shell: bash run: ./gradlew publish -PisSnapshot=false --stacktrace env: + JAVA_HOME: ${{ env.JAVA_HOME }} MAVEN_USERNAME: ${{ inputs.ossr-username }} MAVEN_PASSWORD: ${{ inputs.ossr-token }} SIGNING_KEY: ${{ inputs.signing-key}} diff --git a/.github/workflows/java-release.yml b/.github/workflows/java-release.yml index ddce3e59..00771307 100644 --- a/.github/workflows/java-release.yml +++ b/.github/workflows/java-release.yml @@ -63,6 +63,12 @@ jobs: - if: steps.tag_exists.outputs.exists == 'true' run: exit 1 + # Set JAVA_HOME here and pass it to subsequent steps + - name: Set JAVA_HOME for Gradle + run: echo "JAVA_HOME=/home/runner/.sdkman/candidates/java/current" >> $GITHUB_ENV # This ensures JAVA_HOME is set globally + env: + SDKMAN_DIR: /home/runner/.sdkman + # Publish the release to Maven - uses: ./.github/actions/maven-publish with: @@ -71,6 +77,8 @@ jobs: ossr-token: ${{ secrets.ossr-token }} signing-key: ${{ secrets.signing-key }} signing-password: ${{ secrets.signing-password }} + env: + JAVA_HOME: ${{ env.JAVA_HOME }} # Create a release for the tag - uses: ./.github/actions/release-create From 051e1c3efba283c5dc6812f7bffb715995d4794c Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 29 Jan 2025 10:21:20 +0530 Subject: [PATCH 198/198] Release 4.5.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 577ccf1b..b97fab71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## [4.5.0](https://github.com/auth0/java-jwt/tree/4.5.0) (2025-01-29) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.4.0...4.5.0) + +**Added** +- Upgraded Plugin [\#711](https://github.com/auth0/java-jwt/pull/711) ([tanya732](https://github.com/tanya732)) +- Fix jackson vuln [\#705](https://github.com/auth0/java-jwt/pull/705) ([tanya732](https://github.com/tanya732)) +- Fix typo in example code [\#682](https://github.com/auth0/java-jwt/pull/682) ([kasperkarlsson](https://github.com/kasperkarlsson)) +- Remove dead README links [\#676](https://github.com/auth0/java-jwt/pull/676) ([jimmyjames](https://github.com/jimmyjames)) +- Fix typo on a comment in JWTCreator.java [\#672](https://github.com/auth0/java-jwt/pull/672) ([sgc109](https://github.com/sgc109)) +- Remove CircleCI [\#670](https://github.com/auth0/java-jwt/pull/670) ([jimmyjames](https://github.com/jimmyjames)) +- Empty string audience claim should be deserialized as empty string [\#663](https://github.com/auth0/java-jwt/pull/663) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- empty expected audience array should throw InvalidClaimException [\#679](https://github.com/auth0/java-jwt/pull/679) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.5.0](https://github.com/auth0/java-jwt/tree/4.5.0) (2025-01-28) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.4.0...4.5.0)