From e0a8ef8b3aaa76f59c7568bbf4f9d9faae42e6f2 Mon Sep 17 00:00:00 2001 From: Tanay Pant Date: Sat, 23 Apr 2022 20:14:32 +0200 Subject: [PATCH] Create Okta OAuth Adapter --- app/config/providers.php | 10 + app/views/console/users/oauth/okta.phtml | 12 ++ public/images/users/okta.png | Bin 0 -> 8762 bytes public/scripts/views/forms/oauth-custom.js | 4 + src/Appwrite/Auth/OAuth2/Okta.php | 210 +++++++++++++++++++++ 5 files changed, 236 insertions(+) create mode 100644 app/views/console/users/oauth/okta.phtml create mode 100644 public/images/users/okta.png create mode 100644 src/Appwrite/Auth/OAuth2/Okta.php diff --git a/app/config/providers.php b/app/config/providers.php index 9a6b5fb50f..27f63b88fb 100644 --- a/app/config/providers.php +++ b/app/config/providers.php @@ -141,6 +141,16 @@ return [ // Ordered by ABC. 'beta' => false, 'mock' => false, ], + 'okta' => [ + 'name' => 'Okta', + 'developers' => 'https://developer.okta.com/', + 'icon' => 'icon-okta', + 'enabled' => true, + 'sandbox' => false, + 'form' => 'okta.phtml', + 'beta' => false, + 'mock' => false, + ], 'paypal' => [ 'name' => 'PayPal', 'developers' => 'https://developer.paypal.com/docs/api/overview/', diff --git a/app/views/console/users/oauth/okta.phtml b/app/views/console/users/oauth/okta.phtml new file mode 100644 index 0000000000..44c47c6550 --- /dev/null +++ b/app/views/console/users/oauth/okta.phtml @@ -0,0 +1,12 @@ +getParam('provider', ''); +?> + + + + + + + + + \ No newline at end of file diff --git a/public/images/users/okta.png b/public/images/users/okta.png new file mode 100644 index 0000000000000000000000000000000000000000..c66b273cfdc5618f207f022507c0657ee6a2ed5c GIT binary patch literal 8762 zcmY*;Wmp_rv-RLE!GjJ=aEIV9XmFRoArJ<);2sDX2(H1MU;zdXZb1eQ1a}V(!R;gG zJ>Prp-ThS8>grXicI_X#pYHyot}2IvNsb8s0B{uKWi+3?#a}~5eLg#{Cu;)$$hbDr z(&~27a?(!rPA*!`X78YKP)DeX%{xswDF8q)I$Forno@@doY7p%`f4*alx4%fyK!G=!we>p#P9mLnz;I8*u_X|TYebDo?oY) zj~vW&J$@^fH%W@I-^^RvlGtR|24B3{&O8p-S#~{I#)@lVGfY{gM`R=a#=iA)y|#b5 zw^!J$%YPilf1I5)CYdFf0fAzpyJP|KaJ+W}n~AfvgS zEX0WNeXV4sgTEQ%FHctm{cH=q`_S-a$*y-HSS_}e%b#cksp&G6#U*7e96v{AbI~oL zio>S15B~|;Y=dzM0vNZnhPq&$f_vZT__k8+x|Nm*OmwoAOntG!UkUbd?-CvQaU^qJ z&uyBC=^mh&iM@?S4Om@gVXyYN0#R41*sw&vqjjzM`_Ce-o3=?T5&e(Yt!#v6#)XP3 zBPH36qBhQ6KuW;9x~18vQV*u=jYL1_#llQ7vE2r^-0v@LJ#@3apsyzgkz$d5rsssz zpGpmU(5+`I#5Zkl`w+XnIH0nSIE0YXkwdwsFZ1H7%v z-N!L3ok3IF3Y-cF!Pk^@iJM$6X$yb}jDuPhpDiYY6><6cQ|MeR&YLHuyb(Z}IRa6w zQM;a!KOU_t(pKxn`V?~n@Mp(n$LxlZXB)4ys7Ub*mL2eOg z-~j(-uNHJGRKxqtt3mMuyTPjj8io%W%bVb}QP&)oocAk%1IDWyE~YM~?`d3f ziem0(>h~H1beG9mudg| zZVTl^F>ZaT^%kJomq)PMN#XHY9Tpc+V(uWzZmmw_1TTerFBm54757I|fvM zi@h#2y%>AVR}clKgL}Gk)yT)nY4$G+y&UP60keK>CQo+{KK|!OQ!9X`+$W?bdM~WL zcfa7z8-y2BPr*_}1;Fx*(E%t31OTLGgz$U-5Xb>2|6l+>5rN{rSQCNiUk)Mw5N-oN z{+DC$?Een=XM4{6*CXa4{+D5qi}XMEFEvp?bUo=Ntspl_qC z=ccEkEd0*Nfy2zg$sEez<>34`3n1zx{0tqSZe~C)2YW|XVJ|Vde;C5g_-~k#4)_nn z%}$I?PemOl?c@Rl@^f%;aM6J=fk2?Bi-o1Iri|>r>CabUbk=Te&cd9Wo}Qi@p1d4R zE>@h}LPA2ETs)jSJnYX5c2{plH#0AGM_15)ME<{y4Ak|Vi;c6JjguqrudbQ7le?Q3 z9o^qT{~iCCr<;xC|5S2x{kN^>208!U;pFDv;{31nbE@dysIZ!g4fMJ4UwtsQ=s(Q= z6Z^N0DCghe|7$Y;>GYrIbE{xXQO^G^8yNGY$r>5}K(VJFBdP6$aA=I-r_(q6WMw)) z4U0pOB8aOzh|1NO`Y2^+gkR56UkQm?)m+3!xz)dA_QOPBu~kZrRP3WFT1Si|-VBb5 zkiZ|HPQaIvx)jUX5f>mdks?5BzM_vGB`4QU zVyB~H96U6{lBBDhm!D7I?(Y8kC0g@BR~N!42gk{Yb8tzx$l}uC>iYVWIr!q@;-W zv=O+6iQ9t3UXTN;-irVEgf69hY!b0Y5wjfMhX3M4WOh)%Z$}&{UBr@~fQhWPN*MiW znKJ8L7QKCp=KbGf?3c4XMuKi8oetcryqgib&d%IqbIS=AjVaf5nLw?1zM<_Q=Bj!5 z#kh>+zXM?gzz&1#4W;%TDh-g7^G%wbk*Ej0EG|t1!oyv6e@pniyesgMkzp-ZW#MEfV1~_=$a1|DuuR%meKJk)su3iAV~r1{N9%M z)w**2YOlfgV0e-2S03hM5AgA6DIKjUE0xRo?BkIsMzFuZHnqX=(e#m)-js>){1rZR z1WHFnh@quXuq5e)j!un3c(QvfcHB0|?kXEe?`9IU^-=(FEetIEK&-?}YT~4ZZ?LcY zabtZGfTwGvtvyZ0cp75#zOOY@>-|jCH_{@Yl$Zgd_WtG+40YCEdB{KSEGa?$Jhp%tSe+nfFV zp*1;O<@8Nx_ko2l&jS8C8l7a2S}O$lv$^z#KXkB$gI0SnyZNP~DQ zxvQPG<9!o)?bnXV?Y6BfYK_yw-6}{SY>}VN)}1wlsSWGMk2`9R)`ELv0)_MHU=4_|;pOfv??dYlO$S{25rQCX~S!a`ch2aCbY%FDk9+~00AD=sV0Hn~s-@MYAWdRTECy$dPb6C;`K7JAsnOU&6_Rnq@=UUw(GLd-KIddb_^BM4wlmy-XiDa)*0;mspQ)ZcgccJ6GQTJSXh5Z)YoC$huaf+cawe$OTSuWfX^Krh|h@~nL4KXndO^x&5L{bO$%tNQlSyZ3qwSnx7 zCd*s=il;lRlk*Er^4aEeqfwh7o4EW$o^Vr+BuTDYWRuyq_+&1j_X#Y$F_=HLJuf## z=b|)d!4w}QzqoLeE(LPEvsLwG#N>X62^}Ehe>22X+3L*P(@sx+p|@ZR%UMOL)=xjEVU?CWnuzq)gR_fE3F zCtZTOP|thS(YbNIN2u>$x0b*9)!V>7(&1?-HYhPvZuf#>E!yE}K30dbHLuK-A7O@kA;AiKiBzk=ouj^XKT^&>q|0%085KWT{4R@(CODs@N zz!YOhUUi^LpartJ&MjS}D!W2n?|8dR2Ta!~Dz>8a>17KE_hvGcm2Sz{e!C`sgLo+W zCs_9r!nt(-N*t)_hdjX#V;dYBT9S!lJrE!0y?$+Qq&OE5706V1P;ZQB12 zWS&A+Lvxv&l4UV3lZ;mXQ~x31*>_W;Z*9FLW~N-Ny-K>w z_g8xPO=@eOOvlI>Pw`R6yfnH**Fc*jee1nwG522G|(+JmNuB& z*N3yzJzgiv#jQJtGe=I|`E8W83o6i_>1KI&g`;=B>Ibb$ASX-cVx{U7@nT5b(yMFs ziv=TKH>uLLYLB~k2jauKyVuOwn*sc5*4Hg+yiB)tmq#KYGXnM`Pmh*^V=uWu4;lt$ zG`a|PNhbYCA#7}h?xX?YJC`I5#wvh`snZJqjO){o6)g7}u{#07!VlN88@M>8OkQ7Tjumo;A^w9cq-Tu{;b`?5tsnF}mG7Ox7cDa9=jb_A=}9 zTf7?3{=_sW85o+Yj?862f@{*v)1j_1jTB-^1f1u8bHT+2HumnQAg5=8SA};KLn;_C+rC;PEP7Tn4^R;_R$@sV7 z!d%P}bsndKJ0e>IR$IS@t>+|6^F9||8;PFmzFrjHhv;Cm+fdJYb;ZyKN+k@A3H39U6;OS(@iSC z9aNaf)IZDmE{UO!O^8RB5POMj{*Xz_&%d?WvZxo6GO-Ul>1u_p{~&?(s1=Z)xVjZR zV(6hnlW7v34=`K4BTS6<1sYC?lk!y_OL((`hcvoA(Zr;@olX$4olYQGWe1OAkm7vN z43|X8KOOqsLZ2-_u{=qW^f22W1vy67BPjKZS)wInxF6>`)pJ(_^rzC(n1#Ap`BN|9o0u*<2`Tu zee;-vW#zDOEEkR}E==A4Aq3{ZKHH#D{hNKiucDIOU)S5kDiG!6DKYUm#AIy|M2j{A z+XQ&uzZwrwEkCRm&#<=qm9kxAjuBFKd>x9cNt&n_l9gdZhk)^WFQ)3s64(*Q5r#_t zF@~w!a>tDWdc>;9;O0<2?PoBZ8`W-uNiFV3b{o4Fwb7Ss%UAVfdTVv(V{YOKDUdPz zTNtWDf);wj%{;LjQ5)2aLnX_;LU@u8*gRWA2*pUPL%F_NEzXIRR&aSCYT^7P>+|&r zi-qMUxMTuIgq=OnHg(pychuaHu0`{do%3($@r z{dw@4Vm^aOtl8*-l}M8|qsjRgUl$M4jrUX_C6Km+67*rJQKi4VOo7zzXE5XISNP)8 zR51tLEQ0V!8{R}SUN;E$&*7PxidKSB^U)JmLQuwFAerjD?Vo5&U59Eu-i$e>^0P?> zsw1}7KnCoAvLqd?XjdDpNF75j0n!i!(V zi4dvo9v?+QTek7K!V)8@uE5Vew;!$5q?l8Q(pI3TZv=Z57U1{8hnWQvsN_=YOnSmO zom!vpt$6!kt;!sqE-Xiya2KbHq=X;Tuy8y-KfcDJf%d%(q18NU$%erDWpW9Ge)_m)9t4opZ%U zd>f)LmDlt&&9udaM}moMGut4{w|hA3E11AMmouz)ZI_nCSiXvSNa?F_vnA1hC7Ep< zVazsJA~o+>y(e{@O}w`SC9z>*LHYdj=&&#oho%W*3%2(lrJTpuMvBLpIpS#J+f?e{ z--~fJyv^?;fK znGHi|^^j2xf<8etFBqebehU+SN&Y!gBC>rBGsxIP}`bmFg^#wXEJk`N`OYo)4z?X379+lPoQ zqv3Kc(khZcpFwWvf?nReavlg9Ym*h=8yQ8^$3K|$-P5gf(V0!fRb%54Y#%In+ZR5_ zAWSBg%FG;^W^{DNjNt-hFWpK@`4YcSJV>Es=f-e@I1;?rdOCLpx`l*ZihERZ-NR66 zn&#rH{rfQBIRd}zJazQD2idGf77_g)e^zy|GRQmVd}VNE@W5K*I};7n0M+@n1VL*u zH9)9?EoS$8AJ_&Y;7mp|MB$9;f^Ut`Y zWeL)-k=(p^{gsp>0SO^(VCYQ^k03}5gUrXq)Pi_|)zXN!mS8vmi>$;ZHWBo(69;e+OX=p#l4g%yA{oR{Kpq%qsJ7!w2jZPOqDi2OI{;kCl zyG$!;3N@C>M$c|+GO0U|R~~fi?OaNb?&B|OH#WO3+_ef=BWN6KX2&XnJt{6^Y zgnV5*tZYIBU3nomK8|Q~v)qDJ25l!qJ6RExI=m zU}4gA>{zoTSlgSm>X-BDi11WMKv?s6wpJpj4IF$m%RBdx`ymwzzk`)RBTo#QZ^m!5 zGW|zj%VKb7n(t4qIds~l_XI;U6bN|@Z{WwuLgGkYP)_0mP3?Y!*T}6Y=pT}c@L;n( z9lyKZUM_KlRToMYlUXV1<6zGHVYcC&o5};(r15NM*gtua=X0LvoO9z+vJ$wXG7>9l zvuDsLYrnDf=;Or1cdySw6dgwHdbG8}vMVc8)JifBBw(>S^cZl=#(QUKp70x}Sz)Hh zb;Ng`6)h0b*c29`%8aw&Yx|8rC5sm>HIL zN+^fSswBN}@5@MgC(&*o`<6JlnvN}Tzq=s(s`p!PKbPAH63k@}>EsoE+pECN*%%<$ z6Fdm$)&t9xtvW77J^rC9Rt`X<6*GP@S3O-hmx8(QJwaG>Nv2c?64+v?8k$kwt0gdu zbm1ROfp&uUp)gPkh~|p(^0gt=cTL&;2QYb7+?wNecJf( zh47?&p*VurEG<&1u`MS@C~#DHQFUWDyJ*^@oD5Sp%E7iIO}Z6|I$DrYk#6<6inKHs z-j%q{+gmI^(~-vAr*3Htu2HZ8zhsSVHt8hbx7 za*3A25e^#ZvuUyH^GpZ-xpj8Bh`3YLSt;B?gW zx!rZj6%VH9aHf$V$E>te)3koK4mfqnjZp)emZ|P)u-2t(pwU>rz#nP&8fn6_1umrG z49bz|H{{c>t?5U$NMU`22*SAjbf3PrYjrz37$*C*k2TN@^3p#hB z;h@^cHa$q={qn_U943Iv_ANniB_pY()I9RYN0Bf9# zsF)mEsDSpw9(Z^#r6O}-W=X+r?1|@<;1=#lv181A&D*wUG~|zQS5h$ zKMd>hHm;&x5?`$i3CGbhG7LlBI_EZS`B*1Q>4{s)M*{cLuWQjxvM9fk9c}!wY4J4} zw@QJQcF8LY^oU)V29fHDej5^|=xe}0cO9=_<)D4>PIkFu>aF8a@8_osgChi(itQ(T zmwEN1%)OTVpUEsoW$ch5SKDOh9z~tEU!7p}ugaXL*ZNwAJJZb-N;8$^k0th6;G#Nn zdp%yYp!fWcq6HU-gcryGJSFYau~h<-+)@@=W|WS(iDx3l4SgzR5bLBqeG&cop#^)c z;EH>VC77aFIg!HfW)dv1X|{9`McqN+Ig-tmJK*63^DZLEuH5_}X_e?iAr|%p1y<@}?rnGgl0vWY41fSbMu?UUyHw zz32+}zL0N0FgwzoLB|{+scLPKpbAWWg=S{Fdl1#55zOs0G2+s+Ery9L)&()0zwuBF zM`@;9E)`|P}7p~=zD{`%K&C0DMyqYRxy z3|S?Toy$M{-bmfU^(G_nFt-~6AxUo>x-glq@sc1Dt91SOpV#MYCGRIoMtj&3U5lxE z{@7J%&Sl|cz$)d!0cNt~L;(Sw8ziN(wGt6n=3CYgQX+;C`e>|)<{I6b(HoFhkfj^b z^8Nk>frX&@ZPzO1PEWD@zTYTM@1Y9VyLK>mTqC9B zdZnoAl14)ui{RH?EVangzH)Q8;nmE_+GR#bS@Ayq!lkqvI0F-%H9u5L{6p=&h-kA{ zw6H!y^@p!bT*ex72@s1d(AS+?J< z2gY5i9<<<4lfn5bZy6K=_?^hmc5sBX^R|(H<8}K-XH;8gr%16@*wAEVmQtcm4a_u{AKO6GPVjh~s&*1ajah=;)uU*W z2@_eea=@Vb5#KtM_Sg{b4pu@jcr?mLMxq6sn2YlV87XB-97wyfz?8{yNsxL*$!jM~ zLs}a@<387dE{9h-*gU{W$pcA7w@`?yb(9_;O27KEvgP!a@s2)F@-i^V|IO<0r)*d= zFA8gL4RO7~v5UEEboBl5N&5aG203Y)I+ehB?dL&e|AP`DKp!(5>2LTJ0$KlWBuuyU=&Mz5X$`1DCvmO$c5GHhNG01F90bZt`sV8W9 zpsCs%nwTlobXjg$WOjC;oxir~E;@b!YAT7K*!{xwJPX<&p3ss0*&hS!Jq^(sk+GKhU?|G`8L7JUYY*E{*zic60AKS?)heGE+`10 zJ^H$);wNx6mVK_F=4OWjA;O`^4eivxHPHUJocKmlm$L?f%}9T2o?v#!l&c=(%&=9L lFva`(@*BcO2fimHo4L2E11OHLzyD7a-l)oalY0B{{{hTzPjvtQ literal 0 HcmV?d00001 diff --git a/public/scripts/views/forms/oauth-custom.js b/public/scripts/views/forms/oauth-custom.js index fd8bd855d0..fabb2b17ee 100644 --- a/public/scripts/views/forms/oauth-custom.js +++ b/public/scripts/views/forms/oauth-custom.js @@ -16,6 +16,10 @@ "keyID": "oauth2AppleKeyId", "teamID": "oauth2AppleTeamId", "p8": "oauth2AppleP8" + }, + "Okta": { + "clientSecret": "oauth2OktaClientSecret", + "oktaDomain": "oauth2OktaDomain" } } let provider = element.getAttribute("data-forms-oauth-custom"); diff --git a/src/Appwrite/Auth/OAuth2/Okta.php b/src/Appwrite/Auth/OAuth2/Okta.php new file mode 100644 index 0000000000..70ea0d4b13 --- /dev/null +++ b/src/Appwrite/Auth/OAuth2/Okta.php @@ -0,0 +1,210 @@ +getOktaDomain().'/oauth2/default/v1/authorize?'.\http_build_query([ + 'client_id' => $this->appID, + 'redirect_uri' => $this->callback, + 'state'=> \json_encode($this->state), + 'scope'=> \implode(' ', $this->getScopes()), + 'response_type' => 'code' + ]); + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokens(string $code): array + { + if(empty($this->tokens)) { + $headers = ['Content-Type: application/x-www-form-urlencoded']; + $this->tokens = \json_decode($this->request( + 'POST', + 'https://'.$this->getOktaDomain().'/oauth2/default/v1/token', + $headers, + \http_build_query([ + 'code' => $code, + 'client_id' => $this->appID, + 'client_secret' => $this->getClientSecret(), + 'redirect_uri' => $this->callback, + 'scope' => \implode(' ', $this->getScopes()), + 'grant_type' => 'authorization_code' + ]) + ), true); + } + + return $this->tokens; + } + + + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken): array + { + $headers = ['Content-Type: application/x-www-form-urlencoded']; + $this->tokens = \json_decode($this->request( + 'POST', + 'https://'.$this->getOktaDomain().'/oauth2/default/v1/token', + $headers, + \http_build_query([ + 'refresh_token' => $refreshToken, + 'client_id' => $this->appID, + 'client_secret' => $this->getClientSecret(), + 'grant_type' => 'refresh_token' + ]) + ), true); + + if(empty($this->tokens['refresh_token'])) { + $this->tokens['refresh_token'] = $refreshToken; + } + + return $this->tokens; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserID(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['sub'])) { + return $user['sub']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserEmail(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['email'])) { + return $user['email']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserName(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['name'])) { + return $user['name']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return array + */ + protected function getUser(string $accessToken): array + { + if (empty($this->user)) { + $headers = ['Authorization: Bearer '. \urlencode($accessToken)]; + $user = $this->request('GET', 'https://'.$this->getOktaDomain().'/v1/userinfo', $headers); + $this->user = \json_decode($user, true); + } + + return $this->user; + } + + /** + * Extracts the Client Secret from the JSON stored in appSecret + * + * @return string + */ + protected function getClientSecret(): string + { + $secret = $this->getAppSecret(); + + return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : ''; + } + + /** + * Extracts the Okta Domain from the JSON stored in appSecret + * + * @return string + */ + protected function getOktaDomain(): string + { + $secret = $this->getAppSecret(); + return (isset($secret['oktaDomain'])) ? $secret['oktaDomain'] : ''; + } + + /** + * Decode the JSON stored in appSecret + * + * @return array + */ + protected function getAppSecret(): array + { + try { + $secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR); + } catch (\Throwable $th) { + throw new \Exception('Invalid secret'); + } + return $secret; + } +} \ No newline at end of file